/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org

Telegram Desktop 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 3 of the License, or
(at your option) any later version.

It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.

Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "mtpDC.h"
#include "mtp.h"

#include "localstorage.h"

namespace {

	MTProtoDCMap gDCs;
	bool configLoadedOnce = false;
	bool mainDCChanged = false;
	int32 mainDC = 2;
	int32 userId = 0;

	typedef QMap<int32, mtpAuthKeyPtr> _KeysMapForWrite;
	_KeysMapForWrite _keysMapForWrite;
	QMutex _keysMapForWriteMutex;
}

int32 mtpAuthed() {
	return userId;
}

void mtpAuthed(int32 uid) {
	if (userId != uid) {
		userId = uid;
	}
}

MTProtoDCMap &mtpDCMap() {
	return gDCs;
}

bool mtpNeedConfig() {
	return !configLoadedOnce;
}

int32 mtpMainDC() {
	return mainDC;
}

namespace {
	QMap<int32, mtpRequestId> logoutGuestMap; // dcWithShift to logout request id
	bool logoutDone(mtpRequestId req) {
		for (QMap<int32, mtpRequestId>::iterator i = logoutGuestMap.begin(); i != logoutGuestMap.end(); ++i) {
			if (i.value() == req) {
				MTP::killSession(i.key());
				logoutGuestMap.erase(i);
				return true;
			}
		}
		return false;
	}
}

void mtpLogoutOtherDCs() {
	QList<int32> dcs;
	{
		QMutexLocker lock(&_keysMapForWriteMutex);
		dcs = _keysMapForWrite.keys();
	}
	for (int32 i = 0, cnt = dcs.size(); i != cnt; ++i) {
		if (dcs[i] != MTP::maindc()) {
			logoutGuestMap.insert(MTP::lgt + dcs[i], MTP::send(MTPauth_LogOut(), rpcDone(&logoutDone), rpcFail(&logoutDone), MTP::lgt + dcs[i]));
		}
	}
}

void mtpSetDC(int32 dc, bool firstOnly) {
	if (!dc || (firstOnly && mainDCChanged)) return;
	mainDCChanged = true;
	if (dc != mainDC) {
		mainDC = dc;
	}
}

MTProtoDC::MTProtoDC(int32 id, const mtpAuthKeyPtr &key) : _id(id), _key(key), _connectionInited(false) {
	connect(this, SIGNAL(authKeyCreated()), this, SLOT(authKeyWrite()), Qt::QueuedConnection);

	QMutexLocker lock(&_keysMapForWriteMutex);
	if (_key) {
		_keysMapForWrite[_id] = _key;
	} else {
		_keysMapForWrite.remove(_id);
	}
}

void MTProtoDC::authKeyWrite() {
	DEBUG_LOG(("AuthKey Info: MTProtoDC::authKeyWrite() slot, dc %1").arg(_id));
	if (_key) {
		Local::writeMtpData();
	}
}

void MTProtoDC::setKey(const mtpAuthKeyPtr &key) {
	DEBUG_LOG(("AuthKey Info: MTProtoDC::setKey(%1), emitting authKeyCreated, dc %2").arg(key ? key->keyId() : 0).arg(_id));
	_key = key;
	_connectionInited = false;
	emit authKeyCreated();

	QMutexLocker lock(&_keysMapForWriteMutex);
	if (_key) {
		_keysMapForWrite[_id] = _key;
	} else {
		_keysMapForWrite.remove(_id);
	}
}

QReadWriteLock *MTProtoDC::keyMutex() const {
	return &keyLock;
}

const mtpAuthKeyPtr &MTProtoDC::getKey() const {
	return _key;
}

void MTProtoDC::destroyKey() {
	setKey(mtpAuthKeyPtr());

	QMutexLocker lock(&_keysMapForWriteMutex);
	_keysMapForWrite.remove(_id);
}

namespace {
	MTProtoConfigLoader *configLoader = 0;
	bool loadingConfig = false;
	void configLoaded(const MTPConfig &result) {
		loadingConfig = false;

		const MTPDconfig &data(result.c_config());

		DEBUG_LOG(("MTP Info: got config, chat_size_max: %1, date: %2, test_mode: %3, this_dc: %4, dc_options.length: %5").arg(data.vchat_size_max.v).arg(data.vdate.v).arg(mtpIsTrue(data.vtest_mode)).arg(data.vthis_dc.v).arg(data.vdc_options.c_vector().v.size()));

		mtpUpdateDcOptions(data.vdc_options.c_vector().v);

		Global::SetChatSizeMax(data.vchat_size_max.v);
		Global::SetMegagroupSizeMax(data.vmegagroup_size_max.v);
		Global::SetForwardedCountMax(data.vforwarded_count_max.v);
		Global::SetOnlineUpdatePeriod(data.vonline_update_period_ms.v);
		Global::SetOfflineBlurTimeout(data.voffline_blur_timeout_ms.v);
		Global::SetOfflineIdleTimeout(data.voffline_idle_timeout_ms.v);
		Global::SetOnlineCloudTimeout(data.vonline_cloud_timeout_ms.v);
		Global::SetNotifyCloudDelay(data.vnotify_cloud_delay_ms.v);
		Global::SetNotifyDefaultDelay(data.vnotify_default_delay_ms.v);
		Global::SetChatBigSize(data.vchat_big_size.v); // ?
		Global::SetPushChatPeriod(data.vpush_chat_period_ms.v); // ?
		Global::SetPushChatLimit(data.vpush_chat_limit.v); // ?
		Global::SetSavedGifsLimit(data.vsaved_gifs_limit.v);
		Global::SetEditTimeLimit(data.vedit_time_limit.v); // ?

		configLoadedOnce = true;
		Local::writeSettings();

		mtpConfigLoader()->done();
	}
	bool configFailed(const RPCError &error) {
		if (mtpIsFlood(error)) return false;

		loadingConfig = false;
		LOG(("MTP Error: failed to get config!"));
		return false;
	}
};

