require 'delegate'
require 'singleton'

#TODO: Use logger and log throughout

module Squealer
  class Target

    def self.current
      Queue.instance.current
    end

    def initialize(database_connection, table_name, &block)
      raise BlockRequired, "Block must be given to target (otherwise, there's no work to do)" unless block_given?
      raise ArgumentError, "Table name must be supplied" if table_name.to_s.strip.empty?

      @dbc = database_connection
      @table_name = table_name.to_s
      @binding = block.binding

      verify_table_name_in_scope
      @row_id = infer_row_id
      @column_names = []
      @column_values = []
      @sql = ''

      target(&block)
    end

    def sql
      @sql
    end

    def assign(column_name, &block)
      @column_names << column_name
      if block_given?
        @column_values << yield
      else
        @column_values << infer_value(column_name, @binding)
      end
    end


    private

    def infer_row_id
      (
        (eval "#{@table_name}[:_id]", @binding, __FILE__, __LINE__) ||
        (eval "#{@table_name}['_id']", @binding, __FILE__, __LINE__)
      ).to_s
    end

    def verify_table_name_in_scope
      table = eval "#{@table_name}", @binding, __FILE__, __LINE__
      raise ArgumentError, "The variable '#{@table_name}' is not a hashmap" unless table.is_a? Hash
      raise ArgumentError, "The hashmap '#{@table_name}' must have an '_id' key" unless table.has_key?('_id') || table.has_key?(:_id)
    rescue NameError
      raise NameError, "A variable named '#{@table_name}' must be in scope, and reference a hashmap with at least an '_id' key."
    end


    def infer_value(column_name, binding)
      value = eval "#{@table_name}.#{column_name}", binding, __FILE__, __LINE__
      unless value
        name = column_name.to_s
        if name =~ /_id$/
          related = name[0..-4]  #strip "_id"
          value = eval "#{related}._id", binding, __FILE__, __LINE__
        end
      end
      value
    end

    def target
      Queue.instance.push(self)

      yield self

      insert_statement = %{INSERT INTO "#{@table_name}"}
      insert_statement << %{ (#{pk_name}#{column_names}) VALUES ('#{@row_id}'#{column_value_markers})}
      if Database.instance.upsertable?
        insert_statement << %{ ON DUPLICATE KEY UPDATE #{column_markers}}
        @sql = insert_statement
      else
        update_statement = %{UPDATE "#{@table_name}" SET #{column_markers} WHERE #{pk_name}='#{@row_id}'}
        process_sql(update_statement)
        @sql = update_statement + "; " + insert_statement
      end

      process_sql(insert_statement)

      Queue.instance.pop
    end

    def self.targets
      @@targets
    end

    def targets
      @@targets
    end

    def process_sql(sql)
      values = Database.instance.upsertable? ? typecast_values * 2 : typecast_values
      execute_sql(sql, values)
    end

    def execute_sql(sql, values)
      @dbc.create_command(sql).execute_non_query(*values)
    rescue DataObjects::IntegrityError
      raise "Failed to execute statement: #{sql} with #{values.inspect}.\nOriginal Exception was: #{$!.to_s}" if Database.instance.upsertable?
    rescue
      raise "Failed to execute statement: #{sql} with #{values.inspect}.\nOriginal Exception was: #{$!.to_s}"
    end

    def pk_name
      'id'
    end

    def column_names
      return if @column_names.size == 0
      ",#{@column_names.map { |name| quote_identifier(name) }.join(',')}"
    end

    def column_values
      @column_values
    end

    def column_value_markers
      return if @column_names.size == 0
      result = ""
      @column_names.size.times { result << ',?'}
      result
    end

    def column_markers
      return if @column_names.size == 0
      result = ""
      @column_names.each {|k| result << "#{quote_identifier(k)}=?," }
      result.chop
    end

    def typecast_values
      column_values.map do |value|
        case value
        when Array
          value.join(",")
        when BSON::ObjectId
          value.to_s
        else
          value
        end
      end
    end

    def quote_identifier(name)
      %{"#{name}"}
    end

    class Queue < DelegateClass(Array)
      include Singleton

      def current
        last
      end

      protected

      def initialize
        super([])
      end
    end

    class BlockRequired < ArgumentError; end

  end
end
