#include <QMainWindow>
#include <QTextBrowser>
#include <QTextStream>

#include <helpers.h>
#include <iprovider.h>

#include "packagedescriptionplugin.h"

#include "ipackagedb.h"
#include "iaptmediator.h"

#include "packagenotfoundexception.h"

namespace NPlugin
{

const QString PackageDescriptionPlugin::PLUGIN_NAME = "PackageDescriptionPlugin";

const QString PackageDescriptionPlugin::_emptyString;


/**
 * Constructors/Destructors
 */
PackageDescriptionPlugin::PackageDescriptionPlugin(NApt::IPackageDB* pPackageDB, IAptMediator* pMediator) :
	_pPackageDB(pPackageDB),
	_pMediator(pMediator)
{
	_pDescriptionView = 0;
	_pProvider = 0;
}
 
PackageDescriptionPlugin::~PackageDescriptionPlugin()
{
	delete _pDescriptionView;
}
 
/////////////////////////////////////////////////////
// Plugin Interface
/////////////////////////////////////////////////////

void PackageDescriptionPlugin::init(IProvider* pProvider)
{
	_pProvider = pProvider;
	QMainWindow* pWindow = pProvider->mainWindow();
	_pDescriptionView = new QTextBrowser(pWindow);
	_pDescriptionView->setObjectName("DescriptionView");
}


/////////////////////////////////////////////////////
// InformationPlugin Interface
/////////////////////////////////////////////////////

QWidget* PackageDescriptionPlugin::informationWidget() const	
{ 
	return _pDescriptionView; 
}

QString PackageDescriptionPlugin::informationWidgetTitle() const 
{
	return tr("Description"); 
}

/** Function object. */
class HTMLify
{
	typedef list< pair<QChar, QString> > Repl;
	Repl _repl;
public:
	HTMLify()
	{
		_repl.push_back(make_pair(QChar('<'),QString("&lt;")));
		_repl.push_back(make_pair(QChar('>'),QString("&gt;")));
// 		_repl.push_back(make_pair(QChar('\n'),QString("<br>")));
	}
	void operator()(QString& s)	
	{
		for (Repl::iterator it=_repl.begin(); it!=_repl.end(); ++it)
			s.replace(it->first, it->second);
	}
	/** Converts the handed string to html.
	  * 
	  * It replaces linebreaks by &lt;br&gt; and &lt;/&gt;
	  * by their HTML encoding.
	  *
	  * @param string the string to be converted
	  * @param preserveWs if set to true, spaces will be replace by &amp;nbsp;
	  *  
	  * This function should be static at one point.
	  */
	QString convert(const QString& string, bool preserveWs = false)
	{
		QString result = string;
		if (preserveWs)
			_repl.push_back(make_pair(QChar('>'),QString("&nbsp;")));
		for (Repl::iterator it=_repl.begin(); it!=_repl.end(); ++it)
		{
			result.replace(it->first, it->second);
		}
		if (preserveWs)
			_repl.pop_back();
		return result;
	}
	
	/** Converts the description to html code according to Debian Policy Section 5.6.13. 
	  *
	  * This function should be static at one point.
	  */
	QString convertDescription(const QString& description)
	{
		QStringList lines = description.split("\n");
 		bool inParagraph = false;
 		QString result;
		for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it)
		{
			QString line = *it;
			if (line.startsWith("  "))	// a verbatim line
			{
				QString line = convert(*it, true);
				if (inParagraph)
				{
					result.append("</p>");
					result.append("<br>");	// work around QT bug in QTextBrowser which puts a 
						// line after a paragraph on the same line as the paragraph
					inParagraph	= false;
				}
				result.append(line).append("<br>");
			}
			else if (line.startsWith(" ."))	// an empty line
			{
				QString line = convert(*it);
				if (inParagraph)
				{
					result.append("</p>");
					inParagraph = false;
				}
				else
					result.append("<br>");
			}
			else
			{
				QString line = convert(*it);
				if (!inParagraph)
				{
					result.append("<p>");
					inParagraph = true;
				}
				result.append(line);
			}
		}
		if (inParagraph)
			result.append("</p>");
		return result;
	}
	
};

