/*
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 "base/binary_guard.h"
#include "base/weak_ptr.h"

#include <QtNetwork/QNetworkReply>

namespace Data {
struct FileOrigin;
} // namespace Data

namespace Main {
class Session;
} // namespace Main

namespace Storage {
namespace Cache {
struct Key;
} // namespace Cache

// 10 MB max file could be hold in memory
// This value is used in local cache database settings!
constexpr auto kMaxFileInMemory = 10 * 1024 * 1024;

// 2 MB stickers hold in memory, auto loaded and displayed inline
constexpr auto kMaxStickerBytesSize = 2 * 1024 * 1024;

// 10 MB GIF and mp4 animations held in memory while playing
constexpr auto kMaxWallPaperInMemory = kMaxFileInMemory;
constexpr auto kMaxAnimationInMemory = kMaxFileInMemory;

// 4096x4096 is max area.
constexpr auto kMaxWallPaperDimension = 4096;

} // namespace Storage

struct StorageImageSaved {
	StorageImageSaved() = default;
	explicit StorageImageSaved(const QByteArray &data) : data(data) {
	}

	QByteArray data;

};

class FileLoader : public base::has_weak_ptr {
public:
	enum class FailureReason {
		NoFailure,
		FileWriteFailure,
		OtherFailure,
	};

	struct Error {
		FailureReason failureReason = FailureReason::NoFailure;
		bool started = false;
	};

	FileLoader(
		not_null<Main::Session*> session,
		const QString &toFile,
		int64 loadSize,
		int64 fullSize,
		LocationType locationType,
		LoadToCacheSetting toCache,
		LoadFromCloudSetting fromCloud,
		bool autoLoading,
		uint8 cacheTag);
	virtual ~FileLoader();

	[[nodiscard]] Main::Session &session() const;

	[[nodiscard]] bool finished() const {
		return _finished;
	}
	void finishWithBytes(const QByteArray &data);
	[[nodiscard]] bool cancelled() const {
		return _cancelled;
	}
	[[nodiscard]] const QByteArray &bytes() const {
		return _data;
	}
	[[nodiscard]] virtual uint64 objId() const {
		return 0;
	}
	[[nodiscard]] QImage imageData(int progressiveSizeLimit = 0) const;
	[[nodiscard]] QString fileName() const {
		return _filename;
	}
	// Used in MainWidget::documentLoadFailed.
	[[nodiscard]] virtual Data::FileOrigin fileOrigin() const;
	[[nodiscard]] float64 currentProgress() const;
	[[nodiscard]] virtual int64 currentOffset() const;
	[[nodiscard]] int64 fullSize() const {
		return _fullSize;
	}
	[[nodiscard]] int64 loadSize() const {
		return _loadSize;
	}

	bool setFileName(const QString &filename); // set filename for loaders to cache
	void permitLoadFromCloud();
	void increaseLoadSize(int64 size, bool autoLoading);

	void start();
	void cancel();

	[[nodiscard]] bool loadingLocal() const {
		return (_localStatus == LocalStatus::Loading);
	}
	[[nodiscard]] bool autoLoading() const {
		return _autoLoading;
	}

	void localLoaded(
		const StorageImageSaved &result,
		const QByteArray &imageFormat,
		const QImage &imageData);

	[[nodiscard]] rpl::producer<rpl::empty_value, Error> updates() const {
		return _updates.events();
	}

	[[nodiscard]] rpl::lifetime &lifetime() {
		return _lifetime;
	}

protected:
	enum class LocalStatus {
		NotTried,
		NotFound,
		Loading,
		Loaded,
	};

	void readImage(int progressiveSizeLimit) const;

	bool checkForOpen();
	bool tryLoadLocal();
	void loadLocal(const Storage::Cache::Key &key);
	virtual Storage::Cache::Key cacheKey() const = 0;
	virtual std::optional<MediaKey> fileLocationKey() const = 0;
	virtual void cancelHook() = 0;
	virtual void startLoading() = 0;
	virtual void startLoadingWithPartial(const QByteArray &data) {
		startLoading();
	}

	void cancel(FailureReason failed);

	void notifyAboutProgress();

	bool writeResultPart(int64 offset, bytes::const_span buffer);
	bool finalizeResult();
	[[nodiscard]] QByteArray readLoadedPartBack(int64 offset, int size);

	const not_null<Main::Session*> _session;

	bool _autoLoading = false;
	uint8 _cacheTag = 0;
	bool _finished = false;
	bool _cancelled = false;
	mutable LocalStatus _localStatus = LocalStatus::NotTried;

	QString _filename;
	QFile _file;
	bool _fileIsOpen = false;

	LoadToCacheSetting _toCache;
	LoadFromCloudSetting _fromCloud;

	QByteArray _data;

	int64 _loadSize = 0;
	int64 _fullSize = 0;
	int64 _skippedBytes = 0;
	LocationType _locationType = LocationType();

	base::binary_guard _localLoading;
	mutable QByteArray _imageFormat;
	mutable QImage _imageData;

	rpl::lifetime _lifetime;
	rpl::event_stream<rpl::empty_value, Error> _updates;

};

[[nodiscard]] std::unique_ptr<FileLoader> CreateFileLoader(
	not_null<Main::Session*> session,
	const DownloadLocation &location,
	Data::FileOrigin origin,
	const QString &toFile,
	int64 loadSize,
	int64 fullSize,
	LocationType locationType,
	LoadToCacheSetting toCache,
	LoadFromCloudSetting fromCloud,
	bool autoLoading,
	uint8 cacheTag);
