/***************************************************************************
 *   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.             *
 ***************************************************************************/
#ifndef KTUPNPROUTER_H
#define KTUPNPROUTER_H

#include <qtimer.h>
#include <kio/job.h>
#include <qvaluelist.h>
#include "exitoperation.h"
#include <kurl.h>
#include <qstringlist.h>
#include <kstreamsocket.h>
#include "portlist.h"
#include "forwardportlist.h"

using bt::Uint16;

namespace bt 
{

	/**
	 * @author Joris Guisson <joris.guisson@gmail.com>
	 * 
	 * Job to wait for a certain amount of time or until one or more ExitOperation's have
	 * finished.
	*/
	class WaitJob : public KIO::Job
	{
		Q_OBJECT
	public:
		WaitJob(Uint32 millis);
		virtual ~WaitJob();

		virtual void kill(bool quietly=true);
		
		/**
		 * Add an ExitOperation;
		 * @param op The operation
		 */
		void addExitOperation(kt::ExitOperation* op);
		
		
		/**
		 * Execute a WaitJob
		 * @param job The Job
		 */
		static void execute(WaitJob* job);
		
		/// Are there any ExitOperation's we need to wait for
		bool needToWait() const {return exit_ops.count() > 0;}
		
	private slots:
		void timerDone();
		void operationFinished(kt::ExitOperation* op);
		
	private:
		QTimer timer;
		QValueList<kt::ExitOperation*> exit_ops;
	};
	
	void SynchronousWait(Uint32 millis);
	

}

namespace bt
{
	class HTTPRequest;
	class WaitJob;
}

namespace net
{
    class ForwardPortList;
}
namespace KIO
{
	class Job;
}

namespace kt 
{

	/**
	 * Structure describing a UPnP service found in an xml file.
	*/
	struct UPnPService	
	{ 
		QString serviceid;
		QString servicetype;
		QString controlurl;
		QString eventsuburl;
		QString scpdurl;
		
		UPnPService();
		UPnPService(const UPnPService & s);
		
		/**
		 * Set a property of the service.
		 * @param name Name of the property (matches to variable names)
		 * @param value Value of the property
		 */
		void setProperty(const QString & name,const QString & value);
		
		/**
		 * Set all strings to empty.
		 */
		void clear();
		
		/// Print the data of this service
		void debugPrintData();
		
		/**
		 * Assignment operator
		 * @param s The service to copy
		 * @return *this
		 */
		UPnPService & operator = (const UPnPService & s);
	};
	
	/**
	 *  Struct to hold the description of a device
	 */
	struct UPnPDeviceDescription
	{
		QString friendlyName;
		QString manufacturer;
		QString modelDescription;
		QString modelName;
		QString modelNumber;
		
		/**
		 * Set a property of the description
		 * @param name Name of the property (matches to variable names)
		 * @param value Value of the property
		 */
		void setProperty(const QString & name,const QString & value);
	};
	
	/**
	 * @author Joris Guisson
	 * 
	 * Class representing a UPnP enabled router. This class is also used to communicate
	 * with the router.
	*/
	class UPnPRouter : public QObject
	{
		Q_OBJECT
				
	public:
		struct Forwarding 
		{
			net::Port extport;
            net::Port intport;
			bt::HTTPRequest* pending_req;
			UPnPService* service;
		};

		struct ForwardingRequest 
		{
			net::Port extport;
            net::Port intport;
			bt::HTTPRequest* pending_req;
		};

	private:	
		QString server;
		QString tmp_file;
		KURL location;
		UPnPDeviceDescription desc;
		QValueList<UPnPService> services;
		QValueList<Forwarding> fwds;
		QValueList<ForwardingRequest> fwdreqs;
		QValueList<bt::HTTPRequest*> active_reqs;
        net::ForwardPortList* forwardedPortList;

	public:
		/**
		 * Construct a router.
		 * @param server The name of the router
		 * @param location The location of it's xml description file
		 * @param verbose Print lots of debug info
		 */
		UPnPRouter(const QString & server,const KURL & location,bool verbose = false);	
		virtual ~UPnPRouter();
		
		/// Get the name  of the server
		QString getServer() const {return server;}
		
		/// Get the location of it's xml description
		KURL getLocation() const {return location;}
		
		/// Get the device description
		UPnPDeviceDescription & getDescription() {return desc;}
		
		/// Get the device description (const version)
		const UPnPDeviceDescription & getDescription() const {return desc;}
		
		/**
		 * Download the XML File of the router.
		 */
		void downloadXMLFile();
		
		/**
		 * Add a service to the router.
		 * @param s The service
		 */
		void addService(const UPnPService & s);
			
#if 0
		/**
		 * See if a port is forwarded
		 * @param port The Port
		 */
		void isPortForwarded(const net::Port & port);	
		
		/**
		 * Get the external IP address.
		 */
		void getExternalIP();
#endif
		
		/**
		 * Forward a local port
		 * @param port The local port to forward
		 */
		void forward(const net::Port & externalport,
                     const net::Port & internalport = net::Port::Port(),
                     bool force = false);
		
		/**
		 * Undo forwarding
		 * @param port The port
		 * @param waitjob When this is set the jobs needs to be added to the waitjob, 
		 * so we can wait for their completeion at exit
		 */
		void undoForward(const net::Port & externalport,const net::Port & internalport,
                         bt::WaitJob* waitjob = 0);
		
		void debugPrintData();
		
		QValueList<ForwardingRequest>::iterator beginReqMappings() {return fwdreqs.begin();}
		QValueList<ForwardingRequest>::iterator endReqMappings() {return fwdreqs.end();}
		QValueList<Forwarding>::iterator beginPortMappings() {return fwds.begin();}
		QValueList<Forwarding>::iterator endPortMappings() {return fwds.end();}
		net::ForwardPortList* forwardedPorts() {return forwardedPortList;}
	private slots:
		void onReplyOK(bt::HTTPRequest* r,const QString &,bool);
		void onReplyError(bt::HTTPRequest* r,const QString &,bool);
		void onError(bt::HTTPRequest* r,bool);
		void downloadFinished(KIO::Job* j);
		
		
		
	signals:
		/**
		 * Tell the GUI that it needs to be updated.
		 */
		void updateGUI();
		
		/**
		 * Signal which indicates that the XML was downloaded successfully or not.
		 * @param r The router which emitted the signal
		 * @param success Wether or not it succeeded
		 */
		void xmlFileDownloaded(UPnPRouter* r,bool success);

		void replyOK(kt::UPnPRouter*,bt::HTTPRequest* ,const QString &,bool);
		void replyError(kt::UPnPRouter*,bt::HTTPRequest* ,const QString &,bool);
		
	private:
		QValueList<UPnPService>::iterator findPortForwardingService();		

		bt::HTTPRequest* sendSoapQuery(const QString & query,const QString & soapact,const QString & controlurl,bool fwd, bool at_exit = false );
		bool verbose;
		
		void forward(UPnPService* srv,const net::Port & externalport,const net::Port & internalport = net::Port::Port());
		void undoForward(UPnPService* srv,const net::Port & externalport,const net::Port &
                        internalport,bt::WaitJob* waitjob);
		void httpRequestDone(bt::HTTPRequest* r,bool erase_fwd);
	};

}


#endif