void PackageDescriptionPlugin::updateInformationWidget(const string& package)
{
	QString text="";
	try 
	{
		const NApt::IPackage& pkg = _pPackageDB->getPackageRecord(package);
		if (!pkg.description().isEmpty())
		{
			HTMLify htmlify;
			QString description = pkg.description();
			description = htmlify.convertDescription(description);
			QStringList patterns = _pMediator->searchPatterns();
			for (QStringList::const_iterator it = patterns.begin(); it != patterns.end(); ++it )
			{
				int index = description.indexOf(*it, 0, Qt::CaseInsensitive);
				while ( index != -1 )
				{
					description.insert(index + (*it).length(), "</font>");	// insert the last part first
					description.insert(index, "<font color=\"#ff0000\">");
					// point behind the inserted string
					index += (*it).length() + 29;	// 29 ==  strlen("<font color=\"#ffff00\"></font>")
					index = description.indexOf(*it, index, Qt::CaseInsensitive);
				}
			}
			text = description;
		}
	}
	catch(PackageNotFoundException e)	// the package information could not be retrieved
	{
		text = tr("<h3>Package not found</h3>"
			"<p>Could not find a valid description for <b>") + toQString(package) +
			tr("</b>.<br>") +	// tr required to add something afterwards e.g. for German
			tr("This could mean either that you have selected a virtual package or that the package description "
			" was not found for an unknown reason.</p>");
//			"The original error message was:<br>" + QString(e.desc().c_str())
//		);
	}
	_pDescriptionView->setHtml(text);
}


void PackageDescriptionPlugin::clearInformationWidget()
{
	_pDescriptionView->clear();
}

