/*
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 "history/view/media/history_view_sticker.h"

#include "boxes/sticker_set_box.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_media_common.h"
#include "ui/image/image.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/emoji_config.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/click_handler_types.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "window/window_session_controller.h" // isGifPausedAtLeastFor.
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "lottie/lottie_single_player.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h"
#include "styles/style_chat.h"

namespace HistoryView {
namespace {

constexpr auto kMaxSizeFixed = 512;
constexpr auto kMaxEmojiSizeFixed = 256;
constexpr auto kPremiumMultiplier = (1 + 0.245 * 2);
constexpr auto kEmojiMultiplier = 3;

[[nodiscard]] QImage CacheDiceImage(
		const QString &emoji,
		int index,
		const QImage &image) {
	static auto Cache = base::flat_map<std::pair<QString, int>, QImage>();
	const auto key = std::make_pair(emoji, index);
	const auto i = Cache.find(key);
	if (i != end(Cache) && i->second.size() == image.size()) {
		return i->second;
	}
	Cache[key] = image;
	return image;
}

} // namespace

Sticker::Sticker(
	not_null<Element*> parent,
	not_null<DocumentData*> data,
	bool skipPremiumEffect,
	Element *replacing,
	const Lottie::ColorReplacements *replacements)
: _parent(parent)
, _data(data)
, _replacements(replacements)
, _cachingTag(ChatHelpers::StickerLottieSize::MessageHistory)
, _lottieOncePlayed(false)
, _premiumEffectPlayed(false)
, _nextLastDiceFrame(false)
, _skipPremiumEffect(skipPremiumEffect) {
	if ((_dataMedia = _data->activeMediaView())) {
		dataMediaCreated();
	} else {
		_data->loadThumbnail(parent->data()->fullId());
		if (hasPremiumEffect()) {
			_data->loadVideoThumbnail(parent->data()->fullId());
		}
	}
	if (const auto media = replacing ? replacing->media() : nullptr) {
		_lottie = media->stickerTakeLottie(_data, _replacements);
		if (_lottie) {
			//_externalInfo = media->externalLottieInfo();
			if (hasPremiumEffect() && !_premiumEffectPlayed) {
				_premiumEffectPlayed = true;
				_parent->delegate()->elementStartPremium(_parent, replacing);
			}
			lottieCreated();
		}
	}
}

Sticker::~Sticker() {
	if (_lottie || _dataMedia) {
		if (_lottie) {
			unloadLottie();
		}
		if (_dataMedia) {
			_data->owner().keepAlive(base::take(_dataMedia));
			_parent->checkHeavyPart();
		}
	}
}

bool Sticker::hasPremiumEffect() const {
	return !_skipPremiumEffect && _data->isPremiumSticker();
}

bool Sticker::customEmojiPart() const {
	return (_cachingTag != ChatHelpers::StickerLottieSize::MessageHistory);
}

bool Sticker::isEmojiSticker() const {
	return (_parent->data()->media() == nullptr);
}

void Sticker::initSize() {
	if (isEmojiSticker() || _diceIndex >= 0) {
		const auto &session = _data->owner().session();
		if (session.giftBoxStickersPacks().isGiftSticker(_data)) {
			_size = st::msgServiceGiftBoxStickerSize;
		} else {
			_size = EmojiSize();
		}
		if (_diceIndex > 0) {
			[[maybe_unused]] bool result = readyToDrawLottie();
		}
	} else {
		_size = Size(_data);
	}
}

QSize Sticker::countOptimalSize() {
	if (_size.isEmpty()) {
		initSize();
	}
	return DownscaledSize(_size, Size());
}

bool Sticker::readyToDrawLottie() {
	if (!_lastDiceFrame.isNull()) {
		return true;
	}
	const auto sticker = _data->sticker();
	if (!sticker) {
		return false;
	}

	ensureDataMediaCreated();
	_dataMedia->checkStickerLarge();
	const auto loaded = _dataMedia->loaded();
	const auto waitingForPremium = hasPremiumEffect()
		&& _dataMedia->videoThumbnailContent().isEmpty();
	if (sticker->isLottie() && !_lottie && loaded && !waitingForPremium) {
		setupLottie();
	}
	return (_lottie && _lottie->ready());
}

QSize Sticker::Size() {
	const auto side = std::min(st::maxStickerSize, kMaxSizeFixed);
	return { side, side };
}

QSize Sticker::Size(not_null<DocumentData*> document) {
	return DownscaledSize(document->dimensions, Size());
}

QSize Sticker::PremiumEffectSize(not_null<DocumentData*> document) {
	return Size(document) * kPremiumMultiplier;
}

QSize Sticker::UsualPremiumEffectSize() {
	return DownscaledSize({ kMaxSizeFixed, kMaxSizeFixed }, Size())
		* kPremiumMultiplier;
}

QSize Sticker::EmojiEffectSize() {
	return EmojiSize() * kEmojiMultiplier;
}

QSize Sticker::EmojiSize() {
	const auto side = std::min(st::maxAnimatedEmojiSize, kMaxEmojiSizeFixed);
	return { side, side };
}

void Sticker::draw(
		Painter &p,
		const PaintContext &context,
		const QRect &r) {
	if (!customEmojiPart() && isEmojiSticker()) {
		_parent->clearCustomEmojiRepaint();
	}

	ensureDataMediaCreated();
	if (readyToDrawLottie()) {
		paintLottie(p, context, r);
	} else if (!_data->sticker()
		|| (_data->sticker()->isLottie() && _replacements)
		|| !paintPixmap(p, context, r)) {
		paintPath(p, context, r);
	}
}

ClickHandlerPtr Sticker::link() {
	return _link;
}

DocumentData *Sticker::document() {
	return _data;
}

void Sticker::stickerClearLoopPlayed() {
	_lottieOncePlayed = false;
	_premiumEffectPlayed = false;
}

void Sticker::paintLottie(
		Painter &p,
		const PaintContext &context,
		const QRect &r) {
	auto request = Lottie::FrameRequest();
	request.box = _size * cIntRetinaFactor();
	if (context.selected() && !_nextLastDiceFrame) {
		request.colored = context.st->msgStickerOverlay()->c;
	}
	request.mirrorHorizontal = mirrorHorizontal();
	const auto frame = _lottie
		? _lottie->frameInfo(request)
		: Lottie::Animation::FrameInfo();
	if (_nextLastDiceFrame) {
		_nextLastDiceFrame = false;
		_lastDiceFrame = CacheDiceImage(_diceEmoji, _diceIndex, frame.image);
	}
	const auto &image = _lastDiceFrame.isNull()
		? frame.image
		: _lastDiceFrame;
	const auto prepared = (!_lastDiceFrame.isNull() && context.selected())
		? Images::Colored(
			base::duplicate(image),
			context.st->msgStickerOverlay()->c)
		: image;
	const auto size = prepared.size() / cIntRetinaFactor();
	p.drawImage(
		QRect(
			QPoint(
				r.x() + (r.width() - size.width()) / 2,
				r.y() + (r.height() - size.height()) / 2),
			size),
		prepared);
	if (!_lastDiceFrame.isNull()) {
		return;
	}

	const auto count = _lottie->information().framesCount;
	_frameIndex = frame.index;
	_framesCount = count;
	const auto paused = /*(_externalInfo.frame >= 0)
		? (_frameIndex % _externalInfo.count >= _externalInfo.frame)
		: */_parent->delegate()->elementIsGifPaused();
	_nextLastDiceFrame = !paused
		&& (_diceIndex > 0)
		&& (_frameIndex + 2 == count);
	const auto playOnce = (_diceIndex > 0)
		? true
		: (_diceIndex == 0)
		? false
		: ((!customEmojiPart() && isEmojiSticker())
			|| !Core::App().settings().loopAnimatedStickers());
	const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd();
	const auto switchToNext = /*(_externalInfo.frame >= 0)
		|| */!playOnce
		|| (!lastDiceFrame && (_frameIndex != 0 || !_lottieOncePlayed));
	if (!paused
		&& switchToNext
		&& _lottie->markFrameShown()
		&& playOnce
		&& !_lottieOncePlayed) {
		_lottieOncePlayed = true;
		_parent->delegate()->elementStartStickerLoop(_parent);
	}
	checkPremiumEffectStart();
}

