/*
 *  This file is part of the KDE libraries
 *  Copyright (c) 2001 Michael Goffioul <goffioul@imec.be>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License version 2 as published by the Free Software Foundation.
 *
 *  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; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 **/

#include "kio_print.h"
#include <kdeprint/kmprinter.h>
#include <kdeprint/kmmanager.h>
#include <kdeprint/kmjobmanager.h>
#include <kdeprint/kmjob.h>
#include <kdeprint/driver.h>

#include <qfile.h>
#include <qtextstream.h>
#include <klocale.h>
#include <kdebug.h>
#include <kinstance.h>
#include <kio/global.h>
#include <kstandarddirs.h>
#include <kiconloader.h>
#include <kmimetype.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

#define	PRINT_DEBUG	kdDebug(7019) << "kio_print: "

extern "C"
{
	int kdemain(int argc, char **argv);
}

void addAtom(KIO::UDSEntry& entry, unsigned int ID, long l, const QString& s = QString::null)
{
	KIO::UDSAtom	atom;
	atom.m_uds = ID;
	atom.m_long = l;
	atom.m_str = s;
	entry.append(atom);
}

static void createDirEntry(KIO::UDSEntry& entry, const QString& name, const QString& url, const QString& mime)
{
	entry.clear();
	addAtom(entry, KIO::UDS_NAME, 0, name);
	addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR);
	addAtom(entry, KIO::UDS_ACCESS, 0500);
	addAtom(entry, KIO::UDS_MIME_TYPE, 0, mime);
	addAtom(entry, KIO::UDS_URL, 0, url);
	PRINT_DEBUG << "creating dir entry url=" << url << endl;
	addAtom(entry, KIO::UDS_SIZE, 0);
	addAtom(entry, KIO::UDS_GUESSED_MIME_TYPE, 0, "application/octet-stream");
}

static void createFileEntry(KIO::UDSEntry& entry, const QString& name, const QString& url, const QString& mime)
{
	entry.clear();
	addAtom(entry, KIO::UDS_NAME, 0, name);
	addAtom(entry, KIO::UDS_FILE_TYPE, S_IFREG);
	addAtom(entry, KIO::UDS_URL, 0, url);
	addAtom(entry, KIO::UDS_ACCESS, 0400);
	addAtom(entry, KIO::UDS_MIME_TYPE, 0, mime);
	addAtom(entry, KIO::UDS_SIZE, 0);
	addAtom(entry, KIO::UDS_GUESSED_MIME_TYPE, 0, "application/octet-stream");
}

QString buildMenu(const QStringList& items, const QStringList& links, int active)
{
	if (items.count() == 0 || items.count() != links.count())
		return QString("<td height=20 class=\"menu\">&nbsp;</td>");

	QString	s;
	unsigned int	index = 0;
	for (QStringList::ConstIterator it1=items.begin(), it2=links.begin(); it1!=items.end() && it2!=links.end(); ++it1, ++it2, index++)
	{
		if (index == active)
			s.append("<td height=20 class=\"menuactive\">&nbsp; ").append(*it1).append("&nbsp;</td>");
		else
			s.append("<td height=20 class=\"menu\">&nbsp; <a class=\"menu\" href=\"").append(*it2).append("\">").append(*it1).append("</a>&nbsp;</td>");
		if (index < items.count()-1)
			s.append("<td height=20 class=\"menu\">|</td>");
	}
	return s;
}

QString buildOptionRow(DrBase *opt, bool f)
{
	QString	s("<tr class=\"%1\"><td width=\"41%\">%1</td><td width=\"59%\">%1</td></tr>\n");
	s = s.arg(f ? "contentwhite" : "contentyellow").arg(opt->get("text")).arg(opt->prettyText());
	return s;
}

