/*=============================================================================
                                  registry
===============================================================================
  Test the method registry (server) C++ facilities of XML-RPC for C/C++.
  
=============================================================================*/

#include <string>

#include "xmlrpc-c/girerr.hpp"
using girerr::error;
using girerr::throwf;
#include "xmlrpc-c/base.hpp"
#include "xmlrpc-c/registry.hpp"

#include "tools.hpp"
#include "registry.hpp"

using namespace xmlrpc_c;
using namespace std;


static string const
xmlPrologue("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");

static string const
apacheUrl("http://ws.apache.org/xmlrpc/namespaces/extensions");

static string const
xmlnsApache("xmlns:ex=\"" + apacheUrl + "\"");


namespace {
string const noElementFoundXml(
    xmlPrologue +
    "<methodResponse>\r\n"
    "<fault>\r\n"
    "<value><struct>\r\n"
    "<member><name>faultCode</name>\r\n"
    "<value><i4>-503</i4></value></member>\r\n"
    "<member><name>faultString</name>\r\n"
    "<value><string>Call XML not a proper XML-RPC call.  "
    "Call is not valid XML.  no element found</string></value>"
    "</member>\r\n"
    "</struct></value>\r\n"
    "</fault>\r\n"
    "</methodResponse>\r\n"
    );

string const sampleAddGoodCallXml(
    xmlPrologue +
    "<methodCall>\r\n"
    "<methodName>sample.add</methodName>\r\n"
    "<params>\r\n"
    "<param><value><i4>5</i4></value></param>\r\n"
    "<param><value><i4>7</i4></value></param>\r\n"
    "</params>\r\n"
    "</methodCall>\r\n"
    );

string const sampleAddGoodResponseXml(
    xmlPrologue +
    "<methodResponse>\r\n"
    "<params>\r\n"
    "<param><value><i4>12</i4></value></param>\r\n"
    "</params>\r\n"
    "</methodResponse>\r\n"
    );


string const sampleAddBadCallXml(
    xmlPrologue +
    "<methodCall>\r\n"
    "<methodName>sample.add</methodName>\r\n"
    "<params>\r\n"
    "<param><value><i4>5</i4></value></param>\r\n"
    "</params>\r\n"
    "</methodCall>\r\n"
    );

string const sampleAddBadResponseXml(
    xmlPrologue +
    "<methodResponse>\r\n"
    "<fault>\r\n"
    "<value><struct>\r\n"
    "<member><name>faultCode</name>\r\n"
    "<value><i4>-501</i4></value></member>\r\n"
    "<member><name>faultString</name>\r\n"
    "<value><string>Not enough parameters</string></value></member>\r\n"
    "</struct></value>\r\n"
    "</fault>\r\n"
    "</methodResponse>\r\n"
    );


string const nonexistentMethodCallXml(
    xmlPrologue +
    "<methodCall>\r\n"
    "<methodName>nosuchmethod</methodName>\r\n"
    "<params>\r\n"
    "<param><value><i4>5</i4></value></param>\r\n"
    "<param><value><i4>7</i4></value></param>\r\n"
    "</params>\r\n"
    "</methodCall>\r\n"
    );

string const nonexistentMethodYesDefResponseXml(
    xmlPrologue +
    "<methodResponse>\r\n"
    "<params>\r\n"
    "<param><value><string>no such method: nosuchmethod</string>"
    "</value></param>\r\n"
    "</params>\r\n"
    "</methodResponse>\r\n"
    );

string const nonexistentMethodNoDefResponseXml(
    xmlPrologue +
    "<methodResponse>\r\n"
    "<fault>\r\n"
    "<value><struct>\r\n"
    "<member><name>faultCode</name>\r\n"
    "<value><i4>-506</i4></value></member>\r\n"
    "<member><name>faultString</name>\r\n"
    "<value><string>Method 'nosuchmethod' not defined</string></value>"
    "</member>\r\n"
    "</struct></value>\r\n"
    "</fault>\r\n"
    "</methodResponse>\r\n"
    );

} // namespace


string const echoI8ApacheCall(
    xmlPrologue +
    "<methodCall " + xmlnsApache + ">\r\n"
    "<methodName>echo</methodName>\r\n"
    "<params>\r\n"
    "<param><value><ex:i8>5</ex:i8></value></param>\r\n"
    "</params>\r\n"
    "</methodCall>\r\n"
    );

string const echoI8ApacheResponse(
    xmlPrologue +
    "<methodResponse " + xmlnsApache + ">\r\n"
    "<params>\r\n"
    "<param><value><ex:i8>5</ex:i8></value></param>\r\n"
    "</params>\r\n"
    "</methodResponse>\r\n"
    );

string const echoNilApacheCall(
    xmlPrologue +
    "<methodCall " + xmlnsApache + ">\r\n"
    "<methodName>echo</methodName>\r\n"
    "<params>\r\n"
    "<param><value><nil/></value></param>\r\n"
    "</params>\r\n"
    "</methodCall>\r\n"
    );