bool Sticker::paintPixmap(
		Painter &p,
		const PaintContext &context,
		const QRect &r) {
	const auto pixmap = paintedPixmap(context);
	if (pixmap.isNull()) {
		return false;
	}
	const auto position = QPoint(
		r.x() + (r.width() - _size.width()) / 2,
		r.y() + (r.height() - _size.height()) / 2);
	const auto size = pixmap.size() / pixmap.devicePixelRatio();
	const auto mirror = mirrorHorizontal();
	if (mirror) {
		p.save();
		const auto middle = QPointF(
			position.x() + size.width() / 2.,
			position.y() + size.height() / 2.);
		p.translate(middle);
		p.scale(-1., 1.);
		p.translate(-middle);
	}
	p.drawPixmap(position, pixmap);
	if (mirror) {
		p.restore();
	}
	return true;
}

void Sticker::paintPath(
		Painter &p,
		const PaintContext &context,
		const QRect &r) {
	const auto pathGradient = _parent->delegate()->elementPathShiftGradient();
	if (context.selected()) {
		pathGradient->overrideColors(
			context.st->msgServiceBgSelected(),
			context.st->msgServiceBg());
	} else {
		pathGradient->clearOverridenColors();
	}
	p.setBrush(context.imageStyle()->msgServiceBg);
	ChatHelpers::PaintStickerThumbnailPath(
		p,
		_dataMedia.get(),
		r,
		pathGradient,
		mirrorHorizontal());
}

