/*
 * Copyright (C) 2009,2010 Rolf Eike Beer <kde@opensource.sf-tec.de>
 */

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "kgpgsearchresultmodel.h"

#include <KDateTime>
#include <KDebug>
#include <KLocale>
#include <QString>
#include <QStringList>
#include <QTextCodec>

#include "core/convert.h"
#include "core/kgpgkey.h"

class SearchResult {
private:
	QStringList m_emails;
	QStringList m_names;

public:
	SearchResult(const QString &line);

	bool m_validPub;	// true when the "pub" line passed to constructor was valid

	void addUid(const QString &id);
	const QString &getName(const int &index) const;
	const QString &getEmail(const int &index) const;
	int getUidCount() const;

	QString m_fingerprint;
	unsigned int m_uatCount;

	QVariant summary() const;
private:
	KDateTime m_expiry;
	KDateTime m_creation;
	bool m_revoked;
	unsigned int m_bits;
	KgpgCore::KgpgKeyAlgo m_algo;
};

class KGpgSearchResultModelPrivate {
public:
	explicit KGpgSearchResultModelPrivate();
	~KGpgSearchResultModelPrivate();

	QList<SearchResult *> m_items;

	QString urlDecode(const QString &line);
};

SearchResult::SearchResult(const QString &line)
	: m_validPub(false),
	m_uatCount(0)
{
	const QStringList parts(line.split(QLatin1Char( ':' )));

	if (parts.count() < 6)
		return;

	if (parts.at(1).isEmpty())
		return;

	m_fingerprint = parts.at(1);
	m_algo = KgpgCore::Convert::toAlgo(parts.at(2));
	m_bits = parts.at(3).toUInt();
	m_creation.setTime_t(parts.at(4).toULongLong());
	m_revoked = (parts.at(6) == QLatin1String( "r" ));

	m_validPub = true;
}

void
SearchResult::addUid(const QString &id)
{
	Q_ASSERT(m_emails.count() == m_names.count());
	QRegExp hasmail( QLatin1String( "(.*) <(.*)>" ));

	if (hasmail.exactMatch(id)) {
		m_names.append(hasmail.capturedTexts().at(1));
		m_emails.append(hasmail.capturedTexts().at(2));
	} else {
		m_names.append(id);
		m_emails.append(QString());
	}
}

const QString &
SearchResult::getName(const int &index) const
{
	return m_names.at(index);
}

const QString &
SearchResult::getEmail(const int &index) const
{
	return m_emails.at(index);
}

int
SearchResult::getUidCount() const
{
	Q_ASSERT(m_emails.count() == m_names.count());

	return m_emails.count();
}

QVariant
SearchResult::summary() const
{
	if (m_revoked) {
		return i18nc("example: ID abc123xy, 1024-bit RSA key, created Jan 12 2009, revoked",
				"ID %1, %2-bit %3 key, created %4, revoked", m_fingerprint,
				m_bits, KgpgCore::Convert::toString(m_algo),
				m_creation.toString(KDateTime::LocalDate));
	} else {
		return i18nc("example: ID abc123xy, 1024-bit RSA key, created Jan 12 2009",
				"ID %1, %2-bit %3 key, created %4", m_fingerprint,
				m_bits, KgpgCore::Convert::toString(m_algo),
				m_creation.toString(KDateTime::LocalDate));
	}
}

KGpgSearchResultModelPrivate::KGpgSearchResultModelPrivate()
{
}

KGpgSearchResultModelPrivate::~KGpgSearchResultModelPrivate()
{
	foreach (SearchResult *item, m_items)
		delete item;
}

QString
KGpgSearchResultModelPrivate::urlDecode(const QString &line)
{
	if (!line.contains(QLatin1Char( '%' )))
		return line;

	QByteArray tmp(line.toAscii());
	const QRegExp hex( QLatin1String( "[A-F0-9]{2}" ));	// URL-encoding uses only uppercase

	int pos = -1;	// avoid error if '%' is URL-encoded
	while ((pos = tmp.indexOf("%", pos + 1)) >= 0) {
		const QByteArray hexnum(tmp.mid(pos + 1, 2));

		// the input is not properly URL-encoded, so assume it does not need to be decoded at all
		if (!hex.exactMatch(QLatin1String( hexnum )))
			return line;

		char n[2];
		// this must work as we checked the regexp before
		n[0] = hexnum.toUShort(NULL, 16);
		n[1] = '\0';	// to use n as a 0-terminated string

		tmp.replace(pos, 3, n);
	}

	return QTextCodec::codecForName("utf8")->toUnicode(tmp);
}

KGpgSearchResultModel::KGpgSearchResultModel(QObject *parent)
	: QAbstractItemModel(parent), d(new KGpgSearchResultModelPrivate())
{
}

KGpgSearchResultModel::~KGpgSearchResultModel()
{
	delete d;
}

