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

#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;

/** 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 toHtml(const QString& input, bool preserveWs = false)
{
    list< pair<QChar, QString> > replacements;
    replacements.push_back(make_pair(QChar('<'),QString("&lt;")));
    replacements.push_back(make_pair(QChar('>'),QString("&gt;")));
// 		_repl.push_back(make_pair(QChar('\n'),QString("<br>")));
    QString result = input;
    if (preserveWs)
        replacements.push_back(make_pair(QChar('>'),QString("&nbsp;")));
    for (auto it = replacements.begin(); it != replacements.end(); ++it)
        result.replace(it->first, it->second);
    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 descriptionToHtml(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 = toHtml(*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 = toHtml(*it);
            if (inParagraph)
            {
                result.append("</p>");
                inParagraph = false;
            }
            else
                result.append("<br>");
        }
        else
        {
            QString line = toHtml(*it);
            if (!inParagraph)
            {
                result.append("<p>");
                inParagraph = true;
            }
            result.append(line);
        }
    }
    if (inParagraph)
        result.append("</p>");
    return result;
}



/**
 * 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"); 
}

void PackageDescriptionPlugin::updateInformationWidget(const string& package)
{
	QString text="";
	try 
	{
		const NApt::IPackage& pkg = _pPackageDB->getPackageRecord(package);
		if (!pkg.description().isEmpty())
		{
			QString description = pkg.description();
            description = descriptionToHtml(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)
{
    const NApt::IPackage& pkg = _pPackageDB->getPackageRecord(package);
    QString details;
	
    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>: ") << toHtml(pkg.essential()) << "<br>";
/*		else
			os << "<b>Essential</b>: not available<br>";*/
		if (!pkg.priority().isEmpty())
            os << tr("<b>Priority</b>: ") << toHtml(pkg.priority()) << "<br>";
/*		else
			os << "<b>Priority</b>: not available<br>";*/
		if (!pkg.section().isEmpty())
            os << tr("<b>Section</b>: ") << toHtml(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>: ") << toHtml(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::IPackage::BorderList borderList = pkg.getPackageList(toHtml(pkg.replaces()));
			os << tr("<b>Replaces</b>: ") << createLinks(borderList, pkg.replaces()) << "<br>";
		}
		if (!pkg.provides().isEmpty())
		{
            NApt::IPackage::BorderList borderList = pkg.getPackageList(toHtml(pkg.provides()));
			os << tr("<b>Provides</b>: ") << createLinks(borderList, pkg.provides()) << "<br>";
		}
		if (!pkg.preDepends().isEmpty())
		{
            NApt::IPackage::BorderList borderList = pkg.getPackageList(toHtml(pkg.preDepends()));
			os << tr("<b>Pre-Depends</b>: ") << createLinks(borderList, pkg.preDepends()) << "<br>";
		}
		if (!pkg.depends().isEmpty())
		{
            NApt::IPackage::BorderList borderList = pkg.getPackageList(toHtml(pkg.depends()));
			os << tr("<b>Depends</b>: ") << createLinks(borderList, pkg.depends()) << "<br>";
		}
		if (!pkg.recommends().isEmpty())
		{
            NApt::IPackage::BorderList borderList = pkg.getPackageList(toHtml(pkg.recommends()));
			os << tr("<b>Recommends:</b> ") << createLinks(borderList, pkg.recommends()) << "<br>";
		}
		if (!pkg.suggests().isEmpty())
		{
            NApt::IPackage::BorderList borderList = pkg.getPackageList(toHtml(pkg.suggests()));
			os << tr("<b>Suggests:</b> ") << createLinks(borderList, pkg.suggests()) << "<br>";
		}
		if (!pkg.conflicts().isEmpty())
		{
            NApt::IPackage::BorderList borderList = pkg.getPackageList(toHtml(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>: ") << toHtml(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::IPackage::BorderList packages, const QString & s)
{
    typedef NApt::IPackage::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

