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

#include "settings/settings_common.h"
#include "settings/settings_codes.h"
#include "settings/settings_chat.h"
#include "settings/settings_information.h"
#include "settings/settings_notifications.h"
#include "settings/settings_privacy_security.h"
#include "settings/settings_advanced.h"
#include "settings/settings_folders.h"
#include "settings/settings_calls.h"
#include "settings/settings_premium.h"
#include "boxes/language_box.h"
#include "boxes/username_box.h"
#include "boxes/about_box.h"
#include "ui/basic_click_handlers.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/discrete_sliders.h"
#include "ui/widgets/buttons.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "info/profile/info_profile_badge.h"
#include "info/profile/info_profile_emoji_status_panel.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_cloud_themes.h"
#include "data/data_chat_filters.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue
#include "lang/lang_keys.h"
#include "lang/lang_instance.h"
#include "storage/localstorage.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "api/api_cloud_password.h"
#include "api/api_global_privacy.h"
#include "api/api_sensitive_content.h"
#include "api/api_premium.h"
#include "info/profile/info_profile_values.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "base/call_delayed.h"
#include "base/platform/base_platform_info.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"

#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>

namespace Settings {
namespace {

class Cover final : public Ui::FixedHeightWidget {
public:
	Cover(
		QWidget *parent,
		not_null<Window::SessionController*> controller,
		not_null<UserData*> user);
	~Cover();

private:
	void setupChildGeometry();
	void initViewers();
	void refreshStatusText();
	void refreshNameGeometry(int newWidth);
	void refreshPhoneGeometry(int newWidth);
	void refreshUsernameGeometry(int newWidth);

	const not_null<Window::SessionController*> _controller;
	const not_null<UserData*> _user;
	Info::Profile::EmojiStatusPanel _emojiStatusPanel;
	Info::Profile::Badge _badge;

