module Caren
  
  module Exceptions
    
    class StandardError < ::StandardError ; end
    
    class SignatureMismatch < Caren::Exceptions::StandardError ; end
    
    class ServerSideError < Caren::Exceptions::StandardError
      
      attr_accessor :errors
      
      def initialize errors=[]
        self.errors = errors
      end
      
    end
    
  end

  class Api
    class << self      
      # The public key file path used to verify request coming from Caren
      attr_accessor :caren_public_key_path
      
      # The private key file path used to sign requests coming from you
      attr_accessor :private_key_path
      
      # The care provider url provided by Caren. i.e. https://example.caren-cares.com
      attr_accessor :url
    end
    
    # The public key file used to verify request coming from Caren
    def self.caren_public_key
      @caren_public_key ||= OpenSSL::PKey::RSA.new(File.read(self.caren_public_key_path))
    end
    
    # The private key file used to sign requests coming from you
    def self.private_key
      @private_key ||= OpenSSL::PKey::RSA.new(File.read(self.private_key_path))
    end
    
    def self.put url, xml
      begin
        response = RestClient.put url, xml, :content_type => :xml, :accept => :xml, :signature => Caren::Api.sign(xml)
        return check_signature(response)
      rescue RestClient::Exception => e
        handle_error(e.response)
      end
    end
    
    def self.post url, xml
      begin
        response = RestClient.post url, xml, :content_type => :xml, :accept => :xml, :signature => Caren::Api.sign(xml)
        return check_signature(response)
      rescue RestClient::Exception => e
        handle_error(e.response)
      end
    end
    
    def self.delete url
      begin
        response = RestClient.delete url, :content_type => :xml, :accept => :xml, :signature => Caren::Api.sign
        return check_signature(response)
      rescue RestClient::Exception => e
        handle_error(e.response)
      end
    end
    
    def self.get url
      begin
        response = RestClient.get url, :content_type => :xml, :accept => :xml, :signature => Caren::Api.sign
        return check_signature(response)
      rescue RestClient::Exception => e
        handle_error(e.response)
      end
    end
    
    # Sign your string using Caren::Api.private_key
    def self.sign string=""
      encrypted_digest = Caren::Api.private_key.sign( "sha1", string )
      signature = CGI.escape(Base64.encode64(encrypted_digest))
      return signature
    end
        
    private
        
    # Check the signature of the response from rest-client
    def self.check_signature response
      return response if self.verify_signature( response.headers[:signature], response )
      raise Caren::Exceptions::SignatureMismatch.new
    end
    
    # Verify the signature using the caren public key file
    def self.verify_signature signature, string=""
      signature = Base64.decode64(CGI.unescape(signature))
      Caren::Api.caren_public_key.verify( "sha1", signature, string )
    end
    
    # Raise a Caren exception on errors
    def self.handle_error response
      errors = []
      doc = REXML::Document.new(response)
      doc.elements.each('errors/error') do |error|
        if error.attributes["category"] == "validation"
          attrs  = { :on => error.attributes["on"].to_s.underscore.to_sym }
          errors << Caren::ValidationError.new( error.attributes["category"], error.text.strip, attrs )
        else
          errors << Caren::Error.new( error.attributes["category"], error.text.strip )
        end
      end        
      raise Caren::Exceptions::ServerSideError.new(errors)
    end
    
  end

end