//
// C++ Implementation: packagedisplaywidget
//
// Description: 
//
//
// Author: Benjamin Mesing <bensmail@gmx.net>, (C) 2007
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "packagedisplaywidget.h"


#include <cassert>
#include <algorithm>

#include <QContextMenuEvent>
#include <QFontMetrics>
#include <QHeaderView>
#include <QMenu>
#include <QtDebug>

// NXml
#include <xmldata.h>

// NPlugin
#include "shortinformationplugin.h"
#include "actionplugin.h"
#include "packagenotfoundexception.h"
#include "columncontroldlg.h"

#include <helpers.h>

namespace NPackageSearch {

/*
const int PackageDisplayWidget::WIDGET_CHAR_WIDTH = 7;
const int PackageDisplayWidget::MARGIN = 10;
*/


PackageDisplayWidget::PackageDisplayWidget(QWidget * pParent)
 : QTableWidget(pParent), _shownPlugins(shortInformationPluginCompare)
{
    WIDGET_CHAR_WIDTH = 7;
    MARGIN = 10;
	verticalHeader()->hide();
	QFontMetrics font = fontMetrics();
	verticalHeader()->setDefaultSectionSize(1.07*font.height());
    horizontalHeader()->setSectionsClickable(true);
	horizontalHeader()->setSortIndicatorShown(true);
    horizontalHeader()->setSectionsMovable(true);
	connect(this, SIGNAL(itemSelectionChanged()), SLOT(onItemSelectionChanged()));
	setSelectionMode(SingleSelection);
	setSelectionBehavior(SelectRows);
	horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
	horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(horizontalHeader(), SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(onHeaderContextMenuRequested(const QPoint&)));
	_pCustomizeColumnsAction = new QAction(tr("Customize Columns"), this);
	connect(_pCustomizeColumnsAction, SIGNAL(triggered()), SLOT(showColumnControlDialog()));
	setShowGrid(false);
}


PackageDisplayWidget::~PackageDisplayWidget()
{
}

set<PackageDisplayWidget::ShortInformationPlugin *> PackageDisplayWidget::allPlugins() const
{
   std::set<ShortInformationPlugin*> mergedPlugins;
   mergedPlugins.insert(_shownPlugins.cbegin(), _shownPlugins.end());
   mergedPlugins.insert(_hiddenPlugins.cbegin(), _hiddenPlugins.end());
   return mergedPlugins;
}


void PackageDisplayWidget::setPackages(const set<string>& packages)
{
	using namespace NPlugin;
//	TODO: reimplement selecting the package which was selectd before
	clearContents();
	// temporary deactivate sorting because of 1. performance reasons and 2. a stable item order
	setSortingEnabled(false);
	
	setRowCount(packages.size());
	
	int row = 0;
	for (set<string>::const_iterator it = packages.begin(); it != packages.end(); ++it)
	{
		const string& package = *it;
		// add the item with its name
		int column = 0;
        for (auto jt = _shownPlugins.begin(); jt != _shownPlugins.end(); ++jt)
		{
			try 
			{
				// TODO: do not set the text for columns which are hidden
				QString text = (*jt)->shortInformationText(package);
				setItem(row, column, new QTableWidgetItem(text));
			}
			// simply ignore it if the package was not available for this plugin
			catch (NPlugin::PackageNotFoundException& e) {}
			catch (std::exception& e) {
				qWarning() << "Plugin " << (*jt)->name() << " failed provide short information\n"
						<< "e.what(): " << e.what();
			}
			catch (...) {
				qWarning() << "Plugin " << (*jt)->name() << " failed provide short information description (unknown exception thrown)";
			}
			++column;
		}
		++row;
	}

/*	if (pSelectItem)
	{
		_pPackageView->ensureItemVisible(pSelectItem);
		_pPackageView->setSelected(pSelectItem, true);
	}
	// select the first item of the site to be shown
	else if (_pPackageView->firstChild())
	{
		_pPackageView->setSelected(_pPackageView->firstChild(), true);
	}*/
	setSortingEnabled(true);
}

/////////////////////////////////////////////////////
// IPluginUser Interface
/////////////////////////////////////////////////////

void PackageDisplayWidget::addPlugin(NPlugin::Plugin* pPlugin)
{
	qStrDebug("PackageDisplayWidget::addPlugin(" + pPlugin->name() + ")");
	using namespace NPlugin;

    ShortInformationPlugin* pSIPlugin = dynamic_cast<ShortInformationPlugin*>(pPlugin);
    if (pSIPlugin != 0)
	{
		// if there are loaded settings for the column available
        if (_loadedColumnSettings.find(pSIPlugin->name()) != _loadedColumnSettings.end())
		{
            qDebug() << "Applying settings for column " + pSIPlugin->name();
            Column column = _loadedColumnSettings.find(pSIPlugin->name())->second;
            if (column._hidden) {
                _hiddenPlugins.insert(pSIPlugin);
            } else {
                _shownPlugins.insert(pSIPlugin);
            }
            updateColumnDisplay();
            int columnIndex = columnForPlugin(pSIPlugin);
            setColumnWidth(columnIndex, column._width);
			horizontalHeader()->setSectionHidden(columnIndex, column._hidden);
		}
		else
		{
            _shownPlugins.insert(pSIPlugin);
            updateColumnDisplay();
            int columnIndex = columnForPlugin(pSIPlugin);
            setColumnWidth(columnIndex, pSIPlugin->preferredColumnWidth() * WIDGET_CHAR_WIDTH + MARGIN);
		}
	}
	if (dynamic_cast<ActionPlugin*>(pPlugin))
	{
		_actionPlugins.push_back(dynamic_cast<NPlugin::ActionPlugin*>(pPlugin));
	}
}

	
void PackageDisplayWidget::removePlugin(NPlugin::Plugin* pPlugin)
{
	using namespace NPlugin;
	ShortInformationPlugin* pSIPlugin = dynamic_cast<ShortInformationPlugin*>(pPlugin);
	if (pSIPlugin)
	{
        _hiddenPlugins.erase(pSIPlugin);
        if (_shownPlugins.erase(pSIPlugin) > 0)
            updateColumnDisplay();
	}
    std::remove(_actionPlugins.begin(), _actionPlugins.end(), pPlugin);
}


/////////////////////////////////////////////////////
// Other funtions
/////////////////////////////////////////////////////


void PackageDisplayWidget::initialize()
{
	setSortingEnabled(true);
}

void PackageDisplayWidget::onItemSelectionChanged()
{
	emit(packageSelected(selectedPackage()));
}

void PackageDisplayWidget::onHeaderContextMenuRequested(const QPoint& pos)
{
	QMenu menu;
	QAction* pHideColumn = menu.addAction(tr("Hide Column"));
	menu.insertAction(0, _pCustomizeColumnsAction);
	QPoint globalPos = mapToGlobal(pos);
	QAction* pAction = menu.exec(globalPos);
	QHeaderView* pHeader = horizontalHeader();
	if (pAction == pHideColumn)
	{
		int column = pHeader->logicalIndexAt(globalPos);
		if (column == -1)
			qWarning("Unexpected column selected");
		else
			pHeader->hideSection(column);
	}
}

void PackageDisplayWidget::showColumnControlDialog()
{
	QHeaderView* pHeader = horizontalHeader();
	ColumnControlDlg dlg(this);
	QStringList shownColumns;
	QStringList hiddenColumns;
    std::for_each(
                _shownPlugins.cbegin(), _shownPlugins.cend(),
                [&shownColumns](auto it){ shownColumns.append((*it).shortInformationCaption()); }
    );
    std::for_each(
                _hiddenPlugins.cbegin(), _hiddenPlugins.cend(),
                [&hiddenColumns](auto it){ hiddenColumns.append((*it).shortInformationCaption()); }
    );
	dlg.setContent(shownColumns, hiddenColumns);
	dlg.exec();
	shownColumns = dlg.shownColumns();
	hiddenColumns = dlg.hiddenColumns();
    auto plugins = allPlugins();
    _shownPlugins.clear();
    _hiddenPlugins.clear();
    for (auto shownColumnCaption : shownColumns) {
        _shownPlugins.insert(pluginForCaption(shownColumnCaption, plugins));
    }
    for (auto hiddenColumnCaption : hiddenColumns) {
        _hiddenPlugins.insert(pluginForCaption(hiddenColumnCaption, plugins));
    }
    updateColumnDisplay();
}

QString PackageDisplayWidget::selectedPackage() const
{
	// TODO the const_casts here are neccessary because QT fails to declare selectedItems() as const
	// currently this method assumes a maximum of one selected item
	if (!const_cast<PackageDisplayWidget*>(this)->selectedItems().isEmpty())
	{
		QTableWidgetItem* pSelectedItem = const_cast<PackageDisplayWidget*>(this)->selectedItems().first();
		return packageForRow(pSelectedItem->row());
	}
	else
	{
		return QString();
	}
}


QString PackageDisplayWidget::packageForRow(int row) const
{
    int nameColumn = columnForName("PackageNamePlugin");
	QString packageName = item(row, nameColumn)->text();
	return packageName;	
}

bool PackageDisplayWidget::scoreColumnVisible() const
{
	// translation _must_ match the translation of the column title!
    int scoreColumn = columnForName("ScoreDisplayPlugin");
	if (scoreColumn == -1)
		return false;
	else 
		return !horizontalHeader()->isSectionHidden(scoreColumn);
}


void PackageDisplayWidget::contextMenuEvent(QContextMenuEvent * pEvent)
{
	QList<QAction*> actions = packageActions();
	actions.push_back(_pCustomizeColumnsAction);
	
	QMenu::exec(actions, pEvent->globalPos());
}


void PackageDisplayWidget::updateColumnDisplay()
{
    const auto& plugins = _shownPlugins;
	QStringList labels;
    setColumnCount(plugins.size());
    for (auto pPlugin: plugins) {
        labels.push_back(pPlugin->shortInformationCaption());
	}
	setHorizontalHeaderLabels(labels);
    int columnIndex = 0;
    for (auto pPlugin: plugins) {        
        int width = pPlugin->preferredColumnWidth() * WIDGET_CHAR_WIDTH + MARGIN;
        setColumnWidth(columnForPlugin(pPlugin), width);
        if (pPlugin->isDefaultSortColumn()) {
            sortByColumn(columnIndex, Qt::DescendingOrder);
        }
        ++columnIndex;
    }
}



/////////////////////////////////////////////////////
// IXmlStorable Interface
/////////////////////////////////////////////////////

void PackageDisplayWidget::saveSettings(NXml::XmlData& outData, QDomElement parent) const 
{
	QDomElement listView = outData.addElement(parent, "customListView");
    outData.addAttribute(listView, COLUMN_SETTINGS_XML_VERSION, "settingsVersion");
	
	QDomElement columns = outData.addElement(listView, "columns");

    for (const auto pPlugin : _shownPlugins)
	{
		QDomElement column = outData.addElement(columns, "column");
        int columnIndex = columnForName(pPlugin->name());
        outData.addAttribute(column, pPlugin->name(), "id");
		outData.addAttribute(column, columnWidth(columnIndex), "width");
        outData.addAttribute(column, false, "hidden");
	}
    for (const auto pPlugin : _hiddenPlugins)
    {
        QDomElement column = outData.addElement(columns, "column");
        outData.addAttribute(column, pPlugin->name(), "id");
        outData.addAttribute(column, pPlugin->preferredColumnWidth() * WIDGET_CHAR_WIDTH + MARGIN, "width");
        outData.addAttribute(column, true, "hidden");
    }
}

QDomElement PackageDisplayWidget::loadSettings(const QDomElement source)
{
	if (source.tagName() != "customListView")
        return source;
    QString settingsVersion;
	NXml::getAttribute(source, settingsVersion, "settingsVersion", "0.1");
	// dismiss old settings, we don't want to carry around backward compatibility for an eternity
    if (settingsVersion.toInt() >= COLUMN_SETTINGS_XML_VERSION)
	{
		//qDebug("Loading column setup");
		QDomElement element = NXml::getFirstElement(source.firstChild());
		assert(element.tagName() == "columns");
		// iterate over each <column> element
		QDomElement e = NXml::getFirstElement(element.firstChild());
		while (!e.isNull())
		{	
			Column column;
            NXml::getAttribute(e, column._id, "id", "");
			NXml::getAttribute(e, column._width, "width", 50);
			NXml::getAttribute(e, column._position, "position", 0);
			NXml::getAttribute(e, column._hidden, "hidden", false);
            _loadedColumnSettings.insert(make_pair(column._id, column));
			e = NXml::getNextElement(e);
			//qDebug("Settings for column " + column._caption + ": width %d, position %d", column._width, column._position);
		}
		// note that we set the sort column, _after_ adding all the columns.
// 		QString sortColumn;
// 		int sortOrder;
// 		NXml::getAttribute(element, sortColumn, "shortSortColumn", _sortColumn);
// 		NXml::getAttribute(element, sortOrder, "shortSortOrder", _sortOrder);
// 		if (!sortColumn.isEmpty())
// 			setSorting(sortColumn, (Qt::SortOrder) sortOrder);
	}
	return NXml::getNextElement(source);
}

/////////////////////////////////////////////////////
// Query functions
/////////////////////////////////////////////////////


int PackageDisplayWidget::columnForName(const QString& name) const {
    return findColumn([name](auto pPlugin){ return pPlugin->name() == name; } );
}


int PackageDisplayWidget::columnForCaption(const QString& caption) const {
    return findColumn([&caption](auto pPlugin){ return pPlugin->shortInformationCaption() == caption; } );
}

int PackageDisplayWidget::columnForPlugin(ShortInformationPlugin* pPlugin) const {
    return findColumn([pPlugin](auto pPlugin2){ return pPlugin2 == pPlugin; } );
}

NPlugin::ShortInformationPlugin* PackageDisplayWidget::pluginForCaption(const QString& caption, const set<ShortInformationPlugin*>& pluginsToSearch) const
{
    for (auto pPlugin : pluginsToSearch) {
        if (pPlugin->shortInformationCaption() == caption) {
            return pPlugin;
        }
    }
	return 0;
}



QList<QAction*> PackageDisplayWidget::packageActions() const
{
	QList<QAction*> result;
	for (vector<ActionPlugin*>::const_iterator it = _actionPlugins.begin(); 
		it != _actionPlugins.end(); ++it)
	{
		vector<NPlugin::Action*> actions = (*it)->actions();
		for (vector<NPlugin::Action*>::const_iterator jt = actions.begin(); jt != actions.end(); ++jt)
		{
			const NPlugin::Action* pAction = *jt;
			if (pAction->packageAction())
				result.push_back(pAction->action());
		}
	}
	return result;
}


void  PackageDisplayWidget::debugPrintOrder() const
{
	QHeaderView& header = *horizontalHeader();
	QString headerLabel;
	for (int i=0; i < header.count(); ++i)
	{
		int index = header.logicalIndex(i);
		headerLabel += horizontalHeaderItem(index)->text() + " | ";
	}
	qStrDebug(headerLabel);
}

}