	object_ptr<Ui::UserpicButton> _userpic;
	object_ptr<Ui::FlatLabel> _name = { nullptr };
	object_ptr<Ui::FlatLabel> _phone = { nullptr };
	object_ptr<Ui::FlatLabel> _username = { nullptr };

};

Cover::Cover(
	QWidget *parent,
	not_null<Window::SessionController*> controller,
	not_null<UserData*> user)
: FixedHeightWidget(
	parent,
	st::settingsPhotoTop
		+ st::infoProfileCover.photo.size.height()
		+ st::settingsPhotoBottom)
, _controller(controller)
, _user(user)
, _badge(
	this,
	st::infoPeerBadge,
	user,
	&_emojiStatusPanel,
	[=] {
		return controller->isGifPausedAtLeastFor(
			Window::GifPauseReason::Layer);
	},
	0, // customStatusLoopsLimit
	Info::Profile::BadgeType::Premium)
, _userpic(
	this,
	controller,
	_user,
	Ui::UserpicButton::Role::OpenPhoto,
	Ui::UserpicButton::Source::PeerPhoto,
	st::infoProfileCover.photo)
, _name(this, st::infoProfileCover.name)
, _phone(this, st::defaultFlatLabel)
, _username(this, st::infoProfileMegagroupCover.status) {
	_user->updateFull();

	_name->setSelectable(true);
	_name->setContextCopyText(tr::lng_profile_copy_fullname(tr::now));

	_phone->setSelectable(true);
	_phone->setContextCopyText(tr::lng_profile_copy_phone(tr::now));

	initViewers();
	setupChildGeometry();

	_userpic->switchChangePhotoOverlay(_user->isSelf(), [=](
			Ui::UserpicButton::ChosenImage chosen) {
		auto &image = chosen.image;
		_userpic->showCustom(base::duplicate(image));
		_user->session().api().peerPhoto().upload(_user, std::move(image));
	});

	_badge.setPremiumClickCallback([=] {
		_emojiStatusPanel.show(
			_controller,
			_badge.widget(),
			_badge.sizeTag());
	});
	_badge.updated() | rpl::start_with_next([=] {
		refreshNameGeometry(width());
	}, _name->lifetime());
}

Cover::~Cover() = default;

void Cover::setupChildGeometry() {
	using namespace rpl::mappers;
	widthValue(
	) | rpl::start_with_next([=](int newWidth) {
		_userpic->moveToLeft(
			st::settingsPhotoLeft,
			st::settingsPhotoTop,
			newWidth);
		refreshNameGeometry(newWidth);
		refreshPhoneGeometry(newWidth);
		refreshUsernameGeometry(newWidth);
	}, lifetime());
}

void Cover::initViewers() {
	Info::Profile::NameValue(
		_user
	) | rpl::start_with_next([=](const QString &name) {
		_name->setText(name);
		refreshNameGeometry(width());
	}, lifetime());

	Info::Profile::PhoneValue(
		_user
	) | rpl::start_with_next([=](const TextWithEntities &value) {
		_phone->setText(value.text);
		refreshPhoneGeometry(width());
	}, lifetime());

	Info::Profile::UsernameValue(
		_user
	) | rpl::start_with_next([=](const TextWithEntities &value) {
		_username->setMarkedText(Ui::Text::Link(value.text.isEmpty()
			? tr::lng_settings_username_add(tr::now)
			: value.text));
		refreshUsernameGeometry(width());
	}, lifetime());

	_username->overrideLinkClickHandler([=] {
		const auto username = _user->userName();
		if (username.isEmpty()) {
			_controller->show(Box(UsernamesBox, &_user->session()));
		} else {
			QGuiApplication::clipboard()->setText(
				_user->session().createInternalLinkFull(username));
			Ui::Toast::Show(
				Window::Show(_controller).toastParent(),
				tr::lng_username_copied(tr::now));
		}
	});
}

void Cover::refreshNameGeometry(int newWidth) {
	const auto nameLeft = st::settingsNameLeft;
	const auto nameTop = st::settingsNameTop;
	auto nameWidth = newWidth
		- nameLeft
		- st::infoProfileCover.rightSkip;
	if (const auto width = _badge.widget() ? _badge.widget()->width() : 0) {
		nameWidth -= st::infoVerifiedCheckPosition.x() + width;
	}
	_name->resizeToNaturalWidth(nameWidth);
	_name->moveToLeft(nameLeft, nameTop, newWidth);
	const auto badgeLeft = nameLeft + _name->width();
	const auto badgeTop = nameTop;
	const auto badgeBottom = nameTop + _name->height();
	_badge.move(badgeLeft, badgeTop, badgeBottom);
}

void Cover::refreshPhoneGeometry(int newWidth) {
	const auto phoneLeft = st::settingsPhoneLeft;
	const auto phoneTop = st::settingsPhoneTop;
	const auto phoneWidth = newWidth
		- phoneLeft
		- st::infoProfileCover.rightSkip;
	_phone->resizeToWidth(phoneWidth);
	_phone->moveToLeft(phoneLeft, phoneTop, newWidth);
}

void Cover::refreshUsernameGeometry(int newWidth) {
	const auto usernameLeft = st::settingsUsernameLeft;
	const auto usernameTop = st::settingsUsernameTop;
	const auto usernameRight = st::infoProfileCover.rightSkip;
	const auto usernameWidth = newWidth - usernameLeft - usernameRight;
	_username->resizeToWidth(usernameWidth);
	_username->moveToLeft(usernameLeft, usernameTop, newWidth);
}

} // namespace

void SetupLanguageButton(
		not_null<Ui::VerticalLayout*> container,
		bool icon) {
	const auto button = AddButtonWithLabel(
		container,
		tr::lng_settings_language(),
		rpl::single(
			Lang::GetInstance().id()
		) | rpl::then(
			Lang::GetInstance().idChanges()
		) | rpl::map([] { return Lang::GetInstance().nativeName(); }),
		icon ? st::settingsButton : st::settingsButtonNoIcon,
		{ icon ? &st::settingsIconLanguage : nullptr, kIconDarkOrange });
	const auto guard = Ui::CreateChild<base::binary_guard>(button.get());
	button->addClickHandler([=] {
		const auto m = button->clickModifiers();
		if ((m & Qt::ShiftModifier) && (m & Qt::AltModifier)) {
			Lang::CurrentCloudManager().switchToLanguage({ u"#custom"_q });
		} else {
			*guard = LanguageBox::Show();
		}
	});
}

void SetupSections(
		not_null<Window::SessionController*> controller,
		not_null<Ui::VerticalLayout*> container,
		Fn<void(Type)> showOther) {
	AddDivider(container);
	AddSkip(container);

	const auto addSection = [&](
			rpl::producer<QString> label,
			Type type,
			IconDescriptor &&descriptor) {
		AddButton(
			container,
			std::move(label),
			st::settingsButton,
			std::move(descriptor)
		)->addClickHandler([=] {
			if (type == PremiumId()) {
				controller->setPremiumRef("settings");
			}
			showOther(type);
		});
	};
	if (controller->session().supportMode()) {
		SetupSupport(controller, container);

		AddDivider(container);
		AddSkip(container);
	} else {
		addSection(
			tr::lng_settings_information(),
			Information::Id(),
			{ &st::settingsIconAccount, kIconLightOrange });
	}
	addSection(
		tr::lng_settings_section_notify(),
		Notifications::Id(),
		{ &st::settingsIconNotifications, kIconRed });
	addSection(
		tr::lng_settings_section_privacy(),
		PrivacySecurity::Id(),
		{ &st::settingsIconLock, kIconGreen });
	addSection(
		tr::lng_settings_section_chat_settings(),
		Chat::Id(),
		{ &st::settingsIconChat, kIconLightBlue });

	const auto preload = [=] {
		controller->session().data().chatsFilters().requestSuggested();
	};
	const auto account = &controller->session().account();
	const auto slided = container->add(
		object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
			container,
			CreateButton(
				container,
				tr::lng_settings_section_filters(),
				st::settingsButton,
				{ &st::settingsIconFolders, kIconDarkBlue }))
	)->setDuration(0);
	if (controller->session().data().chatsFilters().has()
		|| controller->session().settings().dialogsFiltersEnabled()) {
		slided->show(anim::type::instant);
		preload();
	} else {
		const auto enabled = [=] {
			const auto result = account->appConfig().get<bool>(
				"dialog_filters_enabled",
				false);
			if (result) {
				preload();
			}
			return result;
		};
		const auto preloadIfEnabled = [=](bool enabled) {
			if (enabled) {
				preload();
			}
		};
		slided->toggleOn(
			rpl::single(rpl::empty) | rpl::then(
				account->appConfig().refreshed()
			) | rpl::map(
				enabled
			) | rpl::before_next(preloadIfEnabled));
	}
	slided->entity()->setClickedCallback([=] {
		showOther(Folders::Id());
	});