QString PackageDescriptionPlugin::informationText(const string& package)
{
	// copy the data to make the package object modifiable
	NApt::Package pkg(_pPackageDB->getPackageRecord(package));
	QString details;
	
	HTMLify htmlify;
	pkg.processEntries(htmlify);
	QTextStream os(&details, QIODevice::WriteOnly);
	{	// fill the details string
		if (!pkg.installedVersion().isEmpty())
			os << tr("<b>Installed Version</b>: ") << pkg.installedVersion() << "<br>";
		if (!pkg.version().isEmpty())
			os << tr("<b>Available Version</b>: ") << pkg.version() << "<br>";
		else
			os << tr("<b>Available Version</b>: not available<br>");
		if (!pkg.essential().isEmpty())
			os << tr("<b>Essential</b>: ") << pkg.essential()<< "<br>";
/*		else
			os << "<b>Essential</b>: not available<br>";*/
		if (!pkg.priority().isEmpty())
			os << tr("<b>Priority</b>: ") << pkg.priority() << "<br>";
/*		else
			os << "<b>Priority</b>: not available<br>";*/
		if (!pkg.section().isEmpty())
			os << tr("<b>Section</b>: ") << pkg.section() << "<br>";
		else
			os << tr("<b>Section</b>: not available<br>");
		if (!pkg.installedSize().isEmpty())
			os << tr("<b>Installed Size</b>: ") << pkg.installedSize() << "<br>";
		else
			os << tr("<b>Installed Size</b>: not available<br>");
		if (!pkg.maintainer().isEmpty())
			os << tr("<b>Maintainer</b>: ") << pkg.maintainer() << "<br>";
		else
			os << tr("<b>Maintainer</b>: not available<br>");
		if (!pkg.architecture().isEmpty())
			os << tr("<b>Architecture</b>: ") << pkg.architecture() << "<br>";
		else
			os << tr("<b>Architecture</b>: not available<br>");
		if (!pkg.source().isEmpty())
			os << tr("<b>Source</b>: ") << pkg.source() << "<br>";
		if (!pkg.replaces().isEmpty())
		{
			NApt::Package::BorderList borderList = pkg.getPackageList(pkg.replaces());
			os << tr("<b>Replaces</b>: ") << createLinks(borderList, pkg.replaces()) << "<br>";
		}
		if (!pkg.provides().isEmpty())
		{
			NApt::Package::BorderList borderList = pkg.getPackageList(pkg.provides());
			os << tr("<b>Provides</b>: ") << createLinks(borderList, pkg.provides()) << "<br>";
		}
		if (!pkg.preDepends().isEmpty())
		{
			NApt::Package::BorderList borderList = pkg.getPackageList(pkg.preDepends());
			os << tr("<b>Pre-Depends</b>: ") << createLinks(borderList, pkg.preDepends()) << "<br>";
		}
		if (!pkg.depends().isEmpty())
		{
			NApt::Package::BorderList borderList = pkg.getPackageList(pkg.depends());
			os << tr("<b>Depends</b>: ") << createLinks(borderList, pkg.depends()) << "<br>";
		}
		if (!pkg.recommends().isEmpty())
		{
			NApt::Package::BorderList borderList = pkg.getPackageList(pkg.recommends());
			os << tr("<b>Recommends:</b> ") << createLinks(borderList, pkg.recommends()) << "<br>";
		}
		if (!pkg.suggests().isEmpty())
		{
			NApt::Package::BorderList borderList = pkg.getPackageList(pkg.suggests());
			os << tr("<b>Suggests:</b> ") << createLinks(borderList, pkg.suggests()) << "<br>";
		}
		if (!pkg.conflicts().isEmpty())
		{
			NApt::Package::BorderList borderList = pkg.getPackageList(pkg.conflicts());
			os << tr("<b>Conflicts</b>: ") << createLinks(borderList, pkg.conflicts()) << "<br>";
		}
		if (!pkg.filename().isEmpty())
			os << tr("<b>Filename</b>: ") << pkg.filename() << "<br>\n";
		else
			os << tr("<b>Filename</b>: not available<br>\n");
		if (!pkg.size().isEmpty())
			os << tr("<b>Size</b>: ") << pkg.size() << "<br>";
		else
			os << tr("<b>Size</b>: not available<br>");
		if (!pkg.md5sum().isEmpty())
			os << tr("<b>MD5sum</b>: ") << pkg.md5sum() << "<br>";
		else
			os << tr("<b>MD5sum</b>: not available<br>");
		if (!pkg.conffiles().isEmpty())
			os << tr("<b>Conffiles</b>: ") << pkg.conffiles() << "<br>";
		if (!pkg.homepage().isEmpty())
			os << tr("<b>Homepage</b>: <a HREF='") << pkg.homepage() << "'>" << pkg.homepage() << "</a><br>";
	}
	return details;
}


/////////////////////////////////////////////////////
// ShortInformationPlugin Interface
/////////////////////////////////////////////////////

const QString PackageDescriptionPlugin::shortInformationText(const string& package)
{
	try 
	{
		return _pPackageDB->getShortDescription(package);
	}
	catch (const PackageNotFoundException& e)
	{
		return _emptyString;
	}
}


/////////////////////////////////////////////////////
// Helper Methods
/////////////////////////////////////////////////////

QString PackageDescriptionPlugin::createLinks( NApt::Package::BorderList packages, const QString & s)
{
	typedef NApt::Package::BorderList BL;
	QString result = s;
	// iterate from behind to not destroy the order
	for (BL::reverse_iterator it = packages.rbegin(); it!=packages.rend(); ++it)
	{
		QString package = result.mid(it->first, it->second - it->first);
		const set<string>& allPackages = _pProvider->packages();
		if (allPackages.find(toString(package)) != allPackages.end())
		{
			result.insert(it->second,"</a>");	// insert behind the package name
			result.insert(it->first,"<a HREF=\"package:"+package+"\">");	// insert before the package name
		}
	}
	return result;
}


}	// namespace NPlugin

