module Mutations

  # Offers a non-localized, english only, non configurable way to get error messages.  This probably isnt good enough for users as-is.
  class DefaultErrorMessageCreator

    MESSAGES = Hash.new("is invalid").tap do |h|
      h.merge!(
        # General
        :nils => "can't be nil",
        :required => "is required",

        # Datatypes
        :string => "isn't a string",
        :integer => "isn't an integer",
        :boolean => "isn't a boolean",
        :hash => "isn't a hash",
        :array => "isn't an array",
        :model => "isn't the right class",

        # Date
        :date => "date doesn't exist",
        :before => "isn't before given date",
        :after => "isn't after given date",

        # String
        :empty => "can't be blank",
        :max_length => "is too long",
        :min_length => "is too short",
        :matches => "isn't in the right format",
        :in => "isn't an option",

        # Array
        :class => "isn't the right class",

        # Integer
        :min => "is too small",
        :max => "is too big",

        # Model
        :new_records => "isn't a saved model"
      )
    end

    # key: the name of the field, eg, :email. Could be nil if it's an array element
    # error_symbol: the validation symbol, eg, :matches or :required
    # options:
    #  :index -- index of error if it's in an array
    def message(key, error_symbol, options = {})
      if options[:index]
        "#{titleize(key || 'array')}[#{options[:index]}] #{MESSAGES[error_symbol]}"
      else
        "#{titleize(key)} #{MESSAGES[error_symbol]}"
      end
    end

    def titleize(key)
      key = key.to_s.downcase
      if key == "id"
        "ID"
      elsif key.end_with?("_id")
        "#{key.titleize} ID"
      else
        key.titleize
      end
    end
  end

  class ErrorAtom

    # NOTE: in the future, could also pass in:
    #  - error type
    #  - value (eg, string :name, length: 5 # value=5)

    # ErrorAtom.new(:name, :too_short)
    # ErrorAtom.new(:name, :too_short, message: "is too short")
    def initialize(key, error_symbol, options = {})
      @key = key
      @symbol = error_symbol
      @message = options[:message]
      @index = options[:index]
    end

    def symbolic
      @symbol
    end

    def message
      @message ||= Mutations.error_message_creator.message(@key, @symbol, :index => @index)
    end

    def message_list
      Array(message)
    end
  end

  # mutation.errors is an ErrorHash instance like this:
  # {
  #   email: ErrorAtom(:matches),
  #   name: ErrorAtom(:too_weird, message: "is too weird"),
  #   adddress: { # Nested ErrorHash object
  #     city: ErrorAtom(:not_found, message: "That's not a city, silly!"),
  #     state: ErrorAtom(:in)
  #   }
  # }
  class ErrorHash < Hash

    # Returns a nested HashWithIndifferentAccess where the values are symbols.  Eg:
    # {
    #   email: :matches,
    #   name: :too_weird,
    #   adddress: {
    #     city: :not_found,
    #     state: :in
    #   }
    # }
    def symbolic
      HashWithIndifferentAccess.new.tap do |hash|
        each do |k, v|
          hash[k] = v.symbolic
        end
      end
    end

    # Returns a nested HashWithIndifferentAccess where the values are messages. Eg:
    # {
    #   email: "isn't in the right format",
    #   name: "is too weird",
    #   adddress: {
    #     city: "is not a city",
    #     state: "isn't a valid option"
    #   }
    # }
    def message
      HashWithIndifferentAccess.new.tap do |hash|
        each do |k, v|
          hash[k] = v.message
        end
      end
    end

    # Returns a flat array where each element is a full sentence. Eg:
    # [
    #   "Email isn't in the right format.",
    #   "Name is too weird",
    #   "That's not a city, silly!",
    #   "State isn't a valid option."
    # ]
    def message_list
      list = []
      each do |k, v|
        list.concat(v.message_list)
      end
      list
    end
  end

  class ErrorArray < Array
    def symbolic
      map {|e| e && e.symbolic }
    end

    def message
      map {|e| e && e.message }
    end

    def message_list
      compact.map {|e| e.message_list }.flatten
    end
  end
end
