module Bmg
  module Operator
    #
    # Union operator.
    #
    # Returns all tuples of the left operand followed by all
    # tuples from the right operand.
    #
    # This implementation is actually a NAry-Union, since it handles
    # an arbitrary number of operands.
    #
    # By default, this operator strips duplicates, as of relational
    # theory. Please set the `:all` option to true to avoid this
    # behavior and save execution time.
    #
    class Union
      include Operator::Nary

      DEFAULT_OPTIONS = {
        all: false
      }

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

    protected

      attr_reader :options

    public

      def all?
        @options[:all]
      end

      def each(&bl)
        return to_enum unless block_given?
        if all?
          operands.each do |op|
            op.each(&bl)
          end
        else
          seen = {}
          operands.each do |op|
            op.each do |tuple|
              yield(tuple) unless seen.has_key?(tuple)
              seen[tuple] = true
            end
          end
        end
      end

      def to_ast
        [ :union ] + operands.map(&:to_ast) + [ options.dup ]
      end

    protected ### optimization

      def _restrict(type, predicate)
        ops = operands
          .map   {|op| op.restrict(predicate)    }
          .reject{|op| op.is_a?(Relation::Empty) }
        case ops.size
        when 0 then Relation.empty(type)
        when 1 then ops.first
        else Union.new(type, ops, options)
        end
      end

      def _union(type, other, options)
        return self if other.is_a?(Relation::Empty)
        norm_options = DEFAULT_OPTIONS.merge(options)
        return super unless norm_options == self.options
        case other
        when Union
          return super unless norm_options == other.send(:options)
          Union.new(type, operands + other.operands, options)
        else
          Union.new(type, operands + [other], options)
        end
      end

    protected ### inspect

      def args
        [ options ]
      end

    end # class Union
  end # module Operator
end # module Bmg
