require 'delegate'
require 'singleton'

#TODO: Use logger and log throughout
#TODO: Counters and timers

module Squealer
  class Target

    def self.current
      Queue.instance.current
    end

    def initialize(database_connection, table_name, row_id=nil, &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?

      @table_name = table_name.to_s
      @binding = block.binding

      verify_table_name_in_scope

      @row_id = obtain_row_id(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 obtain_row_id(row_id)
      #TODO: Remove in version 1.3 - just call infer_row_id in initialize
      if row_id != nil
        puts "\033[33mWARNING - squealer:\033[0m the 'target' row_id parameter is deprecated and will be invalid in version 1.3 and above. Remove it, and ensure the table_name matches a variable containing a hashmap with an _id key"
        row_id
      else
        infer_row_id
      end
    end

    def infer_row_id
      eval "#{@table_name}._id", @binding, __FILE__, __LINE__
    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'
    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

      @sql = "INSERT #{@table_name}"
      @sql << " (#{pk_name}#{column_names}) VALUES (?#{column_value_markers})"
      @sql << " ON DUPLICATE KEY UPDATE #{column_markers}"

      execute_sql(@sql)

      Queue.instance.pop
    end

    def self.targets
      @@targets
    end

    def targets
      @@targets
    end

    def execute_sql(sql)
      statement = Database.instance.export.prepare(sql)
      values = typecast_values * 2

      statement.send(:execute, @row_id, *values) #expand values into distinct arguments
    rescue Mysql::Error, TypeError
      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 true
          1
        when false
          0
        when Symbol
          value.to_s
        when Array
          value.join(",")
        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