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

#include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "lottie/lottie_single_player.h"
#include "ui/cached_round_corners.h"
#include "ui/chat/chat_style.h"
#include "ui/painter.h"
#include "styles/style_chat.h"

namespace HistoryView {
namespace {

constexpr auto kMaxForwardedBarLines = 4;

} // namespace

std::unique_ptr<StickerPlayer> UnwrappedMedia::Content::stickerTakePlayer(
		not_null<DocumentData*> data,
		const Lottie::ColorReplacements *replacements) {
	return nullptr;
}

QSize UnwrappedMedia::Content::countCurrentSize(int newWidth) {
	return countOptimalSize();
}

UnwrappedMedia::UnwrappedMedia(
	not_null<Element*> parent,
	std::unique_ptr<Content> content)
: Media(parent)
, _content(std::move(content)) {
}

QSize UnwrappedMedia::countOptimalSize() {
	_content->refreshLink();
	const auto optimal = _content->countOptimalSize();
	auto maxWidth = optimal.width();
	const auto minimal = st::emojiSize;
	auto minHeight = std::max(optimal.height(), minimal);
	if (_parent->media() == this) {
		const auto item = _parent->data();
		const auto via = item->Get<HistoryMessageVia>();
		const auto reply = _parent->displayedReply();
		const auto forwarded = getDisplayedForwardedInfo();
		if (forwarded) {
			forwarded->create(via);
		}
		maxWidth += additionalWidth(via, reply, forwarded);
		accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
		if (const auto size = _parent->rightActionSize()) {
			minHeight = std::max(
				minHeight,
				st::historyFastShareBottom + size->height());
		}
	}
	return { maxWidth, minHeight };
}

QSize UnwrappedMedia::countCurrentSize(int newWidth) {
	const auto item = _parent->data();
	accumulate_min(newWidth, maxWidth());
	_contentSize = _content->countCurrentSize(newWidth);
	auto newHeight = std::max(minHeight(), _contentSize.height());
	_additionalOnTop = false;
	if (_parent->media() != this) {
		return { newWidth, newHeight };
	}
	if (_parent->hasOutLayout()
		&& !_parent->delegate()->elementIsChatWide()) {
		// Add some height to isolated emoji for the timestamp info.
		const auto infoHeight = st::msgDateImgPadding.y() * 2
			+ st::msgDateFont->height;
		const auto minimal = std::min(
			st::largeEmojiSize + 2 * st::largeEmojiOutline,
			_contentSize.height());
		accumulate_max(newHeight, minimal + st::msgDateImgDelta + infoHeight);
	}
	accumulate_max(newWidth, _parent->reactionsOptimalWidth());
	_topAdded = 0;
	const auto via = item->Get<HistoryMessageVia>();
	const auto reply = _parent->displayedReply();
	const auto forwarded = getDisplayedForwardedInfo();
	if (via || reply || forwarded) {
		const auto paddings = 3 * st::msgReplyPadding.left();
		const auto additional = additionalWidth(via, reply, forwarded);
		const auto optimalw = maxWidth() - additional;
		const auto additionalMinWidth = std::min(additional, st::msgMinWidth / 2);
		_additionalOnTop = (optimalw + paddings + additionalMinWidth) > newWidth;
		const auto surrounding = surroundingInfo(via, reply, forwarded, additional - st::msgReplyPadding.left());
		if (_additionalOnTop) {
			_topAdded = surrounding.height + st::msgMargin.bottom();
			newHeight += _topAdded;
		} else {
			const auto infoHeight = st::msgDateImgPadding.y() * 2
				+ st::msgDateFont->height;
			const auto minimal = surrounding.height
				+ st::msgDateImgDelta
				+ infoHeight;
			newHeight = std::max(newHeight, minimal);
		}
		const auto availw = newWidth
			- (_additionalOnTop ? 0 : optimalw)
			- paddings;
		if (via) {
			via->resize(availw);
		}
		if (reply) {
			reply->resize(availw);
		}
	}
	return { newWidth, newHeight };
}

void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const {
	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
		return;
	}
	const auto rightAligned = context.outbg
		&& !_parent->delegate()->elementIsChatWide();
	const auto inWebPage = (_parent->media() != this);
	const auto item = _parent->data();
	auto usex = 0;
	auto usew = _contentSize.width();
	if (!inWebPage && rightAligned) {
		usex = width() - usew;
	}
	if (rtl()) {
		usex = width() - usex - usew;
	}

