//
// 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)
{
    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()
{
}

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;
		const vector<ShortInformationPlugin*>& plugins = _siPlugins;
		for (vector<ShortInformationPlugin*>::const_iterator jt = plugins.begin(); 
				jt != plugins.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;

	if (dynamic_cast<ShortInformationPlugin*>(pPlugin) != 0)
	{
		ShortInformationPlugin* pSIPlugin = dynamic_cast<ShortInformationPlugin*>(pPlugin);
		_siPlugins.push_back(pSIPlugin);
		updateColumnDisplay();
		int loadedColumnPosition = -1;
		int columnIndex = columnForName(pSIPlugin->shortInformationCaption());
		// if there are loaded settings for the column available
		if (_loadedColumnSettings.find(pSIPlugin->shortInformationCaption()) != _loadedColumnSettings.end())
		{
			//qDebug("Applying settings for column " + pSIPlugin->shortInformationCaption() );
			Column column = _loadedColumnSettings.find(pSIPlugin->shortInformationCaption())->second;
			setColumnWidth(columnIndex, column._width);
			loadedColumnPosition = column._position;
			horizontalHeader()->setSectionHidden(columnIndex, column._hidden);
		}
		else
		{
            setColumnWidth(columnIndex, pSIPlugin->preferredColumnWidth() * WIDGET_CHAR_WIDTH + MARGIN);
		}
		//qDebug("Order before reordering");
		//debugPrintOrder();
		int visualIndex = columnPosition(loadedColumnPosition, columnIndex, pSIPlugin);
		//qDebug("Target visual Index of " + pSIPlugin->shortInformationCaption() + " is %d", visualIndex);
		if (visualIndex != -1)
		{
			horizontalHeader()->moveSection(horizontalHeader()->visualIndex(columnIndex), visualIndex);
			//qDebug("Order after reordering");
			//debugPrintOrder();
		}
	}
	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)
	{
		vector<ShortInformationPlugin*>::iterator it = std::find(_siPlugins.begin(), _siPlugins.end(), pSIPlugin);
		if (it != _siPlugins.end())
			_siPlugins.erase(it);
		updateColumnDisplay();
	}
	ActionPlugin* pAPlugin = dynamic_cast<ActionPlugin*>(pPlugin);
	if (pAPlugin)
	{
		vector<ActionPlugin*>::iterator it = std::find(_actionPlugins.begin(), _actionPlugins.end(), pAPlugin);
		if (it != _actionPlugins.end())
			_actionPlugins.erase(it);
	}
}


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


void PackageDisplayWidget::initialize()
{
	int scoreColumn = columnForName("Score");
// 	(Qt::SortOrder) sortOrder
	if (scoreColumn != -1)
		sortByColumn(scoreColumn, Qt::DescendingOrder);
	else
		sortByColumn(0, Qt::AscendingOrder);
	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;
	for (int i = 0; i < pHeader->count(); ++i)
	{
		int logicalIndex = pHeader->logicalIndex(i);
		QString colCaption = horizontalHeaderItem(logicalIndex)->text(); 
		if (pHeader->isSectionHidden(logicalIndex))
			hiddenColumns.push_back(colCaption);
		else
			shownColumns.push_back(colCaption);
	}
	dlg.setContent(shownColumns, hiddenColumns);
	dlg.exec();
	shownColumns = dlg.shownColumns();
	hiddenColumns = dlg.hiddenColumns();
	for (QStringList::const_iterator it = shownColumns.begin(); 
			it != shownColumns.end(); ++it)
	{
		int col = columnForName(*it);
		if (pHeader->isSectionHidden(col))
		{
			pHeader->showSection(col);
			if (pHeader->sectionSize(col)==0)
			{
				ShortInformationPlugin* pSIPlugin = _siPlugins[col];
                int width = pSIPlugin->preferredColumnWidth() * WIDGET_CHAR_WIDTH + MARGIN;
				setColumnWidth(col, width);
			}
		}
	}
	for (QStringList::const_iterator it = hiddenColumns.begin(); 
			it != hiddenColumns.end(); ++it)
	{
		int col = columnForName(*it);
		if (!pHeader->isSectionHidden(col))
			pHeader->hideSection(col);
	}
}

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
{
	// TODO this relies on the package name column to be present and filled
	// translation _must_ match the translation of the column title!
	int nameColumn = columnForName(tr("Name"));
	QString packageName = item(row, nameColumn)->text();
	return packageName;	
}

