module Bmg
  module Sql
    class Builder

      DISTINCT     = Grammar.sexpr([:set_quantifier, "distinct"])
      ALL          = Grammar.sexpr([:set_quantifier, "all"])
      IS_TABLE_DEE = Grammar.sexpr([:select_list,
                                     [:select_item,
                                       [:literal, true],
                                       [:column_name, "is_table_dee"]]])

      def self.builder(meth)
        old = instance_method(meth)
        define_method(meth) do |*args, &bl|
          Grammar.sexpr(old.bind(self).call(*args, &bl))
        end
      end

      def initialize(start = 0)
        @next_qualifier = (start || 0)
      end

      def distinct
        DISTINCT
      end
      builder :distinct

      def all
        ALL
      end
      builder :all

      def name_intro(name, sexpr)
        [:name_intro, table_name(name), sexpr]
      end
      builder :name_intro

      def select_star_from(name, qualifier = next_qualifier!)
        [ :select_exp,
          all,
          select_star(qualifier),
          from_clause(name, qualifier) ]
      end
      builder :select_star_from

      def select_all(heading, name, qualifier = next_qualifier!)
        [ :select_exp, all,
          select_list(heading, qualifier),
          from_clause(name, qualifier) ]
      end
      builder :select_all

      def select_is_table_dee(subquery)
        [ :select_exp, all, is_table_dee,
          [:where_clause, exists(subquery)] ]
      end
      builder :select_is_table_dee

      def is_table_dee
        IS_TABLE_DEE
      end
      builder :is_table_dee

      def select_list(attrs, qualifier)
        attrs = attrs.to_attr_list if attrs.respond_to?(:to_attr_list)
        attrs.map{|a| select_item(qualifier, a) }.unshift(:select_list)
      end
      builder :select_list

      def select_literal_item(literal, as)
        [:select_item,
          literal(literal),
          column_name(as.to_s)]
      end
      builder :select_literal_item

      def select_item(qualifier, name, as = name)
        [:select_item,
          qualified_name(qualifier, name.to_s),
          column_name(as.to_s)]
      end
      builder :select_item

      def select_star(qualifier = next_qualifier!)
        [:select_star, [:range_var_name, qualifier]]
      end
      builder :select_star

      def from_clause(table, qualifier)
        arg = case table
        when String, Symbol  then table_as(table, qualifier)
        when ::Sequel::SQL::QualifiedIdentifier then table_as(table, qualifier)
        when Expr            then subquery_as(table, qualifier)
        else                      native_table_as(table, qualifier)
        end
        [ :from_clause, arg ]
      end
      builder :from_clause

      def table_as(table, qualifier)
        table = case table
        when String, Symbol then table_name(table)
        when ::Sequel::SQL::QualifiedIdentifier then table_name(table)
        else table
        end
        [:table_as,
          table,
          range_var_name(qualifier) ]
      end
      builder :table_as

      def subquery_as(subquery, qualifier)
        [:subquery_as,
          subquery,
          range_var_name(qualifier) ]
      end
      builder :table_as

      def native_table_as(table, qualifier)
        [ :native_table_as,
          table,
          range_var_name(qualifier) ]
      end

      def qualified_name(qualifier, name)
        [:qualified_name,
          range_var_name(qualifier),
          column_name(name) ]
      end
      builder :qualified_name

      def range_var_name(qualifier)
        [:range_var_name, qualifier]
      end
      builder :range_var_name

      def column_name(name)
        [:column_name, name]
      end
      builder :column_name

      def table_name(name)
        [:table_name, name]
      end
      builder :table_name

      def exists(subquery)
        Predicate::Grammar.sexpr [ :exists, subquery ]
      end

      def group_by_clause(attrlist, &desaliaser)
        attrlist.map{|name|
          name = name.to_s
          (desaliaser && desaliaser[name]) || column_name(name)
        }.unshift(:group_by_clause)
      end
      builder :group_by_clause

      def order_by_clause(ordering, &desaliaser)
        ordering.map{|(name,direction)|
          name = name.to_s
          name = (desaliaser && desaliaser[name]) || column_name(name)
          [:order_by_term, name, direction ? direction.to_s : "asc"]
        }.unshift(:order_by_clause)
      end
      builder :order_by_clause

      def limit_clause(limit)
        [:limit_clause, limit]
      end
      builder :limit_clause

      def offset_clause(limit)
        [:offset_clause, limit]
      end
      builder :offset_clause

      def literal(lit)
        [:literal, lit]
      end
      builder :literal

      def from_self(sexpr)
        Processor::FromSelf.new(self).call(sexpr)
      end

    public

      def last_qualifier
        "t#{@next_qualifier}"
      end

      def next_qualifier!
        "t#{@next_qualifier += 1}"
      end

    end
  end
end
