module ActiveShipping #:nodoc:
  class Location
    ADDRESS_TYPES = %w(residential commercial po_box)

    ATTRIBUTE_ALIASES = {
      name: [:name],
      country: [:country_code, :country],
      postal_code: [:postal_code, :zip, :postal],
      province: [:province_code, :state_code, :territory_code, :region_code, :province, :state, :territory, :region],
      city: [:city, :town],
      address1: [:address1, :address, :street],
      address2: [:address2],
      address3: [:address3],
      phone: [:phone, :phone_number],
      fax: [:fax, :fax_number],
      address_type: [:address_type],
      company_name: [:company, :company_name],
    }.freeze

    attr_reader :options,
                :country,
                :postal_code,
                :province,
                :city,
                :name,
                :address1,
                :address2,
                :address3,
                :phone,
                :fax,
                :address_type,
                :company_name

    alias_method :zip, :postal_code
    alias_method :postal, :postal_code
    alias_method :state, :province
    alias_method :territory, :province
    alias_method :region, :province
    alias_method :company, :company_name

    def initialize(options = {})
      @country = if options[:country].nil? || options[:country].is_a?(ActiveUtils::Country)
        options[:country]
      else
        ActiveUtils::Country.find(options[:country])
      end

      @postal_code = options[:postal_code] || options[:postal] || options[:zip]
      @province = options[:province] || options[:state] || options[:territory] || options[:region]
      @city = options[:city]
      @name = options[:name]
      @address1 = options[:address1]
      @address2 = options[:address2]
      @address3 = options[:address3]
      @phone = options[:phone]
      @fax = options[:fax]
      @company_name = options[:company_name] || options[:company]

      self.address_type = options[:address_type]
    end

    def self.from(object, options = {})
      return object if object.is_a?(ActiveShipping::Location)

      attributes = {}

      hash_access = object.respond_to?(:[])

      ATTRIBUTE_ALIASES.each do |attribute, aliases|
        aliases.detect do |sym|
          value = object[sym] if hash_access
          if !value &&
            object.respond_to?(sym) &&
            (!hash_access || !Hash.public_instance_methods.include?(sym))
            value = object.send(sym)
          end

          attributes[attribute] = value if value
        end
      end

      attributes.delete(:address_type) unless ADDRESS_TYPES.include?(attributes[:address_type].to_s)

      new(attributes.update(options))
    end

    def country_code(format = :alpha2)
      @country.nil? ? nil : @country.code(format).value
    end

    def residential?
      @address_type == 'residential'
    end

    def commercial?
      @address_type == 'commercial'
    end

    def po_box?
      @address_type == 'po_box'
    end

    def unknown?
      country_code == 'ZZ'
    end

    def address_type=(value)
      return unless value.present?
      raise ArgumentError.new("address_type must be one of #{ADDRESS_TYPES.join(', ')}") unless ADDRESS_TYPES.include?(value.to_s)
      @address_type = value.to_s
    end

    def to_hash
      {
        country: country_code,
        postal_code: postal_code,
        province: province,
        city: city,
        name: name,
        address1: address1,
        address2: address2,
        address3: address3,
        phone: phone,
        fax: fax,
        address_type: address_type,
        company_name: company_name
      }
    end

    def to_s
      prettyprint.gsub(/\n/, ' ')
    end

    def prettyprint
      chunks = [@name, @address1, @address2, @address3]
      chunks << [@city, @province, @postal_code].reject(&:blank?).join(', ')
      chunks << @country
      chunks.reject(&:blank?).join("\n")
    end

    def inspect
      string = prettyprint
      string << "\nPhone: #{@phone}" unless @phone.blank?
      string << "\nFax: #{@fax}" unless @fax.blank?
      string
    end

    # Returns the postal code as a properly formatted Zip+4 code, e.g. "77095-2233"
    def zip_plus_4
      "#{$1}-#{$2}" if /(\d{5})-?(\d{4})/ =~ @postal_code
    end

    def address2_and_3
      [address2, address3].reject(&:blank?).join(", ")
    end

    def ==(other)
      to_hash == other.to_hash
    end
  end
end