/*
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 "data/data_auto_download.h"

#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_document.h"

#include <QtCore/QBuffer>

namespace Data {
namespace AutoDownload {
namespace {

constexpr auto kDefaultMaxSize = 8 * int64(1024 * 1024);
constexpr auto kDefaultAutoPlaySize = 50 * int64(1024 * 1024);
constexpr auto kVersion1 = char(1);
constexpr auto kVersion2 = char(2);
constexpr auto kVersion = char(3);

template <typename Enum>
auto enums_view(int from, int till) {
	using namespace ranges::views;
	return ints(from, till) | transform([](int index) {
		return static_cast<Enum>(index);
	});
}

template <typename Enum>
auto enums_view(int till) {
	return enums_view<Enum>(0, till);
}

const Full &Defaults() {
	static auto Result = [] {
		auto result = Full::FullDisabled();
		for (const auto source : enums_view<Source>(kSourcesCount)) {
			SetDefaultsForSource(result, source);
		}
		return result;
	}();
	return Result;
}

Source SourceFromPeer(not_null<PeerData*> peer) {
	if (peer->isUser()) {
		return Source::User;
	} else if (peer->isChat() || peer->isMegagroup()) {
		return Source::Group;
	} else {
		return Source::Channel;
	}
}

Type AutoPlayTypeFromDocument(not_null<DocumentData*> document) {
	return document->isVideoFile()
		? Type::AutoPlayVideo
		: document->isVideoMessage()
		? Type::AutoPlayVideoMessage
		: Type::AutoPlayGIF;
}

[[nodiscard]] int64 ForceAllowLimit(
		const Full &data,
		Source source,
		Type type) {
	const auto user = data.bytesLimit(source, type);
	return (user > 0) ? user : Defaults().bytesLimit(source, type);
}

[[nodiscard]] bool ForceAllowed(
		const Full &data,
		Source source,
		Type type,
		int64 fileSize) {
	if (ranges::find(kStreamedTypes, type) != end(kStreamedTypes)) {
		return false;
	}
	const auto limit = ForceAllowLimit(data, source, type);
	return (limit > 0) && (fileSize <= limit);
}

} // namespace

void SetDefaultsForSource(Full &data, Source source) {
	data.setBytesLimit(source, Type::Photo, kDefaultMaxSize);
	data.setBytesLimit(source, Type::VoiceMessage, kDefaultMaxSize);
	data.setBytesLimit(
		source,
		Type::AutoPlayVideoMessage,
		kDefaultAutoPlaySize);
	data.setBytesLimit(source, Type::AutoPlayGIF, kDefaultAutoPlaySize);
	const auto channelsFileLimit = (source == Source::Channel)
		? 0
		: kDefaultMaxSize;
	data.setBytesLimit(source, Type::File, channelsFileLimit);
	data.setBytesLimit(source, Type::AutoPlayVideo, kDefaultAutoPlaySize);
	data.setBytesLimit(source, Type::Music, channelsFileLimit);
}

void SetDisabledForSource(Full &data, Source source) {
	for (const auto type : enums_view<Type>(kTypesCount)) {
		data.setBytesLimit(source, type, 0);
	}
}

bool HasEnabledTypes(const Full &data, Source source) {
	return ranges::any_of(enums_view<Type>(kTypesCount), [&](Type type) {
		return (ranges::find(kStreamedTypes, type) == end(kStreamedTypes))
			&& (data.bytesLimit(source, type) > 0);
	});
}

void Single::setBytesLimit(int64 bytesLimit) {
	Expects(bytesLimit >= 0 && bytesLimit <= kMaxBytesLimit);

	_limit = int32(uint32(bytesLimit));

	Ensures(hasValue());
}

bool Single::hasValue() const {
	return (_limit != -1);
}

bool Single::shouldDownload(int64 fileSize) const {
	Expects(hasValue());

	const auto realLimit = bytesLimit();
	return (realLimit > 0) && (fileSize <= realLimit);
}

int64 Single::bytesLimit() const {
	Expects(hasValue());

	return uint32(_limit);
}

qint32 Single::serialize() const {
	return _limit;
}

bool Single::setFromSerialized(qint32 serialized) {
	auto realLimit = quint32(serialized);
	if (serialized != -1 && int64(realLimit) > kMaxBytesLimit) {
		return false;
	}
	_limit = serialized;
	return true;
}

const Single &Set::single(Type type) const {
	Expects(static_cast<int>(type) >= 0
		&& static_cast<int>(type) < kTypesCount);

	return _data[static_cast<int>(type)];
}

Single &Set::single(Type type) {
	return const_cast<Single&>(static_cast<const Set*>(this)->single(type));
}

void Set::setBytesLimit(Type type, int64 bytesLimit) {
	single(type).setBytesLimit(bytesLimit);
}

bool Set::hasValue(Type type) const {
	return single(type).hasValue();
}

bool Set::shouldDownload(Type type, int64 fileSize) const {
	return single(type).shouldDownload(fileSize);
}

int64 Set::bytesLimit(Type type) const {
	return single(type).bytesLimit();
}

qint32 Set::serialize(Type type) const {
	return single(type).serialize();
}

bool Set::setFromSerialized(Type type, qint32 serialized) {
	if (static_cast<int>(type) < 0
		|| static_cast<int>(type) >= kTypesCount) {
		return false;
	}
	return single(type).setFromSerialized(serialized);
}

const Set &Full::set(Source source) const {
	Expects(static_cast<int>(source) >= 0
		&& static_cast<int>(source) < kSourcesCount);

	return _data[static_cast<int>(source)];
}

Set &Full::set(Source source) {
	return const_cast<Set&>(static_cast<const Full*>(this)->set(source));
}

const Set &Full::setOrDefault(Source source, Type type) const {
	const auto &my = set(source);
	const auto &result = my.hasValue(type) ? my : Defaults().set(source);

	Ensures(result.hasValue(type));
	return result;
}

void Full::setBytesLimit(Source source, Type type, int64 bytesLimit) {
	set(source).setBytesLimit(type, bytesLimit);
}

bool Full::shouldDownload(Source source, Type type, int64 fileSize) const {
	if (ranges::find(kStreamedTypes, type) != end(kStreamedTypes)) {
		// With streaming we disable autodownload and hide them in Settings.
		return false;
	}
	return setOrDefault(source, type).shouldDownload(type, fileSize);
}

int64 Full::bytesLimit(Source source, Type type) const {
	return setOrDefault(source, type).bytesLimit(type);
}

void Full::setPeerOverride(PeerId peerId, Override value) {
	if (value == Override::Default) {
		_peerOverrides.remove(peerId);
	} else {
		_peerOverrides[peerId] = value;
	}
}

Override Full::peerOverride(PeerId peerId) const {
	const auto i = _peerOverrides.find(peerId);
	return (i != end(_peerOverrides)) ? i->second : Override::Default;
}

void Full::enumeratePeerOverrides(
		Fn<void(PeerId, Override)> callback) const {
	for (const auto &[peerId, value] : _peerOverrides) {
		callback(peerId, value);
	}
}

QByteArray Full::serialize() const {
	auto result = QByteArray();
	auto size = sizeof(qint8);
	size += kSourcesCount * kTypesCount * sizeof(qint32);
	size += sizeof(qint32);
	size += _peerOverrides.size() * (sizeof(quint64) + sizeof(qint8));
	result.reserve(size);
	{
		auto buffer = QBuffer(&result);
		buffer.open(QIODevice::WriteOnly);
		auto stream = QDataStream(&buffer);
		stream << qint8(kVersion);
		for (const auto source : enums_view<Source>(kSourcesCount)) {
			for (const auto type : enums_view<Type>(kTypesCount)) {
				stream << set(source).serialize(type);
			}
		}
		stream << qint32(_peerOverrides.size());
		for (const auto &[peerId, override] : _peerOverrides) {
			stream
				<< SerializePeerId(peerId)
				<< qint8(static_cast<char>(override));
		}
	}
	return result;
}

bool Full::setFromSerialized(const QByteArray &serialized) {
	if (serialized.isEmpty()) {
		return false;
	}

	auto stream = QDataStream(serialized);
	auto version = qint8();
	stream >> version;
	if (stream.status() != QDataStream::Ok) {
		return false;
	} else if (version != kVersion
		&& version != kVersion2
		&& version != kVersion1) {
		return false;
	}
	auto temp = Full();
	for (const auto source : enums_view<Source>(kSourcesCount)) {
		for (const auto type : enums_view<Type>(kTypesCount)) {
			auto value = qint32();
			stream >> value;
			if (!temp.set(source).setFromSerialized(type, value)) {
				return false;
			}
		}
	}
	if (version == kVersion1) {
		for (const auto source : enums_view<Source>(kSourcesCount)) {
			for (const auto type : kAutoPlayTypes) {
				temp.setBytesLimit(source, type, std::max(
					temp.bytesLimit(source, type),
					kDefaultAutoPlaySize));
			}
		}
	}
	if (version >= kVersion && !stream.atEnd()) {
		auto count = qint32();
		stream >> count;
		if (stream.status() != QDataStream::Ok || count < 0) {
			return false;
		}
		for (auto i = 0; i != count; ++i) {
			auto serializedPeerId = quint64();
			auto rawOverride = qint8();
			stream >> serializedPeerId >> rawOverride;
			if (stream.status() != QDataStream::Ok) {
				return false;
			}
			const auto value = (rawOverride == qint8(Override::ForceAllow))
				? Override::ForceAllow
				: (rawOverride == qint8(Override::ForceDeny))
				? Override::ForceDeny
				: Override::Default;
			if (value != Override::Default) {
				temp.setPeerOverride(
					DeserializePeerId(serializedPeerId),
					value);
			}
		}
	}
	_data = temp._data;
	_peerOverrides = std::move(temp._peerOverrides);
	return true;
}

Full Full::FullDisabled() {
	auto result = Full();
	for (const auto source : enums_view<Source>(kSourcesCount)) {
		for (const auto type : enums_view<Type>(kTypesCount)) {
			result.setBytesLimit(source, type, 0);
		}
	}
	return result;
}

bool Should(
		const Full &data,
		Source source,
		not_null<DocumentData*> document) {
	if (document->sticker() || document->isGifv()) {
		return true;
	} else if (document->isVoiceMessage()
		|| document->isVideoMessage()
		|| document->isSong()
		|| document->isVideoFile()) {
		return false;
	}
	return data.shouldDownload(source, Type::File, document->size);
}

bool Should(
		const Full &data,
		not_null<PeerData*> peer,
		not_null<DocumentData*> document) {
	if (document->sticker()) {
		return true;
	}
	const auto override = data.peerOverride(peer->id);
	if (override == Override::ForceDeny) {
		return false;
	} else if (document->isGifv()) {
		return true;
	} else if (override == Override::ForceAllow) {
		if (document->isVoiceMessage()
			|| document->isVideoMessage()
			|| document->isSong()
			|| document->isVideoFile()) {
			return false;
		}
		return ForceAllowed(
			data,
			SourceFromPeer(peer),
			Type::File,
			document->size);
	}
	return Should(data, SourceFromPeer(peer), document);
}

bool Should(
		const Full &data,
		not_null<DocumentData*> document) {
	if (document->sticker()) {
		return true;
	}
	return Should(data, Source::User, document)
		|| Should(data, Source::Group, document)
		|| Should(data, Source::Channel, document);
}

bool Should(
		const Full &data,
		not_null<PeerData*> peer,
		not_null<PhotoData*> photo) {
	const auto override = data.peerOverride(peer->id);
	if (override == Override::ForceDeny) {
		return false;
	} else if (override == Override::ForceAllow) {
		return ForceAllowed(
			data,
			SourceFromPeer(peer),
			Type::Photo,
			photo->imageByteSize(PhotoSize::Large));
	}
	return data.shouldDownload(
		SourceFromPeer(peer),
		Type::Photo,
		photo->imageByteSize(PhotoSize::Large));
}

bool ShouldAutoPlay(
		const Full &data,
		not_null<PeerData*> peer,
		not_null<DocumentData*> document) {
	if (document->sticker()) {
		return true;
	}
	const auto override = data.peerOverride(peer->id);
	if (override == Override::ForceDeny) {
		return false;
	} else if (override == Override::ForceAllow) {
		return ForceAllowed(
			data,
			SourceFromPeer(peer),
			AutoPlayTypeFromDocument(document),
			document->size);
	}
	return data.shouldDownload(
		SourceFromPeer(peer),
		AutoPlayTypeFromDocument(document),
		document->size);
}

bool ShouldAutoPlay(
		const Full &data,
		not_null<PeerData*> peer,
		not_null<PhotoData*> photo) {
	if (!photo->hasVideo()) {
		return false;
	}
	const auto override = data.peerOverride(peer->id);
	if (override == Override::ForceDeny) {
		return false;
	}
	const auto source = SourceFromPeer(peer);
	const auto size = photo->videoByteSize(PhotoSize::Large);
	if (override == Override::ForceAllow) {
		return ForceAllowed(data, source, Type::AutoPlayGIF, size)
			|| ForceAllowed(data, source, Type::AutoPlayVideo, size)
			|| ForceAllowed(data, source, Type::AutoPlayVideoMessage, size);
	}
	return data.shouldDownload(source, Type::AutoPlayGIF, size)
		|| data.shouldDownload(source, Type::AutoPlayVideo, size)
		|| data.shouldDownload(source, Type::AutoPlayVideoMessage, size);
}

Full WithDisabledAutoPlay(const Full &data) {
	auto result = data;
	for (const auto source : enums_view<Source>(kSourcesCount)) {
		for (const auto type : kAutoPlayTypes) {
			result.setBytesLimit(source, type, 0);
		}
	}
	auto toClear = std::vector<PeerId>();
	data.enumeratePeerOverrides([&](PeerId id, Override value) {
		if (value == Override::ForceAllow) {
			toClear.push_back(id);
		}
	});
	for (const auto id : toClear) {
		result.setPeerOverride(id, Override::Default);
	}
	return result;
}

} // namespace AutoDownload
} // namespace Data
