/*
	JsonEngine

	Copyright (c) 2009 by Nigmatullin Ruslan <euroelessar@gmail.com>

 ***************************************************************************
 *                                                                         *
 *   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 "jsonengine.h"
#include "k8json.h"
#include "historywindow.h"
#include "historysettings.h"
#include "src/iconmanager.h"

namespace JsonHistoryNamespace {

JsonEngine::JsonEngine()
{
	m_settings_widget = 0;
	m_save_history = m_show_recent_messages = true;
	m_max_num = 4;
}

bool JsonEngine::init(PluginSystemInterface *plugin_system)
{
	m_name = "json";
	m_version = "0.1.0";
	plugin_system->registerEventHandler("Core/ContactList/ItemAdded", this);
	return true;
}

void JsonEngine::release()
{
	m_history_hash.clear();
}

void JsonEngine::setProfileName(const QString &profile_name)
{
	SystemsCity::instance().setProfileName(profile_name);
	QString path = SystemsCity::PluginSystem()->getProfilePath();
	if(path.endsWith(QDir::separator()))
		path.chop(1);
	path += QDir::separator();
	path += "history";
	m_history_path = path;
	m_history_dir = path;
	loadSettings();
}

void JsonEngine::loadSettings()
{
	QSettings settings(QSettings::defaultFormat(), QSettings::UserScope, "qutim/qutim."+SystemsCity::ProfileName(), "profilesettings");
	settings.beginGroup("history");
	m_save_history = settings.value("save", true).toBool();
	m_show_recent_messages = settings.value("showrecent", true).toBool();
	m_max_num = settings.value("recentcount", 4).toUInt();
	settings.endGroup();
}

void JsonEngine::setLayerInterface( LayerType type, LayerInterface *layer_interface)
{
	LayersCity::instance().setLayerInterface(type, layer_interface);
}

void JsonEngine::saveLayerSettings()
{
	if ( m_settings_widget )
		m_settings_widget->saveSettings();
	loadSettings();
}

QList<SettingsStructure> JsonEngine::getLayerSettingsList()
{
	m_settings.clear();
	if ( !m_settings_widget )
	{
		m_settings_widget = new HistorySettings(SystemsCity::ProfileName());
		m_settings_item = new QTreeWidgetItem;
		m_settings_item->setText(0,QObject::tr("History"));
		m_settings_item->setIcon(0,Icon("history"));
		SettingsStructure tmp_struct;
		tmp_struct.settings_item = m_settings_item;
		tmp_struct.settings_widget = m_settings_widget;
		m_settings.append(tmp_struct);
	}
	return m_settings;
}

void JsonEngine::removeLayerSettings()
{
	if ( m_settings_widget )
	{
		delete m_settings_widget;
		m_settings_widget = 0;
		delete m_settings_item;
		m_settings_item = 0;
	}
}

void JsonEngine::openWindow(const TreeModelItem &item)
{
	QString identification = QString("%1.%2.%3").arg(item.m_protocol_name).arg(item.m_account_name).arg(item.m_item_name);
	if(!m_history_windows.value(identification))
	{
		TreeModelItem tmp = item;
		tmp.m_item_name = m_history_hash.value(identification, item.m_item_name);
		m_history_windows.insert(identification, QPointer<HistoryWindow>(new HistoryWindow(tmp, this)));
	}
}

uint JsonEngine::findEnd(QFile &file)
{
	int len = file.size();
	QByteArray data;
	uchar *fmap = file.map(0, file.size());
	if(!fmap)
	{
		data = file.readAll();
		fmap = (uchar *)data.constData();
	}
	uint end = file.size();
	const uchar *s = K8JSON::skipBlanks(fmap, &len);
	uchar qch = *s;
	if(!s || (qch != '[' && qch != '{'))
	{
		if(data.isEmpty())
			file.unmap(fmap);
		return end;
	}
	qch = (qch == '{' ? '}' : ']');
	s++;
	len--;
	bool first = true;
	while(s)
	{
		s = K8JSON::skipBlanks(s, &len);
		if(len < 2 || (s && *s == qch))
		{
			if(*(s-1) == '\n')
				s--;
			end = (uint)(s - fmap);
			break;
		}
		if(!s)
			break;
		if((!first && *s != ',') || (first && *s == ','))
			break;
		first = false;
		if(*s == ',')
		{
			s++;
			len--;
		}
		if(!(s = K8JSON::skipRec(s, &len)))
			break;
	}
	if(data.isEmpty())
		file.unmap(fmap);
	return end;
}

bool JsonEngine::storeMessage(const HistoryItem &item)
{
	if(!m_save_history)
		return false;
	QFile file(getAccountDir(item.m_user).filePath(getFileName(item)));
	bool new_file = !file.exists();
	if(!file.open(QIODevice::ReadWrite | QIODevice::Text))
		return false;
	if(new_file)
	{
		file.write("[\n");
	}
	else
	{
		uint end = findEnd(file);
		file.resize(end);
		file.seek(end);
		file.write(",\n");
	}
	file.write(" {\n  \"datetime\": \"");
	file.write(item.m_time.toString(Qt::ISODate).toLatin1());
	file.write("\",\n  \"type\": ");
	file.write(QString::number(item.m_type).toLatin1());
	file.write(",\n  \"in\": ");
	file.write(item.m_in ? "true" : "false");
	file.write(",\n  \"text\": ");
	file.write(K8JSON::quote(item.m_message).toUtf8());
	file.write("\n }\n]");
	file.close();
//	It will produce something like this:
//	{
//	 "datetime": "2009-06-20T01:42:22",
//	 "type": 1,
//	 "in": true,
//	 "text": "some cool text"
//	}
	return true;
}

QList<HistoryItem> JsonEngine::getMessages(const TreeModelItem &item, const QDateTime &last_time)
{
	QList<HistoryItem> items;
	if(!m_show_recent_messages)
		return items;
	QDir dir = getAccountDir(item);
	QString identification = QString("%1.%2.%3").arg(item.m_protocol_name).arg(item.m_account_name).arg(item.m_item_name);
	QString filter = quote(m_history_hash.value(identification, item.m_item_name));
	filter += ".*.json";
	QStringList files = dir.entryList(QStringList() << filter, QDir::Readable | QDir::Files | QDir::NoDotAndDotDot,QDir::Name);
	if(files.isEmpty())
		return items;
	for(int i=files.size()-1; i>=0; i--)
	{
		QList<const uchar *> pointers;
		QFile file(dir.filePath(files[i]));
		if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
			continue;
		int len = file.size();
		QByteArray data;
		const uchar *fmap = file.map(0, file.size());
		if(!fmap)
		{
			data = file.readAll();
			fmap = (uchar *)data.constData();
		}
		const uchar *s = K8JSON::skipBlanks(fmap, &len);
		uchar qch = *s;
		if(!s || (qch != '[' && qch != '{'))
			continue;
		qch = (qch == '{' ? '}' : ']');
		s++;
		len--;
		bool first = true;
		while(s)
		{
			s = K8JSON::skipBlanks(s, &len);
			if(len < 2 || (s && *s == qch))
				break;
			if((!first && *s != ',') || (first && *s == ','))
				break;
			first = false;
			if(*s == ',')
			{
				s++;
				len--;
			}
			pointers.prepend(s);
			if(!(s = K8JSON::skipRec(s, &len)))
			{
				pointers.removeFirst();
				break;
			}
		}
		QVariant value;
		for(int i=0; i<pointers.size(); i++)
		{
			value.clear();
			s = pointers[i];
			len = file.size() + 1 - (s - fmap);
			K8JSON::parseRec(value, s, &len);
			QVariantMap message = value.toMap();
			HistoryItem item;
			item.m_time = QDateTime::fromString(message.value("datetime").toString(), Qt::ISODate);
			if(item.m_time >= last_time)
				continue;
			item.m_in = message.value("in", false).toBool();
			item.m_type = message.value("type", 1).toInt();
			item.m_message = message.value("text").toString();
			items.prepend(item);
			if(items.size() >= m_max_num)
				return items;
		}
	}
	return items;
}

QString JsonEngine::quote(const QString &str)
{
	const static bool true_chars[128] =
	{// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
/* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 2 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
/* 3 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0,
/* 4 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 5 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0,
/* 6 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 7 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0
	};
	QString result;
	result.reserve(str.size() * 5); // the worst variant
	const QChar *s = str.data();
	while(!s->isNull())
	{
		if(s->unicode() < 0xff && true_chars[s->unicode()])
			result += *s;
		else
		{
			result += '%';
			if(s->unicode() < 0x1000)
				result += '0';
			if(s->unicode() < 0x100)
				result += '0';
			if(s->unicode() < 0x10)
				result += '0';
			result += QString::number(s->unicode(), 16);
		}
		s++;
	}
	return result;
}

QString JsonEngine::unquote(const QString &str)
{
	QString result;
	bool ok = false;
	result.reserve(str.size()); // the worst variant
	const QChar *s = str.data();
	while(!s->isNull())
	{
		if(s->unicode() == L'%')
		{
			result += QChar(QString(++s, 4).toUShort(&ok, 16));
			s += 3;
		}
		else
			result += *s;
		s++;
	}
	return result;
}

QString JsonEngine::getFileName(const HistoryItem &item) const
{
	QString identification = QString("%1.%2.%3")
							 .arg(item.m_user.m_protocol_name)
							 .arg(item.m_user.m_account_name)
							 .arg(item.m_user.m_item_name);
	QString file = quote(m_history_hash.value(identification, item.m_user.m_item_name));
	file += item.m_time.toString(".yyyyMM.'json'");
	return file;
}

QDir JsonEngine::getAccountDir(const TreeModelItem &item) const
{
	QString path = quote(item.m_protocol_name);
	path += ".";
	path += quote(item.m_account_name);
	if(!m_history_dir.exists(path))
		m_history_dir.mkpath(path);
	return m_history_dir.filePath(path);
}

void JsonEngine::setHistory(const TreeModelItem &item)
{
	if(item.m_item_history.isEmpty())
		return;
	QString identification = QString("%1.%2.%3").arg(item.m_protocol_name).arg(item.m_account_name).arg(item.m_item_name);
	m_history_hash.insert(identification, item.m_item_history);
}

void JsonEngine::processEvent(Event &event)
{
	setHistory(event.at<TreeModelItem>(0));
}

}