void mtpUpdateDcOptions(const QVector<MTPDcOption> &options) {
	QSet<int32> already, restart;
	{
		mtpDcOptions opts;
		{
			QReadLocker lock(mtpDcOptionsMutex());
			opts = cDcOptions();
		}
		for (QVector<MTPDcOption>::const_iterator i = options.cbegin(), e = options.cend(); i != e; ++i) {
			const MTPDdcOption &optData(i->c_dcOption());
			int32 id = optData.vid.v, idWithShift = id + (optData.vflags.v * _mtp_internal::dcShift);
			if (already.constFind(idWithShift) == already.cend()) {
				already.insert(idWithShift);
				mtpDcOptions::const_iterator a = opts.constFind(idWithShift);
				if (a != opts.cend()) {
					if (a.value().ip != optData.vip_address.c_string().v || a.value().port != optData.vport.v) {
						restart.insert(id);
					}
				}
				opts.insert(idWithShift, mtpDcOption(id, optData.vflags.v, optData.vip_address.c_string().v, optData.vport.v));
			}
		}
		{
			QWriteLocker lock(mtpDcOptionsMutex());
			cSetDcOptions(opts);
		}
	}
	for (QSet<int32>::const_iterator i = restart.cbegin(), e = restart.cend(); i != e; ++i) {
		MTP::restart(*i);
	}
}

namespace {
	QReadWriteLock _dcOptionsMutex;
}

QReadWriteLock *mtpDcOptionsMutex() {
	return &_dcOptionsMutex;
}

MTProtoConfigLoader::MTProtoConfigLoader() : _enumCurrent(0), _enumRequest(0) {
	connect(&_enumDCTimer, SIGNAL(timeout()), this, SLOT(enumDC()));
}

void MTProtoConfigLoader::load() {
	if (loadingConfig) return;
	loadingConfig = true;

	MTP::send(MTPhelp_GetConfig(), rpcDone(configLoaded), rpcFail(configFailed));

	_enumDCTimer.start(MTPEnumDCTimeout);
}

void MTProtoConfigLoader::done() {
	_enumDCTimer.stop();
	if (_enumRequest) {
		MTP::cancel(_enumRequest);
		_enumRequest = 0;
	}
	if (_enumCurrent) {
		MTP::killSession(MTP::cfg + _enumCurrent);
		_enumCurrent = 0;
	}
	emit loaded();
}

void MTProtoConfigLoader::enumDC() {
	if (!loadingConfig) return;

	if (_enumRequest) MTP::cancel(_enumRequest);

	if (!_enumCurrent) {
		_enumCurrent = mainDC;
	} else {
		MTP::killSession(MTP::cfg + _enumCurrent);
	}
	OrderedSet<int32> dcs;
	{
		QReadLocker lock(mtpDcOptionsMutex());
		const mtpDcOptions &options(cDcOptions());
		for (mtpDcOptions::const_iterator i = options.cbegin(), e = options.cend(); i != e; ++i) {
			dcs.insert(i.key() % _mtp_internal::dcShift);
		}
	}
	OrderedSet<int32>::const_iterator i = dcs.constFind(_enumCurrent);
	if (i == dcs.cend() || (++i) == dcs.cend()) {
		_enumCurrent = dcs.cbegin().key();
	} else {
		_enumCurrent = i.key();
	}
	_enumRequest = MTP::send(MTPhelp_GetConfig(), rpcDone(configLoaded), rpcFail(configFailed), MTP::cfg + _enumCurrent);

	_enumDCTimer.start(MTPEnumDCTimeout);
}

MTProtoConfigLoader *mtpConfigLoader() {
	if (!configLoader) configLoader = new MTProtoConfigLoader();
	return configLoader;
}

void mtpDestroyConfigLoader() {
	delete configLoader;
	configLoader = 0;
}

mtpKeysMap mtpGetKeys() {
	mtpKeysMap result;
	QMutexLocker lock(&_keysMapForWriteMutex);
	for (_KeysMapForWrite::const_iterator i = _keysMapForWrite.cbegin(), e = _keysMapForWrite.cend(); i != e; ++i) {
		result.push_back(i.value());
	}
	return result;
}

void mtpSetKey(int32 dcId, mtpAuthKeyPtr key) {
	MTProtoDCPtr dc(new MTProtoDC(dcId, key));
	gDCs.insert(dcId, dc);
}