module Ambition #:nodoc:
  module Processors #:nodoc:
    class Select < Base
      def initialize(context, block)
        @context  = context
        @block    = block
      end

      def process_call(args)
        # Operation (m.name == 'chris')
        #   [[:call, [:dvar, :m], :name], :==, [:array, [:str, "chris"]]]
        if args.size == 3
          left, operator, right = args

          # params are passed as an array, even when only one element:
          #  abc(1)
          #  => [:fcall, :abc, [:array, [:lit, 1]]
          #  abc([1])
          #  => [:fcall, :abc, [:array, [:array, [:lit, 1]]]]
          if right.first == :array 
            right = process(right)
            right = right.is_a?(Array) ? right.first : right
          else
            right = process(right)
          end

          translator.send(process_operator(operator), process(left), right)

        # Property of passed arg:
        #   [[:dvar, :m], :name]
        elsif args.first.last == @receiver
          translator.call(*args[1..-1])

        # Method call: 
        #   [[:call, [:dvar, :m], :name], :upcase]
        elsif args.first.first == :call && args.first[1].last == @receiver
          receiver, method = args
          translator.chained_call(receiver.last, method)

        # Deep, chained call:
        #   [[:call, [:call, [:call, [:dvar, :m], :created_at], :something], :else], :perhaps]
        elsif args.flatten.include? @receiver
          calls = []

          until args.empty?
            args = args.last if args.last.is_a?(Array)
            break if args.last == @receiver
            calls << args.pop
          end

          translator.chained_call(*calls.reverse)

        else
          raise args.inspect
        end
      end

      def process_match3(exp)
        right, left = exp
        process_call [ left, :=~, right ]
      end

      def process_and(exp)
        joined_expressions exp, :both
      end

      def process_or(exp)
        joined_expressions exp, :either
      end

      def joined_expressions(exp, with = nil)
        expressions = []

        while expression = exp.shift
          expressions << process(expression)
        end

        translator.send(with, *expressions)
      end

      def process_not(args)
        negate { process(args.first) }
      end

      def process_operator(operator)
        @negated ? negate_operator(operator) : operator
      end

      def negate_operator(operator)
        case operator
        when :== then :not_equal
        when :=~ then :not_regexp
        else raise "Missing negated operator definition: #{operator}"
        end
      end

      def negate
        @negated = translator.negated = true
        yield
      ensure
        @negated = translator.negated = false
      end
    end
  end
end