QPixmap Sticker::paintedPixmap(const PaintContext &context) const {
	const auto colored = context.selected()
		? &context.st->msgStickerOverlay()
		: nullptr;
	const auto good = _dataMedia->goodThumbnail();
	if (const auto image = _dataMedia->getStickerLarge()) {
		return image->pix(_size, { .colored = colored });
	//
	// Inline thumbnails can't have alpha channel.
	//
	//} else if (const auto blurred = _data->thumbnailInline()) {
	//	return blurred->pix(
	//		_size,
	//		{ .colored = colored, .options = Images::Option::Blur });
	} else if (good) {
		return good->pix(_size, { .colored = colored });
	} else if (const auto thumbnail = _dataMedia->thumbnail()) {
		return thumbnail->pix(
			_size,
			{ .colored = colored, .options = Images::Option::Blur });
	}
	return QPixmap();
}

bool Sticker::mirrorHorizontal() const {
	if (!hasPremiumEffect()) {
		return false;
	}
	const auto rightAligned = _parent->hasOutLayout()
		&& !_parent->delegate()->elementIsChatWide();
	return !rightAligned;
}

ClickHandlerPtr Sticker::ShowSetHandler(not_null<DocumentData*> document) {
	return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
		const auto my = context.other.value<ClickHandlerContext>();
		if (const auto window = my.sessionWindow.get()) {
			StickerSetBox::Show(window, document);
		}
	});
}

void Sticker::refreshLink() {
	if (_link) {
		return;
	}
	const auto sticker = _data->sticker();
	if (isEmojiSticker()) {
		const auto weak = base::make_weak(this);
		_link = std::make_shared<LambdaClickHandler>([weak] {
			if (const auto that = weak.get()) {
				that->emojiStickerClicked();
			}
		});
	} else if (sticker && sticker->set) {
		if (hasPremiumEffect()) {
			const auto weak = base::make_weak(this);
			_link = std::make_shared<LambdaClickHandler>([weak] {
				if (const auto that = weak.get()) {
					that->premiumStickerClicked();
				}
			});
		} else {
			_link = ShowSetHandler(_data);
		}
	} else if (sticker
		&& (_data->dimensions.width() > kStickerSideSize
			|| _data->dimensions.height() > kStickerSideSize)
		&& !_parent->data()->isSending()
		&& !_parent->data()->hasFailed()) {
		// In case we have a .webp file that is displayed as a sticker, but
		// that doesn't fit in 512x512, we assume it may be a regular large
		// .webp image and we allow to open it in media viewer.
		_link = std::make_shared<DocumentOpenClickHandler>(
			_data,
			crl::guard(this, [=](FullMsgId id) {
				_parent->delegate()->elementOpenDocument(_data, id);
			}),
			_parent->data()->fullId());
	}
}

void Sticker::emojiStickerClicked() {
	if (_lottie) {
		_parent->delegate()->elementStartInteraction(_parent);
	}
	_lottieOncePlayed = false;
	_parent->history()->owner().requestViewRepaint(_parent);
}

