/***************************************************************************
 *   Copyright (C) 2005 by Joris Guisson                                   *
 *   joris.guisson@gmail.com                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 ***************************************************************************/

#include <tqstringlist.h>
#include <tqdir.h>
#include <tqfile.h>

#include <tdelocale.h>
#include <kdebug.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <tdeio/netaccess.h>
#include <tdeio/job.h>
 
#include <stdlib.h>
#include <fcntl.h>

#include "httprequest.h"
#include "upnprouter.h"
#include "upnpdescriptionparser.h"
#include "soap.h"
#include "../functions.h"

using namespace bt;
using namespace net;

namespace kt 
{
	UPnPService::UPnPService()
	{
	}
	
	UPnPService::UPnPService(const UPnPService & s)
	{
		this->servicetype = s.servicetype;
		this->controlurl = s.controlurl;
		this->eventsuburl = s.eventsuburl;
		this->serviceid = s.serviceid;
		this->scpdurl = s.scpdurl;
	}

	void UPnPService::setProperty(const TQString & name,const TQString & value)
	{
		if (name == "serviceType")
			servicetype = value;
		else if (name == "controlURL")
			controlurl = value;
		else if (name == "eventSubURL")
			eventsuburl = value;
		else if (name == "SCPDURL")
			scpdurl = value;
		else if (name == "serviceId")
			serviceid = value;
	}
	
	void UPnPService::clear()
	{
		servicetype = controlurl = eventsuburl = scpdurl = serviceid = "";
	}
	
	void UPnPService::debugPrintData()
	{
		kdDebug()  << "    servicetype = " << servicetype << endl;
		kdDebug()  << "    controlurl = " << controlurl << endl;
		kdDebug()  << "    eventsuburl = " << eventsuburl << endl;
		kdDebug()  << "    scpdurl = " << scpdurl << endl;
		kdDebug()  << "    serviceid = " << serviceid << endl;
	}
	
	UPnPService & UPnPService::operator = (const UPnPService & s)
	{
		this->servicetype = s.servicetype;
		this->controlurl = s.controlurl;
		this->eventsuburl = s.eventsuburl;
		this->serviceid = s.serviceid;
		this->scpdurl = s.scpdurl;
		return *this;
	}
	
	///////////////////////////////////////
	
	void UPnPDeviceDescription::setProperty(const TQString & name,const TQString & value)
	{
		if (name == "friendlyName")
			friendlyName = value;
		else if (name == "manufacturer")
			manufacturer = value;
		else if (name == "modelDescription")
			modelDescription = value;
		else if (name == "modelName")
			modelName = value;
		else if (name == "modelNumber")
			modelNumber == value;
	}
	
	///////////////////////////////////////
	
	UPnPRouter::UPnPRouter(const TQString & server,const KURL & location,bool verbose) : server(server),location(location),verbose(verbose)
	{
        forwardedPortList = new ForwardPortList();
		// make the tmp_file unique, current time * a random number should be enough
		tmp_file = TQString("/tmp/tork_upnp_description-%1.xml").arg(bt::GetCurrentTime() * rand());
	}
	
	
	UPnPRouter::~UPnPRouter()
	{
		TQValueList<HTTPRequest*>::iterator i = active_reqs.begin();
		while (i != active_reqs.end())
		{
			(*i)->deleteLater();
			i++;
		}
	}
	
	void UPnPRouter::addService(const UPnPService & s)
	{
		TQValueList<UPnPService>::iterator i = services.begin();
		while (i != services.end())
		{
			UPnPService & os = *i;
			if (s.servicetype == os.servicetype)
				return;
			i++;
		}
		services.append(s);
	}
	
