/***************************************************************************
 *   Copyright (C) 2004, 2005 by Jakub Stachowski                                *
 *   qbast@go2.pl                                                          *
 *                                                                         *
 *   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 <tqcstring.h>
#include <tqsocket.h>
#include <tqdatetime.h>
#include <tqbitarray.h>

#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>

#include <tdeconfig.h>
#include <kdebug.h>
#include <tdemessagebox.h>
#include <tdeinstance.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <tdesocketaddress.h>
#include <kprotocolinfo.h>
#include <tdecmdlineargs.h>
#include <tdelocale.h>
#include <kurl.h>
#include <ksock.h>
#include <tqmap.h>
#include <tdeapplication.h>
#include <tqeventloop.h>
#include <dnssd/domainbrowser.h>
#include <krun.h>


#include "dnssd.h"

static const TDECmdLineOptions options[] =
{
	{ "+protocol", I18N_NOOP( "Protocol name" ), 0 },
	{ "+pool", I18N_NOOP( "Socket name" ), 0 },
	{ "+app", I18N_NOOP( "Socket name" ), 0 },
	TDECmdLineLastOption
};

ZeroConfProtocol::ZeroConfProtocol(const TQCString& protocol, const TQCString &pool_socket, const TQCString &app_socket)
		: SlaveBase(protocol, pool_socket, app_socket), browser(0),toResolve(0),
		configData(0)
{}

ZeroConfProtocol::~ZeroConfProtocol()
{
   delete configData;
}

void ZeroConfProtocol::get(const KURL& url )
{
	if (!dnssdOK()) return;
	UrlType t = checkURL(url);
	switch (t) {
	case HelperProtocol:
	{
		resolveAndRedirect(url,true);
		mimeType("text/html");
		TQString reply= "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n";
		reply+="</head>\n<body>\n<h2>"+i18n("Requested service has been launched in separate window.");
		reply+="</h2>\n</body></html>";
		data(reply.utf8());
		data(TQByteArray());
		finished();
		break;
	}
	case Service:
		resolveAndRedirect(url);
		break;
	default:
		error(ERR_MALFORMED_URL,i18n("invalid URL"));
	}
}
void ZeroConfProtocol::mimetype(const KURL& url )
{
	resolveAndRedirect(url);
}

UrlType ZeroConfProtocol::checkURL(const KURL& url)
{
	if (url.path()=="/") return RootDir;
	TQString service, type, domain;
	dissect(url,service,type,domain);
	const TQString& proto = type.section('.',1,-1);
	if (type[0]!='_' || (proto!="_udp" && proto!="_tcp")) return Invalid;
	if (service.isEmpty()) return ServiceDir;
	if (!domain.isEmpty()) {
		if (!setConfig(type)) return Invalid;
		if (!configData->readEntry("Exec").isNull()) return HelperProtocol;
		return (KProtocolInfo::isHelperProtocol( configData->readEntry( "Protocol",
			type.section(".",0,0).mid(1)))) ? HelperProtocol : Service;
		}
	return Invalid;
}

// URL zeroconf://domain/_http._tcp/some%20service
// URL invitation://host:port/_http._tcp/some%20service?u=username&root=directory
void ZeroConfProtocol::dissect(const KURL& url,TQString& name,TQString& type,TQString& domain)
{
	type = url.path().section("/",1,1);
	domain = url.host();
	name = url.path().section("/",2,-1);

}

bool ZeroConfProtocol::dnssdOK()
{
	switch(ServiceBrowser::isAvailable()) {	    
        	case ServiceBrowser::Stopped:
			error(TDEIO::ERR_UNSUPPORTED_ACTION,
			i18n("The Zeroconf daemon (mdnsd) is not running."));
			return false;
		case ServiceBrowser::Unsupported:
	    		error(TDEIO::ERR_UNSUPPORTED_ACTION,
			i18n("TDE has been built without Zeroconf support."));
			return false;
          default:
			return true;
        }
}

void ZeroConfProtocol::stat(const KURL& url)
{
	UDSEntry entry;
	if (!dnssdOK()) return;
	UrlType t = checkURL(url);
	switch (t) {
	case RootDir:
	case ServiceDir:
		buildDirEntry(entry,"");
		statEntry(entry);
		finished();
		break;
	case Service:
		resolveAndRedirect(url);
	  	break;
	case HelperProtocol:
	{
		TQString name,type,domain;
		dissect(url,name,type,domain);
		buildServiceEntry(entry,name,type,domain);
		statEntry(entry);
		finished();
		break;
	}
	default:
		error(ERR_MALFORMED_URL,i18n("invalid URL"));
	}
}
TQString ZeroConfProtocol::getAttribute(const TQString& name)
{
	TQString entry = configData->readEntry(name);
	return (entry.isNull()) ? TQString() : toResolve->textData()[entry];
}

void ZeroConfProtocol::resolveAndRedirect(const KURL& url, bool useKRun)
{
	TQString name,type,domain;
	dissect(url,name,type,domain);
	if (url.protocol()=="invitation") {
		delete toResolve;
		toResolve=0;
		toResolve= new RemoteService(url);
		if (!toResolve->isResolved()) error(ERR_MALFORMED_URL,i18n("Invalid URL"));
	} else {
		kdDebug() << "Resolve for  " << name << ", " << type << ", " << domain  << "\n";
		if (toResolve!=0)
			if (toResolve->serviceName()==name && toResolve->type()==type &&
			        toResolve->domain()==domain && toResolve->isResolved()) {
			}  else {
				delete toResolve;
				toResolve = 0;
			}
		if (toResolve==0) {
			toResolve = new RemoteService(name,type,domain);
			// or maybe HOST_NOT_FOUND?
			if (!toResolve->resolve()) error(ERR_SERVICE_NOT_AVAILABLE,i18n("Unable to resolve service"));
		}
	}
	KURL destUrl;
	kdDebug() << "Resolved: " << toResolve->hostName() << "\n";
	destUrl.setProtocol(getProtocol(type));
	destUrl.setUser(getAttribute("UserEntry"));
	destUrl.setPass(getAttribute("PasswordEntry"));
	destUrl.setPath(getAttribute("PathEntry"));
	destUrl.setHost(toResolve->hostName());
	destUrl.setPort(toResolve->port());
	// get exec from config or try getting it from helper protocol
	if (useKRun) KRun::run(configData->readEntry("Exec",KProtocolInfo::exec(getProtocol(type))),destUrl);
	else {
		redirection(destUrl);
		finished();
	}
}

bool ZeroConfProtocol::setConfig(const TQString& type)
{
	kdDebug() << "Setting config for " << type << endl;
	if (configData)
	{
		if (configData->readEntry("Type")!=type)
		{
		       	delete configData;
			configData=0L;
		}
		else 
			return true;
	}
	configData = new TDEConfig("zeroconf/"+type,false,false,"data");
	return (configData->readEntry("Type")==type);
}

inline void buildAtom(UDSEntry& entry,UDSAtomTypes type, const TQString& data)
{
	UDSAtom atom;
	atom.m_uds=type;
	atom.m_str=data;
	entry.append(atom);
}
inline void buildAtom(UDSEntry& entry,UDSAtomTypes type, long data)
{
	UDSAtom atom;
	atom.m_uds=type;
	atom.m_long=data;
	entry.append(atom);
}


void ZeroConfProtocol::buildDirEntry(UDSEntry& entry,const TQString& name,const TQString& type, const TQString& host)
{
	entry.clear();
	buildAtom(entry,UDS_NAME,name);
	buildAtom(entry,UDS_ACCESS,0555);
	buildAtom(entry,UDS_SIZE,0);
	buildAtom(entry,UDS_FILE_TYPE,S_IFDIR);
	buildAtom(entry,UDS_MIME_TYPE,"inode/directory");
	if (!type.isNull()) buildAtom(entry,UDS_URL,"zeroconf:/"+((!host.isNull()) ? "/"+host+"/" : "" )+type+"/");
}
TQString ZeroConfProtocol::getProtocol(const TQString& type)
{
	setConfig(type);
	return configData->readEntry("Protocol",type.section(".",0,0).mid(1));
}

void ZeroConfProtocol::buildServiceEntry(UDSEntry& entry,const TQString& name,const TQString& type,const TQString& domain)
{
	setConfig(type);
	entry.clear();
	buildAtom(entry,UDS_NAME,name);
	buildAtom(entry,UDS_ACCESS,0666);
	TQString icon=configData->readEntry("Icon",KProtocolInfo::icon(getProtocol(type)));
	if (!icon.isNull()) buildAtom(entry,UDS_ICON_NAME,icon);
	KURL protourl;
	protourl.setProtocol(getProtocol(type));
	TQString encname = "zeroconf://" + domain +"/" +type+ "/" + name;
	if (KProtocolInfo::supportsListing(protourl)) {
		buildAtom(entry,UDS_FILE_TYPE,S_IFDIR);
		encname+="/";
	} else buildAtom(entry,UDS_FILE_TYPE,S_IFREG);
	buildAtom(entry,UDS_URL,encname);
}

void ZeroConfProtocol::listDir(const KURL& url )
{

	if (!dnssdOK()) return;
	UrlType t  = checkURL(url);
	UDSEntry entry;
	switch (t) {
	case RootDir:
		if (allDomains=url.host().isEmpty())
			browser = new ServiceBrowser(ServiceBrowser::AllServices);
			else browser = new ServiceBrowser(ServiceBrowser::AllServices,url.host());
		connect(browser,TQ_SIGNAL(serviceAdded(DNSSD::RemoteService::Ptr)),
			this,TQ_SLOT(newType(DNSSD::RemoteService::Ptr)));
		break;
	case ServiceDir:
		if (url.host().isEmpty())
			browser = new ServiceBrowser(url.path(-1).section("/",1,-1));
			else browser = new ServiceBrowser(url.path(-1).section("/",1,-1),url.host());
		connect(browser,TQ_SIGNAL(serviceAdded(DNSSD::RemoteService::Ptr)),
			this,TQ_SLOT(newService(DNSSD::RemoteService::Ptr)));
		break;
	case Service:
		resolveAndRedirect(url);
	  	return;
	default:
		error(ERR_MALFORMED_URL,i18n("invalid URL"));
		return;
	}
	connect(browser,TQ_SIGNAL(finished()),this,TQ_SLOT(allReported()));
	browser->startBrowse();
	kapp->eventLoop()->enterLoop();
}
void ZeroConfProtocol::allReported()
{
	UDSEntry entry;
	listEntry(entry,true);
	finished();
	delete browser;
	browser=0;
	mergedtypes.clear();
	kapp->eventLoop()->exitLoop();
}
void ZeroConfProtocol::newType(DNSSD::RemoteService::Ptr srv)
{
	if (mergedtypes.contains(srv->type())>0) return;
	mergedtypes << srv->type();
	UDSEntry entry;
	kdDebug() << "Got new entry " << srv->type() << endl;
	if (!setConfig(srv->type())) return;
	TQString name = configData->readEntry("Name");
	if (!name.isNull()) {
		buildDirEntry(entry,name,srv->type(), (allDomains) ? TQString() : 
			browser->browsedDomains()->domains()[0]);
		listEntry(entry,false);
	}
}
void ZeroConfProtocol::newService(DNSSD::RemoteService::Ptr srv)
{
	UDSEntry entry;
	buildServiceEntry(entry,srv->serviceName(),srv->type(),srv->domain());
	listEntry(entry,false);
}


extern "C"
{
	int TDE_EXPORT kdemain( int argc, char **argv )
	{
		// TDEApplication is necessary to use other ioslaves
		putenv(strdup("SESSION_MANAGER="));
		TDECmdLineArgs::init(argc, argv, "tdeio_zeroconf", 0, 0, 0, 0);
		TDECmdLineArgs::addCmdLineOptions( options );
		TDEApplication::disableAutoDcopRegistration();
		TDEApplication app;
		TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();
		ZeroConfProtocol slave( args->arg(0), args->arg(1), args->arg(2) );
		slave.dispatchLoop();
		return 0;
	}
}


#include "dnssd.moc"

