/*
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
*/
#pragma once

#include "data/data_abstract_sparse_ids.h"
#include "data/data_messages.h"

namespace Storage {
struct SparseIdsListResult;
struct SparseIdsSliceUpdate;
} // namespace Storage

class SparseIdsSlice final : public AbstractSparseIds<base::flat_set<MsgId>> {
public:
	using Key = MsgId;
	using AbstractSparseIds<base::flat_set<MsgId>>::AbstractSparseIds;

};

using SparseUnsortedIdsSlice = AbstractSparseIds<std::vector<MsgId>>;

class SparseIdsMergedSlice {
public:
	using UniversalMsgId = MsgId;
	static constexpr MsgId kScheduledTopicId = ScheduledMaxMsgId;

	struct Key {
		Key(
			PeerId peerId,
			MsgId topicRootId,
			PeerId migratedPeerId,
			UniversalMsgId universalId)
		: peerId(peerId)
		, topicRootId(topicRootId)
		, migratedPeerId(topicRootId ? 0 : migratedPeerId)
		, universalId(universalId) {
		}

		friend inline constexpr bool operator==(
			const Key &,
			const Key &) = default;

		PeerId peerId = 0;
		MsgId topicRootId = 0;
		PeerId migratedPeerId = 0;
		UniversalMsgId universalId = 0;
	};

	SparseIdsMergedSlice(Key key);
	SparseIdsMergedSlice(
		Key key,
		SparseIdsSlice part,
		std::optional<SparseIdsSlice> migrated);
	SparseIdsMergedSlice(
		Key key,
		SparseUnsortedIdsSlice unsorted);

	std::optional<int> fullCount() const;
	std::optional<int> skippedBefore() const;
	std::optional<int> skippedAfter() const;
	std::optional<int> indexOf(FullMsgId fullId) const;
	int size() const;
	FullMsgId operator[](int index) const;
	std::optional<int> distance(const Key &a, const Key &b) const;
	std::optional<FullMsgId> nearest(UniversalMsgId id) const;

	using SimpleViewerFunction = rpl::producer<SparseIdsSlice>(
		PeerId peerId,
		MsgId topicRootId,
		SparseIdsSlice::Key simpleKey,
		int limitBefore,
		int limitAfter);
	static rpl::producer<SparseIdsMergedSlice> CreateViewer(
		SparseIdsMergedSlice::Key key,
		int limitBefore,
		int limitAfter,
		Fn<SimpleViewerFunction> simpleViewer);

private:
	static SparseIdsSlice::Key PartKey(const Key &key) {
		return (key.universalId < 0) ? 1 : key.universalId;
	}
	static SparseIdsSlice::Key MigratedKey(const Key &key) {
		return (key.universalId < 0)
			? (ServerMaxMsgId + key.universalId)
			: (key.universalId > 0) ? (ServerMaxMsgId - 1) : 0;
	}
	static std::optional<SparseIdsSlice> MigratedSlice(const Key &key) {
		return key.migratedPeerId
			? base::make_optional(SparseIdsSlice())
			: std::nullopt;
	}

	static bool IsFromSlice(PeerId peerId, FullMsgId fullId) {
		return (peerId == fullId.peer);
	}
	static FullMsgId ComputeId(PeerId peerId, MsgId msgId) {
		return FullMsgId(peerId, msgId);
	}
	static FullMsgId ComputeId(const Key &key) {
		return (key.universalId >= 0)
			? ComputeId(key.peerId, key.universalId)
			: ComputeId(key.migratedPeerId, ServerMaxMsgId + key.universalId);
	}
	static std::optional<int> Add(
			const std::optional<int> &a,
			const std::optional<int> &b) {
		return (a && b) ? base::make_optional(*a + *b) : std::nullopt;
	}

	bool isFromPart(FullMsgId fullId) const {
		return IsFromSlice(_key.peerId, fullId);
	}
	bool isFromMigrated(FullMsgId fullId) const {
		return _migrated
			? IsFromSlice(_key.migratedPeerId, fullId)
			: false;
	}
	int migratedSize() const {
		return isolatedInPart() ? 0 : _migrated->size();
	}
	bool isolatedInPart() const {
		return IsServerMsgId(_key.universalId)
			&& (!_migrated || _part.skippedBefore() != 0);
	}
	bool isolatedInMigrated() const {
		return IsServerMsgId(ServerMaxMsgId + _key.universalId)
			&& (_migrated->skippedAfter() != 0);
	}

	Key _key;
	SparseIdsSlice _part;
	std::optional<SparseIdsSlice> _migrated;
	std::optional<SparseUnsortedIdsSlice> _unsorted;

};

class SparseIdsSliceBuilder {
public:
	using Key = SparseIdsSlice::Key;

	SparseIdsSliceBuilder(Key key, int limitBefore, int limitAfter);

	bool applyInitial(const Storage::SparseIdsListResult &result);
	bool applyUpdate(const Storage::SparseIdsSliceUpdate &update);
	bool removeOne(MsgId messageId);
	bool removeAll();
	bool invalidateBottom();

	void checkInsufficient();
	struct AroundData {
		MsgId aroundId = 0;
		Data::LoadDirection direction = Data::LoadDirection::Around;

		inline bool operator<(const AroundData &other) const {
			return (aroundId < other.aroundId)
				|| ((aroundId == other.aroundId)
					&& (direction < other.direction));
		}
	};
	auto insufficientAround() const {
		return _insufficientAround.events();
	}

	SparseIdsSlice snapshot() const;

private:
	enum class RequestDirection {
		Before,
		After,
	};
	void requestMessages(RequestDirection direction);
	void requestMessagesCount();
	void fillSkippedAndSliceToLimits();
	void sliceToLimits();

	void mergeSliceData(
		std::optional<int> count,
		const base::flat_set<MsgId> &messageIds,
		std::optional<int> skippedBefore = std::nullopt,
		std::optional<int> skippedAfter = std::nullopt);

	Key _key;
	base::flat_set<MsgId> _ids;
	std::optional<int> _fullCount;
	std::optional<int> _skippedBefore;
	std::optional<int> _skippedAfter;
	int _limitBefore = 0;
	int _limitAfter = 0;

	rpl::event_stream<AroundData> _insufficientAround;

};
