module Bmg
  module Operator
    #
    # Transform operator.
    #
    # Transforms existing attributes through computations
    #
    # Example:
    #
    #   [{ a: 1 }] transform { a: ->(t){ t[:a]*2 } } => [{ a: 4 }]
    #
    class Transform
      include Operator::Unary

      DEFAULT_OPTIONS = {}

      def initialize(type, operand, transformation, options = {})
        @type = type
        @operand = operand
        @transformation = transformation
        @options = DEFAULT_OPTIONS.merge(options)
      end

    protected

      attr_reader :transformation, :options

    public

      def each
        return to_enum unless block_given?
        t = transformer
        @operand.each do |tuple|
          yield t.call(tuple)
        end
      end

      def to_ast
        [ :transform, operand.to_ast, transformation.dup ]
      end

    protected ### optimization

      def _allbut(type, butlist)
        # `allbut` can always be pushed down the tree. unlike
        # `extend` the Proc that might be used cannot use attributes
        # in butlist, so it's safe to strip them away.
        if transformer.knows_attrlist?
          # We just need to clean the transformation
          attrlist = transformer.to_attrlist
          thrown = attrlist & butlist
          t = transformation.dup.reject{|k,v| thrown.include?(k) }
          operand.allbut(butlist).transform(t, options)
        else
          operand.allbut(butlist).transform(transformation, options)
        end
      end

      def _project(type, attrlist)
        if transformer.knows_attrlist?
          t = transformation.dup.select{|k,v| attrlist.include?(k) }
          operand.project(attrlist).transform(t, options)
        else
          operand.project(attrlist).transform(transformation, options)
        end
      end

      def _restrict(type, predicate)
        return super unless transformer.knows_attrlist?
        top, bottom = predicate.and_split(transformer.to_attrlist)
        if top == predicate
          super
        else
          operand
            .restrict(bottom)
            .transform(transformation, options)
            .restrict(top)
        end
      end

    protected ### inspect

      def args
        [ transformation ]
      end

    private

      def transformer
        @transformer ||= TupleTransformer.new(transformation)
      end

    end # class Transform
  end # module Operator
end # module Bmg
