require 'xmlrpc/marshal'
require 'action_web_service/client/xmlrpc_client'

module XMLRPC # :nodoc:
  class FaultException # :nodoc:
    alias :message :faultString
  end
end

module ActionWebService # :nodoc:
  module API # :nodoc: 
    class Base # :nodoc:
      def self.xmlrpc_client(endpoint_uri, options={})
        ActionWebService::Client::XmlRpc.new self, endpoint_uri, options
      end
    end
  end

  module Protocol # :nodoc:
    module XmlRpc # :nodoc:
      def self.included(base)
        base.register_protocol(XmlRpcProtocol)
      end
      
      class XmlRpcProtocol < AbstractProtocol # :nodoc:
        def self.create(controller)
          XmlRpcProtocol.new
        end

        def decode_action_pack_request(action_pack_request)
          service_name = action_pack_request.parameters['action']
          decode_request(action_pack_request.raw_post, service_name)
        end

        def decode_request(raw_request, service_name)
          method_name, params = XMLRPC::Marshal.load_call(raw_request)
          Request.new(self, method_name, params, service_name)
        end

        def encode_request(method_name, params, param_types)
          if param_types
            params = params.dup
            param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) }
          end
          XMLRPC::Marshal.dump_call(method_name, *params)
        end

        def decode_response(raw_response)
          [nil, XMLRPC::Marshal.load_response(raw_response)]
        end

        def encode_response(method_name, return_value, return_type, protocol_options={})
          if return_value && return_type
            return_value = value_to_xmlrpc_wire_format(return_value, return_type)
          end
          return_value = false if return_value.nil?
          raw_response = XMLRPC::Marshal.dump_response(return_value)
          Response.new(raw_response, 'text/xml', return_value)
        end

        def encode_multicall_response(responses, protocol_options={})
          result = responses.map do |return_value, return_type|
            if return_value && return_type
              return_value = value_to_xmlrpc_wire_format(return_value, return_type) 
              return_value = [return_value] unless return_value.nil?
            end
            return_value = false if return_value.nil?
            return_value
          end
          raw_response = XMLRPC::Marshal.dump_response(result)
          Response.new(raw_response, 'text/xml', result)
        end

        def protocol_client(api, protocol_name, endpoint_uri, options={})
          return nil unless protocol_name == :xmlrpc
          ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
        end

        def value_to_xmlrpc_wire_format(value, value_type)
          if value_type.array?
            value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) }
          else
            if value.is_a?(ActionWebService::Struct)
              struct = {}
              value.class.members.each do |name, type|
                member_value = value[name]
                next if member_value.nil?
                struct[name.to_s] = value_to_xmlrpc_wire_format(member_value, type)
              end
              struct
            elsif value.is_a?(ActiveRecord::Base)
              struct = {}
              value.attributes.each do |key, member_value|
                next if member_value.nil?
                struct[key.to_s] = member_value
              end
              struct
            elsif value.is_a?(ActionWebService::Base64)
              XMLRPC::Base64.new(value)
            elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
              XMLRPC::FaultException.new(2, value.message)
            else
              value
            end
          end
        end
      end
    end
  end
end
