require 'cgi'
require 'builder'

module ActiveMerchant
  module Shipping
    class Shipwire < Carrier
      self.retry_safe = true
      
      cattr_reader :name
      @@name = "Shipwire"
      
      URL = 'https://api.shipwire.com/exec/RateServices.php'
      SCHEMA_URL = 'http://www.shipwire.com/exec/download/RateRequest.dtd'      
      WAREHOUSES = { 'CHI' => 'Chicago',
                     'LAX' => 'Los Angeles',
                     'REN' => 'Reno',
                     'VAN' => 'Vancouver',
                     'TOR' => 'Toronto',
                     'UK'  => 'United Kingdom'
                   }
                   
      CARRIERS = [ "UPS", "USPS", "FedEx", "Royal Mail", "Parcelforce", "Pharos", "Eurotrux", "Canada Post", "DHL" ]
                   
      SUCCESS = "OK"
      SUCCESS_MESSAGE = "Successfully received the shipping rates"
      NO_RATES_MESSAGE = "No shipping rates could be found for the destination address"
      REQUIRED_OPTIONS = [:login, :password].freeze
      
      def find_rates(origin, destination, packages, options = {})
        requires!(options, :items)
        commit(origin, destination, options)
      end
      
      def valid_credentials?
        location = self.class.default_location
        find_rates(location, location, Package.new(100, [5,15,30]),
          :items => [ { :sku => '', :quantity => 1 } ]
        )
      rescue ActiveMerchant::Shipping::ResponseError => e
        e.message != "Could not verify e-mail/password combination"
      end
      
      private
      def requirements
        REQUIRED_OPTIONS
      end
      
      def build_request(destination, options)
        xml = Builder::XmlMarkup.new
        xml.instruct!
        xml.declare! :DOCTYPE, :RateRequest, :SYSTEM, SCHEMA_URL
        xml.tag! 'RateRequest' do
          add_credentials(xml)
          add_order(xml, destination, options) 
        end
        xml.target!
      end
      
      def add_credentials(xml)
        xml.tag! 'EmailAddress', @options[:login]
        xml.tag! 'Password', @options[:password]
      end

      def add_order(xml, destination, options)
        xml.tag! 'Order', :id => options[:order_id] do
          xml.tag! 'Warehouse', options[:warehouse] || '00'

          add_address(xml, destination)
          Array(options[:items]).each_with_index do |line_item, index|
            add_item(xml, line_item, index)
          end
        end
      end

      def add_address(xml, destination)
        xml.tag! 'AddressInfo', :type => 'Ship' do
          if destination.name.present?
            xml.tag! 'Name' do
              xml.tag! 'Full', destination.name
            end
          end
          xml.tag! 'Address1', destination.address1
          xml.tag! 'Address2', destination.address2 unless destination.address2.blank?
          xml.tag! 'Address3', destination.address3 unless destination.address3.blank?
          xml.tag! 'City', destination.city
          xml.tag! 'State', destination.state unless destination.state.blank?
          xml.tag! 'Country', destination.country_code
          xml.tag! 'Zip', destination.zip  unless destination.zip.blank?
        end
      end
      
     # Code is limited to 12 characters
      def add_item(xml, item, index)
        xml.tag! 'Item', :num => index do
          xml.tag! 'Code', item[:sku]
          xml.tag! 'Quantity', item[:quantity]
        end
      end

      def commit(origin, destination, options)
        request = build_request(destination, options)
        save_request(request)
        
        response = parse( ssl_post(URL, "RateRequestXML=#{CGI.escape(request)}") )
        
        RateResponse.new(response["success"], response["message"], response, 
          :xml     => response,
          :rates   => build_rate_estimates(response, origin, destination),
          :request => last_request
        )
      end
      
      def build_rate_estimates(response, origin, destination)
        response["rates"].collect do |quote|
          RateEstimate.new(origin, destination, carrier_for(quote["service"]), quote["service"],
            :service_code  => quote["method"],
            :total_price   => quote["cost"],
            :currency      => quote["currency"]
          )
        end
      end
      
      def carrier_for(service)
        CARRIERS.dup.find{ |carrier| service.to_s =~ /^#{carrier}/i } || service.to_s.split(" ").first
      end

      def parse(xml)
        response = {}
        response["rates"] = []

        document = REXML::Document.new(xml)
    
        response["status"] = parse_child_text(document.root, "Status")
             
        document.root.elements.each("Order/Quotes/Quote") do |e|
          rate = {}
          rate["method"]    = e.attributes["method"]
          rate["warehouse"] = parse_child_text(e, "Warehouse")
          rate["service"]   = parse_child_text(e, "Service")
          rate["cost"]      = parse_child_text(e, "Cost")
          rate["currency"]  = parse_child_attribute(e, "Cost", "currency")
          response["rates"] << rate
        end

        if response["status"] == SUCCESS && response["rates"].any?
          response["success"] = true
          response["message"] = SUCCESS_MESSAGE
        elsif response["status"] == SUCCESS && response["rates"].empty?
          response["success"] = false
          response["message"] = NO_RATES_MESSAGE
        else
          response["success"] = false
          response["message"] = parse_child_text(document.root, "ErrorMessage")
        end
        
        response
      end
      
      def parse_child_text(parent, name)
        if element = parent.elements[name]
          element.text
        end
      end
      
      def parse_child_attribute(parent, name, attribute)
        if element = parent.elements[name]
          element.attributes[attribute]
        end
      end      
    end
  end
end