/*
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/flags.h"
#include "base/binary_guard.h"
#include "data/data_types.h"
#include "ui/image/image.h"

class mtpFileLoader;

namespace Images {
class Source;
} // namespace Images

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

namespace Media {
namespace Streaming {
class Loader;
} // namespace Streaming
} // namespace Media

namespace Data {
class Session;
} // namespace Data

namespace Main {
class Session;
} // namespace Main

inline uint64 mediaMix32To64(int32 a, int32 b) {
	return (uint64(*reinterpret_cast<uint32*>(&a)) << 32)
		| uint64(*reinterpret_cast<uint32*>(&b));
}

// version field removed from document.
inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id) {
	return MediaKey(mediaMix32To64(type, dc), id);
}

struct DocumentAdditionalData {
	virtual ~DocumentAdditionalData() = default;

};

struct StickerData : public DocumentAdditionalData {
	Data::FileOrigin setOrigin() const;

	std::unique_ptr<Image> image;
	bool animated = false;
	QString alt;
	MTPInputStickerSet set = MTP_inputStickerSetEmpty();
	StorageImageLocation loc; // doc thumb location
};

struct SongData : public DocumentAdditionalData {
	int32 duration = 0;
	QString title, performer;

};

struct VoiceData : public DocumentAdditionalData {
	~VoiceData();

	int duration = 0;
	VoiceWaveform waveform;
	char wavemax = 0;
};

bool fileIsImage(const QString &name, const QString &mime);

namespace Serialize {
class Document;
} // namespace Serialize;

class DocumentData {
public:
	DocumentData(not_null<Data::Session*> owner, DocumentId id);

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

	void setattributes(
		const QVector<MTPDocumentAttribute> &attributes);

	void automaticLoad(
		Data::FileOrigin origin,
		const HistoryItem *item);
	void automaticLoadSettingsChanged();

	enum class FilePathResolve {
		Cached,
		Checked,
		SaveFromData,
		SaveFromDataSilent,
	};
	[[nodiscard]] bool loaded(
		FilePathResolve resolve = FilePathResolve::Cached) const;
	[[nodiscard]] bool loading() const;
	[[nodiscard]] QString loadingFilePath() const;
	[[nodiscard]] bool displayLoading() const;
	void save(
		Data::FileOrigin origin,
		const QString &toFile,
		LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal,
		bool autoLoading = false);
	void cancel();
	[[nodiscard]] bool cancelled() const;
	[[nodiscard]] float64 progress() const;
	[[nodiscard]] int loadOffset() const;
	[[nodiscard]] bool uploading() const;
	[[nodiscard]] bool loadedInMediaCache() const;
	void setLoadedInMediaCache(bool loaded);

	void setWaitingForAlbum();
	[[nodiscard]] bool waitingForAlbum() const;

	[[nodiscard]] QByteArray data() const;
	[[nodiscard]] const FileLocation &location(bool check = false) const;
	void setLocation(const FileLocation &loc);

	[[nodiscard]] QString filepath(
		FilePathResolve resolve = FilePathResolve::Cached) const;

	[[nodiscard]] bool saveToCache() const;

	void unload();
	[[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin);

	[[nodiscard]] StickerData *sticker() const;
	void checkStickerLarge();
	void checkStickerSmall();
	[[nodiscard]] Image *getStickerSmall();
	[[nodiscard]] Image *getStickerLarge();
	[[nodiscard]] Data::FileOrigin stickerSetOrigin() const;
	[[nodiscard]] Data::FileOrigin stickerOrGifOrigin() const;
	[[nodiscard]] bool isStickerSetInstalled() const;
	[[nodiscard]] SongData *song();
	[[nodiscard]] const SongData *song() const;
	[[nodiscard]] VoiceData *voice();
	[[nodiscard]] const VoiceData *voice() const;

	[[nodiscard]] bool isVoiceMessage() const;
	[[nodiscard]] bool isVideoMessage() const;
	[[nodiscard]] bool isSong() const;
	[[nodiscard]] bool isAudioFile() const;
	[[nodiscard]] bool isVideoFile() const;
	[[nodiscard]] bool isAnimation() const;
	[[nodiscard]] bool isGifv() const;
	[[nodiscard]] bool isTheme() const;
	[[nodiscard]] bool isSharedMediaMusic() const;
	[[nodiscard]] TimeId getDuration() const;
	[[nodiscard]] bool isImage() const;
	void recountIsImage();
	[[nodiscard]] bool supportsStreaming() const;
	void setNotSupportsStreaming();
	void setData(const QByteArray &data) {
		_data = data;
	}
	void setDataAndCache(const QByteArray &data);
	bool checkWallPaperProperties();
	[[nodiscard]] bool isWallPaper() const;
	[[nodiscard]] bool isPatternWallPaper() const;

	[[nodiscard]] bool hasThumbnail() const;
	void loadThumbnail(Data::FileOrigin origin);
	[[nodiscard]] Image *thumbnailInline() const;
	[[nodiscard]] Image *thumbnail() const;
	void updateThumbnails(
		ImagePtr thumbnailInline,
		ImagePtr thumbnail);

	[[nodiscard]] Image *goodThumbnail() const;
	[[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const;
	void setGoodThumbnailOnUpload(QImage &&image, QByteArray &&bytes);
	void refreshGoodThumbnail();
	void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source);

	[[nodiscard]] auto bigFileBaseCacheKey() const
	-> std::optional<Storage::Cache::Key>;

	void setRemoteLocation(
		int32 dc,
		uint64 access,
		const QByteArray &fileReference);
	void setContentUrl(const QString &url);
	void setWebLocation(const WebFileLocation &location);
	[[nodiscard]] bool hasRemoteLocation() const;
	[[nodiscard]] bool hasWebLocation() const;
	[[nodiscard]] bool isNull() const;
	[[nodiscard]] MTPInputDocument mtpInput() const;
	[[nodiscard]] QByteArray fileReference() const;
	void refreshFileReference(const QByteArray &value);
	void refreshStickerThumbFileReference();

	// When we have some client-side generated document
	// (for example for displaying an external inline bot result)
	// and it has downloaded data, we can collect that data from it
	// to (this) received from the server "same" document.
	void collectLocalData(not_null<DocumentData*> local);

	[[nodiscard]] QString filename() const;
	[[nodiscard]] QString mimeString() const;
	[[nodiscard]] bool hasMimeType(QLatin1String mime) const;
	void setMimeString(const QString &mime);

	[[nodiscard]] MediaKey mediaKey() const;
	[[nodiscard]] Storage::Cache::Key cacheKey() const;
	[[nodiscard]] uint8 cacheTag() const;

	[[nodiscard]] static QString ComposeNameString(
		const QString &filename,
		const QString &songTitle,
		const QString &songPerformer);
	[[nodiscard]] QString composeNameString() const;

	[[nodiscard]] bool canBePlayed() const;
	[[nodiscard]] bool canBeStreamed() const;
	[[nodiscard]] auto createStreamingLoader(
		Data::FileOrigin origin,
		bool forceRemoteLoader) const
	-> std::unique_ptr<Media::Streaming::Loader>;

	void setInappPlaybackFailed();
	[[nodiscard]] bool inappPlaybackFailed() const;

	~DocumentData();

	DocumentId id = 0;
	DocumentType type = FileDocument;
	QSize dimensions;
	int32 date = 0;
	int32 size = 0;

	FileStatus status = FileReady;

	std::unique_ptr<Data::UploadState> uploadingData;

private:
	enum class Flag : uchar {
		StreamingMaybeYes = 0x01,
		StreamingMaybeNo = 0x02,
		StreamingPlaybackFailed = 0x04,
		ImageType = 0x08,
		DownloadCancelled = 0x10,
		LoadedInMediaCache = 0x20,
	};
	using Flags = base::flags<Flag>;
	friend constexpr bool is_flag_type(Flag) { return true; };

	static constexpr Flags kStreamingSupportedMask = Flags()
		| Flag::StreamingMaybeYes
		| Flag::StreamingMaybeNo;
	static constexpr Flags kStreamingSupportedUnknown = Flags()
		| Flag::StreamingMaybeYes
		| Flag::StreamingMaybeNo;
	static constexpr Flags kStreamingSupportedMaybeYes = Flags()
		| Flag::StreamingMaybeYes;
	static constexpr Flags kStreamingSupportedMaybeNo = Flags()
		| Flag::StreamingMaybeNo;
	static constexpr Flags kStreamingSupportedNo = Flags();

	friend class Serialize::Document;

	LocationType locationType() const;
	void validateLottieSticker();
	void validateGoodThumbnail();
	void setMaybeSupportsStreaming(bool supports);
	void setLoadedInMediaCacheLocation();

	void destroyLoader() const;

	[[nodiscard]] bool useStreamingLoader() const;
	[[nodiscard]] bool thumbnailEnoughForSticker() const;

	// Two types of location: from MTProto by dc+access or from web by url
	int32 _dc = 0;
	uint64 _access = 0;
	QByteArray _fileReference;
	QString _url;
	QString _filename;
	QString _mimeString;
	WebFileLocation _urlLocation;

	ImagePtr _thumbnailInline;
	ImagePtr _thumbnail;
	std::unique_ptr<Image> _goodThumbnail;
	Data::ReplyPreview _replyPreview;

	not_null<Data::Session*> _owner;

	FileLocation _location;
	QByteArray _data;
	std::unique_ptr<DocumentAdditionalData> _additional;
	int32 _duration = -1;
	mutable Flags _flags = kStreamingSupportedUnknown;
	mutable std::unique_ptr<FileLoader> _loader;

};

VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);

class DocumentClickHandler : public FileClickHandler {
public:
	DocumentClickHandler(
		not_null<DocumentData*> document,
		FullMsgId context = FullMsgId())
	: FileClickHandler(context)
	, _document(document) {
	}
	not_null<DocumentData*> document() const {
		return _document;
	}

private:
	not_null<DocumentData*> _document;

};

class DocumentSaveClickHandler : public DocumentClickHandler {
public:
	enum class Mode {
		ToCacheOrFile,
		ToFile,
		ToNewFile,
	};
	using DocumentClickHandler::DocumentClickHandler;
	static void Save(
		Data::FileOrigin origin,
		not_null<DocumentData*> document,
		Mode mode = Mode::ToCacheOrFile);

protected:
	void onClickImpl() const override;

};

class DocumentOpenClickHandler : public DocumentClickHandler {
public:
	using DocumentClickHandler::DocumentClickHandler;
	static void Open(
		Data::FileOrigin origin,
		not_null<DocumentData*> document,
		HistoryItem *context);

protected:
	void onClickImpl() const override;

};

class DocumentCancelClickHandler : public DocumentClickHandler {
public:
	using DocumentClickHandler::DocumentClickHandler;

protected:
	void onClickImpl() const override;

};

class DocumentOpenWithClickHandler : public DocumentClickHandler {
public:
	using DocumentClickHandler::DocumentClickHandler;
	static void Open(
		Data::FileOrigin origin,
		not_null<DocumentData*> document);

protected:
	void onClickImpl() const override;

};

class VoiceSeekClickHandler : public DocumentOpenClickHandler {
public:
	using DocumentOpenClickHandler::DocumentOpenClickHandler;

protected:
	void onClickImpl() const override {
	}

};

class DocumentWrappedClickHandler : public DocumentClickHandler {
public:
	DocumentWrappedClickHandler(
		ClickHandlerPtr wrapped,
		not_null<DocumentData*> document,
		FullMsgId context = FullMsgId())
	: DocumentClickHandler(document, context)
	, _wrapped(wrapped) {
	}

protected:
	void onClickImpl() const override {
		_wrapped->onClick({ Qt::LeftButton });
	}

private:
	ClickHandlerPtr _wrapped;

};

QString FileNameForSave(
	const QString &title,
	const QString &filter,
	const QString &prefix,
	QString name,
	bool savingAs,
	const QDir &dir = QDir());

namespace Data {

QString FileExtension(const QString &filepath);
bool IsValidMediaFile(const QString &filepath);
bool IsExecutableName(const QString &filepath);
base::binary_guard ReadImageAsync(
	not_null<DocumentData*> document,
	FnMut<QImage(QImage)> postprocess,
	FnMut<void(QImage&&)> done);

} // namespace Data
