/****************************************************************************
 *   Copyright (C) 2003-2008 by Albert Astals Cid                           *
 *   aacid@kde.org                                                          *
 *                                                                          *
 *   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.                                    *
 ****************************************************************************/

#include "tdeio_gopher.h"

#include <stdlib.h>
#include <kurl.h>
#include <kdebug.h>
#include <kmdcodec.h>
#include <tdeinstance.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kmimetype.h>

#include <tqbuffer.h>
#include <tqfile.h>

using namespace TDEIO;

extern "C"
{
	int TDE_EXPORT kdemain( int argc, char **argv )
	{
  	TDEInstance instance( "tdeio_gopher" );

		if (argc != 4)
		{
			fprintf(stderr, "Usage: tdeio_gopher protocol domain-socket1 domain-socket2\n");
			exit(-1);
		}

		GopherProtocol slave(argv[2], argv[3]);
		slave.dispatchLoop();
		return 0;
	}
}


/* gopher */

GopherProtocol::GopherProtocol(const TQCString &pool_socket, const TQCString &app_socket)
   : TCPSlaveBase(70, "gopher", pool_socket, app_socket)
{
}

void GopherProtocol::get(const KURL& url )
{
	// gopher urls are
	// gopher://<host>:<port>/<gopher-path>
	//
	//  where <gopher-path> is one of
	//
	//  <gophertype><selector>
	//  <gophertype><selector>%09<search>
	//  <gophertype><selector>%09<search>%09<gopher+_string>
	int port;
	TQChar type;
	TQString path(url.path());
	TQString query(url.query());

	// determine the type
	if (path != "/" && path != "") type = path[1];
	else type = '1';
	
	// determine the port
	if (url.port() > 0) port = url.port();
	else port = 70;

	setBlockConnection(true);
	
	// connect to the host
	if (!connectToHost(url.host(), port)) return;
	
	if (type == '7' && query.isNull())
	{
		closeDescriptor();
		handleSearch(url.host(), path, port);
	}
	else
	{
		int i, bytes;
		char aux[10240];
		TQBuffer received;
		received.open(IO_WriteOnly);
		
		infoMessage(i18n("Connecting to %1...").arg(url.host()));
		infoMessage(i18n("%1 contacted. Retrieving data...").arg(url.host()));
		bytes = 0;
		
		// send the selector
		path.remove(0, 2);
		write(path.latin1(), path.length());
		write(query.latin1(), query.length());
		write("\r\n", 2);

		// read the data
		while((i = read(aux, 10240)) > 0)
		{
			bytes += i;
			received.writeBlock(aux, i);
			processedSize(bytes);
			infoMessage(i18n("Retrieved %1 bytes from %2...").arg(bytes).arg(url.host()));
		}

		if (type == '1' || type =='7')
		{
			processDirectory(received.buffer().data(), url.host(), url.path());
		}
		else
		{
			KMimeType::Ptr result = KMimeType::findByContent(received.buffer());
			mimeType(result->name());
			data(received.buffer());
		}
		closeDescriptor();
	}
	finished();
}

void GopherProtocol::processDirectory(const TQString &received_str, const TQString &host, const TQString &path)
{
  TQString received(received_str);
	TQString pathToShow;
	if (path == "/" || path == "/1")
	{
		pathToShow = "";
	}
	else
	{
		pathToShow = path;
	}
	mimeType("text/html");
	TQString info;
	TQString show("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
			"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\t<head>\n\t\t<title>");
	show += host.utf8();
	show += pathToShow.utf8();
	show += TQString("</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
	    "\t\t<style type=\"text/css\">\n\t\t\t.info{ font-size : small; display : block; font-family : monospace; "
	    "white-space : pre; margin-left : 18px; }\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<h1>");
	show += host.utf8();
	show += pathToShow.utf8();
	show += "</h1>\n";
	int i, remove;
	findLine(received, &i, &remove);
	while(i != -1)
	{
		processDirectoryLine(received.left(i), show, info);
		received.remove(0, i + remove);
		findLine(received, &i, &remove);
	}
	show += "\t</body>\n</html>\n";
	TQByteArray showdata;
	showdata.duplicate(show.utf8(), show.length());
	data(showdata);
}