bool PackageDisplayWidget::scoreColumnVisible() const
{
	// translation _must_ match the translation of the column title!
	int scoreColumn = columnForName(tr("Score"));
	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 vector<ShortInformationPlugin*>& plugins = _siPlugins;
	QStringList labels;
	setColumnCount(_siPlugins.size());
	for (vector<ShortInformationPlugin*>::const_iterator it = plugins.begin(); 
			it != plugins.end(); ++it)
	{
		labels.push_back((*it)->shortInformationCaption());
	}
	setHorizontalHeaderLabels(labels);
}



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

void PackageDisplayWidget::saveSettings(NXml::XmlData& outData, QDomElement parent) const 
{
	QDomElement listView = outData.addElement(parent, "customListView");
	// in settings version 1, we had a shownColumns and and a hiddenColumns element
	outData.addAttribute(listView, QString("2"), "settingsVersion");
	
	QDomElement columns = outData.addElement(listView, "columns");

	const vector<ShortInformationPlugin*>& plugins = _siPlugins;
	for (vector<ShortInformationPlugin*>::const_iterator it = plugins.begin(); 
			it != plugins.end(); ++it)
	{
		QDomElement column = outData.addElement(columns, "column");
		int columnIndex = columnForName((*it)->shortInformationCaption());
		outData.addAttribute(column, (*it)->shortInformationCaption(), "caption");
		outData.addAttribute(column, columnWidth(columnIndex), "width");
		outData.addAttribute(column, horizontalHeader()->visualIndex(columnIndex), "position");
		outData.addAttribute(column, horizontalHeader()->isSectionHidden(columnIndex), "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 >= QString("2"))
	{
		//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._caption, "caption", "");
			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._caption, 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::columnPosition(int position, int logicalIndex, const NPlugin::ShortInformationPlugin* pPlugin) const
{
	// iterate over all columns by visual index (i.e. starting with the one with visible index 0)
	for (int visualColIndex = 0; visualColIndex < columnCount(); ++visualColIndex)
	{
		int logicalColIndex = horizontalHeader()->logicalIndex(visualColIndex);
		// skip the column if it is the requested column
		if (logicalColIndex == logicalIndex)
			continue;
		QString colCaption = horizontalHeaderItem(logicalColIndex)->text(); 
		// if the requested column has no position information or the current column has no position information
		if (position == -1 || _loadedColumnSettings.find(colCaption) == _loadedColumnSettings.end())
		{
			uint colPriority = pluginForCaption(colCaption)->shortInformationPriority();
			if (colPriority > pPlugin->shortInformationPriority())
				return visualColIndex;
		}
		else
		{
			int colPosition = _loadedColumnSettings.find(colCaption)->second._position;
			// if the given position is before the current one, return the index of the current column
			if (colPosition > position)
			{
				return visualColIndex;
			}
		}
	}
	return -1;
}

int PackageDisplayWidget::columnForName(const QString& name) const
{
	for (int i=0; i < columnCount(); ++i)
	{
		if (horizontalHeaderItem(i)->text() == name)
			return i;
	}
	return -1;
}

NPlugin::ShortInformationPlugin* PackageDisplayWidget::pluginForCaption(const QString& caption) const
{
	for (vector<ShortInformationPlugin*>::const_iterator it = _siPlugins.begin(); 
			it != _siPlugins.end(); ++it)
	{
		if ((*it)->shortInformationCaption() == caption)
			return *it;
	}
	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);
}

}
