module Bmg
  module Operator
    #
    # Constants operator.
    #
    # Extends operand's tuples with attributes given at construction.
    # This is a special case of an extension, where the values are
    # statically known.
    #
    class Constants
      include Operator::Unary

      def initialize(type, operand, the_constants)
        @type = type
        @operand = operand
        @the_constants = the_constants
      end

    protected

      attr_reader :the_constants

    public

      def each
        return to_enum unless block_given?
        @operand.each do |tuple|
          yield extend_it(tuple)
        end
      end

      def insert(arg)
        case arg
        when Hash       then operand.insert(allbut_constants(arg))
        when Relation   then operand.insert(arg.allbut(the_constants.keys))
        when Enumerable then operand.insert(arg.map{|t| allbut_constants(t) })
        else
          super
        end
      end

      def update(tuple, predicate = Predicate.tautology)
        shared = tuple.keys & the_constants.keys
        on_tuple = TupleAlgebra.project(tuple, shared)
        on_const = TupleAlgebra.project(the_constants, shared)
        raise InvalidUpdateError, "Cannot violate relvar predicate" unless on_tuple == on_const
        operand.update(allbut_constants(tuple), predicate)
      end

      def delete(predicate = Predicate.tautology)
        operand.delete(predicate)
      end

      def to_ast
        [ :constants, operand.to_ast, the_constants.dup ]
      end

    public ### for internal reasons

      def _count
        operand._count
      end

    protected ### optimization

      def _page(type, ordering, page_index, options)
        attrs = ordering.map{|(k,v)| k }
        cs_attrs = the_constants.keys
        if (attrs & cs_attrs).empty?
          operand
            .page(ordering, page_index, options)
            .constants(the_constants)
        else
          super
        end
      rescue UnsupportedError
        super
      end

      def _restrict(type, predicate)
        # bottom_p makes no reference to the_constants, top_p possibly
        # does...
        top_p, bottom_p = predicate.and_split(the_constants.keys)
        if top_p.tautology?
          # push all situation: predicate made no reference to the_constants
          result = operand
          result = result.restrict(bottom_p)
          result = result.constants(the_constants)
          result
        elsif (top_p.free_variables - the_constants.keys).empty?
          # top_p applies to the_constants only
          if eval = top_p.evaluate(the_constants)
            result = operand
            result = result.restrict(bottom_p)
            result = result.constants(the_constants)
            result
          else
            Relation.empty(type)
          end
        elsif bottom_p.tautology?
          # push none situation, no optimization possible since top_p
          # is not a tautology
          super
        else
          # top_p and bottom_p are complex predicates. Let apply each
          # of them
          result = operand
          result = result.restrict(bottom_p)
          result = result.constants(the_constants)
          result = result.restrict(top_p)
          result
        end
      rescue Predicate::NotSupportedError
        super
      end

    protected ### inspect

      def args
        [ the_constants ]
      end

    private

      def extend_it(tuple)
        tuple.merge(@the_constants)
      end

      def allbut_constants(tuple)
        TupleAlgebra.allbut(tuple, the_constants.keys)
      end

    end # class Constants
  end # module Operator
end # module Bmg