QVariant
KGpgSearchResultModel::data(const QModelIndex &index, int role) const
{
	if (!index.isValid())
		return QVariant();

	if (role != Qt::DisplayRole)
		return QVariant();

	if (index.row() < 0)
		return QVariant();

	SearchResult *tmp = static_cast<SearchResult *>(index.internalPointer());
	int row;

	if (tmp == NULL) {
		// this is a "top" item, show the first uid
		if (index.row() >= d->m_items.count())
			return QVariant();

		tmp = d->m_items.at(index.row());
		row = 0;
	} else {
		row = index.row() + 1;
		int summaryRow = tmp->getUidCount();
		int uatRow;
		if (tmp->m_uatCount != 0) {
			uatRow = summaryRow;
			summaryRow++;
		} else {
			uatRow = -1;
		}

		if (row == uatRow) {
			if (index.column() == 0)
				return i18np("One Photo ID", "%1 Photo IDs", tmp->m_uatCount);
			else
				return QVariant();
		} else if (row == summaryRow) {
			if (index.column() == 0)
				return tmp->summary();
			else
				return QVariant();
		}
		Q_ASSERT(row < tmp->getUidCount());
	}

	switch (index.column()) {
	case 0:
		return tmp->getName(row);
	case 1:
		return tmp->getEmail(row);
	default:
		return QVariant();
	}
}

int
KGpgSearchResultModel::columnCount(const QModelIndex &parent) const
{
	if (parent.isValid()) {
		if (parent.column() != 0)
			return 0;

		SearchResult *tmp = static_cast<SearchResult *>(parent.internalPointer());

		if (tmp == NULL)
			return 2;
		else
			return 0;
	} else {
		return 2;
	}
}

QModelIndex
KGpgSearchResultModel::index(int row, int column, const QModelIndex &parent) const
{
	// there are three hierarchy levels:
	// root: this is simply QModelIndex()
	// key items: parent is invalid, internalPointer is NULL
	// uid entries: parent is key item, internalPointer is set to SearchResult*

	if (parent.isValid()) {
		if (parent.internalPointer() != NULL) {
			return QModelIndex();
		} else {
			if (parent.row() >= d->m_items.count())
				return QModelIndex();
			SearchResult *tmp = d->m_items.at(parent.row());
			int maxRow = tmp->getUidCount();
			if (tmp->m_uatCount != 0)
				maxRow++;
			if ((row >= maxRow) || (column > 1))
				return QModelIndex();
			return createIndex(row, column, tmp);
		}
	} else {
		if ((row >= d->m_items.count()) || (column > 1) || (row < 0) || (column < 0))
			return QModelIndex();
		return createIndex(row, column);
	}
}

QModelIndex
KGpgSearchResultModel::parent(const QModelIndex &index) const
{
	if (!index.isValid())
		return QModelIndex();

	SearchResult *tmp = static_cast<SearchResult *>(index.internalPointer());

	if (tmp == NULL)
		return QModelIndex();

	return createIndex(d->m_items.indexOf(tmp), 0);
}

int
KGpgSearchResultModel::rowCount(const QModelIndex &parent) const
{
	if (!parent.isValid()) {
		return d->m_items.count();
	} else if (parent.column() == 0) {
		if (parent.internalPointer() != NULL)
			return 0;

		SearchResult *item = d->m_items.at(parent.row());
		int cnt = item->getUidCount();
		if (item->m_uatCount != 0)
			cnt++;

		return cnt;
	} else {
		return 0;
	}
}

QVariant
KGpgSearchResultModel::headerData(int section, Qt::Orientation orientation, int role) const
{
	if (role != Qt::DisplayRole)
		return QVariant();

	if (orientation != Qt::Horizontal)
		return QVariant();

	switch (section) {
	case 0:
		return i18n("Name");
	case 1:
		return QString(i18nc("@title:column Title of a column of emails", "Email"));
	default:
		return QVariant();
	}
}

const QString &
KGpgSearchResultModel::idForIndex(const QModelIndex &index) const
{
	Q_ASSERT(index.isValid());

	SearchResult *tmp = static_cast<SearchResult *>(index.internalPointer());
	if (tmp == NULL)
		tmp = d->m_items.at(index.row());

	return tmp->m_fingerprint;
}

void
KGpgSearchResultModel::slotAddKey(QStringList lines)
{
	Q_ASSERT(!lines.isEmpty());
	Q_ASSERT(lines.first().startsWith(QLatin1String("pub:")));

	if (lines.count() == 1)
		return;

	SearchResult *nkey = new SearchResult(lines.takeFirst());
	if (!nkey->m_validPub) {
		delete nkey;
		return;
	}

	foreach (const QString &line, lines) {
		if (line.startsWith(QLatin1String("uid:"))) {
			QString kid = d->urlDecode(line.section(QLatin1Char( ':' ), 1, 1));

			nkey->addUid(kid);
		} else if (line.startsWith(QLatin1String("uat:"))) {
			nkey->m_uatCount++;
		} else {
			kDebug(2100) << "ignored search result line" << line;
		}
	}

	if (nkey->getUidCount() > 0) {
		beginInsertRows(QModelIndex(), d->m_items.count(), d->m_items.count());
		d->m_items.append(nkey);
		endInsertRows();
	} else {
		// key server sent back a crappy key
		delete nkey;
	}
}

#include "kgpgsearchresultmodel.moc"
