/* 
 * EasySoap++ - A C++ library for SOAP (Simple Object Access Protocol)
 * Copyright (C) 2001 David Crowley; SciTegic, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */



//
// This is a test harness for running interop tests
// with EasySoap++.  For more information on interop
// testing, see:
//
//    http://www.xmethods.net/ilab/ilab.html
//

#ifdef _WIN32
#pragma warning (disable: 4786)
#endif // _WIN32

#include <iostream>
#include <algorithm>

#include <math.h>
#include <time.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#endif

#include <easysoap/SOAP.h>
#include <easysoap/SOAPonHTTP.h>
#include <easysoap/SOAPDebugger.h>
#include <easysoap/SOAPSocket.h>

#include "interopstruct.h"

static const char *default_server = "http://easysoap.sourceforge.net/cgi-bin/interopserver";
static const char *default_server_name = "EasySoap++ at Sourceforge";
static const char *default_interop_namespace = "http://soapinterop.org/";
static const char *default_interop_soapaction = "urn:soapinterop";
static const char *round2_soapaction = "http://soapinterop.org/";


XMLComposer testresults;
bool cgimode = false;

//
// The main library doesn't include iostream,
// so we define an overload to write out
// a SOAPString here....
inline std::ostream&
operator<<(std::ostream& os, const SOAPString& str)
{
	const char *s = str;
	return os << (s ? s : "(null)");
}

inline std::ostream&
operator<<(std::ostream& os, const SOAPQName& name)
{
	return os << name.GetName();
}

double
randdouble()
{
	double f1 = rand();
	double f2 = rand();
	double f3 = (rand() % 40) - 20;
	if (f2 == 0.0)
		return 0.0;
	return f1/f2*pow(10.0, f3);
}

//
// Exception we throw when there is a problem
// with floating point accuracy.
class FPLossException : public SOAPException
{
public:
	FPLossException(float a, float b)
		: SOAPException("Floating point loss: expecting %.9G, got %.9G", a, b)
	{
	}

	FPLossException(double a, double b)
		: SOAPException("Floating point loss: expecting %.18G, got %.18G", a, b)
	{
	}
};

//
// Exception we throw when something passes that
// we're not expecting to...
class UnexpectedSuccessException : public SOAPException
{
public:
	UnexpectedSuccessException(const char *fmt, ...)
	{
		va_list args;
		va_start(args, fmt);
		FormattedMessage(fmt, args);
		va_end(args);
	}
};


inline bool
almostequal(float a, float b)
{
	if (a != b && fabs(a - b) <= fabs(a) * 0.0000005)
		throw FPLossException(a, b);
	return false;
}

inline bool
almostequal(double a, double b)
{
	if (a != b && fabs(a - b) <= fabs(a) * 0.0000005)
		throw FPLossException(a, b);
	return false;
}

inline bool
almostequal(const SOAPArray<float>& a, const SOAPArray<float>& b)
{
	if (a.Size() != b.Size())
		return false;
	bool retval = true;
	for (size_t i = 0; i < a.Size(); ++i)
	{
		if (!almostequal(a[i], b[i]))
		{
			retval = false;
		}
	}
	return retval;
}


inline bool
almostequal(const SOAPArray<double>& a, const SOAPArray<double>& b)
{
	if (a.Size() != b.Size())
		return false;
	bool retval = true;
	for (size_t i = 0; i < a.Size(); ++i)
	{
		if (!almostequal(a[i], b[i]))
		{
			retval = false;
		}
	}
	return retval;
}


inline bool
almostequal(const SOAPArray<SOAPStruct>& a, const SOAPArray<SOAPStruct>& b)
{
	if (a.Size() != b.Size())
		return false;
	bool retval = true;
	for (size_t i = 0; i < a.Size(); ++i)
	{
		if (a[i] != b[i] && !almostequal(a[i].varFloat, b[i].varFloat))
		{
			retval = false;
		}
	}
	return retval;
}


//
// The struct used to hold endpoint information
//

struct Endpoint
{
	Endpoint() {skip = false;}

	SOAPString name;
	SOAPString wsdl;
	SOAPUrl    endpoint;
	SOAPString soapaction;
	bool	   needsappend;
	SOAPString nspace;
	SOAPString dir;
	bool       skip;

	bool operator<(const Endpoint& p) const
	{
		return name < p.name;
	}
};

const SOAPParameter&
operator>>(const SOAPParameter& param, Endpoint& e)
{
	SOAPString endpoint;
	param.GetParameter("name") >> e.name;
	param.GetParameter("wsdl") >> e.wsdl;
	param.GetParameter("endpoint") >> endpoint; e.endpoint = endpoint;
	param.GetParameter("soapaction") >> e.soapaction;
	e.needsappend = (param.GetParameter("soapactionNeedsMethod").GetInt() != 0);
	param.GetParameter("methodNamespace") >> e.nspace;

	// Try to fix wrong values.
	if (e.nspace == "http://soapinterop.org")
		e.nspace = default_interop_namespace;

	return param;
}

void
GetRound1Endpoints(SOAPArray<Endpoint>& ea)
{
	SOAPProxy proxy("http://www.xmethods.net/perl/soaplite.cgi");
	SOAPMethod getAllEndpoints("getAllEndpoints",
		"http://soapinterop.org/ilab",
		"http://soapinterop.org/ilab#", true);

	const SOAPResponse& response = proxy.Execute(getAllEndpoints);
	const SOAPParameter& p = response.GetReturnValue();

	
	for (SOAPParameter::Array::ConstIterator i = p.GetArray().Begin();
			i != p.GetArray().End();
			++i)
	{
		Endpoint& e = ea.Add();
		*(*i) >> e;
		e.skip = false;
	}
}

void
GetRound2Endpoints(SOAPArray<Endpoint>& ea, const char *groupName)
{
	SOAPProxy proxy("http://www.whitemesa.net/interopInfo");
	SOAPMethod getEndpointInfo("GetEndpointInfo",
		"http://soapinterop.org/info/",
		"http://soapinterop.org/info/", false);

	getEndpointInfo.AddParameter("groupName") << groupName;

	const SOAPResponse& response = proxy.Execute(getEndpointInfo);
	const SOAPParameter& p = response.GetReturnValue();

	
	SOAPString endpoint;
	for (SOAPParameter::Array::ConstIterator i = p.GetArray().Begin();
			i != p.GetArray().End();
			++i)
	{
		Endpoint& e = ea.Add();
		(*i)->GetParameter("endpointName") >> e.name;
		(*i)->GetParameter("endpointURL") >> endpoint; e.endpoint = endpoint;
		(*i)->GetParameter("wsdlURL") >> e.wsdl;
		e.nspace = default_interop_namespace;
		e.soapaction = round2_soapaction;
		e.skip = false;

		if (e.name == "OpenLink")
			e.skip = true;
		if (e.name == "SilverStream")
			e.skip = true;
	}
}

