#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 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)
    {
        // skip first line because it is the shortDescription
        if (it == lines.begin()) {
            continue;
        }

        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 
	{
        auto pkg = _pPackageDB->getPackageDetails(package);
        if (!pkg.longDescription().isEmpty())
		{
            QString description = pkg.longDescription();
            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)
{
    auto& pkg = _pPackageDB->getPackageRecord(package);
    auto details = _pPackageDB->getPackageDetails(package);
    QString text;

    QTextStream os(&text, QIODevice::WriteOnly);
	{	// fill the details string
        if (!pkg.installedVersion().isEmpty())
            os << tr("<b>Installed Version</b>: ") << pkg.installedVersion() << "<br>";
        if (!pkg.availableVersion().isEmpty())
            os << tr("<b>Available Version</b>: ") << pkg.availableVersion() << "<br>";
		else
			os << tr("<b>Available Version</b>: not available<br>");
        if (!details.essential().isEmpty())
            os << tr("<b>Essential</b>: ") << toHtml(details.essential()) << "<br>";
/*		else
			os << "<b>Essential</b>: not available<br>";*/
        if (!details.priority().isEmpty())
            os << tr("<b>Priority</b>: ") << toHtml(details.priority()) << "<br>";
/*		else
			os << "<b>Priority</b>: not available<br>";*/
        if (!details.section().isEmpty())
            os << tr("<b>Section</b>: ") << toHtml(details.section()) << "<br>";
		else
			os << tr("<b>Section</b>: not available<br>");
        if (!details.installedSize().isEmpty())
            os << tr("<b>Installed Size</b>: ") << details.installedSize() << "<br>";
		else
			os << tr("<b>Installed Size</b>: not available<br>");
        if (!details.maintainer().isEmpty())
            os << tr("<b>Maintainer</b>: ") << toHtml(details.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 (!details.source().isEmpty())
            os << tr("<b>Source</b>: ") << details.source() << "<br>";
        if (!details.replaces().isEmpty())
		{
            QString entry = toHtml(details.replaces());
            NApt::IPackageDetails::BorderList borderList = details.getPackageList(entry);
            os << tr("<b>Replaces</b>: ") << createLinks(borderList, entry) << "<br>";
		}
        if (!details.provides().isEmpty())
		{
            QString entry = toHtml(details.provides());
            NApt::IPackageDetails::BorderList borderList = details.getPackageList(entry);
            os << tr("<b>Provides</b>: ") << createLinks(borderList, entry) << "<br>";
		}
        if (!details.preDepends().isEmpty())
		{
            QString entry = toHtml(details.preDepends());
            NApt::IPackageDetails::BorderList borderList = details.getPackageList(entry);
            os << tr("<b>Pre-Depends</b>: ") << createLinks(borderList, entry) << "<br>";
		}
        if (!details.depends().isEmpty())
		{
            QString entry = toHtml(details.depends());
            NApt::IPackageDetails::BorderList borderList = details.getPackageList(entry);
            os << tr("<b>Depends</b>: ") << createLinks(borderList, entry) << "<br>";
		}
        if (!details.recommends().isEmpty())
		{
            QString entry = toHtml(details.recommends());
            NApt::IPackageDetails::BorderList borderList = details.getPackageList(entry);
            os << tr("<b>Recommends:</b> ") << createLinks(borderList, entry) << "<br>";
		}
        if (!details.suggests().isEmpty())
		{
            QString entry = toHtml(details.suggests());
            NApt::IPackageDetails::BorderList borderList = details.getPackageList(entry);
            os << tr("<b>Suggests:</b> ") << createLinks(borderList, entry) << "<br>";
		}
        if (!details.conflicts().isEmpty())
		{
            QString entry = toHtml(details.conflicts());
            NApt::IPackageDetails::BorderList borderList = details.getPackageList(entry);
            os << tr("<b>Conflicts</b>: ") << createLinks(borderList, entry) << "<br>";
		}
        if (!details.filename().isEmpty())
            os << tr("<b>Filename</b>: ") << details.filename() << "<br>\n";
		else
			os << tr("<b>Filename</b>: not available<br>\n");
        if (!details.size().isEmpty())
            os << tr("<b>Size</b>: ") << details.size() << "<br>";
		else
			os << tr("<b>Size</b>: not available<br>");
        if (!details.md5sum().isEmpty())
            os << tr("<b>MD5sum</b>: ") << details.md5sum() << "<br>";
		else
			os << tr("<b>MD5sum</b>: not available<br>");
        if (!details.conffiles().isEmpty())
            os << tr("<b>Conffiles</b>: ") << toHtml(details.conffiles()) << "<br>";
        if (!details.homepage().isEmpty())
            os << tr("<b>Homepage</b>: <a HREF='") << details.homepage()
               << "'>" << details.homepage() << "</a><br>";
	}
    return text;
}


/////////////////////////////////////////////////////
// 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::IPackageDetails::BorderList packages, const QString & s)
{
    typedef NApt::IPackageDetails::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

