/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.

For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/mtproto_config.h"

#include "storage/serialize_common.h"
#include "mtproto/type_utils.h"
#include "logs.h"

namespace MTP {
namespace {

constexpr auto kVersion = 1;

} // namespace

QString ConfigDefaultReactionEmoji() {
	static const auto result = QString::fromUtf8("\xf0\x9f\x91\x8d");
	return result;
}

Config::Config(Environment environment) : _dcOptions(environment) {
	_fields.webFileDcId = _dcOptions.isTestMode() ? 2 : 4;
	_fields.txtDomainString = _dcOptions.isTestMode()
		? u"tapv3.stel.com"_q
		: u"apv3.stel.com"_q;
}

Config::Config(const Config &other)
: _dcOptions(other.dcOptions())
, _fields(other._fields) {
}

QByteArray Config::serialize() const {
	auto options = _dcOptions.serialize();
	auto size = sizeof(qint32) * 2 // version + environment
		+ Serialize::bytearraySize(options)
		+ 19 * sizeof(qint32)
		+ Serialize::stringSize(_fields.internalLinksDomain)
		+ 6 * sizeof(qint32)
		+ Serialize::stringSize(_fields.txtDomainString)
		+ 3 * sizeof(qint32)
		+ Serialize::stringSize(_fields.reactionDefaultEmoji)
		+ sizeof(quint64);

	auto result = QByteArray();
	result.reserve(size);
	{
		QDataStream stream(&result, QIODevice::WriteOnly);
		stream.setVersion(QDataStream::Qt_5_1);
		stream
			<< qint32(kVersion)
			<< qint32(_dcOptions.isTestMode()
				? Environment::Test
				: Environment::Production)
			<< options
			<< qint32(_fields.chatSizeMax)
			<< qint32(_fields.megagroupSizeMax)
			<< qint32(_fields.forwardedCountMax)
			<< qint32(_fields.onlineUpdatePeriod)
			<< qint32(_fields.offlineBlurTimeout)
			<< qint32(_fields.offlineIdleTimeout)
			<< qint32(_fields.onlineFocusTimeout)
			<< qint32(_fields.onlineCloudTimeout)
			<< qint32(_fields.notifyCloudDelay)
			<< qint32(_fields.notifyDefaultDelay)
			<< qint32(_fields.savedGifsLimit)
			<< qint32(_fields.editTimeLimit)
			<< qint32(_fields.revokeTimeLimit)
			<< qint32(_fields.revokePrivateTimeLimit)
			<< qint32(_fields.revokePrivateInbox ? 1 : 0)
			<< qint32(_fields.stickersRecentLimit)
			<< qint32(_fields.stickersFavedLimit)
			<< qint32(_fields.pinnedDialogsCountMax.current())
			<< qint32(_fields.pinnedDialogsInFolderMax.current())
			<< _fields.internalLinksDomain
			<< qint32(_fields.channelsReadMediaPeriod)
			<< qint32(_fields.callReceiveTimeoutMs)
			<< qint32(_fields.callRingTimeoutMs)
			<< qint32(_fields.callConnectTimeoutMs)
			<< qint32(_fields.callPacketTimeoutMs)
			<< qint32(_fields.webFileDcId)
			<< _fields.txtDomainString
			<< qint32(1) // legacy phoneCallsEnabled
			<< qint32(_fields.blockedMode ? 1 : 0)
			<< qint32(_fields.captionLengthMax)
			<< _fields.reactionDefaultEmoji
			<< quint64(_fields.reactionDefaultCustom);
	}
	return result;
}

std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) {
	auto result = std::unique_ptr<Config>();
	auto raw = result.get();

	QDataStream stream(serialized);
	stream.setVersion(QDataStream::Qt_5_1);

	auto version = qint32();
	stream >> version;
	if (version != kVersion) {
		return result;
	}
	auto environment = qint32();
	stream >> environment;
	switch (environment) {
	case qint32(Environment::Test):
		result = std::make_unique<Config>(Environment::Test);
		break;
	case qint32(Environment::Production):
		result = std::make_unique<Config>(Environment::Production);
		break;
	}
	if (!(raw = result.get())) {
		return result;
	}

	auto dcOptionsSerialized = QByteArray();
	auto legacyPhoneCallsEnabled = rpl::variable<bool>();
	const auto read = [&](auto &field) {
		using Type = std::remove_reference_t<decltype(field)>;
		if constexpr (std::is_same_v<Type, int>
			|| std::is_same_v<Type, rpl::variable<int>>) {
			auto value = qint32();
			stream >> value;
			field = value;
		} else if constexpr (std::is_same_v<Type, uint64>) {
			auto value = quint64();
			stream >> value;
			field = value;
		} else if constexpr (std::is_same_v<Type, bool>
			|| std::is_same_v<Type, rpl::variable<bool>>) {
			auto value = qint32();
			stream >> value;
			field = (value == 1);
		} else if constexpr (std::is_same_v<Type, QByteArray>
			|| std::is_same_v<Type, QString>) {
			stream >> field;
		} else {
			static_assert(false_(field), "Bad read() call.");
		}
	};

	read(dcOptionsSerialized);
	read(raw->_fields.chatSizeMax);
	read(raw->_fields.megagroupSizeMax);
	read(raw->_fields.forwardedCountMax);
	read(raw->_fields.onlineUpdatePeriod);
	read(raw->_fields.offlineBlurTimeout);
	read(raw->_fields.offlineIdleTimeout);
	read(raw->_fields.onlineFocusTimeout);
	read(raw->_fields.onlineCloudTimeout);
	read(raw->_fields.notifyCloudDelay);
	read(raw->_fields.notifyDefaultDelay);
	read(raw->_fields.savedGifsLimit);
	read(raw->_fields.editTimeLimit);
	read(raw->_fields.revokeTimeLimit);
	read(raw->_fields.revokePrivateTimeLimit);
	read(raw->_fields.revokePrivateInbox);
	read(raw->_fields.stickersRecentLimit);
	read(raw->_fields.stickersFavedLimit);
	read(raw->_fields.pinnedDialogsCountMax);
	read(raw->_fields.pinnedDialogsInFolderMax);
	read(raw->_fields.internalLinksDomain);
	read(raw->_fields.channelsReadMediaPeriod);
	read(raw->_fields.callReceiveTimeoutMs);
	read(raw->_fields.callRingTimeoutMs);
	read(raw->_fields.callConnectTimeoutMs);
	read(raw->_fields.callPacketTimeoutMs);
	read(raw->_fields.webFileDcId);
	read(raw->_fields.txtDomainString);
	read(legacyPhoneCallsEnabled);
	read(raw->_fields.blockedMode);
	read(raw->_fields.captionLengthMax);
	if (!stream.atEnd()) {
		read(raw->_fields.reactionDefaultEmoji);
		read(raw->_fields.reactionDefaultCustom);
	}

	if (stream.status() != QDataStream::Ok
		|| !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) {
		return nullptr;
	}
	return result;
}

