#include <iostream>
#include <iomanip>
#include "XmlRpcCpp.h"


//=========================================================================
//  Test Harness
//=========================================================================
//  This is a super light-weight test harness. It's vaguely inspired by
//  Kent Beck's book on eXtreme Programming (XP)--the output is succinct,
//  new tests can be coded quickly, and the whole thing runs in a few
//  second's time.
//
//  To run the tests, type './cpptest'.
//  To check for memory leaks, install RedHat's 'memprof' utility, and
//  type 'memprof cpptest'.
//
//  If you add new tests to this file, please deallocate any data
//  structures you use in the appropriate fashion. This allows us to test
//  various destructor code for memory leaks.
//
//  (This code used iostreams instead of stdio.)

int total_tests = 0;
int total_failures = 0;

// This is a good place to set a breakpoint.
void test_failure (char* file, int line, char* statement)
{
    total_failures++;
    cout << endl
	 << file << ":" << line << ": expected (" << statement << ")" << endl;
}

#define TEST(statement) \
    do { \
        total_tests++; \
        if ((statement)) { \
            cout << "."; \
	} else { \
            test_failure(__FILE__, __LINE__, #statement); \
        } \
    } while (0)

#define TEST_PASSED() \
    do { \
        total_tests++; \
        cout << "."; \
    } while (0)

#define TEST_FAILED(reason) \
    do { \
        total_tests++; \
        test_failure(__FILE__, __LINE__, (reason)); \
    } while (0)


//=========================================================================
//  Test Suites
//=========================================================================

void test_fault (void) {

    // Create a new fault and perform basic operations.
    XmlRpcFault fault1 = XmlRpcFault(6, "Sample fault");
    TEST(fault1.getFaultCode() == 6);
    TEST(fault1.getFaultString() == "Sample fault");

    // Extract and examine the underlying xmlrpc_env struct.
    xmlrpc_env *env1 = fault1.getFaultEnv();
    TEST(env1 != NULL);
    TEST(env1->fault_occurred);
    TEST(env1->fault_code == 6);
    TEST(strcmp(env1->fault_string, "Sample fault") == 0);

    // Test our copy constructor.
    XmlRpcFault fault2 = fault1;
    TEST(fault2.getFaultCode() == 6);
    TEST(fault2.getFaultString() == "Sample fault");
    
    // Construct a fault from a pre-existing xmlrpc_env structure.
    xmlrpc_env env3;
    xmlrpc_env_init(&env3);
    xmlrpc_env_set_fault(&env3, 7, "Another fault");
    XmlRpcFault fault3 = XmlRpcFault(&env3);
    xmlrpc_env_clean(&env3);
    TEST(fault3.getFaultCode() == 7);
    TEST(fault3.getFaultString() == "Another fault");
    
    // Attempt to construct a fault from a fault-free xmlrpc_env.
    xmlrpc_env env4;
    xmlrpc_env_init(&env4);
    try {
	XmlRpcFault fault4 = XmlRpcFault(&env4);
	TEST_FAILED("Constructed invalid XmlRpcFault");
    } catch (XmlRpcFault& fault) {
	TEST_PASSED();
	TEST(fault.getFaultCode() == XMLRPC_INTERNAL_ERROR);
    }
    xmlrpc_env_clean(&env4);
}

void test_env (void) {
    
    // Perform simple environment tests.
    XmlRpcEnv env1;
    TEST(!env1.hasFaultOccurred());
    xmlrpc_env_set_fault(env1, 8, "Fault 8");
    TEST(env1.hasFaultOccurred());
    XmlRpcFault fault1 = env1.getFault();
    TEST(fault1.getFaultCode() == 8);
    TEST(fault1.getFaultString() == "Fault 8");

    // Test throwIfFaultOccurred.
    XmlRpcEnv env2;
    try {
	env2.throwIfFaultOccurred();
	TEST_PASSED();
    } catch (XmlRpcFault& fault) {
	TEST_FAILED("We threw a fault when one hadn't occurred");
    } 
    xmlrpc_env_set_fault(env2, 9, "Fault 9");
    try {
	env2.throwIfFaultOccurred();
	TEST_FAILED("A fault occurred, and we didn't throw it");
    } catch (XmlRpcFault& fault) {
	TEST_PASSED();
	TEST(fault.getFaultCode() == 9);
	TEST(fault.getFaultString() == "Fault 9");
    } 
    
    // Make sure we can't get a fault if one hasn't occurred.
    XmlRpcEnv env3;
    try {
	XmlRpcFault fault3 = env3.getFault();
	TEST_FAILED("We retrieved a non-existant fault");
    } catch (XmlRpcFault& fault) {
	TEST_PASSED();
	TEST(fault.getFaultCode() == XMLRPC_INTERNAL_ERROR);
    }
}

void test_value (void) {
    XmlRpcEnv env;

    // Test basic reference counting behavior.
    xmlrpc_value *v = xmlrpc_build_value(env, "i", (xmlrpc_int32) 1);
    env.throwIfFaultOccurred();
    XmlRpcValue val1 = XmlRpcValue(v, XmlRpcValue::CONSUME_REFERENCE);
    v = xmlrpc_build_value(env, "i", (xmlrpc_int32) 2);
    env.throwIfFaultOccurred();
    XmlRpcValue val2 = v;
    xmlrpc_DECREF(v);

    // Borrow a reference.
    v = xmlrpc_build_value(env, "i", (xmlrpc_int32) 3);
    env.throwIfFaultOccurred();
    XmlRpcValue val3 = XmlRpcValue(v, XmlRpcValue::CONSUME_REFERENCE);
    xmlrpc_value *borrowed = val3.borrowReference();
    TEST(borrowed == v);

    // Make a reference.
    v = xmlrpc_build_value(env, "i", (xmlrpc_int32) 4);
    env.throwIfFaultOccurred();
    XmlRpcValue val4 = XmlRpcValue(v, XmlRpcValue::CONSUME_REFERENCE);
    xmlrpc_value *made = val4.makeReference();
    TEST(made == v);
    xmlrpc_DECREF(made);

    // Test our default constructor.
    XmlRpcValue val5;
    TEST(val5.getBool() == false);

    // Test our type introspection.
    TEST(XmlRpcValue::makeInt(0).getType() == XMLRPC_TYPE_INT);
    
    // Test our basic data types.
    TEST(XmlRpcValue::makeInt(30).getInt() == 30);
    TEST(XmlRpcValue::makeInt(-30).getInt() == -30);
    TEST(XmlRpcValue::makeBool(true).getBool() == true);
    TEST(XmlRpcValue::makeBool(false).getBool() == false);
    TEST(XmlRpcValue::makeDateTime("19980717T14:08:55").getRawDateTime() ==
	 "19980717T14:08:55");
    TEST(XmlRpcValue::makeString("foo").getString() == "foo");
    TEST(XmlRpcValue::makeString("bar", 3).getString() == "bar");
    TEST(XmlRpcValue::makeString("bar", 3).getString() == "bar");
    TEST(XmlRpcValue::makeString("a\0b").getString() == string("a\0b"));
    XmlRpcValue::makeArray().getArray();
    XmlRpcValue::makeStruct().getStruct();

    // Test Base64 values.
    const unsigned char *b64_data;
    size_t b64_len;
    XmlRpcValue val6 = XmlRpcValue::makeBase64((unsigned char*) "a\0\0b", 4);
    val6.getBase64(b64_data, b64_len);
    TEST(b64_len == 4);
    TEST(memcmp(b64_data, "a\0\0b", 4) == 0);

    // Test arrays.
    XmlRpcValue array = XmlRpcValue::makeArray();
    TEST(array.arraySize() == 0);
    array.arrayAppendItem(XmlRpcValue::makeString("foo"));
    TEST(array.arraySize() == 1);
    array.arrayAppendItem(XmlRpcValue::makeString("bar"));
    TEST(array.arraySize() == 2);
    TEST(array.arrayGetItem(0).getString() == "foo");
    TEST(array.arrayGetItem(1).getString() == "bar");

    // Test structs.
    XmlRpcValue strct = XmlRpcValue::makeStruct();
    TEST(strct.structSize() == 0);
    strct.structSetValue("foo", XmlRpcValue::makeString("fooval"));
    TEST(strct.structSize() == 1);
    strct.structSetValue("bar", XmlRpcValue::makeString("barval"));
    TEST(strct.structSize() == 2);
    TEST(strct.structHasKey("bar"));
    TEST(!strct.structHasKey("nosuch"));
    for (int i = 0; i < strct.structSize(); i++) {
	string key;
	XmlRpcValue value;
	strct.structGetKeyAndValue(i, key, value);
	TEST(key + "val" == value.getString());
    }
}

void test_errors (void) {
    // XXX - Test typechecks on get* methods.
    // XXX - Test typechceks on array and struct methods.
    // XXX - Test bounds checks on arrayGetItem, structGetKeyAndValue.
}


//=========================================================================
//  Test Driver
//=========================================================================

int main (int argc, char** argv)
{
    try {

	// Add your test suites here.
	test_fault();
	test_env();
        test_value();
	test_errors();

    } catch (XmlRpcFault& fault) {
	cout << "Unexpected XML-RPC fault when running test suites." << endl
	     << "Fault #" << fault.getFaultCode()
	     << ": " << fault.getFaultString() << endl
	     << "FAILED" << endl;
	exit(1);
    } catch (...) {
	cout << "Unexpected exception when running test suites." << endl
	     << "FAILED" << endl;
	exit(1);
    }

    // Summarize our test run.
    cout << endl << "Ran " << total_tests << " tests, "
	 << total_failures << " failed, "
	 << setprecision(4)
	 << (100.0 - (100.0 * total_failures) / total_tests)
	 << "% passed" << endl;

    // Print the final result.
    if (total_failures == 0) {
	cout << "OK" << endl;
	return 0;
    }

    cout << "FAILED" << endl;
    return 1;
}
