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

#include "base/qthelp_url.h"
#include "lang/lang_keys.h"
#include "ui/dynamic_image.h"
#include "ui/effects/ripple_animation.h"
#include "ui/layers/generic_box.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/horizontal_fit_container.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/tooltip.h"
#include "ui/emoji_config.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"

namespace UrlAuthBox {
namespace {

constexpr auto kEmojiAnimationActiveFor = crl::time(250);

void PrepareFullWidthRoundButton(
		not_null<Ui::RoundButton*> button,
		not_null<Ui::VerticalLayout*> content,
		const style::margins &padding) {
	button->setFullRadius(true);
	const auto paddingHorizontal = padding.left() + padding.right();
	content->widthValue() | rpl::on_next([=](int w) {
		button->resize(w - paddingHorizontal, button->height());
	}, button->lifetime());
}

} // namespace

void ShowMatchCodesBox(
		not_null<Ui::GenericBox*> box,
		Fn<std::shared_ptr<Ui::DynamicImage>(QString)> emojiImageFactory,
		const QString &domain,
		const QStringList &codes,
		Fn<void(QString)> callback,
		bool isApp) {
	box->setWidth(st::boxWidth);
	box->setStyle(st::urlAuthBox);

	const auto content = box->verticalLayout();

	Ui::AddSkip(content);
	content->add(
		object_ptr<Ui::FlatLabel>(
			content,
			tr::lng_url_auth_match_code_title(),
			st::urlAuthCodesTitle),
		st::boxRowPadding,
		style::al_top);

	Ui::AddSkip(content);
	Ui::AddSkip(content);

	const auto buttons = content->add(
		object_ptr<Ui::HorizontalFitContainer>(
			content,
			st::boxRowPadding.left() * 2),
		st::boxRowPadding);
	buttons->resize(0, st::urlAuthCodesButton.height);

	for (const auto &code : codes) {
		auto emojiLength = 0;
		const auto emoji = Ui::Emoji::Find(code, &emojiLength);
		const auto emojiCode = (emoji && (emojiLength == code.size()));
		const auto button = Ui::CreateChild<Ui::RoundButton>(
			buttons,
			rpl::single(emojiCode ? QString() : code),
			st::urlAuthCodesButton);
		if (emojiCode) {
			button->setTextFgOverride(QColor(Qt::transparent));
			const auto overlay = Ui::CreateChild<Ui::RpWidget>(button);
			overlay->setAttribute(Qt::WA_TransparentForMouseEvents);
			overlay->show();
			struct State {
				std::shared_ptr<Ui::DynamicImage> image;
				bool hovered = false;
				crl::time lastFrameUpdate = 0;
			};
			const auto state = overlay->lifetime().make_state<State>();
			const auto animationActive = [=] {
				return state->lastFrameUpdate
					&& (crl::now() - state->lastFrameUpdate
						<= kEmojiAnimationActiveFor);
			};
			const auto refreshImage = [=] {
				if (state->image) {
					state->image->subscribeToUpdates(nullptr);
				}
				state->image = emojiImageFactory
					? emojiImageFactory(code)
					: nullptr;
				if (state->image) {
					state->image->subscribeToUpdates([=] {
						state->lastFrameUpdate = crl::now();
						overlay->update();
					});
				}
				overlay->update();
			};
			refreshImage();
			overlay->lifetime().add([=] {
				if (state->image) {
					state->image->subscribeToUpdates(nullptr);
				}
			});
			button->events() | rpl::on_next([=](not_null<QEvent*> e) {
				switch (e->type()) {
				case QEvent::Enter:
					if (!state->hovered) {
						state->hovered = true;
						if (!animationActive()) {
							refreshImage();
						}
					}
					break;
				case QEvent::Leave:
					state->hovered = false;
					break;
				default:
					break;
				}
			}, overlay->lifetime());
			button->sizeValue() | rpl::on_next([=](QSize size) {
				overlay->resize(size);
			}, overlay->lifetime());
			overlay->paintOn([=](QPainter &p) {
				if (state->image) {
					const auto side = st::urlAuthCodesButton.height;
					const auto frame = state->image->image(side);
					const auto visible = frame.isNull()
						? 0
						: (frame.width() / frame.devicePixelRatio());
					p.drawImage(
						QPoint(
							(overlay->width() - visible) / 2,
							(overlay->height() - visible) / 2),
						frame);
					return;
				}
				const auto size = Ui::Emoji::GetSizeLarge();
				const auto visible = size / style::DevicePixelRatio();
				Ui::Emoji::Draw(
					p,
					emoji,
					size,
					(overlay->width() - visible) / 2,
					(overlay->height() - visible) / 2);
			});
		}
		button->setFullRadius(true);
		button->setClickedCallback([=] {
			callback(code);
			box->closeBox();
		});
		buttons->add(button);
	}

	Ui::AddSkip(content);

	const auto domainUrl = isApp ? QString() : qthelp::validate_url(domain);
	if (!domainUrl.isEmpty() || isApp) {
		Ui::AddSkip(content);
		Ui::AddSkip(content);
		content->add(
			object_ptr<Ui::FlatLabel>(
				content,
				tr::lng_url_auth_login_title(
					lt_domain,
					rpl::single(isApp
						? tr::bold(domain)
						: Ui::Text::Link(domain, domainUrl)),
					tr::marked),
				st::urlAuthCheckboxAbout),
			st::boxRowPadding,
			style::al_top);
	}
	Ui::AddSkip(content);
	Ui::AddSkip(content);
	{
		const auto &padding = st::boxRowPadding;
		const auto button = content->add(
			object_ptr<Ui::RoundButton>(
				content,
				tr::lng_cancel(),
				st::attentionBoxButton),
			padding);
		PrepareFullWidthRoundButton(button, content, padding);
		button->setClickedCallback([=] {
			box->closeBox();
		});
	}
}

SwitchableUserpicButton::SwitchableUserpicButton(
	not_null<Ui::RpWidget*> parent,
	int size)
: RippleButton(parent, st::defaultRippleAnimation)
, _size(size)
, _userpicSize(st::restoreUserpicIcon.photoSize)
, _skip((_size - _userpicSize) / 2) {
	resize(_size, _size);
}

void SwitchableUserpicButton::setUserpic(not_null<Ui::RpWidget*> userpic) {
	_userpic = userpic;
	_userpic->setParent(this);
	_userpic->moveToRight(_skip, _skip);
	_userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
	_userpic->show();
	update();
}

void SwitchableUserpicButton::setExpanded(bool expanded) {
	if (_expanded == expanded) {
		return;
	}
	_expanded = expanded;
	const auto w = _expanded
		? (_size * 2.5 - _userpicSize)
		: _size;
	resize(w, _size);
	if (_userpic) {
		_userpic->moveToRight(_skip, _skip);
	}
	update();
}

void SwitchableUserpicButton::paintEvent(QPaintEvent *e) {
	auto p = QPainter(this);
	paintRipple(p, 0, 0);

	if (!_expanded) {
		return;
	}

	const auto arrowSize = st::lineWidth * 10;
	const auto center = QPoint(_size / 2, height() / 2 + st::lineWidth * 3);

	auto pen = QPen(st::windowSubTextFg);
	pen.setWidthF(st::lineWidth * 1.5);
	p.setPen(pen);
	p.setRenderHint(QPainter::Antialiasing);

	p.drawLine(center, center + QPoint(-arrowSize / 2, -arrowSize / 2));
	p.drawLine(center, center + QPoint(arrowSize / 2, -arrowSize / 2));
}

QImage SwitchableUserpicButton::prepareRippleMask() const {
	return _expanded
		? Ui::RippleAnimation::RoundRectMask(size(), height() / 2)
		: Ui::RippleAnimation::EllipseMask(size());
}

QPoint SwitchableUserpicButton::prepareRippleStartPosition() const {
	return mapFromGlobal(QCursor::pos());
}

void AddAuthInfoRow(
		not_null<Ui::VerticalLayout*> container,
		const QString &topText,
		const QString &bottomText,
		const QString &leftText,
		const style::icon &icon) {
	const auto row = container->add(
		object_ptr<Ui::RpWidget>(container),
		st::boxRowPadding);

	const auto topLabel = Ui::CreateChild<Ui::FlatLabel>(
		row,
		topText,
		st::urlAuthBoxRowTopLabel);
	topLabel->setSelectable(true);
	Ui::InstallTooltip(topLabel, [=] {
		return (topLabel->textMaxWidth() > topLabel->width())
			? topText
			: QString();
	});
	const auto bottomLabel = Ui::CreateChild<Ui::FlatLabel>(
		row,
		bottomText,
		st::urlAuthBoxRowBottomLabel);
	bottomLabel->setSelectable(true);
	Ui::InstallTooltip(bottomLabel, [=] {
		return (bottomLabel->textMaxWidth() > bottomLabel->width())
			? bottomText
			: QString();
	});
	const auto leftLabel = Ui::CreateChild<Ui::FlatLabel>(
		row,
		leftText,
		st::boxLabel);

	rpl::combine(
		row->widthValue(),
		topLabel->sizeValue(),
		bottomLabel->sizeValue()
	) | rpl::on_next([=](int rowWidth, QSize topSize, QSize bottomSize) {
		const auto totalHeight = topSize.height() + bottomSize.height();
		row->resize(rowWidth, totalHeight);

		const auto left = st::sessionValuePadding.left();
		const auto availableWidth = rowWidth
			- leftLabel->width()
			- left
			- st::defaultVerticalListSkip;

		topLabel->resizeToNaturalWidth(availableWidth);
		topLabel->moveToRight(0, 0);
		bottomLabel->resizeToNaturalWidth(availableWidth);
		bottomLabel->moveToRight(0, topSize.height());

		leftLabel->moveToLeft(left, (totalHeight - leftLabel->height()) / 2);
	}, row->lifetime());

	{
		const auto widget = Ui::CreateChild<Ui::RpWidget>(row);
		widget->resize(icon.size());

		rpl::combine(
			row->widthValue(),
			topLabel->sizeValue(),
			bottomLabel->sizeValue()
		) | rpl::on_next([=](int rowWidth, QSize topSize, QSize bottomSize) {
			const auto totalHeight = topSize.height() + bottomSize.height();
			widget->moveToLeft(0, (totalHeight - leftLabel->height()) / 2);
		}, row->lifetime());

		widget->paintRequest() | rpl::on_next([=, &icon] {
			auto p = QPainter(widget);
			icon.paintInCenter(p, widget->rect());
		}, widget->lifetime());
	}
}

void Show(
		not_null<Ui::GenericBox*> box,
		const QString &url,
		const QString &domain,
		const QString &selfName,
		const QString &botName,
		Fn<void(Result)> callback) {
	box->setWidth(st::boxWidth);

	box->addRow(
		object_ptr<Ui::FlatLabel>(
			box,
			tr::lng_url_auth_open_confirm(tr::now, lt_link, url),
			st::boxLabel),
		st::boxPadding);

	const auto addCheckbox = [&](const TextWithEntities &text) {
		const auto checkbox = box->addRow(
			object_ptr<Ui::Checkbox>(
				box,
				text,
				true,
				st::urlAuthCheckbox),
			style::margins(
				st::boxPadding.left(),
				st::boxPadding.bottom(),
				st::boxPadding.right(),
				st::boxPadding.bottom()));
		checkbox->setAllowTextLines();
		return checkbox;
	};

	const auto auth = addCheckbox(
		tr::lng_url_auth_login_option(
			tr::now,
			lt_domain,
			tr::bold(domain),
			lt_user,
			tr::bold(selfName),
			tr::marked));

	const auto allow = !botName.isEmpty()
		? addCheckbox(tr::lng_url_auth_allow_messages(
			tr::now,
			lt_bot,
			tr::bold(botName),
			tr::marked))
		: nullptr;

	if (allow) {
		rpl::single(
			auth->checked()
		) | rpl::then(
			auth->checkedChanges()
		) | rpl::on_next([=](bool checked) {
			if (!checked) {
				allow->setChecked(false);
			}
			allow->setDisabled(!checked);
		}, auth->lifetime());
	}

	box->addButton(tr::lng_open_link(), [=] {
		const auto authed = auth->checked();
		const auto allowed = (authed && allow && allow->checked());
		callback({
			.auth = authed,
			.allowWrite = allowed,
		});
	});
	box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}

void ShowDetails(
		not_null<Ui::GenericBox*> box,
		const QString &url,
		const QString &domain,
		Fn<std::shared_ptr<Ui::DynamicImage>(QString)> emojiImageFactory,
		Fn<void(Result)> callback,
		object_ptr<Ui::RpWidget> userpicOwned,
		rpl::producer<QString> botName,
		const QString &browser,
		const QString &platform,
		const QString &ip,
		const QString &region,
		rpl::producer<QStringList> matchCodes,
		bool isApp) {
	box->setWidth(st::boxWidth);
	box->setStyle(st::urlAuthBox);

	const auto content = box->verticalLayout();

	Ui::AddSkip(content);
	Ui::AddSkip(content);
	if (userpicOwned) {
		const auto userpic = content->add(
			std::move(userpicOwned),
			st::boxRowPadding,
			style::al_top);
		userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
		Ui::AddSkip(content);
		Ui::AddSkip(content);
	}

	const auto domainUrl = isApp ? QString() : qthelp::validate_url(domain);
	const auto userpicButtonWidth = st::restoreUserpicIcon.photoSize;
	const auto titlePadding = style::margins(
		st::boxRowPadding.left(),
		st::boxRowPadding.top(),
		st::boxRowPadding.right() + userpicButtonWidth,
		st::boxRowPadding.bottom());
	content->add(
		object_ptr<Ui::FlatLabel>(
			content,
			(isApp
				? tr::lng_url_auth_login_title(
					lt_domain,
					rpl::single(tr::bold(domain)),
					tr::marked)
				: domainUrl.isEmpty()
					? tr::lng_url_auth_login_button(tr::marked)
					: tr::lng_url_auth_login_title(
						lt_domain,
						rpl::single(Ui::Text::Link(domain, domainUrl)),
						tr::marked)),
			st::boxTitle),
		titlePadding,
		style::al_top);
	Ui::AddSkip(content);

	content->add(
		object_ptr<Ui::FlatLabel>(
			content,
			(isApp
				? tr::lng_url_auth_app_access(tr::rich)
				: tr::lng_url_auth_site_access(tr::rich)),
			st::urlAuthCheckboxAbout),
		st::boxRowPadding);

	Ui::AddSkip(content);
	Ui::AddSkip(content);
	if (!platform.isEmpty() || !browser.isEmpty()) {
		AddAuthInfoRow(
			content,
			platform,
			browser,
			tr::lng_url_auth_device_label(tr::now),
			st::menuIconDevices);
	}
	Ui::AddSkip(content);
	Ui::AddSkip(content);

	if (!ip.isEmpty() || !region.isEmpty()) {
		AddAuthInfoRow(
			content,
			ip,
			region,
			tr::lng_url_auth_ip_label(tr::now),
			st::menuIconAddress);
	}
	Ui::AddSkip(content);
	Ui::AddSkip(content);

	Ui::AddDividerText(
		content,
		rpl::single(tr::lng_url_auth_login_attempt(tr::now)));
	Ui::AddSkip(content);

	auto allowMessages = (Ui::SettingsButton*)(nullptr);
	if (botName) {
		allowMessages = content->add(
			object_ptr<Ui::SettingsButton>(
				content,
				tr::lng_url_auth_allow_messages_label()));
		allowMessages->toggleOn(rpl::single(false));
		Ui::AddSkip(content);
		Ui::AddDividerText(
			content,
			tr::lng_url_auth_allow_messages_about(
				lt_bot,
				std::move(botName)));
		Ui::AddSkip(content);
	}

	struct State {
		QStringList matchCodes;
	};
	const auto state = box->lifetime().make_state<State>();
	std::move(matchCodes) | rpl::on_next([=](const QStringList &codes) {
		state->matchCodes = codes;
	}, box->lifetime());

	Ui::AddSkip(content);
	{
		const auto &padding = st::boxRowPadding;
		const auto button = content->add(
			object_ptr<Ui::RoundButton>(
				content,
				tr::lng_url_auth_login_button(),
				st::defaultLightButton),
			padding);
		PrepareFullWidthRoundButton(button, content, padding);
		button->setClickedCallback([=] {
			if (state->matchCodes.isEmpty()) {
				callback({
					.auth = true,
					.allowWrite = (allowMessages && allowMessages->toggled()),
				});
				return;
			}
			box->uiShow()->show(Box([=](
					not_null<Ui::GenericBox*> matchCodesBox) {
				ShowMatchCodesBox(
					matchCodesBox,
					emojiImageFactory,
					domain,
					state->matchCodes,
					[=](QString matchCode) {
						callback({
							.auth = true,
							.allowWrite = (allowMessages
								&& allowMessages->toggled()),
							.matchCode = std::move(matchCode),
						});
					},
					isApp);
			}));
		});
	}
	Ui::AddSkip(content);
	{
		const auto &padding = st::boxRowPadding;
		const auto button = content->add(
			object_ptr<Ui::RoundButton>(
				content,
				tr::lng_suggest_action_decline(),
				st::attentionBoxButton),
			padding);
		PrepareFullWidthRoundButton(button, content, padding);
		button->setClickedCallback([=] {
			box->closeBox();
		});
	}
}

} // namespace UrlAuthBox