void Sticker::premiumStickerClicked() {
	_premiumEffectPlayed = false;
	_parent->history()->owner().requestViewRepaint(_parent);
}

void Sticker::ensureDataMediaCreated() const {
	if (_dataMedia) {
		return;
	}
	_dataMedia = _data->createMediaView();
	dataMediaCreated();
}

void Sticker::dataMediaCreated() const {
	Expects(_dataMedia != nullptr);

	_dataMedia->goodThumbnailWanted();
	if (_dataMedia->thumbnailPath().isEmpty()) {
		_dataMedia->thumbnailWanted(_parent->data()->fullId());
	}
	if (hasPremiumEffect()) {
		_data->loadVideoThumbnail(_parent->data()->fullId());
	}
	_parent->history()->owner().registerHeavyViewPart(_parent);
}

void Sticker::setDiceIndex(const QString &emoji, int index) {
	_diceEmoji = emoji;
	_diceIndex = index;
}

void Sticker::setCustomEmojiPart(
		int size,
		ChatHelpers::StickerLottieSize tag) {
	_size = { size, size };
	_cachingTag = tag;
}

void Sticker::setupLottie() {
	Expects(_dataMedia != nullptr);

	_lottie = ChatHelpers::LottiePlayerFromDocument(
		_dataMedia.get(),
		_replacements,
		_cachingTag,
		countOptimalSize() * style::DevicePixelRatio(),
		Lottie::Quality::High);
	checkPremiumEffectStart();
	lottieCreated();
}

void Sticker::checkPremiumEffectStart() {
	if (!_premiumEffectPlayed && hasPremiumEffect()) {
		_premiumEffectPlayed = true;
		_parent->delegate()->elementStartPremium(_parent, nullptr);
	}
}

void Sticker::lottieCreated() {
	Expects(_lottie != nullptr);

	_parent->history()->owner().registerHeavyViewPart(_parent);

	_lottie->updates(
	) | rpl::start_with_next([=](Lottie::Update update) {
		v::match(update.data, [&](const Lottie::Information &information) {
			_parent->customEmojiRepaint();
			//markFramesTillExternal();
		}, [&](const Lottie::DisplayFrameRequest &request) {
			_parent->customEmojiRepaint();
		});
	}, _lifetime);
}

bool Sticker::hasHeavyPart() const {
	return _lottie || _dataMedia;
}

void Sticker::unloadHeavyPart() {
	unloadLottie();
	_dataMedia = nullptr;
}

void Sticker::unloadLottie() {
	if (!_lottie) {
		return;
	}
	if (_diceIndex > 0 && _lastDiceFrame.isNull()) {
		_nextLastDiceFrame = false;
		_lottieOncePlayed = false;
	}
	_lottie = nullptr;
	if (hasPremiumEffect()) {
		_parent->delegate()->elementCancelPremium(_parent);
	}
	_parent->checkHeavyPart();
}

std::unique_ptr<Lottie::SinglePlayer> Sticker::stickerTakeLottie(
		not_null<DocumentData*> data,
		const Lottie::ColorReplacements *replacements) {
	return (data == _data && replacements == _replacements)
		? std::move(_lottie)
		: nullptr;
}

//void Sticker::externalLottieProgressing(bool external) {
//	_externalInfo = !external
//		? ExternalLottieInfo{}
//		: (_externalInfo.frame > 0)
//		? _externalInfo
//		: ExternalLottieInfo{ 0, 2 };
//}
//
//bool Sticker::externalLottieTill(ExternalLottieInfo info) {
//	if (_externalInfo.frame >= 0) {
//		_externalInfo = info;
//	}
//	return markFramesTillExternal();
//}
//
//ExternalLottieInfo Sticker::externalLottieInfo() const {
//	return _externalInfo;
//}
//
//bool Sticker::markFramesTillExternal() {
//	if (_externalInfo.frame < 0 || !_lottie) {
//		return true;
//	} else if (!_lottie->ready()) {
//		return false;
//	}
//	const auto till = _externalInfo.frame % _lottie->framesCount();
//	while (_lottie->frameIndex() < till) {
//		if (!_lottie->markFrameShown()) {
//			return false;
//		}
//	}
//	return true;
//}

} // namespace HistoryView
