/*
 * kxmlrpcclient.cpp - (c) 2003 Frerich Raabe <raabe@kde.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "xmlrpciface.h"

#include <kdebug.h>
#include <tdeio/job.h>
#include <tdelocale.h>
#include <kmdcodec.h>

#include <tqdom.h>

using namespace KXMLRPC;

Query *Query::create( TQObject *parent, const char *name )
{
	return new Query( parent, name );
}

void Query::call( const TQString &server, const TQString &method,
                  const TQValueList<TQVariant> &args, const TQString &userAgent )
{
	m_buffer.open( IO_ReadWrite );
	m_server = server;
	m_method = method;
	m_args = args;

	const TQString xmlMarkup = markupCall( method, args );

	TQByteArray postData;
	TQDataStream stream( postData, IO_WriteOnly );
	stream.writeRawBytes( xmlMarkup.utf8(), xmlMarkup.length() );

	TDEIO::TransferJob *job = TDEIO::http_post( KURL( server ), postData, false );
	job->addMetaData( "UserAgent", userAgent );
	job->addMetaData( "content-type", "Content-Type: text/xml; charset=utf-8" );
	connect( job, TQ_SIGNAL( infoMessage( TDEIO::Job *, const TQString & ) ),
	         this, TQ_SLOT( slotInfoMessage( TDEIO::Job *, const TQString & ) ) );
	connect( job, TQ_SIGNAL( data( TDEIO::Job *, const TQByteArray & ) ),
	         this, TQ_SLOT( slotData( TDEIO::Job *, const TQByteArray & ) ) );
	connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ),
	         this, TQ_SLOT( slotResult( TDEIO::Job * ) ) );
}

void Query::slotInfoMessage( TDEIO::Job *, const TQString &msg )
{
	emit infoMessage( msg );
}

void Query::slotData( TDEIO::Job *, const TQByteArray &data )
{
	m_buffer.writeBlock( data );
}

void Query::slotResult( TDEIO::Job *job )
{
	Result response;
	response.m_server = m_server;
	response.m_method = m_method;
	response.m_args = m_args;

	response.m_success = false;

	if ( job->error() != 0 ) {
		response.m_errorCode = job->error();
		response.m_errorString = job->errorString();
		emit finished( response );
		delete this;
		return;
	}

	TQDomDocument doc;
	if ( !doc.setContent( m_buffer.buffer() ) ) {
			response.m_errorCode = -1;
			response.m_errorString = i18n( "Received invalid XML markup" );
			emit finished( response );
			delete this;
			return;
	}

	m_buffer.close();

	if ( isMessageResponse( doc ) )
		response = parseMessageResponse( doc );
	else if ( isFaultResponse( doc ) )
		response = parseFaultResponse( doc );
	else {
		response.m_errorCode = 1;
		response.m_errorString = i18n( "Unknown type of XML markup received" );
	}

	// parserMessageResponse and parseFaultResponse overwrite these fields.
	response.m_server = m_server;
	response.m_method = m_method;
	response.m_args = m_args;

	emit finished( response );
	delete this;
}

bool Query::isMessageResponse( const TQDomDocument &doc ) const
{
	return doc.documentElement().firstChild().toElement().tagName().lower() == "params";
}

Query::Result Query::parseMessageResponse( const TQDomDocument &doc ) const
{
	Result response;
	response.m_success = true;

	TQDomNode paramNode = doc.documentElement().firstChild().firstChild();
	while ( !paramNode.isNull() ) {
		response.m_data << demarshal( paramNode.firstChild().toElement() );
		paramNode = paramNode.nextSibling();
	}

	return response;
}

bool Query::isFaultResponse( const TQDomDocument &doc ) const
{
	return doc.documentElement().firstChild().toElement().tagName().lower() == "fault";
}

Query::Result Query::parseFaultResponse( const TQDomDocument &doc ) const
{
	Result response;
	response.m_success = false;

	TQDomNode errorNode = doc.documentElement().firstChild().firstChild();
	const TQVariant errorVariant = demarshal( errorNode.toElement() );
	response.m_errorCode = errorVariant.toMap()[ "faultCode" ].toInt();
	response.m_errorString = errorVariant.toMap()[ "faultString" ].toString();

	return response;
}

TQString Query::markupCall( const TQString &cmd,
                           const TQValueList<TQVariant> &args ) const
{
	TQString markup = "<?xml version='1.0' ?><methodCall>";

	markup += "<methodName>" + cmd + "</methodName>";

	if ( !args.isEmpty() ) {
		markup += "<params>";
		TQValueList<TQVariant>::ConstIterator it = args.begin();
		TQValueList<TQVariant>::ConstIterator end = args.end();
		for ( ; it != end; ++it )
			markup += "<param>" + marshal( *it ) + "</param>";
		markup += "</params>";
	}

	markup += "</methodCall>";

	return markup;
}

TQString Query::marshal( const TQVariant &arg )
{
	TQString s = "<value>";
	switch ( arg.type() ) {
		case TQVariant::String:
		case TQVariant::CString:
			s += "<string>" + arg.toString() + "</string>";
			break;
		case TQVariant::Int:
			s += "<int>" + TQString::number( arg.toInt() ) + "</int>";
			break;
		case TQVariant::Double:
			s += "<double>" + TQString::number( arg.toDouble() ) + "</double>";
			break;
		case TQVariant::Bool:
			s += "<boolean>";
			s += arg.toBool() ? "true" : "false";
			s += "</boolean>";
			break;
		case TQVariant::ByteArray:
			s += "<base64>" + KCodecs::base64Encode( arg.toByteArray() ) + "</base64>";
			break;
		case TQVariant::DateTime:
			s += "<datetime.iso8601>" + arg.toDateTime().toString( TQt::ISODate ) + "</datetime.iso8601>";
			break;
		case TQVariant::List: {
			s += "<array><data>";
			const TQValueList<TQVariant> args = arg.toList();
			TQValueList<TQVariant>::ConstIterator it = args.begin();
			TQValueList<TQVariant>::ConstIterator end = args.end();
			for ( ; it != end; ++it )
				s += marshal( *it );
			s += "</data></array>";
			break;
		}
		case TQVariant::Map: {
			s += "<struct>";
			TQStringVariantMap map = arg.toMap();
			TQStringVariantMap::ConstIterator it = map.begin();
			TQStringVariantMap::ConstIterator end = map.end();
			for ( ; it != end; ++it ) {
				s += "<member>";
				s += "<name>" + it.key() + "</name>";
				s += marshal( it.data() );
				s += "</member>";
			}
			s += "</struct>";
			break;
		}
		default:
			kdWarning() << "Failed to marshal unknown variant type: " << arg.type() << endl;
			return "<value/>";
	};
	return s + "</value>";
}

TQVariant Query::demarshal( const TQDomElement &elem )
{
	Q_ASSERT( elem.tagName().lower() == "value" );

	if ( !elem.firstChild().isElement() )
		return TQVariant( elem.text() );

	const TQDomElement typeElement = elem.firstChild().toElement();
	const TQString typeName = typeElement.tagName().lower();

	if ( typeName == "string" )
		return TQVariant( typeElement.text() );
	else if ( typeName == "i4" || typeName == "int" )
		return TQVariant( typeElement.text().toInt() );
	else if ( typeName == "double" )
		return TQVariant( typeElement.text().toDouble() );
	else if ( typeName == "boolean" ) {
		if ( typeElement.text().lower() == "true" || typeElement.text() == "1" )
			return TQVariant( true );
		else
			return TQVariant( false );
	} else if ( typeName == "base64" )
		return TQVariant( KCodecs::base64Decode( TQCString(typeElement.text().latin1() )) );
	else if ( typeName == "datetime" || typeName == "datetime.iso8601" )
		return TQVariant( TQDateTime::fromString( typeElement.text(), TQt::ISODate ) );
	else if ( typeName == "array" ) {
		TQValueList<TQVariant> values;
		TQDomNode valueNode = typeElement.firstChild().firstChild();
		while ( !valueNode.isNull() ) {
			values << demarshal( valueNode.toElement() );
			valueNode = valueNode.nextSibling();
		}
		return TQVariant( values );
	} else if ( typeName == "struct" ) {
		TQStringVariantMap map;
		TQDomNode memberNode = typeElement.firstChild();
		while ( !memberNode.isNull() ) {
			const TQString key = memberNode.toElement().elementsByTagName( "name" ).item( 0 ).toElement().text();
			const TQVariant data = demarshal( memberNode.toElement().elementsByTagName( "value" ).item( 0 ).toElement() );
			map[ key ] = data;
			memberNode = memberNode.nextSibling();
		}
		return TQVariant( map );
	} else
		kdWarning() << "Cannot demarshal unknown type " << typeName << endl;

	return TQVariant();
}

Query::Query( TQObject *parent, const char *name ) : TQObject( parent, name )
{
}

TQValueList<TQVariant> Server::toVariantList( const TQVariant &arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( int arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( bool arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( double arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( const TQString &arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( const TQCString &arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( const TQByteArray &arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( const TQDateTime &arg )
{
	TQValueList<TQVariant> args;
	args << arg ;
	return args;
}

TQValueList<TQVariant> Server::toVariantList( const TQStringList &arg )
{
	TQValueList<TQVariant> args;
	TQStringList::ConstIterator it = arg.begin();
	TQStringList::ConstIterator end = arg.end();
	for ( ; it != end; ++it )
		args << TQVariant( *it );
	return args;
}

Server::Server( const KURL &url, TQObject *parent, const char *name )
	: TQObject( parent, name )
{
	if ( url.isValid() )
		m_url = url;
}

void Server::setUrl( const KURL &url )
{
	m_url = url.isValid() ? url : KURL();
}

void Server::call( const TQString &method, const TQValueList<TQVariant> &args,
                   TQObject *receiver, const char *slot )
{
	if ( m_url.isEmpty() ) {
		kdWarning() << "Cannot execute call to " << method << ": empty server URL" << endl;
		return;
	}

	Query *query = Query::create( this );
	connect( query, TQ_SIGNAL( infoMessage( const TQString & ) ),
	         this, TQ_SIGNAL( infoMessage( const TQString & ) ) );
	connect( query, TQ_SIGNAL( finished( const KXMLRPC::Query::Result & ) ),
	         receiver, slot );
	query->call( m_url.url(), method, args, m_userAgent );
}

void Server::call( const TQString &method, const TQValueList<TQVariant> &args,
                   TQObject *receiver, const char *slot,
                   TQObject *infoObject, const char *infoSlot )
{
	if ( m_url.isEmpty() ) {
		kdWarning() << "Cannot execute call to " << method << ": empty server URL" << endl;
		return;
	}

	Query *query = Query::create( this );
	connect( query, TQ_SIGNAL( infoMessage( const TQString &msg ) ),
	         infoObject, infoSlot );
	connect( query, TQ_SIGNAL( finished( const KXMLRPC::Query::Result & ) ),
	         receiver, slot );
	query->call( m_url.url(), method, args, m_userAgent );
}

#include "xmlrpciface.moc"