	void UPnPRouter::downloadFinished(TDEIO::Job* j)
	{
		if (j->error())
		{
			kdDebug()  << "Failed to download " << location << " : " << j->errorString() << endl;
			return;
		}
		
		TQString target = tmp_file;
		// load in the file (target is always local)
		UPnPDescriptionParser desc_parse;
		bool ret = desc_parse.parse(target,this);
		if (!ret)
		{
			kdDebug()  << "Error parsing router description !" << endl;
			TQString dest = TDEGlobal::dirs()->saveLocation("data","tork") + "upnp_failure";
			TDEIO::file_copy(target,dest,-1,true,false,false);
		}
		else
		{
			if (verbose)
				debugPrintData();
		}
		xmlFileDownloaded(this,ret);
        remove(TQFile::encodeName(target));
	}
	
	void UPnPRouter::downloadXMLFile()
	{
		// downlaod XML description into a temporary file in /tmp
		TDEIO::Job* job = TDEIO::file_copy(location,tmp_file,-1,true,false,false);
		connect(job,TQ_SIGNAL(result(TDEIO::Job *)),this,TQ_SLOT(downloadFinished( TDEIO::Job* )));
	}
	
	void UPnPRouter::debugPrintData()
	{
		kdDebug()  << "UPnPRouter : " << endl;
		kdDebug()  << "Friendly name = " << desc.friendlyName << endl;
		kdDebug()  << "Manufacterer = " << desc.manufacturer << endl;
		kdDebug()  << "Model description = " << desc.modelDescription << endl;
		kdDebug()  << "Model name = " << desc.modelName << endl;
		kdDebug()  << "Model number = " << desc.modelNumber << endl;
		for (TQValueList<UPnPService>::iterator i = services.begin();i != services.end();i++)
		{
			UPnPService & s = *i;
			kdDebug() << "Service : " << endl;
			s.debugPrintData();
			kdDebug()  << "Done" << endl;
		}
		kdDebug()  << "Done" << endl;
	}
	
 
	void UPnPRouter::forward(UPnPService* srv,const net::Port & externalport,
                             const net::Port & internalport)
	{


		// add all the arguments for the command
		TQValueList<SOAP::Arg> args;
		SOAP::Arg a;
		a.element = "NewRemoteHost";
		args.append(a);
		
		// the external port
		a.element = "NewExternalPort";
		a.value = TQString::number(externalport.number);
		args.append(a);
		
		// the protocol
		a.element = "NewProtocol";
		a.value = externalport.proto == TCP ? "TCP" : "UDP";
		args.append(a);
		
		// the local port
		a.element = "NewInternalPort";
        if (internalport.number)
		    a.value = TQString::number(internalport.number);
        else
		    a.value = TQString::number(externalport.number);
		args.append(a);
		
		// the local IP address
		a.element = "NewInternalClient";
		a.value = "$LOCAL_IP";// will be replaced by our local ip in bt::HTTPRequest
		args.append(a);
		
		a.element = "NewEnabled";
		a.value = "1";
		args.append(a);
		
		a.element = "NewPortMappingDescription";
		static Uint32 cnt = 0;
		a.value = TQString("TorK UPNP %1").arg(cnt++);	// TODO: change this
		args.append(a);
		
		a.element = "NewLeaseDuration";
		a.value = "0";
		args.append(a);
		
		TQString action = "AddPortMapping";
		TQString comm = SOAP::createCommand(action,srv->servicetype,args);
		
		Forwarding fw = {externalport,internalport,0,srv};
		// erase old forwarding if one exists
		TQValueList<Forwarding>::iterator itr = fwds.begin();
		while (itr != fwds.end())
		{
			Forwarding & fwo = *itr;
			if (fwo.extport == externalport && fwo.intport == internalport && fwo.service == srv)
				itr = fwds.erase(itr);
			else
				itr++;
		}
		
		fw.pending_req = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl, true);
		fwds.append(fw);

