module PluckMap
  class Attribute
    attr_reader :id, :model, :selects, :name, :value, :block
    attr_accessor :indexes

    def initialize(id, model, options={})
      @id = id
      @model = model
      @selects = Array(options.fetch(:select, id))
      @name = options.fetch(:as, id)
      @block = options[:map]

      if options.key? :value
        @value = options[:value]
        @selects = []
      else
        raise ArgumentError, "You must select at least one column" if @selects.empty?
        raise ArgumentError, "You must define a block if you are going to select " <<
          "more than one expression from the database" if @selects.length > 1 && !@block

        @selects.each do |select|
          if select.is_a?(String) && !select.is_a?(Arel::Nodes::SqlLiteral)
            raise ArgumentError, "#{select.inspect} is not a valid value for :select. " <<
              "If a string of raw SQL is safe, wrap it in Arel.sql()."
          end
        end
      end
    end

    def apply(object)
      block.call(*object)
    end

    def value?
      defined?(@value)
    end

    def will_map?
      !block.nil?
    end

    def nested?
      false
    end

    def preload!(results)
    end

    # When the PluckMapPresenter performs the query, it will
    # receive an array of rows. Each row will itself be an
    # array of values.
    #
    # This method constructs a Ruby expression that will
    # extract the appropriate values from each row that
    # correspond to this Attribute.
    def to_ruby
      return @value.inspect if value?
      return "values[#{indexes[0]}]" if indexes.length == 1 && !block
      ruby = "values.values_at(#{indexes.join(", ")})"
      ruby = "invoke(:\"#{id}\", #{ruby})" if block
      ruby
    end

    def exec(values)
      return @value if value?
      return values[indexes[0]] if indexes.length == 1 && !block
      _values = values.values_at(*indexes)
      _values = apply(_values) if block
      _values
    end



    def values
      [id, selects, name, value, block]
    end

    def ==(other)
      return false if self.class != other.class
      self.values == other.values
    end

    def hash
      values.hash
    end

    def eql?(other)
      return true if self.equal?(other)
      return false if self.class != other.class
      self.values.eql?(other.values)
    end
  end
end