	const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
	const auto useh = rightAligned
		? std::max(
			_contentSize.height(),
			(height()
				- _topAdded
				- st::msgDateImgPadding.y() * 2
				- st::msgDateFont->height))
		: _contentSize.height();
	const auto inner = QRect(usex, usey, usew, useh);
	if (context.skipDrawingParts != PaintContext::SkipDrawingParts::Content) {
		_content->draw(p, context, inner);
	}

	if (!inWebPage && (context.skipDrawingParts
			!= PaintContext::SkipDrawingParts::Surrounding)) {
		const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
		const auto reply = inWebPage ? nullptr : _parent->displayedReply();
		const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
		drawSurrounding(p, inner, context, via, reply, forwarded);
	}
}

UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo(
		const HistoryMessageVia *via,
		const HistoryMessageReply *reply,
		const HistoryMessageForwarded *forwarded,
		int outerw) const {
	if (!via && !reply && !forwarded) {
		return {};
	}
	auto height = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
	const auto innerw = outerw - st::msgReplyPadding.left() - st::msgReplyPadding.right();
	auto forwardedHeightReal = forwarded
		? forwarded->text.countHeight(innerw)
		: 0;
	auto forwardedHeight = std::min(
		forwardedHeightReal,
		kMaxForwardedBarLines * st::msgServiceNameFont->height);
	const auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
	if (forwarded) {
		height += forwardedHeight;
	} else if (via) {
		height += st::msgServiceNameFont->height
			+ (reply ? st::msgReplyPadding.top() : 0);
	}
	if (reply) {
		height += st::msgReplyBarSize.height();
	}
	return { height, forwardedHeight, breakEverywhere };
}

void UnwrappedMedia::drawSurrounding(
		Painter &p,
		const QRect &inner,
		const PaintContext &context,
		const HistoryMessageVia *via,
		const HistoryMessageReply *reply,
		const HistoryMessageForwarded *forwarded) const {
	const auto st = context.st;
	const auto sti = context.imageStyle();
	const auto rightAligned = context.outbg
		&& !_parent->delegate()->elementIsChatWide();
	const auto rightActionSize = _parent->rightActionSize();
	const auto fullRight = calculateFullRight(inner);
	auto fullBottom = height();
	if (needInfoDisplay()) {
		_parent->drawInfo(
			p,
			context,
			fullRight,
			fullBottom,
			inner.x() * 2 + inner.width(),
			InfoDisplayType::Background);
	}
	auto replyRight = 0;
	auto rectw = _additionalOnTop
		? std::min(width() - st::msgReplyPadding.left(), additionalWidth(via, reply, forwarded))
		: (width() - inner.width() - st::msgReplyPadding.left());
	if (const auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) {
		auto recth = surrounding.height;
		int rectx = _additionalOnTop
			? (rightAligned ? (inner.x() + inner.width() - rectw) : 0)
			: (rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()));
		int recty = 0;
		if (rtl()) rectx = width() - rectx - rectw;

		Ui::FillRoundRect(p, rectx, recty, rectw, recth, sti->msgServiceBg, sti->msgServiceBgCorners);
		p.setPen(st->msgServiceFg());
		rectx += st::msgReplyPadding.left();
		rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right();
		if (forwarded) {
			p.setTextPalette(st->serviceTextPalette());
			forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxForwardedBarLines, style::al_left, 0, -1, 0, surrounding.forwardedBreakEverywhere);
			p.restoreTextPalette();

			const auto skip = std::min(
				forwarded->text.countHeight(rectw),
				kMaxForwardedBarLines * st::msgServiceNameFont->height);
			recty += skip;
		} else if (via) {
			p.setFont(st::msgDateFont);
			p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text);

			const auto skip = st::msgServiceNameFont->height
				+ (reply ? st::msgReplyPadding.top() : 0);
			recty += skip;
		}
		if (reply) {
			reply->paint(p, _parent, context, rectx, recty, rectw, false);
		}
		replyRight = rectx + rectw;
	}
	if (rightActionSize) {
		const auto position = calculateFastActionPosition(
			fullBottom,
			replyRight,
			fullRight,
			*rightActionSize);
		const auto outer = 2 * inner.x() + inner.width();
		_parent->drawRightAction(p, context, position.x(), position.y(), outer);
	}
}