	addSection(
		tr::lng_settings_advanced(),
		Advanced::Id(),
		{ &st::settingsIconGeneral, kIconPurple });
	addSection(
		tr::lng_settings_section_call_settings(),
		Calls::Id(),
		{ &st::settingsIconCalls, kIconGreen });

	SetupLanguageButton(container);

	if (controller->session().premiumPossible()) {
		AddSkip(container);
		AddDivider(container);
		AddSkip(container);

		const auto icon = &st::settingsPremiumIconStar;
		auto gradient = QLinearGradient(
			0,
			icon->height(),
			icon->width() + icon->width() / 3,
			0 - icon->height() / 3);
		gradient.setStops(QGradientStops{
			{ 0.0, st::premiumButtonBg1->c },
			{ 1.0, st::premiumButtonBg3->c },
		});
		addSection(
			tr::lng_premium_summary_title(),
			PremiumId(),
			{ .icon = icon, .backgroundBrush = QBrush(gradient) });
	}

	AddSkip(container);
}

bool HasInterfaceScale() {
	return true;
}

void SetupInterfaceScale(
		not_null<Window::Controller*> window,
		not_null<Ui::VerticalLayout*> container,
		bool icon) {
	if (!HasInterfaceScale()) {
		return;
	}

	const auto toggled = Ui::CreateChild<rpl::event_stream<bool>>(
		container.get());

	const auto switched = (cConfigScale() == style::kScaleAuto);
	const auto button = AddButton(
		container,
		tr::lng_settings_default_scale(),
		icon ? st::settingsButton : st::settingsButtonNoIcon,
		{ icon ? &st::settingsIconInterfaceScale : nullptr, kIconLightOrange }
	)->toggleOn(toggled->events_starting_with_copy(switched));

	const auto slider = container->add(
		object_ptr<Ui::SettingsSlider>(container, st::settingsSlider),
		icon ? st::settingsScalePadding : st::settingsBigScalePadding);

	static const auto ScaleValues = [&] {
		auto values = (cIntRetinaFactor() > 1)
			? std::vector<int>{ 100, 110, 120, 130, 140, 150 }
			: std::vector<int>{ 100, 125, 150, 200, 250, 300 };
		if (cConfigScale() == style::kScaleAuto) {
			return values;
		}
		if (ranges::find(values, cConfigScale()) == end(values)) {
			values.push_back(cConfigScale());
		}
		return values;
	}();

	const auto sectionFromScale = [](int scale) {
		scale = cEvalScale(scale);
		auto result = 0;
		for (const auto value : ScaleValues) {
			if (scale == value) {
				break;
			}
			++result;
		}
		return (result == ScaleValues.size()) ? (result - 1) : result;
	};
	const auto inSetScale = container->lifetime().make_state<bool>();
	const auto setScale = [=](int scale, const auto &repeatSetScale) -> void {
		if (*inSetScale) {
			return;
		}
		*inSetScale = true;
		const auto guard = gsl::finally([=] { *inSetScale = false; });

		toggled->fire(scale == style::kScaleAuto);
		slider->setActiveSection(sectionFromScale(scale));
		if (cEvalScale(scale) != cEvalScale(cConfigScale())) {
			const auto confirmed = crl::guard(button, [=] {
				cSetConfigScale(scale);
				Local::writeSettings();
				Core::Restart();
			});
			const auto cancelled = crl::guard(button, [=](Fn<void()> close) {
				base::call_delayed(
					st::defaultSettingsSlider.duration,
					button,
					[=] { repeatSetScale(cConfigScale(), repeatSetScale); });
				close();
			});
			window->show(Ui::MakeConfirmBox({
				.text = tr::lng_settings_need_restart(),
				.confirmed = confirmed,
				.cancelled = cancelled,
				.confirmText = tr::lng_settings_restart_now(),
			}));
		} else if (scale != cConfigScale()) {
			cSetConfigScale(scale);
			Local::writeSettings();
		}
	};

	const auto label = [](int scale) {
		if constexpr (Platform::IsMac()) {
			return QString::number(scale) + '%';
		} else {
			return QString::number(scale * cIntRetinaFactor()) + '%';
		}
	};
	const auto scaleByIndex = [](int index) {
		return *(ScaleValues.begin() + index);
	};

	for (const auto value : ScaleValues) {
		slider->addSection(label(value));
	}
	slider->setActiveSectionFast(sectionFromScale(cConfigScale()));
	slider->sectionActivated(
	) | rpl::map([=](int section) {
		return scaleByIndex(section);
	}) | rpl::filter([=](int scale) {
		return cEvalScale(scale) != cEvalScale(cConfigScale());
	}) | rpl::start_with_next([=](int scale) {
		setScale(
			(scale == cScreenScale()) ? style::kScaleAuto : scale,
			setScale);
	}, slider->lifetime());

	button->toggledValue(
	) | rpl::map([](bool checked) {
		return checked ? style::kScaleAuto : cEvalScale(cConfigScale());
	}) | rpl::start_with_next([=](int scale) {
		setScale(scale, setScale);
	}, button->lifetime());
}