typedef enum
{
	round1,
	round2a,
	round2b,
	round2c,
	misc
} TestType;


/////////////////////////////////////////////////////////////////////////////////
//
// Round 1 + Round 2 base methods
//
////////////////////////////////////////////////////////////////////////////////

void
TestEchoVoid(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPMethod method("echoVoid", e.nspace, e.soapaction, e.needsappend);

	const SOAPResponse& response = proxy.Execute(method);
	if (response.GetBody().GetMethod().GetNumParameters() != 0)
		throw SOAPException("Received unexpected return values.");
}

void
TestEchoString(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	SOAPString inputValue = value;

	SOAPMethod method("echoString", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputString") << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	SOAPString outputValue;
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue)
		throw SOAPException("Values are not equal");
}

void
TestEchoInteger(SOAPProxy& proxy, const Endpoint& e, int value)
{
	int inputValue = value;

	SOAPMethod method("echoInteger", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputInteger") << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	int outputValue = 0;
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue)
		throw SOAPException("Values are not equal");
}

const SOAPResponse&
TestEchoInteger(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	SOAPMethod method("echoInteger", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputInteger").SetInt(value);

	return proxy.Execute(method);
}

void
TestEchoIntegerForFail(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	const SOAPResponse& response = TestEchoInteger(proxy, e, value);
	throw UnexpectedSuccessException("Returned value: %s",
			(const char *)response.GetReturnValue().GetString());
}

void
TestEchoIntegerForPass(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	const SOAPResponse& response = TestEchoInteger(proxy, e, value);
	int returnValue;
	response.GetReturnValue() >> returnValue;
}

void
TestEchoFloat(SOAPProxy& proxy, const Endpoint& e, float value)
{
	float inputValue = value;

	SOAPMethod method("echoFloat", e.nspace, e.soapaction, e.needsappend);
	SOAPParameter& inputParam = method.AddParameter("inputFloat");
	inputParam << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	const SOAPParameter& outputParam = response.GetReturnValue();
	float outputValue = 0;
	outputParam >> outputValue;

	if (inputValue != outputValue && !almostequal(inputValue, outputValue))
		throw SOAPException("Values are not equal: %s != %s",
			(const char *)inputParam.GetString(),
			(const char *)outputParam.GetString());
}

void
TestEchoDouble(SOAPProxy& proxy, const Endpoint& e, double value)
{
	double inputValue = value;

	SOAPMethod method("echoDouble", e.nspace, e.soapaction, e.needsappend);
	SOAPParameter& inputParam = method.AddParameter("inputDouble");
	inputParam << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	const SOAPParameter& outputParam = response.GetReturnValue();
	double outputValue = 0;
	outputParam >> outputValue;

	if (inputValue != outputValue && !almostequal(inputValue, outputValue))
		throw SOAPException("Values are not equal: %s != %s",
			(const char *)inputParam.GetString(),
			(const char *)outputParam.GetString());
}

void
TestEchoDouble(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoDouble(proxy, e, 12445e63);
}

void
TestEchoFloatStringValue(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	SOAPMethod method("echoFloat", e.nspace, e.soapaction, e.needsappend);
	SOAPParameter& inputParam = method.AddParameter("inputFloat");
	inputParam.SetFloat(value);

	const SOAPResponse& response = proxy.Execute(method);
	if (response.GetReturnValue().GetString() != value)
		throw SOAPException("Wrong return value: %s",
			(const char *)response.GetReturnValue().GetString());
}

const SOAPResponse&
TestEchoFloat(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	SOAPMethod method("echoFloat", e.nspace, e.soapaction, e.needsappend);
	SOAPParameter& inputParam = method.AddParameter("inputFloat");
	inputParam.SetFloat(value);

	return proxy.Execute(method);
}

void
TestEchoFloatForFail(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	const SOAPResponse& response = TestEchoFloat(proxy, e, value);
	throw UnexpectedSuccessException("Returned value: %s",
		(const char *)response.GetReturnValue().GetString());
}

void
TestEchoFloatForPass(SOAPProxy& proxy, const Endpoint& e, const char *value)
{
	const SOAPResponse& response = TestEchoFloat(proxy, e, value);
	float result;
	response.GetReturnValue() >> result;
}

void
TestEchoStruct(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPStruct inputValue;
	
	inputValue.varString = "This is a struct string.";
	inputValue.varInt = 68;
	inputValue.varFloat = (float)25.24345356;

	SOAPMethod method("echoStruct", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputStruct") << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	SOAPStruct outputValue;
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue && !almostequal(inputValue.varFloat, outputValue.varFloat))
		throw SOAPException("Values are not equal");
}

void
TestEchoIntegerArray(SOAPProxy& proxy, const Endpoint& e, int numvals)
{
	SOAPArray<int> inputValue;
	SOAPArray<int> outputValue;

	for (int i = 0; i < numvals; ++i)
		inputValue.Add(rand());

	SOAPMethod method("echoIntegerArray", e.nspace, e.soapaction, e.needsappend);
	// Here I call SetArrayType() to make sure that for zero-length
	// arrays the array type is correct.  We have to set it manually
	// for zero length arrays because we can't determine the type from
	// elements in the array!
	SOAPParameter& param = method.AddParameter("inputIntegerArray");
	param << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue)
		throw SOAPException("Values are not equal");
}


void
TestEchoFloatArray(SOAPProxy& proxy, const Endpoint& e, int numvals)
{
	SOAPArray<float> inputValue;
	for (int i = 0; i < numvals; ++i)
		inputValue.Add((float)randdouble());

	SOAPMethod method("echoFloatArray", e.nspace, e.soapaction, e.needsappend);
	// Here I call SetArrayType() to make sure that for zero-length
	// arrays the array type is correct.  We have to set it manually
	// for zero length arrays because we can't determine the type from
	// elements in the array!
	SOAPParameter& param = method.AddParameter("inputFloatArray");
	param << inputValue;

	const SOAPResponse& response = proxy.Execute(method);

	SOAPArray<float> outputValue;
	response.GetReturnValue() >> outputValue;

	if (inputValue != outputValue && !almostequal(inputValue, outputValue))
		throw SOAPException("Values are not equal");
}