PointState UnwrappedMedia::pointState(QPoint point) const {
	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
		return PointState::Outside;
	}

	const auto rightAligned = _parent->hasOutLayout()
		&& !_parent->delegate()->elementIsChatWide();
	const auto inWebPage = (_parent->media() != this);
	auto usex = 0;
	auto usew = _contentSize.width();
	if (!inWebPage && rightAligned) {
		usex = width() - usew;
	}
	if (rtl()) {
		usex = width() - usex - usew;
	}

	const auto datey = height() - st::msgDateImgPadding.y() * 2
		- st::msgDateFont->height;
	const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
	const auto useh = rightAligned
		? std::max(_contentSize.height(), datey)
		: _contentSize.height();
	const auto inner = QRect(usex, usey, usew, useh);

	// Rectangle of date bubble.
	if (point.x() < calculateFullRight(inner) && point.y() > datey) {
		return PointState::Inside;
	}

	return inner.contains(point) ? PointState::Inside : PointState::Outside;
}

TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
	auto result = TextState(_parent);
	if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
		return result;
	}

	const auto rightAligned = _parent->hasOutLayout()
		&& !_parent->delegate()->elementIsChatWide();
	const auto inWebPage = (_parent->media() != this);
	const auto item = _parent->data();
	auto usex = 0;
	auto usew = _contentSize.width();
	if (!inWebPage && rightAligned) {
		usex = width() - usew;
	}
	if (rtl()) {
		usex = width() - usex - usew;
	}

	const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
	const auto useh = rightAligned
		? std::max(
			_contentSize.height(),
			height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height)
		: _contentSize.height();
	const auto inner = QRect(usex, usey, usew, useh);

	if (_parent->media() == this) {
		const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
		const auto reply = inWebPage ? nullptr : _parent->displayedReply();
		const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
		auto replyRight = 0;
		auto rectw = _additionalOnTop
			? std::min(width() - st::msgReplyPadding.left(), additionalWidth(via, reply, forwarded))
			: (width() - inner.width() - st::msgReplyPadding.left());
		if (const auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) {
			auto recth = surrounding.height;
			int rectx = _additionalOnTop
				? (rightAligned ? (inner.width() + st::msgReplyPadding.left() - rectw) : 0)
				: (rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()));
			int recty = 0;
			if (rtl()) rectx = width() - rectx - rectw;

			if (forwarded) {
				if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + surrounding.forwardedHeight).contains(point)) {
					auto textRequest = request.forText();
					if (surrounding.forwardedBreakEverywhere) {
						textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere;
					}
					const auto innerw = rectw - st::msgReplyPadding.left() - st::msgReplyPadding.right();
					result = TextState(_parent, forwarded->text.getState(
						point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()),
						innerw,
						textRequest));
					result.symbol = 0;
					result.afterSymbol = false;
					if (surrounding.forwardedBreakEverywhere) {
						result.cursor = CursorState::Forwarded;
					} else {
						result.cursor = CursorState::None;
					}
					return result;
				}
				recty += surrounding.forwardedHeight;
				recth -= surrounding.forwardedHeight;
			} else if (via) {
				int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
				if (QRect(rectx, recty, rectw, viah).contains(point)) {
					result.link = via->link;
					return result;
				}
				int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
				recty += skip;
				recth -= skip;
			}
			if (reply) {
				if (QRect(rectx, recty, rectw, recth).contains(point)) {
					result.link = reply->replyToLink();
					return result;
				}
			}
			replyRight = rectx + rectw - st::msgReplyPadding.right();
		}
		const auto fullRight = calculateFullRight(inner);
		const auto rightActionSize = _parent->rightActionSize();
		auto fullBottom = height();
		const auto bottomInfoResult = _parent->bottomInfoTextState(
			fullRight,
			fullBottom,
			point,
			InfoDisplayType::Background);
		if (bottomInfoResult.link
			|| bottomInfoResult.cursor != CursorState::None) {
			return bottomInfoResult;
		}
		if (rightActionSize) {
			const auto position = calculateFastActionPosition(
				fullBottom,
				replyRight,
				fullRight,
				*rightActionSize);
			if (QRect(position.x(), position.y(), rightActionSize->width(), rightActionSize->height()).contains(point)) {
				result.link = _parent->rightActionLink();
				return result;
			}
		}
	}

	// Link of content can be nullptr (e.g. sticker without stickerpack).
	// So we have to process it to avoid overriding the previous result.
	if (_content->link() && inner.contains(point)) {
		result.link = _content->link();
		return result;
	}
	return result;
}

bool UnwrappedMedia::hasTextForCopy() const {
	return _content->hasTextForCopy();
}