QString buildGroupTable(DrGroup *grp, bool showHeader = true)
{
	QString	s("<tr class=\"top\"><td colspan=\"2\">%1</td></tr>\n");
	if (showHeader)
		s = s.arg(grp->get("text"));
	else
		s = QString::null;

	QPtrListIterator<DrBase>	oit(grp->options());
	bool	f(false);
	for (; oit.current(); ++oit, f = !f)
		s.append(buildOptionRow(oit.current(), f));

	QPtrListIterator<DrGroup>	git(grp->groups());
	for (; git.current(); ++git)
		s.append(buildGroupTable(git.current()));

	return s;
}

int kdemain(int argc, char **argv)
{
	KInstance  instance("kio_print");

	PRINT_DEBUG << "starting ioslave" << endl;
	if (argc != 4)
	{
		fprintf(stderr, "Usage: kio_print protocol domain-socket1 domain-socket2\n");
		exit(-1);
	}

	KIO_Print	 slave(argv[2], argv[3]);
	slave.dispatchLoop();

	PRINT_DEBUG << "done" << endl;
	return 0;
}

KIO_Print::KIO_Print(const QCString& pool, const QCString& app)
: KIO::SlaveBase("print", pool, app)
{
}

void KIO_Print::listDir(const KURL& url)
{
	QStringList	path = QStringList::split('/', url.path(), false);

	PRINT_DEBUG << "listing " << url.path() << endl;
	QString	group = path[0].lower();
	if (path.count() == 0)
		listRoot();
	else if (path.count() == 1 && group != "manager" && group != "jobs")
	{
		PRINT_DEBUG << "listing group " << path[0] << endl;

		int	mask;
		QString	mimeType;
		KIO::UDSEntry	entry;

		if (group == "printers")
		{
			mask = KMPrinter::Printer;
			mimeType = "print/printer";
		}
		else if (group == "classes")
		{
			mask = KMPrinter::Class | KMPrinter::Implicit;
			mimeType = "print/class";
		}
		else if (group == "specials")
		{
			mask = KMPrinter::Special;
			mimeType = "print/printer";
		}
		else
		{
			error(KIO::ERR_DOES_NOT_EXIST, url.url());
			return;
		}

		QPtrListIterator<KMPrinter>	it(*(KMManager::self()->printerList()));
		for (;it.current();++it)
		{
			if (!(it.current()->type() & mask) || !it.current()->instanceName().isEmpty())
			{
				PRINT_DEBUG << "rejecting " << it.current()->name() << endl;
				continue;
			}

			//createFileEntry(entry, it.current()->name(), ("print:/"+path[0]+"/"+it.current()->name()), mimeType, "text/html", S_IFDIR);
			createDirEntry(entry, it.current()->name(), ("print:/"+group+"/"+KURL::encode_string_no_slash(it.current()->name())), mimeType);
			PRINT_DEBUG << "accepting " << it.current()->name() << endl;
			listEntry(entry, false);
		}

		listEntry(KIO::UDSEntry(), true);
		finished();
	}
	else
	{
		//error(KIO::ERR_UNSUPPORTED_ACTION, i18n("Unsupported path %1").arg(url.path()));
		// better do nothing
		listEntry(KIO::UDSEntry(), true);
		totalSize(0);
		finished();
	}
}

void KIO_Print::listRoot()
{
	PRINT_DEBUG << "listing root entry" << endl;

	KIO::UDSEntry	entry;

	// Classes entry
	createDirEntry(entry, i18n("Classes"), "print:/classes", "print/folder");
	listEntry(entry, false);

	// Printers entry
	createDirEntry(entry, i18n("Printers"), "print:/printers", "print/folder");
	listEntry(entry, false);

	// Specials entry
	createDirEntry(entry, i18n("Specials"), "print:/specials", "print/folder");
	listEntry(entry, false);

	// Management entry
	//createFileEntry(entry, i18n("Manager"), "print:/manager", "print/manager", QString::null, S_IFDIR);
	createDirEntry(entry, i18n("Manager"), "print:/manager", "print/manager");
	listEntry(entry, false);

	// Jobs entry
	createDirEntry(entry, i18n("Jobs"), "print:/jobs", "print/jobs");
	listEntry(entry, false);

	// finish
	totalSize(4);
	listEntry(entry, true);
	finished();
}

