module Groonga
  class ExpressionSizeEstimator
    def initialize(expression, table)
      @expression = expression
      @table = table
      @table_size = @table.size
    end

    def estimate
      builder = ScanInfoBuilder.new(@expression, Operator::OR, false)
      data_list = builder.build
      return @table_size if data_list.nil?

      begin
        estimate_data_list(data_list)
      ensure
        data_list.each(&:unlink)
      end
    end

    private
    def estimate_data_list(data_list)
      current_size = 0
      sizes = []
      data_list.each do |data|
        if (data.flags & ScanInfo::Flags::POP) != 0
          size = sizes.pop
          case data.logical_op
          when Operator::AND, Operator::AND_NOT
            current_size = size if size < current_size
          when Operator::OR
            current_size = size if size > current_size
          else
            message = "invalid logical operator: <#{data.logical_op.inspect}>"
            raise InvalidArgument, message
          end
        else
          if (data.flags & ScanInfo::Flags::PUSH) != 0
            sizes.push(current_size)
            current_size = 0
          end

          estimator = ScanInfoDataSizeEstimator.new(data, @table)
          size = estimator.estimate
          case data.logical_op
          when Operator::AND
            current_size = size if size < current_size
          when Operator::AND_NOT
            size = @table_size - size
            size = 0 if size < 0
            current_size = size if size < current_size
          when Operator::OR
            current_size = size if size > current_size
          else
            message = "invalid logical operator: <#{data.logical_op.inspect}>"
            raise InvalidArgument, message
          end
        end
      end
      current_size
    end
  end
end