QRect UnwrappedMedia::contentRectForReactions() const {
	const auto inWebPage = (_parent->media() != this);
	if (inWebPage) {
		return QRect(0, 0, width(), height());
	}
	const auto rightAligned = _parent->hasOutLayout()
		&& !_parent->delegate()->elementIsChatWide();
	auto usex = 0;
	auto usew = _contentSize.width();
	accumulate_max(usew, _parent->reactionsOptimalWidth());
	if (rightAligned) {
		usex = width() - usew;
	}
	if (rtl()) {
		usex = width() - usex - usew;
	}
	const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
	const auto useh = rightAligned
		? std::max(
			_contentSize.height(),
			height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height)
		: _contentSize.height();
	return QRect(usex, usey, usew, useh);
}

std::optional<int> UnwrappedMedia::reactionButtonCenterOverride() const {
	const auto fullRight = calculateFullRight(contentRectForReactions());
	const auto right = fullRight
		- _parent->infoWidth()
		- st::msgDateImgPadding.x() * 2
		- st::msgReplyPadding.left();
	return right - st::reactionCornerSize.width() / 2;
}

QPoint UnwrappedMedia::resolveCustomInfoRightBottom() const {
	const auto inner = contentRectForReactions();
	const auto fullBottom = inner.y() + inner.height();
	const auto fullRight = calculateFullRight(inner);
	const auto skipx = st::msgDateImgPadding.x();
	const auto skipy = st::msgDateImgPadding.y();
	return QPoint(fullRight - skipx, fullBottom - skipy);
}

std::unique_ptr<StickerPlayer> UnwrappedMedia::stickerTakePlayer(
		not_null<DocumentData*> data,
		const Lottie::ColorReplacements *replacements) {
	return _content->stickerTakePlayer(data, replacements);
}

int UnwrappedMedia::calculateFullRight(const QRect &inner) const {
	const auto rightAligned = _parent->hasOutLayout()
		&& !_parent->delegate()->elementIsChatWide();
	const auto infoWidth = _parent->infoWidth()
		+ st::msgDateImgPadding.x() * 2
		+ st::msgReplyPadding.left();
	const auto rightActionSize = _parent->rightActionSize();
	const auto rightSkip = st::msgPadding.left()
		+ (_parent->hasFromPhoto()
			? st::msgMargin.right()
			: st::msgPadding.right());
	const auto rightActionWidth = rightActionSize
		? (st::historyFastShareLeft * 2
			+ rightActionSize->width())
		: 0;
	auto fullRight = inner.x()
		+ inner.width()
		+ (rightAligned ? 0 : infoWidth);
	if (fullRight + rightActionWidth + rightSkip > _parent->width()) {
		fullRight = _parent->width() - rightActionWidth - rightSkip;
	}
	return fullRight;
}

QPoint UnwrappedMedia::calculateFastActionPosition(
		int fullBottom,
		int replyRight,
		int fullRight,
		QSize size) const {
	const auto fastShareTop = (fullBottom
		- st::historyFastShareBottom
		- size.height());
	const auto doesRightActionHitReply = replyRight && (fastShareTop <
		st::msgReplyBarSize.height()
		+ st::msgReplyPadding.top()
		+ st::msgReplyPadding.bottom());
	const auto fastShareLeft = ((doesRightActionHitReply
		? replyRight
		: fullRight) + st::historyFastShareLeft);
	return QPoint(fastShareLeft, fastShareTop);
}

bool UnwrappedMedia::needInfoDisplay() const {
	return _parent->data()->isSending()
		|| _parent->data()->hasFailed()
		|| _parent->isUnderCursor()
		|| _parent->rightActionSize()
		|| _parent->isLastAndSelfMessage()
		|| (_parent->hasOutLayout()
			&& !_parent->delegate()->elementIsChatWide()
			&& _content->alwaysShowOutTimestamp());
}

int UnwrappedMedia::additionalWidth(
		const HistoryMessageVia *via,
		const HistoryMessageReply *reply,
		const HistoryMessageForwarded *forwarded) const {
	auto result = st::msgReplyPadding.left() + _parent->infoWidth() + 2 * st::msgDateImgPadding.x();
	if (forwarded) {
		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right());
	} else if (via) {
		accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
	}
	if (reply) {
		accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
	}
	return result;
}

auto UnwrappedMedia::getDisplayedForwardedInfo() const
-> const HistoryMessageForwarded * {
	return _parent->displayForwardedFrom()
		? _parent->data()->Get<HistoryMessageForwarded>()
		: nullptr;
}

} // namespace HistoryView