void KIO_Print::stat(const KURL& url)
{
	PRINT_DEBUG << "stat: " << url.url() << endl;
	QStringList	path = QStringList::split('/', url.encodedPathAndQuery(-1), false);
	KIO::UDSEntry	entry;
	QString	mime;
	bool err(false);

	PRINT_DEBUG << "path components: " << path.join(", ") << endl;

	switch (path.count())
	{
		case 0:
			createDirEntry(entry, i18n("Print System"), "print:/", "print/folder");
			break;
		case 1:
			if (path[0].lower() == "classes")
				createDirEntry(entry, i18n("Classes"), "print:/classes", "print/folder");
			else if (path[0].lower() == "printers")
				createDirEntry(entry, i18n("Printers"), "print:/printers", "print/folder");
			else if (path[0].lower() == "specials")
				createDirEntry(entry, i18n("Specials"), "print:/specials", "print/folder");
			else if (path[0].lower() == "manager")
				createFileEntry(entry, i18n("Manager"), "print:/manager", "print/manager");
			else if (path[0].lower().startsWith("jobs"))
				createFileEntry(entry, i18n("Jobs"), url.url(), "text/html");
			else
				err = true;
			break;
		case 2:
			if (path[0].lower() == "printers")
				mime = "print/printer";
			else if (path[0].lower() == "classes")
				mime = "print/class";
			else if (path[0].lower() == "specials")
				mime = "print/printer";
			else
				err = true;
			createFileEntry(entry, path[1], "print:/"+path[0]+"/"+path[1], "text/html");
			break;
	}

	if (!err)
	{
		statEntry(entry);
		finished();
	}
	else
		error(KIO::ERR_DOES_NOT_EXIST, url.path());
}

void KIO_Print::get(const KURL& url)
{
	QStringList	elems = QStringList::split('/', url.encodedPathAndQuery(), false);
	QString		group(elems[0].lower()), printer(KURL::decode_string(elems[1])), path, query;
	KMPrinter	*mprinter(0);

	if (group == "manager")
	{
		PRINT_DEBUG << "opening print management part" << endl;

		mimeType("print/manager");
		finished();
		return;
	}

	PRINT_DEBUG << "getting " << url.url() << endl;

	if (group.startsWith("jobs"))
	{
		int	p = group.find('?');
		if (p != -1)
			query = group.mid(p+1);
		if (!query.isEmpty() && query != "jobs" && query != "completed_jobs")
		{
			error(KIO::ERR_MALFORMED_URL, QString::null);
			return;
		}
		PRINT_DEBUG << "listing jobs for all printers" << endl;
		showJobs(0, query == "completed_jobs");
		return;
	}

	int	p = printer.find('?');
	if (p != -1)
	{
		query = printer.mid(p+1);
		printer = printer.left(p);
	}

	PRINT_DEBUG << "opening " << url.url() << endl;
	PRINT_DEBUG << "extracted printer name = " << printer << endl;

	KMManager::self()->printerList(false);
	mprinter = KMManager::self()->findPrinter(printer);
	if (!mprinter)
		path = locateData(printer.isEmpty() ? group : printer);

	if (elems.count() > 2 || (path.isEmpty() && group != "printers" && group != "classes" && group != "specials")
	    || (mprinter == 0 && path.isEmpty()))
	{
		error(KIO::ERR_DOES_NOT_EXIST, url.path());
		return;
	}

	if (mprinter != 0)
	{
		if (!query.isEmpty() && query != "general")
		{
			if (query == "jobs")
				showJobs(mprinter, false);
			else if (query == "completed_jobs")
				showJobs(mprinter, true);
			else if (query == "driver")
				showDriver(mprinter);
			else
				error(KIO::ERR_MALFORMED_URL, KURL::decode_string(elems[1]));
		}
		else if (group == "printers" && mprinter->isPrinter())
			showPrinterInfo(mprinter);
		else if (group == "classes" && mprinter->isClass(true))
			showClassInfo(mprinter);
		else if (group == "specials" && mprinter->isSpecial())
			showSpecialInfo(mprinter);
		else
			error(KIO::ERR_INTERNAL, i18n("Unable to determine object type for %1.").arg(printer));
	}
	else if (!path.isEmpty())
		showData(path);
	else
		error(KIO::ERR_INTERNAL, i18n("Unable to determine source type for %1.").arg(printer));
}

