require "pluck_map/version"
require "pluck_map/attribute"

module PluckMap
  class Presenter
    attr_reader :attributes

    def initialize(&block)
      @attributes = []
      if block.arity == 1
        block.call(self)
      else
        instance_eval(&block)
      end
      @initialized = true

      @attributes_by_id = attributes.index_by(&:id).with_indifferent_access
      @keys = attributes.flat_map(&:keys).uniq

      define_presenters!
    end

    def method_missing(attribute_name, *args, &block)
      return super if initialized?
      attributes.push PluckMap::Attribute.new(attribute_name, args.extract_options!)
      :attribute_added
    end

    def no_map?
      attributes.all?(&:no_map?)
    end

  protected

    def define_presenters!
      define_to_h!
    end

    def initialized?
      @initialized
    end

    def pluck(query)
      # puts "\e[95m#{query.select(*selects(query.table_name)).to_sql}\e[0m"
      results = benchmark("pluck(#{query.table_name})") { query.pluck(*selects(query.table_name)) }
      return results unless block_given?
      benchmark("map(#{query.table_name})") { yield results }
    end

    def benchmark(title)
      result = nil
      ms = Benchmark.ms { result = yield }
      Rails.logger.info "\e[33m#{title}: \e[1m%.1fms\e[0m" % ms
      result
    end

  private
    attr_reader :attributes_by_id, :keys

    def selects(table_name)
      attributes.flat_map do |attribute|
        if attribute.selects.length != 1
          attribute.selects
        else
          select = attribute.selects[0]
          select = "\"#{table_name}\".\"#{select}\"" if select.is_a?(Symbol)
          "#{select} AS \"#{attribute.alias}\""
        end
      end.uniq
    end

    def invoke(attribute_id, object)
      attributes_by_id.fetch(attribute_id).apply(object)
    end

    def define_to_h!
      ruby = <<-RUBY
      def to_h(query)
        pluck(query) do |results|
          results.map { |values| values = Array(values); { #{attributes.map { |attribute| "#{attribute.name.inspect} => #{(attribute.to_ruby(keys))}"}.join(", ")} } }
        end
      end
      RUBY
      # puts "\e[34m#{ruby}\e[0m" # <-- helps debugging PluckMapPresenter
      class_eval ruby, __FILE__, __LINE__ - 7
    end

  end
end