void GopherProtocol::processDirectoryLine(const TQString &d, TQString &show, TQString &info)
{
	// gopher <type><display><tab><selector><tab><server><tab><port><\r><\n>
	// gopher+ <type><display><tab><selector><tab><server><tab><port><tab><things><\r><\n>
	int i;
	TQString data(d);
	
	TQString type = data.left(1);
	data.remove(0, 1);
	
	i = data.find("\t");
	TQString name = data.left(i);
	data.remove(0, i + 1);

	i = data.find("\t");
	TQString url = data.left(i);
	data.remove(0, i + 1);
	
	i = data.find("\t");
	TQString server = data.left(i);
	data.remove(0, i + 1);
	
	TQString port = parsePort(data);
		
	if (type == "i")
	{
		if (!info.isEmpty())
			info.append("\n");
		info.append(name);
	}
	else
	{
		if (!info.isEmpty())
		{
			show.append("\t\t<div class=\"info\">");
			show.append(info);
			show.append("</div>\n");
			info = "";
		}
		// it's the final line, ignore it
		if (type == ".") return;
		// those are the standard gopher types defined in the rfc
		//  0   Item is a file
		//  1   Item is a directory
		//  2   Item is a CSO phone-book server
		//  3   Error
		//  4   Item is a BinHexed Macintosh file.
		//  5   Item is DOS binary archive of some sort. Client must read until the TCP connection closes.  Beware.
		//  6   Item is a UNIX uuencoded file.
		//  7   Item is an Index-Search server.
		//  8   Item points to a text-based telnet session.
		//  9   Item is a binary file! Client must read until the TCP connection closes.  Beware.
		//  +   Item is a redundant server
		//  T   Item points to a text-based tn3270 session.
		//  g   Item is a GIF format graphics file.
		//  I   Item is some kind of image file.  Client decides how to display.
		show.append("\t\t\t<div>");
		// support the non-standard extension for URL to external sites
		// in this case, url begins with 'URL:'
		TQString finalUrl;
		TQString iconUrl;
		if (url.startsWith("URL:"))
		{
			finalUrl = url.mid(4);
			iconUrl = finalUrl;
		}
		else
		{
			finalUrl = "gopher://" + server;
			if (port != "70")
			{
				finalUrl.append(":");
				finalUrl.append(port);
			}
			finalUrl.append('/' + type + url);
			iconUrl = url;
		}
		show.append("\t\t\t\t<a href=\"");
		show.append(finalUrl);
		show.append("\">");
		addIcon(type, iconUrl, show);
		show.append(name);
		show.append("</a><br />\n");
		show.append("\t\t\t</div>");
	}
}

TQString GopherProtocol::parsePort(TQString &received)
{
	uint i = 0;
	TQString port;
	bool found = false;
	TQChar c;
	while (!found && i < received.length())
	{
		c = received[i];
		if (c.isDigit())
			i++;
		else
			found = true;
	}
	port = received.left(i);
	received.remove(0, i);
	return port;
}

void GopherProtocol::findLine(const TQString &received, int *i, int *remove)
{
	// it's not in the rfc but most servers don't follow the spec
	// find lines ending only in \n and in \r\n
	int aux, aux2;
	aux = received.find("\r\n");
	aux2 = received.find("\n");

	if (aux == -1)
	{
		*i = aux2;
		*remove = 1;
	}
	else
	{
		if (aux2 < aux)
		{
			*remove = 1;
			*i = aux2;
		}
		else
		{
			*remove = 2;
			*i = aux;
		}
	}
}

void GopherProtocol::handleSearch(const TQString &host, const TQString &path, int port)
{
	TQString sPort;
	if (port != 70) sPort = ':' + TQString::number(port);
	mimeType("text/html");
	TQString show("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
	    "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\t<head>\n\t\t<title>");
	show += host.utf8();
	show += path.utf8();
	show += "</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
	    "\t\t<script type=\"text/javascript\">\n\t\t\tfunction search()\n\t\t\t{\n\t\t\t\tdocument.location = 'gopher://";
	show += host.utf8();
	show += sPort.utf8();
	show += path.utf8();
	show += "?' + document.getElementById('what').value;\n\t\t\t}\n\t\t</script>\n\t</head>\n\t<body>\n\t\t<h1>";
	show.append(host.utf8());
	show.append(path.utf8());
	show += "</h1>\n\t\t";
	show += i18n("Enter a search term:").utf8();
	show += "<br />\n\t\t<input id=\"what\" type=\"text\">\n\t\t<input type=\"button\" value=\"";
	show += i18n("Text on a search button, like at a search engine", "Search").utf8();
	show += "\" onClick=\"search()\">\n\t</body>\n</html>\n";
	TQByteArray showdata;
	showdata.duplicate(show.utf8(), show.length());
	data(showdata);
}

void GopherProtocol::addIcon(const TQString &type, const TQString &url, TQString &show)
{
	TQString icon;
	if (type == "1") icon = "inode-directory.png";
	else if (type == "3") icon = "dialog-error.png";
	else if (type == "7") icon = "system-search.png";
	else if (type == "g") icon = "image-gif.png";
	else if (type == "I") icon = "image-x-generic.png";
	else
	{
		KMimeType::Ptr mime = KMimeType::findByURL(KURL(url), 0, false, true);
		icon = mime->icon(TQString::null, false);
	}
	TQFile file(m_iconLoader.iconPath(icon, -16));
	file.open(IO_ReadOnly);
	TQByteArray ba = file.readAll();
	show.append("<img width=\"16\" height=\"16\" src=\"data:image/png;base64,");
	show.append(KCodecs::base64Encode(ba));
	show.append("\" /> ");
}