void KIO_Print::showPrinterInfo(KMPrinter *printer)
{
	if (!KMManager::self()->completePrinter(printer))
		error(KIO::ERR_INTERNAL, i18n("Unable to retrieve printer information for %1.").arg(printer->name()));
	else
	{
		mimeType("text/html");

		QString	content;
		if (!loadTemplate(QString::fromLatin1("printer.template"), content))
		{
			error(KIO::ERR_INTERNAL, i18n("Unable to load template %1").arg("printer.template"));
			return;
		}

		content = content
				 .arg(i18n("Properties of %1").arg(printer->printerName()))
				 .arg(buildMenu(QStringList::split('|', i18n("General|Driver|Active jobs|Completed jobs"), false),
							 QStringList::split('|', "?general|?driver|?jobs|?completed_jobs", true),
							 0))
				 .arg(printer->pixmap())
				 .arg(printer->name())
				 .arg(i18n("General Properties"))
				 .arg(i18n("Type")).arg(printer->isRemote() ? i18n("Remote") : i18n("Local"))
				 .arg(i18n("State")).arg(printer->stateString())
				 .arg(i18n("Location")).arg(printer->location())
				 .arg(i18n("Description")).arg(printer->description())
				 .arg(i18n("URI")).arg(printer->uri().prettyURL())
				 .arg(i18n("Interface (Backend)")).arg(printer->device().prettyURL())
				 .arg(i18n("Driver"))
				 .arg(i18n("Manufacturer")).arg(printer->manufacturer())
				 .arg(i18n("Model")).arg(printer->model())
				 .arg(i18n("Driver Information")).arg(printer->driverInfo());

		data(content.local8Bit());
		finished();
	}
}

void KIO_Print::showClassInfo(KMPrinter *printer)
{
	if (!KMManager::self()->completePrinter(printer))
		error(KIO::ERR_INTERNAL, i18n("Unable to retrieve class information for %1.").arg(printer->name()));
	else
	{
		mimeType("text/html");

		QString	content;
		if (!loadTemplate(QString::fromLatin1("class.template"), content))
		{
			error(KIO::ERR_INTERNAL, i18n("Unable to load template %1").arg("class.template"));
			return;
		}

		QString		memberContent("<ul>\n");
		QStringList	members(printer->members());
		for (QStringList::ConstIterator it=members.begin(); it!=members.end(); ++it)
		{
			memberContent.append(QString::fromLatin1("<li><a href=\"print:/printers/%1\">%2</a></li>\n").arg(*it).arg(*it));
		}
		memberContent.append("</ul>\n");

		QString		typeContent = (printer->isImplicit() ? i18n("Implicit") : (printer->isRemote() ? i18n("Remote") : i18n("Local")));

		content = content
				 .arg(i18n("Properties of %1").arg(printer->printerName()))
				 .arg(buildMenu(QStringList::split('|', i18n("General|Active jobs|Completed jobs"), false),
							 QStringList::split('|', "?general|?jobs|?completed_jobs", true),
							 0))
				 .arg(printer->pixmap())
				 .arg(printer->name())
				 .arg(i18n("General Properties"))
				 .arg(i18n("Type")).arg(typeContent)
				 .arg(i18n("State")).arg(printer->stateString())
				 .arg(i18n("Location")).arg(printer->location())
				 .arg(i18n("Description")).arg(printer->description())
				 .arg(i18n("URI")).arg(printer->uri().prettyURL())
				 .arg(i18n("Members")).arg(memberContent);

		data(content.local8Bit());
		finished();
	}
}