void OpenFaq() {
	File::OpenUrl(telegramFaqLink());
}

void SetupFaq(not_null<Ui::VerticalLayout*> container, bool icon) {
	AddButton(
		container,
		tr::lng_settings_faq(),
		icon ? st::settingsButton : st::settingsButtonNoIcon,
		{ icon ? &st::settingsIconFaq : nullptr, kIconLightBlue }
	)->addClickHandler(OpenFaq);
}

void SetupHelp(
		not_null<Window::SessionController*> controller,
		not_null<Ui::VerticalLayout*> container) {
	AddDivider(container);
	AddSkip(container);

	SetupFaq(container);

	AddButton(
		container,
		tr::lng_settings_features(),
		st::settingsButton,
		{ &st::settingsIconTips, kIconLightOrange }
	)->setClickedCallback([=] {
		UrlClickHandler::Open(tr::lng_telegram_features_url(tr::now));
	});

	const auto button = AddButton(
		container,
		tr::lng_settings_ask_question(),
		st::settingsButton,
		{ &st::settingsIconAskQuestion, kIconGreen });
	const auto requestId = button->lifetime().make_state<mtpRequestId>();
	button->lifetime().add([=] {
		if (*requestId) {
			controller->session().api().request(*requestId).cancel();
		}
	});
	button->addClickHandler([=] {
		const auto sure = crl::guard(button, [=] {
			if (*requestId) {
				return;
			}
			*requestId = controller->session().api().request(
				MTPhelp_GetSupport()
			).done([=](const MTPhelp_Support &result) {
				*requestId = 0;
				result.match([&](const MTPDhelp_support &data) {
					auto &owner = controller->session().data();
					if (const auto user = owner.processUser(data.vuser())) {
						controller->showPeerHistory(user);
					}
				});
			}).fail([=] {
				*requestId = 0;
			}).send();
		});
		auto box = Ui::MakeConfirmBox({
			.text = tr::lng_settings_ask_sure(),
			.confirmed = sure,
			.cancelled = OpenFaq,
			.confirmText = tr::lng_settings_ask_ok(),
			.cancelText = tr::lng_settings_faq_button(),
			.strictCancel = true,
		});
		controller->show(std::move(box));
	});
}