        //Track the forwarding request so we can know whether it was successful or not
        //and keep a map of successfully forwarded ports in forwardedPorts
		ForwardingRequest fwreq = {externalport,internalport,fw.pending_req};
		TQValueList<ForwardingRequest>::iterator itrq = fwdreqs.begin();
		while (itrq != fwdreqs.end())
		{
			ForwardingRequest & fwo = *itrq;
			if (fwo.extport == externalport && fwo.intport == internalport)
				itrq = fwdreqs.erase(itrq);
			else
				itrq++;
		}
		fwdreqs.append(fwreq);

	}

	void UPnPRouter::forward(const net::Port & externalport,
                             const net::Port & internalport,
                             bool force)
	{

        if ((forwardedPortList->contains(net::ForwardPort(externalport.number,
                                      internalport.number,
                                      net::TCP,false)))
           && (!force))
          return;

		kdDebug() << "Forwarding port " << externalport.number << " (" << (externalport.proto == UDP ? "UDP" : "TCP") << ")" << endl;
		// first find the right service
		TQValueList<UPnPService>::iterator i = services.begin();
		while (i != services.end())
		{
			UPnPService & s = *i;
			if (s.servicetype == "urn:schemas-upnp-org:service:WANIPConnection:1" ||
				s.servicetype == "urn:schemas-upnp-org:service:WANPPPConnection:1")
			{
                if (internalport.number)
				    forward(&s,externalport,internalport);
                else
				    forward(&s,externalport);
			}
			i++;
		}
		
	}
	
	void UPnPRouter::undoForward(UPnPService* srv,const net::Port & extport,
                                 const net::Port & intport,bt::WaitJob* waitjob)
	{
		// add all the arguments for the command
		TQValueList<SOAP::Arg> args;
		SOAP::Arg a;
		a.element = "NewRemoteHost";
		args.append(a);
		
		// the external port
		a.element = "NewExternalPort";
		a.value = TQString::number(extport.number);
		args.append(a);
		
		// the protocol
		a.element = "NewProtocol";
		a.value = extport.proto == TCP ? "TCP" : "UDP";
		args.append(a);
		
		
		TQString action = "DeletePortMapping";
		TQString comm = SOAP::createCommand(action,srv->servicetype,args);
		bt::HTTPRequest* r = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl,waitjob != 0,false);

		ForwardingRequest fwreq = {extport,intport,r};
		TQValueList<ForwardingRequest>::iterator itrq = fwdreqs.begin();
		while (itrq != fwdreqs.end())
		{
			ForwardingRequest & fwo = *itrq;
			if (fwo.extport == extport && fwo.intport == intport)
				itrq = fwdreqs.erase(itrq);
			else
				itrq++;
		}
		fwdreqs.append(fwreq);

		
		if (waitjob)
			waitjob->addExitOperation(r);
		
		updateGUI();
	}
	
	
	void UPnPRouter::undoForward(const net::Port & externalport,
                                 const net::Port & ,bt::WaitJob* waitjob)
	{
		kdDebug() << "Undoing forward of port " << externalport.number 
				<< " (" << (externalport.proto == UDP ? "UDP" : "TCP") << ")" << endl;
		
		TQValueList<Forwarding>::iterator itr = fwds.begin();
		while (itr != fwds.end())
		{
			Forwarding & wd = *itr;
			if (wd.extport == externalport)
			{
				undoForward(wd.service,wd.extport,wd.intport,waitjob);
				itr = fwds.erase(itr);
			}
			else
			{
				itr++;
			}
		}
	}
	
	bt::HTTPRequest* UPnPRouter::sendSoapQuery(const TQString & query,const TQString & soapact,const TQString & controlurl, bool fwd,bool at_exit)
	{
		// if port is not set, 0 will be returned 
		// thanks to Diego R. Brogna for spotting this bug
		if (location.port()==0)
			location.setPort(80);
		
		TQString http_hdr = TQString(
				"POST %1 HTTP/1.1\r\n"
				"HOST: %2:%3\r\n"
				"Content-length: $CONTENT_LENGTH\r\n"
				"Content-Type: text/xml\r\n"
				"SOAPAction: \"%4\"\r\n"
				"\r\n").arg(controlurl).arg(location.host()).arg(location.port()).arg(soapact);

		
		HTTPRequest* r = new HTTPRequest(http_hdr,query,location.host(),location.port(),verbose, fwd);
		connect(r,TQ_SIGNAL(replyError(bt::HTTPRequest* ,const TQString& ,bool)),
				this,TQ_SLOT(onReplyError(bt::HTTPRequest* ,const TQString& ,bool)));
		connect(r,TQ_SIGNAL(replyOK(bt::HTTPRequest* ,const TQString& ,bool)),
				this,TQ_SLOT(onReplyOK(bt::HTTPRequest* ,const TQString& ,bool)));
		connect(r,TQ_SIGNAL(error(bt::HTTPRequest*, bool )),
				this,TQ_SLOT(onError(bt::HTTPRequest*, bool )));
		r->start();
		if (!at_exit)
			active_reqs.append(r);
		return r;
	}
	
	void UPnPRouter::httpRequestDone(bt::HTTPRequest* r,bool erase_fwd)
	{
		TQValueList<Forwarding>::iterator i = fwds.begin();
		while (i != fwds.end())
		{
			Forwarding & fw = *i;
			if (fw.pending_req == r)
			{
				fw.pending_req = 0;
				if (erase_fwd)
					fwds.erase(i);
				break;
			}
			i++;
		}
		
		updateGUI();
		active_reqs.remove(r);
		r->deleteLater();
	}
	
	void UPnPRouter::onReplyOK(bt::HTTPRequest* r,const TQString & s,bool fwd)
	{
		kdDebug() << "UPnPRouter : OK" << endl;
        kdDebug() << "FWD : " << fwd << endl;
		TQValueList<ForwardingRequest>::iterator i = fwdreqs.begin();
		while (i != fwdreqs.end())
		{
			ForwardingRequest & fw = *i;
			if (fw.pending_req == r)
			{
                if (fwd)
                    forwardedPortList->addNewForwardPort(fw.extport.number,
                                      fw.intport.number,net::TCP,false);
                else
                    forwardedPortList->removeForwardPort(fw.extport.number,
                                      fw.intport.number,net::TCP);
				break;
			}
			i++;
		}

		emit replyOK(this,r,s,fwd);
		httpRequestDone(r,false);
	}
	
	void UPnPRouter::onReplyError(bt::HTTPRequest* r,const TQString & s,bool fwd)
	{
		if (verbose)
			kdDebug()  << "UPnPRouter : Error" << endl;
		kdDebug() << r->showPayload() << endl;
		httpRequestDone(r,true);
		emit replyError(this,r,s,fwd);
	}
	
	void UPnPRouter::onError(bt::HTTPRequest* r,bool)
	{
		httpRequestDone(r,true);
	}
	
	
}