void KIO_Print::showSpecialInfo(KMPrinter *printer)
{
	mimeType("text/html");

	QString	content;
	if (!loadTemplate(QString::fromLatin1("pseudo.template"), content))
	{
		error(KIO::ERR_INTERNAL, i18n("Unable to load template %1").arg("pseudo.template"));
		return;
	}

	QString	reqContent("<ul>\n");
	QStringList	requirements = QStringList::split(",", printer->option("kde-special-require"), false);
	for (QStringList::ConstIterator it=requirements.begin(); it!=requirements.end(); ++it)
		reqContent += ("<li>" + (*it) + "</li>\n");
	reqContent.append("</ul>\n");

	content = content
			 .arg(i18n("Properties of %1").arg(printer->printerName()))
			 .arg(buildMenu(QStringList::split('|', i18n("General"), false),
						 QStringList::split('|', "?general", true),
						 0))
			 .arg(printer->pixmap())
			 .arg(printer->name())
			 .arg(i18n("General Properties"))
			 .arg(i18n("Location")).arg(printer->location())
			 .arg(i18n("Description")).arg(printer->description())
			 .arg(i18n("Requirements")).arg(reqContent)
			 .arg(i18n("Command Properties"))
			 .arg(i18n("Command")).arg("<tt>"+printer->option("kde-special-command")+"</tt>")
			 .arg(i18n("Use Output File")).arg(printer->option("kde-special-file") == "1" ? i18n("Yes") : i18n("No"))
			 .arg(i18n("Default Extension")).arg(printer->option("kde-special-extension"));

	data(content.local8Bit());
	finished();
}

bool KIO_Print::loadTemplate(const QString& filename, QString& buffer)
{
	QFile	f(locate("data", QString::fromLatin1("kdeprint/template/")+filename));
	if (f.exists() && f.open(IO_ReadOnly))
	{
		QTextStream	t(&f);
		buffer = t.read();
		return true;
	}
	else
	{
		buffer = QString::null;
		return false;
	}
}

void KIO_Print::showData(const QString& pathname)
{
	PRINT_DEBUG << "sending data: " << pathname << endl;
	QFile	f(pathname);
	if (f.exists() && f.open(IO_ReadOnly))
	{
		QByteArray	arr(f.readAll());
		mimeType(KMimeType::findByURL(KURL(pathname), 0, true, true)->name());
		data(arr);
		finished();
	}
	else
	{
		PRINT_DEBUG << "file not found" << endl;
		error(KIO::ERR_DOES_NOT_EXIST, pathname);
	}
}

/**
 * Locate a data in this order:
 *	- $KDEDIR/share/apps/kdeprint/template/
 *	- as a desktop icon
 */
QString KIO_Print::locateData(const QString& item)
{
	QString	path = locate("data", "kdeprint/template/"+item);
	if (path.isEmpty())
		path = KGlobal::iconLoader()->iconPath(item, KIcon::Desktop, true);
	return path;
}

