module Bmg
  module Sql
    class Processor < Sexpr::Rewriter
      grammar Sql::Grammar

      UnexpectedError = Class.new(Bmg::Error)

      def initialize(builder)
        @builder = builder
      end
      attr_reader :builder

      def on_with_exp(sexpr)
        applied = apply(sexpr.select_exp)
        if applied.with_exp?
          merge_with_exps(sexpr, applied, applied.select_exp)
        else
          sexpr.with_update(-1, applied)
        end
      end

      def on_set_operator(sexpr)
        sexpr.each_with_index.map{|child,index|
          index <= 1 ? child : apply(child)
        }
      end
      alias :on_union     :on_set_operator
      alias :on_except    :on_set_operator
      alias :on_intersect :on_set_operator

      def on_select_exp(sexpr)
        sexpr.with_update(2, apply(sexpr[2]))
      end

    private

      def merge_with_exps(left, right, main)
        if left.with_exp? and right.with_exp?
          [ :with_exp,
            merge_with_specs(left.with_spec, right.with_spec),
            main ]
        elsif left.with_exp?
          left.with_update(-1, main)
        elsif right.with_exp?
          right.with_update(-1, main)
        else
          main
        end
      end

      def merge_with_specs(left, right)
        hash = left.to_hash.merge(right.to_hash){|k,v1,v2|
          unless v1 == v2
            msg = "Unexpected different SQL expr: "
            msg << "`#{v1.inspect}` vs. `#{v2.inspect}`"
            raise UnexpectedError, msg
          end
          v1
        }
        hash.map{|(k,v)| builder.name_intro(k,v) }.unshift(:with_spec)
      end

      def tautology
        Predicate::Factory.tautology
      end

    end
  end
end
require_relative 'processor/distinct'
require_relative 'processor/all'
require_relative 'processor/clip'
require_relative 'processor/constants'
require_relative 'processor/star'
require_relative 'processor/rename'
require_relative 'processor/order_by'
require_relative 'processor/limit_offset'
require_relative 'processor/from_self'
require_relative 'processor/reorder'
require_relative 'processor/merge'
require_relative 'processor/where'
require_relative 'processor/join_support'
require_relative 'processor/join'
require_relative 'processor/semi_join'
require_relative 'processor/flatten'
require_relative 'processor/requalify'
require_relative 'processor/summarize'
require_relative 'processor/bind'