void
TestEchoDoubleArray(SOAPProxy& proxy, const Endpoint& e, int numvals)
{
	SOAPArray<double> inputValue;
	for (int i = 0; i < numvals; ++i)
		inputValue.Add(randdouble());

	SOAPMethod method("echoDoubleArray", e.nspace, e.soapaction, e.needsappend);
	// Here I call SetArrayType() to make sure that for zero-length
	// arrays the array type is correct.  We have to set it manually
	// for zero length arrays because we can't determine the type from
	// elements in the array!
	SOAPParameter& param = method.AddParameter("inputDoubleArray");
	param << inputValue;

	const SOAPResponse& response = proxy.Execute(method);

	SOAPArray<double> outputValue;
	response.GetReturnValue() >> outputValue;

	if (inputValue != outputValue && !almostequal(inputValue, outputValue))
		throw SOAPException("Values are not equal");
}


void
TestEchoStringArray(SOAPProxy& proxy, const Endpoint& e, int numvals)
{
	SOAPArray<SOAPString> inputValue;
	for (int i = 0; i < numvals; ++i)
	{
		char buffer[256];
		sprintf(buffer, "This is test string #%d, rn=%d", i, rand());
		inputValue.Add(buffer);
	}

	SOAPMethod method("echoStringArray", e.nspace, e.soapaction, e.needsappend);
	// Here I call SetArrayType() to make sure that for zero-length
	// arrays the array type is correct.  We have to set it manually
	// for zero length arrays because we can't determine the type from
	// elements in the array!
	SOAPParameter& param = method.AddParameter("inputStringArray");
	param << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	SOAPArray<SOAPString> outputValue;
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue)
		throw SOAPException("Values are not equal");
}


void
TestEchoStructArray(SOAPProxy& proxy, const Endpoint& e, int numvals)
{
	SOAPArray<SOAPStruct> inputValue;
	for (int i = 0; i < numvals; ++i)
	{
		SOAPStruct& val = inputValue.Add();
		char buffer[256];
		sprintf(buffer, "This is struct string #%d, rn=%d", i, rand());
		val.varString = buffer;
		val.varFloat = (float)randdouble();
		val.varInt = rand();
	}

	SOAPMethod method("echoStructArray", e.nspace, e.soapaction, e.needsappend);
	// Here I call SetArrayType() to make sure that for zero-length
	// arrays the array type is correct.  We have to set it manually
	// for zero length arrays because we can't determine the type from
	// elements in the array!
	SOAPParameter& param = method.AddParameter("inputStructArray");
	param << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	SOAPArray<SOAPStruct> outputValue;
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue && !almostequal(inputValue, outputValue))
		throw SOAPException("Values are not equal");
}

void
TestEchoInteger(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoInteger(proxy, e, rand());
}

void
TestEchoInteger_MostPositive(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoInteger(proxy, e, 2147483647);
}

void
TestEchoInteger_MostNegative(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoInteger(proxy, e, -2147483647 - 1);
}

void
TestEchoInteger_Overflow(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoIntegerForFail(proxy, e, "2147483648");
}

void
TestEchoInteger_Underflow(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoIntegerForFail(proxy, e, "-2147483649");
}

void
TestEchoInteger_Junk1(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoIntegerForFail(proxy, e, "1234junk");
}

void
TestEchoInteger_Junk2(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoIntegerForPass(proxy, e, "\r\n\t 1234 \r\n\t");
}

void
TestEchoFloat(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloat(proxy, e, 31391236.0);
}

void
TestEchoFloat_NaN(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatStringValue(proxy, e, "NaN");
}

void
TestEchoFloat_INF(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatStringValue(proxy, e, "INF");
}

void
TestEchoFloat_negINF(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatStringValue(proxy, e, "-INF");
}

void
TestEchoFloat_DoubleOverflow(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatForFail(proxy, e, "1.8e308");
}

void
TestEchoFloat_DoubleUnderflow(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatForFail(proxy, e, "2.4e-360");
}

void
TestEchoFloat_SingleOverflow(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatForFail(proxy, e, "3.5e38");
}

void
TestEchoFloat_SingleUnderflow(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatForFail(proxy, e, "6.9e-46");
}

void
TestEchoFloat_Junk1(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatForFail(proxy, e, "1234junk");
}

void
TestEchoFloat_Junk2(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatForPass(proxy, e, "\r\n\t 1234 \r\n\t");
}

void
TestEchoString(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoString(proxy, e, "This is a test string from EasySoap++");
}

void
TestEchoString_newlines(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoString(proxy, e, "This\ris\na\r\ntest\tstring\n\rfrom EasySoap++");
}

void
TestEchoIntegerArray(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoIntegerArray(proxy, e, rand() % 10 + 5);
}

void
TestEchoFloatArray(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatArray(proxy, e, rand() % 10 + 5);
}

void
TestEchoStringArray(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoStringArray(proxy, e, rand() % 10 + 5);
}

void
TestEchoStructArray(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoStructArray(proxy, e, rand() % 10 + 5);
}

void
TestEchoIntegerArrayZeroLen(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoIntegerArray(proxy, e, 0);
}

void
TestEchoFloatArrayZeroLen(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoFloatArray(proxy, e, 0);
}

void
TestEchoStringArrayZeroLen(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoStringArray(proxy, e, 0);
}

void
TestEchoStructArrayZeroLen(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoStructArray(proxy, e, 0);
}

void
TestEchoDoubleArray(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoDoubleArray(proxy, e, rand() % 10 + 5);
}

void
TestEchoDoubleArrayZeroLen(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoDoubleArray(proxy, e, 0);
}

/////////////////////////////////////////////////////////////////////////////////
//
// Additional Round 2 base methods
//
////////////////////////////////////////////////////////////////////////////////