void KIO_Print::showJobs(KMPrinter *prt, bool completed)
{
	mimeType("text/html");

	// Add the printer to the current list in the job manager
	KMJobManager::JobType	type = (completed ? KMJobManager::CompletedJobs : KMJobManager::ActiveJobs);
	KMJobManager	*mgr = KMJobManager::self();
	if (prt)
		mgr->addPrinter(prt->printerName(), type);
	else
	{
		QPtrListIterator<KMPrinter>	pit(*(KMManager::self()->printerList()));
		for (; pit.current(); ++pit)
			if (pit.current()->isVirtual() || pit.current()->isSpecial())
				continue;
			else
				mgr->addPrinter(pit.current()->printerName(), type);
	}

	QString	content;
	if (!loadTemplate(QString::fromLatin1("jobs.template"), content))
	{
		error(KIO::ERR_INTERNAL, i18n("Unable to load template %1").arg("pseudo.template"));
		return;
	}

	if (prt)
	{
		content = content
				 .arg(i18n("Jobs of %1").arg(prt->printerName()))
				 .arg(prt->isClass () ?
						 buildMenu(QStringList::split('|', i18n("General|Active jobs|Completed jobs"), false),
							 QStringList::split('|', "?general|?jobs|?completed_jobs", true),
							 (completed ? 2 : 1)) :
						 buildMenu(QStringList::split('|', i18n("General|Driver|Active jobs|Completed jobs"), false),
							 QStringList::split('|', "?general|?driver|?jobs|?completed_jobs", true),
							 (completed ? 3 : 2)))
				 .arg(prt->pixmap())
				 .arg(prt->printerName());
	}
	else
	{
		content = content
				 .arg(i18n("All jobs"))
				 .arg(buildMenu(QStringList::split('|', i18n("Active jobs|Completed jobs"), false),
							 QStringList::split('|', "?jobs|?completed_jobs", true),
							 (completed ? 1 : 0)))
				 .arg("fileprint")
				 .arg(i18n("All jobs"));
	}
	content = content.arg(i18n("ID")).arg(i18n("Owner")).arg(i18n("Printer")).arg(i18n("Name")).arg(i18n("State"));

	QString	jobContent, cellContent("<td>%1</td>\n");
	QPtrListIterator<KMJob>	it(mgr->jobList());
	bool	flag(true);
	for (; it.current(); ++it, flag = !flag)
	{
		jobContent.append("<tr class=\"").append(flag ? "contentyellow" : "contentwhite").append("\">\n");
		jobContent.append(cellContent.arg(it.current()->id()));
		jobContent.append(cellContent.arg(it.current()->owner()));
		jobContent.append(cellContent.arg(it.current()->printer()));
		jobContent.append(cellContent.arg(it.current()->name()));
		jobContent.append(cellContent.arg(it.current()->stateString()));
		jobContent.append("</tr>\n");
	}
	content = content.arg(jobContent);

	// remove the current printer to the current list in the job manager
	if (prt)
		mgr->removePrinter(prt->printerName(), type);
	else
	{
		QPtrListIterator<KMPrinter>	pit(*(KMManager::self()->printerList()));
		for (; pit.current(); ++pit)
			if (pit.current()->isVirtual() || pit.current()->isSpecial())
				continue;
			else
				mgr->removePrinter(pit.current()->printerName(), type);
	}

	data(content.local8Bit());
	finished();
}

void KIO_Print::showDriver(KMPrinter *prt)
{
	mimeType("text/html");

	QString	content;
	if (!loadTemplate(QString::fromLatin1("driver.template"), content))
	{
		error(KIO::ERR_INTERNAL, i18n("Unable to load template %1").arg("pseudo.template"));
		return;
	}

	DrMain	*driver = KMManager::self()->loadPrinterDriver(prt, true);
	content = content
			 .arg(i18n("Driver of %1").arg(prt->printerName()))
			 .arg(buildMenu(QStringList::split('|', i18n("General|Driver|Active jobs|Completed jobs"), false),
						 QStringList::split('|', "?general|?driver|?jobs|?completed_jobs", true),
						 1))
			 .arg(prt->pixmap())
			 .arg(prt->printerName() + "&nbsp;(" + (driver ? driver->get("text") : i18n("No driver found")) + ")");

	if (driver)
		content = content.arg(buildGroupTable(driver, false));
	else
		content = content.arg(QString::null);

	data(content.local8Bit());
	finished();
}