string const echoNilApacheResponse(
    xmlPrologue +
    "<methodResponse " + xmlnsApache + ">\r\n"
    "<params>\r\n"
    "<param><value><ex:nil/></value></param>\r\n"
    "</params>\r\n"
    "</methodResponse>\r\n"
    );


class sampleAddMethod : public method {
public:
    sampleAddMethod() {
        this->_signature = "i:ii";
        this->_help = "This method adds two integers together";
    }
    void
    execute(xmlrpc_c::paramList const& paramList,
            value *             const  retvalP) {
        
        int const addend(paramList.getInt(0));
        int const adder(paramList.getInt(1));
        
        paramList.verifyEnd(2);
        
        *retvalP = value_int(addend + adder);
    }
};



class nameMethod : public defaultMethod {

    void
    execute(string              const& methodName,
            xmlrpc_c::paramList const& ,  // paramList
            value *             const  retvalP) {
        
        *retvalP = value_string(string("no such method: ") + methodName);
    }
};



class echoMethod : public method {
public:
    void
    execute(xmlrpc_c::paramList const& paramList,
            value *             const  retvalP) {
        
        paramList.verifyEnd(1);
        
        *retvalP = paramList[0];
    }
};



class registryRegMethodTestSuite : public testSuite {

public:
    virtual string suiteName() {
        return "registryRegMethodTestSuite";
    }
    virtual void runtests(unsigned int const) {

        xmlrpc_c::registry myRegistry;
        
        myRegistry.addMethod("sample.add", 
                             xmlrpc_c::methodPtr(new sampleAddMethod));
        
        myRegistry.disableIntrospection();
        {
            string response;
            myRegistry.processCall("", &response);
            TEST(response == noElementFoundXml);
        }
        {
            string response;
            myRegistry.processCall(sampleAddGoodCallXml, &response);
            TEST(response == sampleAddGoodResponseXml);
        }
        {
            string response;
            myRegistry.processCall(sampleAddBadCallXml, &response);
            TEST(response == sampleAddBadResponseXml);
        }
    }
};



class registryDefaultMethodTestSuite : public testSuite {

public:
    virtual string suiteName() {
        return "registryDefaultMethodTestSuite";
    }
    virtual void runtests(unsigned int const) {

        xmlrpc_c::registry myRegistry;
        
        myRegistry.addMethod("sample.add", methodPtr(new sampleAddMethod));

        {
            string response;
            myRegistry.processCall(sampleAddGoodCallXml, &response);
            TEST(response == sampleAddGoodResponseXml);
        }
        {
            string response;
            myRegistry.processCall(nonexistentMethodCallXml, &response);
            TEST(response == nonexistentMethodNoDefResponseXml);
        }
        // We're actually violating the spirit of setDefaultMethod by
        // doing this to a registry that's already been used, but as long
        // as it works, it's a convenient way to implement this test.
        myRegistry.setDefaultMethod(defaultMethodPtr(new nameMethod));

        {
            string response;
            myRegistry.processCall(nonexistentMethodCallXml, &response);
            TEST(response == nonexistentMethodYesDefResponseXml);
        }
    }
};



class registryShutdownTestSuite : public testSuite {

public:
    virtual string suiteName() {
        return "registryShutdownTestSuite";
    }
    virtual void runtests(unsigned int const) {

        xmlrpc_c::registry myRegistry;

        class myshutdown : public xmlrpc_c::registry::shutdown {
        public:
            void doit(string const&,
                      void * const) const {
                
            }
        };

        myshutdown shutdown;
        
        myRegistry.setShutdown(&shutdown);
    }
};



string
registryTestSuite::suiteName() {
    return "registryTestSuite";
}



void
registryTestSuite::runtests(unsigned int const indentation) {

    {
        registryPtr myRegistryP(new registry);
    
        myRegistryP->addMethod("sample.add", methodPtr(new sampleAddMethod));
    }

    registryRegMethodTestSuite().run(indentation+1);
    registryDefaultMethodTestSuite().run(indentation+1);

    registry myRegistry;
        
    myRegistry.addMethod("sample.add", methodPtr(new sampleAddMethod));
    myRegistry.addMethod("echo", methodPtr(new echoMethod));

    string response;

    myRegistry.disableIntrospection();

    myRegistry.setDialect(xmlrpc_dialect_i8);

    myRegistry.setDialect(xmlrpc_dialect_apache);

    registryShutdownTestSuite().run(indentation+1);

    myRegistry.processCall(echoI8ApacheCall, &response);

    TEST(response == echoI8ApacheResponse);

    myRegistry.processCall(echoNilApacheCall, &response);

    TEST(response == echoNilApacheResponse);

    EXPECT_ERROR(  // invalid dialect
        myRegistry.setDialect(static_cast<xmlrpc_dialect>(300));
        );
}