Main::Main(
	QWidget *parent,
	not_null<Window::SessionController*> controller)
: Section(parent)
, _controller(controller) {
	setupContent(controller);
	_controller->session().api().premium().reload();
}

rpl::producer<QString> Main::title() {
	return tr::lng_menu_settings();
}

void Main::keyPressEvent(QKeyEvent *e) {
	crl::on_main(this, [=, text = e->text()]{
		CodesFeedString(_controller, text);
	});
	return Section::keyPressEvent(e);
}

void Main::setupContent(not_null<Window::SessionController*> controller) {
	const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);

	content->add(object_ptr<Cover>(
		content,
		controller,
		controller->session().user()));

	SetupSections(controller, content, [=](Type type) {
		_showOther.fire_copy(type);
	});
	if (HasInterfaceScale()) {
		AddDivider(content);
		AddSkip(content);
		SetupInterfaceScale(&controller->window(), content);
		AddSkip(content);
	}
	SetupHelp(controller, content);

	Ui::ResizeFitChild(this, content);

	// If we load this in advance it won't jump when we open its' section.
	controller->session().api().cloudPassword().reload();
	controller->session().api().reloadContactSignupSilent();
	controller->session().api().sensitiveContent().reload();
	controller->session().api().globalPrivacy().reload();
	controller->session().data().cloudThemes().refresh();
}

rpl::producer<Type> Main::sectionShowOther() {
	return _showOther.events();
}

} // namespace Settings