void
TestEchoBooleanJunk(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPMethod method("echoBoolean", e.nspace, e.soapaction, e.needsappend);
	(method.AddParameter("inputBoolean") << "junk").SetType("boolean", XMLSchema2001::xsd);

	const SOAPResponse& response = proxy.Execute(method);

	throw UnexpectedSuccessException("Returned value: %s",
		(const char *)response.GetReturnValue().GetString());
}

void
TestEchoBooleanTrue(SOAPProxy& proxy, const Endpoint& e)
{
	bool val = true;
	SOAPMethod method("echoBoolean", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputBoolean") << val;

	const SOAPResponse& response = proxy.Execute(method);
	response.GetReturnValue() >> val;
	if (!val)
		throw SOAPException("Values are not equal");
}

void
TestEchoBooleanFalse(SOAPProxy& proxy, const Endpoint& e)
{
	bool val = false;
	SOAPMethod method("echoBoolean", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputBoolean") << val;

	const SOAPResponse& response = proxy.Execute(method);
	response.GetReturnValue() >> val;
	if (val)
		throw SOAPException("Values are not equal");
}


/////////////////////////////////////////////////////////////////////////////////
//
// Round 2 Group B methods
//
////////////////////////////////////////////////////////////////////////////////


void
TestEchoNestedStruct(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPStructStruct inputValue;
	
	inputValue.varString = "This is a struct string.";
	inputValue.varInt = 68;
	inputValue.varFloat = (float)25.24345356;
	inputValue.varStruct.varFloat = (float)12.5;
	inputValue.varStruct.varInt = 86;
	inputValue.varStruct.varString = "This is a nested struct.";

	SOAPMethod method("echoNestedStruct", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputStruct") << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	SOAPStructStruct outputValue;
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue && !almostequal(inputValue.varFloat, outputValue.varFloat))
		throw SOAPException("Values are not equal");
}

void
TestEchoNestedArray(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPArrayStruct inputValue;
	
	inputValue.varString = "This is a struct string.";
	inputValue.varInt = 68;
	inputValue.varFloat = 12.5;
	inputValue.varArray.Add() = "This is string 1";
	inputValue.varArray.Add() = "This is string 2";
	inputValue.varArray.Add() = "This is string 3";
	inputValue.varArray.Add() = "This is string 4";

	SOAPMethod method("echoNestedArray", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputStruct") << inputValue;

	const SOAPResponse& response = proxy.Execute(method);
	SOAPArrayStruct outputValue;
	response.GetReturnValue() >> outputValue;
	if (inputValue != outputValue && !almostequal(inputValue.varFloat, outputValue.varFloat))
		throw SOAPException("Values are not equal");
}

void
TestEcho2DStringArray(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPMethod method("echo2DStringArray", e.nspace,
			e.soapaction, e.needsappend);

	SOAP2DArray<SOAPString> twod, result;

	size_t rows = rand() % 5 + 3;
	size_t cols = rand() % 5 + 3;

	char buff[64];
	twod.Resize(rows, cols);

	for (size_t i = 0; i < rows; ++i)
		for (size_t j = 0; j < cols; ++j)
		{
			snprintf(buff, sizeof(buff), "%d,%d", i, j);
			twod[i][j] = buff;
		}

	method.AddParameter("input2DStringArray") << twod;

	const SOAPResponse& response = proxy.Execute(method);
	response.GetReturnValue() >> result;

	if (result != twod)
		throw SOAPException("2D Array values differ.");
}

void
TestEchoStructAsSimpleTypes(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPMethod method("echoStructAsSimpleTypes", e.nspace,
			e.soapaction, e.needsappend);

	SOAPStruct s;
	s.varString = "This is a test";
	s.varInt = rand() % 10000;
	s.varFloat = (float)randdouble();

	method.AddParameter("inputStruct") << s;

	const SOAPResponse& response = proxy.Execute(method);

	if (response.GetReturnValue("outputString").GetString() != s.varString)
		throw SOAPException("String values are not equal.");

	if (response.GetReturnValue("outputInteger").GetInt() != s.varInt)
		throw SOAPException("Int values are not equal.");

	if (response.GetReturnValue("outputFloat").GetFloat() != s.varFloat)
		throw SOAPException("Float values are not equal.");
}

void
TestEchoSimpleTypesAsStruct(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPMethod method("echoSimpleTypesAsStruct", e.nspace,
			e.soapaction, e.needsappend);

	SOAPStruct s;
	s.varString = "This is a test";
	s.varInt = rand() % 10000;
	s.varFloat = (float)randdouble();

	method.AddParameter("inputString") << s.varString;
	method.AddParameter("inputInteger") << s.varInt;
	method.AddParameter("inputFloat") << s.varFloat;

	const SOAPResponse& response = proxy.Execute(method);
	SOAPStruct out;
	response.GetReturnValue() >> out;

	if (s != out)
		throw SOAPException("Values are not equal.");
}

void
TestEchoBase64(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPArray<char> inputBinary, outputBinary;

	int size = rand() % 501 + 500;
	inputBinary.Resize(size);
	for (int i = 0; i < size; ++i)
		inputBinary[i] = rand();

	SOAPMethod method("echoBase64", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputBase64") << SOAPBase64(inputBinary);
	const SOAPResponse& response = proxy.Execute(method);

	SOAPBase64 base64(outputBinary);
	response.GetReturnValue() >> base64;

	if (inputBinary != outputBinary)
		throw SOAPException("Values are not equal");
}

/////////////////////////////////////////////////////////////////////////////////
//
// Round 2 Group C methods
//
//
// echoHdrString
// echoHdrStruct
//
// Variations:
//  mustUnderstand = "1", "0"
//  namespace = "http://soapinterop.org/echoheader/", other
//  actor = none (default), next, other
//
/////////////////////////////////////////////////////////////////////////////////

void
TestEchoHdrString(SOAPProxy& proxy, const Endpoint& e,
				  bool mustUnderstand,
				  bool understandable,
				  int actor)
{
	static const char *headerNamespace = "http://soapinterop.org/echoheader/";
	static const char *otherNamespace = "http://other.soapinterop.org/echoheader/";

	SOAPQName headname("echoMeStringRequest", understandable ? headerNamespace : otherNamespace);

	SOAPEnvelope env;
	SOAPParameter& header = env.GetHeader().AddHeader(headname);
	SOAPMethod& method = env.GetBody().GetMethod();

	method.SetName("echoVoid", e.nspace);
	method.SetSoapAction(e.soapaction, e.needsappend);

	SOAPString str = "This is a string in the header";
	header << str;


	header.AddAttribute(SOAPEnv::mustUnderstand) = mustUnderstand ? "1" : "0";
	if (actor == 1)
		header.AddAttribute(SOAPEnv::actor) = SOAPHeader::actorNext;
	else if (actor == 2)
		header.AddAttribute(SOAPEnv::actor) = "http://jackandjill.com/wentup/thehill";

	const SOAPResponse& response = proxy.Execute(env);

	if (understandable && actor != 2)
	{
		SOAPQName rheadname("echoMeStringResponse", understandable ? headerNamespace : otherNamespace);
		const SOAPParameter& rhead = response.GetHeader().GetHeader(rheadname);

		SOAPString rstr;
		rhead >> rstr;

		if (rstr != str)
			throw SOAPException("Values are not equal: '%s' and '%s'",
				(const char *)str,
				(const char *)rstr);
	}
}

void
TestEchoHdrStruct(SOAPProxy& proxy, const Endpoint& e,
				  bool mustUnderstand,
				  bool understandable,
				  int actor)
{
	static const char *headerNamespace = "http://soapinterop.org/echoheader/";
	static const char *otherNamespace = "http://other.soapinterop.org/echoheader/";

	SOAPQName headname("echoMeStructRequest", understandable ? headerNamespace : otherNamespace);

	SOAPEnvelope env;
	SOAPParameter& header = env.GetHeader().AddHeader(headname);
	SOAPMethod& method = env.GetBody().GetMethod();

	method.SetName("echoVoid", e.nspace);
	method.SetSoapAction(e.soapaction, e.needsappend);

	SOAPStruct str;

	str.varString = "This is a string in the header";
	str.varFloat = (float)randdouble();
	str.varInt = rand();

	header << str;

	header.AddAttribute(SOAPEnv::mustUnderstand) = mustUnderstand ? "1" : "0";
	if (actor == 1)
		header.AddAttribute(SOAPEnv::actor) = SOAPHeader::actorNext;
	else if (actor == 2)
		header.AddAttribute(SOAPEnv::actor) = "http://jackandjill.com/wentup/thehill";

	const SOAPResponse& response = proxy.Execute(env);

	if (understandable && actor != 2)
	{
		SOAPQName rheadname("echoMeStructResponse", understandable ? headerNamespace : otherNamespace);
		const SOAPParameter& rhead = response.GetHeader().GetHeader(rheadname);

		SOAPStruct rstr;
		rhead >> rstr;

		if (rstr != str)
			throw SOAPException("Values are not equal");
	}
}

static bool HeaderMustUnderstand;
static bool HeaderUnderstandable;
static int HeaderActor;

void
TestEchoHdrString(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoHdrString(proxy, e, HeaderMustUnderstand, HeaderUnderstandable, HeaderActor);
}

void
TestEchoHdrStruct(SOAPProxy& proxy, const Endpoint& e)
{
	TestEchoHdrStruct(proxy, e, HeaderMustUnderstand, HeaderUnderstandable, HeaderActor);
}

/////////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous methods
//
/////////////////////////////////////////////////////////////////////////////////

void
TestBogusMethod(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPMethod method("BogusMethod", e.nspace, e.soapaction, e.needsappend);
	proxy.Execute(method);
}

void
TestBogusNamespace(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPMethod method("echoVoid", "http://bogusns.com/", e.soapaction, e.needsappend);
	proxy.Execute(method);
	throw UnexpectedSuccessException("Method executed with bogus namespace.");
}

void
TestMustUnderstand(SOAPProxy& proxy, const Endpoint& e, const char *mu)
{
	SOAPEnvelope mustUnderstand;
	SOAPParameter& header = mustUnderstand.GetHeader().AddHeader();

	header.SetName("Transaction", "uri:my-transaction");
	header.SetValue("5");
	header.AddAttribute(SOAPEnv::mustUnderstand) = mu;

	SOAPMethod& method = mustUnderstand.GetBody().GetMethod();
	method.SetName("echoVoid", e.nspace);
	method.SetSoapAction(e.soapaction, e.needsappend);

	proxy.Execute(mustUnderstand);
}

void
TestMustUnderstand_1(SOAPProxy& proxy, const Endpoint& e)
{
	TestMustUnderstand(proxy, e, "1");
}

void
TestMustUnderstand_0(SOAPProxy& proxy, const Endpoint& e)
{
	TestMustUnderstand(proxy, e, "0");
}


BEGIN_EASYSOAP_NAMESPACE

//
// We have to declare the type traits for our map
template<>
class SOAPTypeTraits< SOAPHashMap<SOAPString, int> > : public SOAPMapTypeTraits
{
};

END_EASYSOAP_NAMESPACE

void
TestEchoMap(SOAPProxy& proxy, const Endpoint& e)
{
    SOAPMethod method("echoMap", e.nspace,
			e.soapaction, e.needsappend);

    SOAPHashMap<SOAPString, int> map, outmap;
    map["one"] = 1;
    map["two"] = 2;
    map["three"] = 3;

    method.AddParameter("inputMap") << map;
    const SOAPResponse& response = proxy.Execute(method);

    response.GetReturnValue() >> outmap;
    if (map.Size() != outmap.Size())
        throw SOAPException("Maps have differing number of values.");
}

void
TestEchoHexBinary(SOAPProxy& proxy, const Endpoint& e)
{
	SOAPArray<char> inputBinary, outputBinary;

	int size = rand() % 501 + 500;
	inputBinary.Resize(size);
	for (int i = 0; i < size; ++i)
		inputBinary[i] = rand();

	SOAPMethod method("echoHexBinary", e.nspace, e.soapaction, e.needsappend);
	method.AddParameter("inputHexBinary") << SOAPHex(inputBinary);
	const SOAPResponse& response = proxy.Execute(method);

	SOAPHex hex(outputBinary);
	response.GetReturnValue() >> hex;

	if (inputBinary != outputBinary)
		throw SOAPException("Values are not equal");
}


/////////////////////////////////////////////////////////////////////////////////



typedef void (*TestFunction)(SOAPProxy&, const Endpoint&);

void
BeginEndpointTesting(const Endpoint& e, TestType test)
{
	if (cgimode)
	{
		std::cout << "<html><head><title>"
			<< "EasySoap++ Interop tests"
			<< "</title></head><body>"
			<< "<h2>EasySoap++ Interop tests</h2>"
			<< "<h3>" << e.name;

		switch (test)
		{
			case round1:
				std::cout << ": Round 1";
				break;
			case round2a:
				std::cout << ": Round 2 Base";
				break;
			case round2b:
				std::cout << ": Round 2 Group B";
				break;
			case round2c:
				std::cout << ": Round 2 Group C";
				break;
			case misc:
				std::cout << ": Miscellaneous";
				break;
		}

		std::cout << "</h3>"
			<< "<table border='1'>"
			<< "<tr><th>Test</th><th>Result</th><th>Message</th></tr>";
	}
	else
	{
		std::cout	<< "      Name: " << e.name << std::endl
					<< "  Endpoint: " << e.endpoint.GetString() << std::endl
					<< " Namespace: " << e.nspace << std::endl
					<< "SOAPAction: " << e.soapaction
					<< (e.needsappend ? "(method)" : "") <<	std::endl
					<< std::endl;
	}
}

void
EndEndpointTesting(const Endpoint& e)
{
	if (cgimode)
	{
		std::cout << "</table></body></html>";
	}
	else
	{
		std::cout
			<< std::endl
			<< "--------------------------------------------------------------"
			<< std::endl
			<< std::endl;
	}
}

void
BeginTest(const Endpoint& e, const char *testname)
{
	char buffer[256];
	snprintf(buffer, sizeof(buffer), "%s/%s.txt",
			(const char *)e.dir, testname);
	bool created = SOAPDebugger::SetFile(buffer);

	if (cgimode)
	{
		std::cout << "<tr><td>";
		if (created)
			std::cout << "<a href='/interoptests/" << e.dir
				<< "/" << testname <<".txt'>" << testname << "</a>";
		else
			std::cout << testname;
		std::cout << "</td>";
	}
	else
	{
		std::cout << "Testing " << testname << ": ";
	}
}

void
EndTest(const Endpoint& e, const SOAPString& type, const SOAPString& msg)
{
	if (cgimode)
	{
		if (type == "PASS")
		{
			std::cout << "<td><font color='green'>" << type << "</font></td>";
			if (msg != "PASS")
				std::cout << "<td>" << msg << "</td></tr>";
			else
				std::cout << "<td></td></tr>";
		}
		else
		{
			std::cout << "<td><font color='red'>" << type << "</font></td>";
			if (msg != "FAIL")
				std::cout << "<td>" << msg << "</td></tr>";
			else
				std::cout << "<td></td></tr>";
		}
		std::cout << std::endl;
		std::cout.flush();
	}
	else
	{
		std::cout << type;
		if (!msg.IsEmpty() && msg != "PASS")
			std::cout << ": " << msg;
		std::cout << std::endl;
	}
}

void
TestForPass(SOAPProxy& proxy, const Endpoint& e, const SOAPString& testname, TestFunction func)
{
	SOAPString type;
	SOAPString msg;

	try
	{
		BeginTest(e, testname);
		func(proxy, e);
		type = "PASS";
		msg = "PASS";
	}
	catch (FPLossException& sex)
	{
		type = "FP LOSS";
		msg = sex.What();
	}
	catch (SOAPSocketException& sex)
	{
		type = "NETWORK ERROR";
		msg = sex.What();
	}
	catch (SOAPFaultException& sex)
	{
		type = "FAULT";
		msg = sex.What();
	}
	catch (SOAPException& sex)
	{
		type = "FAIL";
		msg = sex.What();
	}
	catch (...)
	{
		type = "UNKNOWN";
		msg = "Unknown error, problem with EasySoap++";
	}

	EndTest(e, type, msg);

	testresults.StartTag("Test");
	testresults.AddAttr("name", testname);

	testresults.StartTag("Result");
	testresults.WriteValue(type);
	testresults.EndTag("Result");

	testresults.StartTag("Message");
	testresults.WriteValue(msg);
	testresults.EndTag("Message");

	testresults.EndTag("Test");
}

void
TestForFault(SOAPProxy& proxy, const Endpoint& e, const SOAPString& testname, TestFunction func)
{
	SOAPString type;
	SOAPString msg;
	try
	{
		BeginTest(e, testname);
		func(proxy, e);
		type = "FAIL";
		msg = "FAIL";
	}
	catch (SOAPFaultException& sex)
	{
		type = "PASS";
		msg = sex.What();
	}
	catch (UnexpectedSuccessException& sex)
	{
		type = "FAIL";
		msg = sex.What();
	}
	catch (SOAPSocketException& sex)
	{
		type = "NETWORK ERROR";
		msg = sex.What();
	}
	catch (SOAPException& sex)
	{
		type = "FAIL";
		msg = sex.What();
	}
	catch (...)
	{
		type = "UNKNOWN";
		msg = "Unknown error, problem with EasySoap++";
	}

	EndTest(e, type, msg);

	testresults.StartTag("Test");
	testresults.AddAttr("name", testname);

	testresults.StartTag("Result");
	testresults.WriteValue(type);
	testresults.EndTag("Result");

	testresults.StartTag("Message");
	testresults.WriteValue(msg);
	testresults.EndTag("Message");

	testresults.EndTag("Test");
}

void
TestInterop(const Endpoint& e, TestType test)
{
	SOAPonHTTP transport(e.endpoint);
	transport.SetTimeout(30);
	SOAPProxy proxy(&transport);

	testresults.StartTag("Server");
	testresults.AddAttr("name", e.name);

	testresults.StartTag("Endpoint");
	testresults.WriteValue(e.endpoint.GetString());
	testresults.EndTag("Endpoint");

	testresults.StartTag("SoapAction");
	testresults.WriteValue(e.soapaction);
	testresults.EndTag("SoapAction");

	testresults.StartTag("NameSpace");
	testresults.WriteValue(e.nspace);
	testresults.EndTag("NameSpace");

	if (test == round1 || test == round2a)
	{
	//
	// Round 1
	TestForPass(proxy, e, "echoVoid",					TestEchoVoid);
	TestForPass(proxy, e, "echoInteger",				TestEchoInteger);
	TestForPass(proxy, e, "echoInteger_MostPositive",	TestEchoInteger_MostPositive);
	TestForPass(proxy, e, "echoInteger_MostNegative",	TestEchoInteger_MostNegative);
	TestForFault(proxy, e, "echoInteger_Overflow",		TestEchoInteger_Overflow);
	TestForFault(proxy, e, "echoInteger_Underflow",		TestEchoInteger_Underflow);
	TestForFault(proxy, e, "echoInteger_Junk1"	,		TestEchoInteger_Junk1);
	TestForPass(proxy, e, "echoInteger_Junk2"	,		TestEchoInteger_Junk2);
	TestForPass(proxy, e, "echoFloat",					TestEchoFloat);
	TestForPass(proxy, e, "echoFloat_NaN",				TestEchoFloat_NaN);
	TestForPass(proxy, e, "echoFloat_INF",				TestEchoFloat_INF);
	TestForPass(proxy, e, "echoFloat_negINF",			TestEchoFloat_negINF);
	TestForFault(proxy, e, "echoFloat_SingleOverflow",	TestEchoFloat_SingleOverflow);
	TestForFault(proxy, e, "echoFloat_SingleUnderflow",	TestEchoFloat_SingleUnderflow);
	TestForFault(proxy, e, "echoFloat_DoubleOverflow",	TestEchoFloat_DoubleOverflow);
	TestForFault(proxy, e, "echoFloat_DoubleUnderflow",	TestEchoFloat_DoubleUnderflow);
	TestForFault(proxy, e, "echoFloat_Junk1",			TestEchoFloat_Junk1);
	TestForPass(proxy, e, "echoFloat_Junk2",			TestEchoFloat_Junk2);
	TestForPass(proxy, e, "echoString",					TestEchoString);
	TestForPass(proxy, e, "echoString_newlines",		TestEchoString_newlines);
	TestForPass(proxy, e, "echoStruct",					TestEchoStruct);
	TestForPass(proxy, e, "echoIntegerArray",			TestEchoIntegerArray);
	TestForPass(proxy, e, "echoFloatArray",				TestEchoFloatArray);
	TestForPass(proxy, e, "echoStringArray",			TestEchoStringArray);
	TestForPass(proxy, e, "echoStructArray",			TestEchoStructArray);
	TestForPass(proxy, e, "echoIntegerArray_ZeroLen",	TestEchoIntegerArrayZeroLen);
	TestForPass(proxy, e, "echoFloatArray_ZeroLen",		TestEchoFloatArrayZeroLen);
	TestForPass(proxy, e, "echoStringArray_ZeroLen",	TestEchoStringArrayZeroLen);
	TestForPass(proxy, e, "echoStructArray_ZeroLen",	TestEchoStructArrayZeroLen);

	}

	if (test == round2a)
	{
	//
	// Additional Round 2 base methods
	TestForPass(proxy, e, "echoBase64",					TestEchoBase64);
	TestForPass(proxy, e, "echoBoolean_true",			TestEchoBooleanTrue);
	TestForPass(proxy, e, "echoBoolean_false",			TestEchoBooleanFalse);
	TestForFault(proxy, e, "echoBoolean_junk",			TestEchoBooleanJunk);
	// echoDate
	// echoDecimal
	}

	if (test == round2b)
	{
	//
	// Round 2/Group B methods
	TestForPass(proxy, e, "echoStructAsSimpleTypes",	TestEchoStructAsSimpleTypes);
	TestForPass(proxy, e, "echoSimpleTypesAsStruct",	TestEchoSimpleTypesAsStruct);
	TestForPass(proxy, e, "echo2DStringArray",			TestEcho2DStringArray);
	TestForPass(proxy, e, "echoNestedStruct",			TestEchoNestedStruct);
	TestForPass(proxy, e, "echoNestedArray",			TestEchoNestedArray);
	}

	if (test == round2c)
	{
		//
		// Round 2/Group C methods
		for (int actor = 0; actor < 3; ++actor)
			for (int mu = 0; mu < 2; ++mu)
				for (int und = 0; und < 2; ++und)
				{
					char stringbuff[64];
					char structbuff[64];

					snprintf(stringbuff, sizeof(stringbuff), "echoHdrString mu=%d understandable=%d actor=%s",
						mu, und, (actor == 0 ? "default" : (actor == 1 ? "next" : "other")));

					snprintf(structbuff, sizeof(structbuff), "echoHdrStruct mu=%d understandable=%d actor=%s",
						mu, und, (actor == 0 ? "default" : (actor == 1 ? "next" : "other")));

					HeaderMustUnderstand = (mu != 0);
					HeaderUnderstandable = (und != 0);
					HeaderActor = actor;

					if (!und && mu && actor != 2)
					{
						TestForFault(proxy, e, stringbuff,	TestEchoHdrString);
						TestForFault(proxy, e, structbuff,	TestEchoHdrStruct);
					}
					else
					{
						TestForPass(proxy, e, stringbuff,	TestEchoHdrString);
						TestForPass(proxy, e, structbuff,	TestEchoHdrStruct);
					}
				}

	}

	if (test == misc)
	{
	// Miscellaneous methods
	TestForFault(proxy, e, "BogusMethod",				TestBogusMethod);
	TestForFault(proxy, e, "BogusNamespace",			TestBogusNamespace);
	TestForFault(proxy, e, "MustUnderstand=1",			TestMustUnderstand_1);
	TestForPass(proxy, e, "MustUnderstand=0",			TestMustUnderstand_0);
	TestForPass(proxy, e, "echoMap",					TestEchoMap);
	TestForPass(proxy, e, "echoDouble",					TestEchoDouble);
	TestForPass(proxy, e, "echoDoubleArray",			TestEchoDoubleArray);
	TestForPass(proxy, e, "echoDoubleArray_ZeroLen",	TestEchoDoubleArrayZeroLen);
	TestForPass(proxy, e, "echoHexBinary",				TestEchoHexBinary);
	}

	testresults.EndTag("Server");
}

int
hexval(int c)
{
	switch (c)
	{
	case '0': return 0;
	case '1': return 1;
	case '2': return 2;
	case '3': return 3;
	case '4': return 4;
	case '5': return 5;
	case '6': return 6;
	case '7': return 7;
	case '8': return 8;
	case '9': return 9;
	case 'A': case 'a': return 10;
	case 'B': case 'b': return 11;
	case 'C': case 'c': return 12;
	case 'D': case 'd': return 13;
	case 'E': case 'e': return 14;
	case 'F': case 'f': return 15;
	}
	return 0;
}

char *
hexdecode(char *str)
{
	char *s = str;
	char *w = str;
	while (*w)
	{
		if (*w == '%')
		{
			++w;
			*s++ = (hexval(*w++) << 4) | hexval(*w++);
		}
		else
			*s++ = *w++;
	}
	*s++ = 0;
	return str;
}

void
ParseCGIQuery(SOAPHashMap<SOAPString,SOAPString>& jar, const char *str)
{
	SOAPString query = str;
	char *n = query.Str();
	while (n)
	{
		char *t = n;
		char *v;
		if (n = sp_strchr(n, '&'))
			*n++ = 0;
		if (v = sp_strchr(t, '='))
			*v++ = 0;
		jar[hexdecode(t)] = hexdecode(v);
	}
}

int
main(int argc, char* argv[])
{
	int ret = 0;
	srand(time(0));
	const char *xmlname = 0;
	try
	{
		const char *servicename = 0;
		bool doall = false;
		bool execute = true;
		bool doappend = false;
		bool makedirs = false;
		TestType test = round1;

		SOAPArray<Endpoint> endpoints;
		XMLComposer::SetAddWhiteSpace(true);
		SOAPHashMapNoCase<SOAPString, bool> skips;

		const char *soapaction = default_interop_soapaction;
		const char *nspace = default_interop_namespace;

		for (int i = 1; i < argc;)
		{
			SOAPString val = argv[i++];
			if (val == "-cgi")
			{
				//
				// We're running as a CGI.  Get args
				// from QUERY_STRING
				SOAPHashMap<SOAPString,SOAPString> jar;
				cgimode = true;
				makedirs = true;

				ParseCGIQuery(jar, getenv("QUERY_STRING"));

				const SOAPString& type = jar["type"];
				if (type == "round2a")
					test = round2a;
				else if (type == "round2b")
					test = round2b;
				else if (type == "round2c")
					test = round2c;
				else if (type == "misc")
					test = misc;

				Endpoint& e = endpoints.Add();
				e.endpoint = jar["endpoint"];
				e.name = e.endpoint.Hostname();
				char buff[32];
				sprintf(buff, "%d", rand() % 200);
				e.dir = buff;

				if (test == round1)
				{
					e.nspace = default_interop_namespace;
					e.soapaction = default_interop_soapaction;
					e.needsappend = false;
				}
				else
				{
					e.nspace = default_interop_namespace;
					e.soapaction = round2_soapaction;
					e.needsappend = false;
				}

			}
			else if (val == "-2a")
			{
				test = round2a;
				soapaction = round2_soapaction;
			}
			else if (val == "-2b")
			{
				test = round2b;
				soapaction = round2_soapaction;
			}
			else if (val == "-2c")
			{
				test = round2c;
				soapaction = round2_soapaction;
			}
			else if (val == "-misc")
			{
				test = misc;
			}
			else if (val == "-a")
			{
				doall = true;
			}
			else if (val == "-xml")
			{
				xmlname = argv[i++];
			}
			else if (val == "-l")
			{
				execute = false;
			}
			else if (val == "-a+")
			{
				doappend = true;
			}
			else if (val == "-a-")
			{
				doappend = false;
			}
			else if (val == "-n")
			{
				servicename = argv[i++];
			}
			else if (val == "-ns")
			{
				nspace = argv[++i];
			}
			else if (val == "-sa")
			{
				soapaction = argv[i++];
			}
			else if (val == "-skip")
			{
				skips[argv[i++]] = true;
			}
			else if (val == "-mkdir")
			{
				makedirs = true;
			}
			else if (val[0] == '-')
			{
				throw SOAPException("Unknown commandline argument: %s", (const char *)val);
			}
			else
			{
				Endpoint& e = endpoints.Add();
				e.endpoint = val;
				e.name = servicename ? servicename : (const char *)e.endpoint.Hostname();
				e.nspace = nspace;
				e.soapaction = soapaction;
				e.needsappend = doappend;
				servicename = 0;
			}
		}

		if (doall)
		{
			switch (test)
			{
			case round1:
				GetRound1Endpoints(endpoints);
				break;
			case round2a:
				GetRound2Endpoints(endpoints, "base");
				break;
			case round2b:
				GetRound2Endpoints(endpoints, "GroupB");
				break;
			case round2c:
				GetRound2Endpoints(endpoints, "GroupC");
				break;
			}
		}
		else if (endpoints.Size() == 0)
		{
			// Just test against sourceforge
			Endpoint& e = endpoints.Add();
			e.name = default_server_name;
			e.endpoint = default_server;
			e.nspace = default_interop_namespace;
			e.soapaction = default_interop_soapaction;
			e.needsappend = false;
		}


		char buffer[256];
		time_t ltime = time(0);
		struct tm *ltm = localtime(&ltime);
		strftime(buffer, 256, "%d-%b-%Y %H:%M", ltm);

		testresults.StartTag("InteropTests");
		testresults.StartTag("Date");
		testresults.WriteValue(buffer);
		testresults.EndTag("Date");


		std::sort(endpoints.Begin(), endpoints.End());

		for (size_t j = 0; j < endpoints.Size(); ++j)
		{
			Endpoint& e = endpoints[j];

			if (e.skip)
				continue;

			if (e.dir.IsEmpty())
				e.dir = e.name;

			if (makedirs)
			{
#ifdef _WIN32
				_mkdir(e.dir);
#else
				mkdir(e.dir, 0755);
#endif
			}

			if (execute && !skips.Find(e.name))
			{
				BeginEndpointTesting(e, test);
				TestInterop(e, test);
				EndEndpointTesting(e);
			}
		}

		testresults.EndTag("InteropTests");
	}
	catch (const SOAPMemoryException&)
	{
		std::cerr << "SOAP out of memory." << std::endl;
		ret = -1;
	}
	catch (const SOAPException& ex)
	{
		std::cerr << "Caught SOAP exception: " << ex.What() << std::endl;
		ret = 1;
	}

	if (xmlname)
	{
		FILE *xmlfile = fopen(xmlname, "wb");
		if (xmlfile)
		{
			fwrite(testresults.GetBytes(), 1, testresults.GetLength(), xmlfile);
			fclose(xmlfile);
		}
	}

	SOAPDebugger::Close();
	return ret;
}



syntax highlighted by Code2HTML, v. 0.9.1