const ConfigFields &Config::values() const {
	return _fields;
}

void Config::apply(const MTPDconfig &data) {
	if (mtpIsTrue(data.vtest_mode()) != _dcOptions.isTestMode()) {
		LOG(("MTP Error: config with wrong test mode field received!"));
		return;
	}

	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().v.size()));

	_fields.chatSizeMax = data.vchat_size_max().v;
	_fields.megagroupSizeMax = data.vmegagroup_size_max().v;
	_fields.forwardedCountMax = data.vforwarded_count_max().v;
	_fields.onlineUpdatePeriod = data.vonline_update_period_ms().v;
	_fields.offlineBlurTimeout = data.voffline_blur_timeout_ms().v;
	_fields.offlineIdleTimeout = data.voffline_idle_timeout_ms().v;
	_fields.onlineCloudTimeout = data.vonline_cloud_timeout_ms().v;
	_fields.notifyCloudDelay = data.vnotify_cloud_delay_ms().v;
	_fields.notifyDefaultDelay = data.vnotify_default_delay_ms().v;
	_fields.savedGifsLimit = data.vsaved_gifs_limit().v;
	_fields.editTimeLimit = data.vedit_time_limit().v;
	_fields.revokeTimeLimit = data.vrevoke_time_limit().v;
	_fields.revokePrivateTimeLimit = data.vrevoke_pm_time_limit().v;
	_fields.revokePrivateInbox = data.is_revoke_pm_inbox();
	_fields.stickersRecentLimit = data.vstickers_recent_limit().v;
	_fields.stickersFavedLimit = data.vstickers_faved_limit().v;
	_fields.pinnedDialogsCountMax =
		std::max(data.vpinned_dialogs_count_max().v, 1);
	_fields.pinnedDialogsInFolderMax =
		std::max(data.vpinned_infolder_count_max().v, 1);
	_fields.internalLinksDomain = qs(data.vme_url_prefix());
	_fields.channelsReadMediaPeriod = data.vchannels_read_media_period().v;
	_fields.webFileDcId = data.vwebfile_dc_id().v;
	_fields.callReceiveTimeoutMs = data.vcall_receive_timeout_ms().v;
	_fields.callRingTimeoutMs = data.vcall_ring_timeout_ms().v;
	_fields.callConnectTimeoutMs = data.vcall_connect_timeout_ms().v;
	_fields.callPacketTimeoutMs = data.vcall_packet_timeout_ms().v;
	_fields.blockedMode = data.is_blocked_mode();
	_fields.captionLengthMax = data.vcaption_length_max().v;
	_fields.reactionDefaultEmoji = ConfigDefaultReactionEmoji();
	_fields.reactionDefaultCustom = 0;
	if (const auto reaction = data.vreactions_default()) {
		reaction->match([&](const MTPDreactionEmpty &) {
		}, [&](const MTPDreactionEmoji &data) {
			_fields.reactionDefaultEmoji = qs(data.vemoticon());
		}, [&](const MTPDreactionCustomEmoji &data) {
			_fields.reactionDefaultCustom = data.vdocument_id().v;
		});
	}

	if (data.vdc_options().v.empty()) {
		LOG(("MTP Error: config with empty dc_options received!"));
	} else {
		dcOptions().setFromList(data.vdc_options());
	}

	_updates.fire({});
}

rpl::producer<> Config::updates() const {
	return _updates.events();
}

void Config::setChatSizeMax(int value) {
	_fields.chatSizeMax = value;
}

void Config::setSavedGifsLimit(int value) {
	_fields.savedGifsLimit = value;
}

void Config::setStickersRecentLimit(int value) {
	_fields.stickersRecentLimit = value;
}

void Config::setStickersFavedLimit(int value) {
	_fields.stickersFavedLimit = value;
}

void Config::setMegagroupSizeMax(int value) {
	_fields.megagroupSizeMax = value;
}

void Config::setTxtDomainString(const QString &value) {
	_fields.txtDomainString = value;
}

} // namespace MTP