namespace bt
{

	WaitJob::WaitJob(Uint32 millis) : TDEIO::Job(false)
	{
		connect(&timer,TQ_SIGNAL(timeout()),this,TQ_SLOT(timerDone()));
		timer.start(millis,true);
	}


	WaitJob::~WaitJob()
	{}

	void WaitJob::kill(bool)
	{
		m_error = 0;
		emitResult();
	}
		
	void WaitJob::timerDone()
	{
		// set the error to null and emit the result
		m_error = 0;
		emitResult();
	}
	
	void WaitJob::addExitOperation(kt::ExitOperation* op)
	{
		exit_ops.append(op);
		connect(op,TQ_SIGNAL(operationFinished( kt::ExitOperation* )),
				this,TQ_SLOT(operationFinished( kt::ExitOperation* )));
	}
	
	void WaitJob::operationFinished(kt::ExitOperation* op)
	{
		if (exit_ops.count() > 0)
		{
			exit_ops.remove(op);
			if (op->deleteAllowed())
				op->deleteLater();
			
			if (exit_ops.count() == 0)
				timerDone();
		}
	}
	
	void WaitJob::execute(WaitJob* job)
	{
		TDEIO::NetAccess::synchronousRun(job,0);
	}
	
	void SynchronousWait(Uint32 millis)
	{
		kdDebug() << "SynchronousWait" << endl;
		WaitJob* j = new WaitJob(millis);
		TDEIO::NetAccess::synchronousRun(j,0);
	}


// 	void UpdateCurrentTime()
// 	{
// 		global_time_stamp = Now();
// 	}

}

#include "upnprouter.moc"
