#include <cassert>
#include <string>
#include <memory>
#include <algorithm>

#include "xmlrpc-c/girerr.hpp"
using girerr::error;
#include "xmlrpc-c/girmem.hpp"
using girmem::autoObject;
using girmem::autoObjectPtr;
#include "xmlrpc-c/base.h"
#include "xmlrpc-c/base.hpp"
#include "env_wrap.hpp"

#include "xmlrpc-c/registry.hpp"

using namespace std;
using namespace xmlrpc_c;


namespace {

void
throwIfError(env_wrap const& env) {

    if (env.env_c.fault_occurred)
        throw(error(env.env_c.fault_string));
}


} // namespace

namespace xmlrpc_c {


method::method() : 
        _signature("?"),
        _help("No help is available for this method")
        {};



method::~method() {}



methodPtr::methodPtr(method * const methodP) {
    this->point(methodP);
}



method *
methodPtr::operator->() const {

    autoObject * const p(this->objectP);
    return dynamic_cast<method *>(p);
}



defaultMethod::~defaultMethod() {}



defaultMethodPtr::defaultMethodPtr() {}


defaultMethodPtr::defaultMethodPtr(defaultMethod * const methodP) {
    this->point(methodP);
}



defaultMethod *
defaultMethodPtr::operator->() const {

    autoObject * const p(this->objectP);
    return dynamic_cast<defaultMethod *>(p);
}



defaultMethod *
defaultMethodPtr::get() const {

    autoObject * const p(this->objectP);
    return dynamic_cast<defaultMethod *>(p);
}



registry::registry() {

    env_wrap env;

    this->c_registryP = xmlrpc_registry_new(&env.env_c);

    throwIfError(env);
}



registry::~registry(void) {

    xmlrpc_registry_free(this->c_registryP);
}



registryPtr::registryPtr() {}



registryPtr::registryPtr(registry * const registryP) {
    this->point(registryP);
}



registry *
registryPtr::operator->() const {

    autoObject * const p(this->objectP);
    return dynamic_cast<registry *>(p);
}



registry *
registryPtr::get() const {

    autoObject * const p(this->objectP);
    return dynamic_cast<registry *>(p);
}



static xmlrpc_c::paramList
pListFromXmlrpcArray(xmlrpc_value * const arrayP) {
/*----------------------------------------------------------------------------
   Convert an XML-RPC array in C (not C++) form to a parameter list object
   that can be passed to a method execute method.

   This is glue code to allow us to hook up C++ Xmlrpc-c code to 
   C Xmlrpc-c code.
-----------------------------------------------------------------------------*/
    env_wrap env;

    XMLRPC_ASSERT_ARRAY_OK(arrayP);

    unsigned int const arraySize = xmlrpc_array_size(&env.env_c, arrayP);

    assert(!env.env_c.fault_occurred);

    xmlrpc_c::paramList paramList(arraySize);
    
    for (unsigned int i = 0; i < arraySize; ++i) {
        xmlrpc_value * arrayItemP;

        xmlrpc_array_read_item(&env.env_c, arrayP, i, &arrayItemP);
        assert(!env.env_c.fault_occurred);

        paramList.add(xmlrpc_c::value(arrayItemP));
        
        xmlrpc_DECREF(arrayItemP);
    }
    return paramList;
}



static xmlrpc_value *
c_executeMethod(xmlrpc_env *   const envP,
                xmlrpc_value * const paramArrayP,
                void *         const methodPtr) {
/*----------------------------------------------------------------------------
   This is a function designed to be called via a C registry to
   execute an XML-RPC method, but use a C++ method object to do the
   work.  You register this function as the method function and a
   pointer to the C++ method object as the method data in the C
   registry.

   If we had a pure C++ registry, this would be unnecessary.

   Since we can't throw an error back to the C code, we catch anything
   the XML-RPC method's execute() method throws, and any error we
   encounter in processing the result it returns, and turn it into an
   XML-RPC method failure.  This will cause a leak if the execute()
   method actually created a result, since it will not get destroyed.
-----------------------------------------------------------------------------*/
    xmlrpc_c::method * const methodP = 
        static_cast<xmlrpc_c::method *>(methodPtr);
    xmlrpc_c::paramList const paramList(pListFromXmlrpcArray(paramArrayP));

    xmlrpc_value * retval;

    try {
        xmlrpc_c::value result;

        try {
            methodP->execute(paramList, &result);
        } catch (xmlrpc_c::fault const& fault) {
            xmlrpc_env_set_fault(envP, fault.getCode(), 
                                 fault.getDescription().c_str()); 
        } catch (girerr::error const& error) {
            xmlrpc_env_set_fault(envP, 0, error.what());
        }
        if (envP->fault_occurred)
            retval = NULL;
        else
            retval = result.cValue();
    } catch (...) {
        xmlrpc_env_set_fault(envP, XMLRPC_INTERNAL_ERROR,
                             "Unexpected error executing the code for this "
                             "particular method, detected by the Xmlrpc-c "
                             "method registry code.  The method did not "
                             "fail; rather, it did not complete at all.");
        retval = NULL;
    }
    return retval;
}
 


static xmlrpc_value *
c_executeDefaultMethod(xmlrpc_env *   const envP,
                       const char *   const , // host
                       const char *   const methodName,
                       xmlrpc_value * const paramArrayP,
                       void *         const methodPtr) {
/*----------------------------------------------------------------------------
   This is a function designed to be called via a C registry to
   execute an XML-RPC method, but use a C++ method object to do the
   work.  You register this function as the default method function and a
   pointer to the C++ default method object as the method data in the C
   registry.

   If we had a pure C++ registry, this would be unnecessary.

   Since we can't throw an error back to the C code, we catch anything
   the XML-RPC method's execute() method throws, and any error we
   encounter in processing the result it returns, and turn it into an
   XML-RPC method failure.  This will cause a leak if the execute()
   method actually created a result, since it will not get destroyed.
-----------------------------------------------------------------------------*/
    defaultMethod * const methodP = 
        static_cast<defaultMethod *>(methodPtr);
    paramList const paramList(pListFromXmlrpcArray(paramArrayP));

    xmlrpc_value * retval;

    try {
        value result;
        
        try {
            methodP->execute(methodName, paramList, &result);
        } catch (xmlrpc_c::fault const& fault) {
            xmlrpc_env_set_fault(envP, fault.getCode(), 
                                 fault.getDescription().c_str()); 
        } catch (girerr::error const& error) {
            xmlrpc_env_set_fault(envP, 0, error.what());
        }
        if (envP->fault_occurred)
            retval = NULL;
        else
            retval = result.cValue();
    } catch (...) {
        xmlrpc_env_set_fault(envP, XMLRPC_INTERNAL_ERROR,
                             "Unexpected error executing the default "
                             "method code, detected by the Xmlrpc-c "
                             "method registry code.  The method did not "
                             "fail; rather, it did not complete at all.");
        retval = NULL;
    }
    return retval;
}
 


void
registry::addMethod(string    const name,
                    methodPtr const methodP) {

    this->methodList.push_back(methodP);

    env_wrap env;
    
	xmlrpc_registry_add_method_w_doc(
        &env.env_c, this->c_registryP, NULL,
        name.c_str(), &c_executeMethod, 
        (void*) methodP.get(), 
        methodP->signature().c_str(), methodP->help().c_str());

    throwIfError(env);
}



void
registry::setDefaultMethod(defaultMethodPtr const methodP) {

    this->defaultMethodP = methodP;

    env_wrap env;
    
    xmlrpc_registry_set_default_method(
        &env.env_c, this->c_registryP,
        &c_executeDefaultMethod, (void*) methodP.get());

    throwIfError(env);
}



void
registry::disableIntrospection() {

    xmlrpc_registry_disable_introspection(this->c_registryP);
}



void
registry::processCall(string   const& callXml,
                      string * const  responseXmlP) const {
/*----------------------------------------------------------------------------
   Process an XML-RPC call whose XML is 'callXml'.

   Return the response XML as *responseXmlP.

   If we are unable to execute the call, we throw an error.  But if
   the call executes and the method merely fails in an XML-RPC sense, we
   don't.  In that case, *responseXmlP indicates the failure.
-----------------------------------------------------------------------------*/
    env_wrap env;
    xmlrpc_mem_block * output;

    // For the pure C++ version, this will have to parse 'callXml'
    // into a method name and parameters, look up the method name in
    // the registry, call the method's execute() method, then marshall
    // the result into XML and return it as *responseXmlP.  It will
    // also have to execute system methods (e.g. introspection)
    // itself.  This will be more or less like what
    // xmlrpc_registry_process_call() does.

    output = xmlrpc_registry_process_call(
        &env.env_c, this->c_registryP, NULL,
        callXml.c_str(), callXml.length());

    throwIfError(env);

    *responseXmlP = string(XMLRPC_MEMBLOCK_CONTENTS(char, output),
                           XMLRPC_MEMBLOCK_SIZE(char, output));
    
    xmlrpc_mem_block_free(output);
}

xmlrpc_registry *
registry::c_registry() const {

    return this->c_registryP;
}

}  // namespace
