/*
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 "export/view/export_view_panel_controller.h"

#include "export/view/export_view_settings.h"
#include "export/view/export_view_progress.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/separate_panel.h"
#include "ui/wrap/padding_wrap.h"
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "core/file_utilities.h"
#include "platform/platform_specific.h"
#include "auth_session.h"
#include "data/data_session.h"
#include "styles/style_export.h"
#include "styles/style_boxes.h"

namespace Export {
namespace View {
namespace {

constexpr auto kSaveSettingsTimeout = crl::time(1000);

class SuggestBox : public BoxContent {
public:
	SuggestBox(QWidget*);

protected:
	void prepare() override;

};

SuggestBox::SuggestBox(QWidget*) {
}

void SuggestBox::prepare() {
	setTitle(langFactory(lng_export_suggest_title));

	addButton(langFactory(lng_box_ok), [=] {
		closeBox();
		Auth().data().startExport(Local::ReadExportSettings().singlePeer);
	});
	addButton(langFactory(lng_export_suggest_cancel), [=] { closeBox(); });
	setCloseByOutsideClick(false);

	const auto content = Ui::CreateChild<Ui::FlatLabel>(
		this,
		lang(lng_export_suggest_text),
		Ui::FlatLabel::InitType::Simple,
		st::boxLabel);
	widthValue(
	) | rpl::start_with_next([=](int width) {
		const auto contentWidth = width
			- st::boxPadding.left()
			- st::boxPadding.right();
		content->resizeToWidth(contentWidth);
		content->moveToLeft(st::boxPadding.left(), 0);
	}, content->lifetime());
	content->heightValue(
	) | rpl::start_with_next([=](int height) {
		setDimensions(st::boxWidth, height + st::boxPadding.bottom());
	}, content->lifetime());
}

} // namespace

Environment PrepareEnvironment() {
	auto result = Environment();
	const auto utfLang = [](LangKey key) {
		return lang(key).toUtf8();
	};
	result.internalLinksDomain = Global::InternalLinksDomain();
	result.aboutTelegram = utfLang(lng_export_about_telegram);
	result.aboutContacts = utfLang(lng_export_about_contacts);
	result.aboutFrequent = utfLang(lng_export_about_frequent);
	result.aboutSessions = utfLang(lng_export_about_sessions);
	result.aboutWebSessions = utfLang(lng_export_about_web_sessions);
	result.aboutChats = utfLang(lng_export_about_chats);
	result.aboutLeftChats = utfLang(lng_export_about_left_chats);
	return result;
}

QPointer<BoxContent> SuggestStart() {
	ClearSuggestStart();
	return Ui::show(Box<SuggestBox>(), LayerOption::KeepOther).data();
}

void ClearSuggestStart() {
	Auth().data().clearExportSuggestion();

	auto settings = Local::ReadExportSettings();
	if (settings.availableAt) {
		settings.availableAt = 0;
		Local::WriteExportSettings(settings);
	}
}

bool IsDefaultPath(const QString &path) {
	const auto check = [](const QString &value) {
		const auto result = value.endsWith('/')
			? value.mid(0, value.size() - 1)
			: value;
		return (cPlatform() == dbipWindows) ? result.toLower() : result;
	};
	return (check(path) == check(File::DefaultDownloadPath()));
}

void ResolveSettings(Settings &settings) {
	if (settings.path.isEmpty()) {
		settings.path = File::DefaultDownloadPath();
		settings.forceSubPath = true;
	} else {
		settings.forceSubPath = IsDefaultPath(settings.path);
	}
	if (!settings.onlySinglePeer()) {
		settings.singlePeerFrom = settings.singlePeerTill = 0;
	}
}

PanelController::PanelController(not_null<Controller*> process)
: _process(process)
, _settings(std::make_unique<Settings>(Local::ReadExportSettings()))
, _saveSettingsTimer([=] { saveSettings(); }) {
	ResolveSettings(*_settings);

	_process->state(
	) | rpl::start_with_next([=](State &&state) {
		updateState(std::move(state));
	}, _lifetime);
}

void PanelController::activatePanel() {
	_panel->showAndActivate();
}

void PanelController::createPanel() {
	const auto singlePeer = _settings->onlySinglePeer();
	_panel = base::make_unique_q<Ui::SeparatePanel>();
	_panel->setTitle(Lang::Viewer(singlePeer
		? lng_export_header_chats
		: lng_export_title));
	_panel->setInnerSize(st::exportPanelSize);
	_panel->closeRequests(
	) | rpl::start_with_next([=] {
		LOG(("Export Info: Panel Hide By Close."));
		_panel->hideGetDuration();
	}, _panel->lifetime());
	_panelCloseEvents.fire(_panel->closeEvents());

	showSettings();
}

void PanelController::showSettings() {
	auto settings = base::make_unique_q<SettingsWidget>(
		_panel,
		*_settings);
	settings->setShowBoxCallback([=](object_ptr<BoxContent> box) {
		_panel->showBox(
			std::move(box),
			LayerOption::KeepOther,
			anim::type::normal);
	});

	settings->startClicks(
	) | rpl::start_with_next([=]() {
		showProgress();
		_process->startExport(*_settings, PrepareEnvironment());
	}, settings->lifetime());

	settings->cancelClicks(
	) | rpl::start_with_next([=] {
		LOG(("Export Info: Panel Hide By Cancel."));
		_panel->hideGetDuration();
	}, settings->lifetime());

	settings->changes(
	) | rpl::start_with_next([=](Settings &&settings) {
		*_settings = std::move(settings);
	}, settings->lifetime());

	_panel->showInner(std::move(settings));
}

void PanelController::showError(const ApiErrorState &error) {
	LOG(("Export Info: API Error '%1'.").arg(error.data.type()));

	if (error.data.type() == qstr("TAKEOUT_INVALID")) {
		showError(lang(lng_export_invalid));
	} else if (error.data.type().startsWith(qstr("TAKEOUT_INIT_DELAY_"))) {
		const auto seconds = std::max(error.data.type().mid(
			qstr("TAKEOUT_INIT_DELAY_").size()).toInt(), 1);
		const auto now = QDateTime::currentDateTime();
		const auto when = now.addSecs(seconds);
		const auto hours = seconds / 3600;
		const auto hoursText = [&] {
			if (hours <= 0) {
				return lang(lng_export_delay_less_than_hour);
			}
			return lng_export_delay_hours(lt_count, hours);
		}();
		showError(lng_export_delay(
			lt_hours,
			hoursText,
			lt_date,
			langDateTimeFull(when)));

		_settings->availableAt = unixtime() + seconds;
		_saveSettingsTimer.callOnce(kSaveSettingsTimeout);

		Auth().data().suggestStartExport(_settings->availableAt);
	} else {
		showCriticalError("API Error happened :(\n"
			+ QString::number(error.data.code()) + ": " + error.data.type()
			+ "\n" + error.data.description());
	}
}

void PanelController::showError(const OutputErrorState &error) {
	showCriticalError("Disk Error happened :(\n"
		"Could not write path:\n" + error.path);
}

void PanelController::showCriticalError(const QString &text) {
	auto container = base::make_unique_q<Ui::PaddingWrap<Ui::FlatLabel>>(
		_panel.get(),
		object_ptr<Ui::FlatLabel>(
			_panel.get(),
			text,
			Ui::FlatLabel::InitType::Simple,
			st::exportErrorLabel),
		style::margins(0, st::exportPanelSize.height() / 4, 0, 0));
	container->widthValue(
	) | rpl::start_with_next([label = container->entity()](int width) {
		label->resize(width, label->height());
	}, container->lifetime());

	_panel->showInner(std::move(container));
	_panel->setHideOnDeactivate(false);
}

void PanelController::showError(const QString &text) {
	auto box = Box<InformBox>(text);
	const auto weak = make_weak(box.data());
	const auto hidden = _panel->isHidden();
	_panel->showBox(
		std::move(box),
		LayerOption::CloseOther,
		hidden ? anim::type::instant : anim::type::normal);
	weak->setCloseByEscape(false);
	weak->setCloseByOutsideClick(false);
	weak->boxClosing(
	) | rpl::start_with_next([=] {
		LOG(("Export Info: Panel Hide By Error: %1.").arg(text));
		_panel->hideGetDuration();
	}, weak->lifetime());
	if (hidden) {
		_panel->showAndActivate();
	}
	_panel->setHideOnDeactivate(false);
}

void PanelController::showProgress() {
	_settings->availableAt = 0;
	ClearSuggestStart();

	_panel->setTitle(Lang::Viewer(lng_export_progress_title));

	auto progress = base::make_unique_q<ProgressWidget>(
		_panel.get(),
		rpl::single(
			ContentFromState(ProcessingState())
		) | rpl::then(progressState()));

	progress->cancelClicks(
	) | rpl::start_with_next([=] {
		stopWithConfirmation();
	}, progress->lifetime());

	progress->doneClicks(
	) | rpl::start_with_next([=] {
		if (const auto finished = base::get_if<FinishedState>(&_state)) {
			File::ShowInFolder(finished->path);
			LOG(("Export Info: Panel Hide By Done: %1."
				).arg(finished->path));
			_panel->hideGetDuration();
		}
	}, progress->lifetime());

	_panel->showInner(std::move(progress));
	_panel->setHideOnDeactivate(true);
}

void PanelController::stopWithConfirmation(FnMut<void()> callback) {
	if (!_state.is<ProcessingState>()) {
		LOG(("Export Info: Stop Panel Without Confirmation."));
		stopExport();
		if (callback) {
			callback();
		}
		return;
	}
	auto stop = [=, callback = std::move(callback)]() mutable {
		if (auto saved = std::move(callback)) {
			LOG(("Export Info: Stop Panel With Confirmation."));
			stopExport();
			saved();
		} else {
			_process->cancelExportFast();
		}
	};
	const auto hidden = _panel->isHidden();
	const auto old = _confirmStopBox;
	auto box = Box<ConfirmBox>(
		lang(lng_export_sure_stop),
		lang(lng_export_stop),
		st::attentionBoxButton,
		std::move(stop));
	_confirmStopBox = box.data();
	_panel->showBox(
		std::move(box),
		LayerOption::CloseOther,
		hidden ? anim::type::instant : anim::type::normal);
	if (hidden) {
		_panel->showAndActivate();
	}
	if (old) {
		old->closeBox();
	}
}

void PanelController::stopExport() {
	_stopRequested = true;
	_panel->showAndActivate();
	LOG(("Export Info: Panel Hide By Stop"));
	_panel->hideGetDuration();
}

rpl::producer<> PanelController::stopRequests() const {
	return _panelCloseEvents.events(
	) | rpl::flatten_latest(
	) | rpl::filter([=] {
		return !_state.is<ProcessingState>() || _stopRequested;
	});
}

void PanelController::fillParams(const PasswordCheckState &state) {
	_settings->singlePeer = state.singlePeer;
}

void PanelController::updateState(State &&state) {
	if (const auto start = base::get_if<PasswordCheckState>(&state)) {
		fillParams(*start);
	}
	if (!_panel) {
		createPanel();
	}
	_state = std::move(state);
	if (const auto apiError = base::get_if<ApiErrorState>(&_state)) {
		showError(*apiError);
	} else if (const auto error = base::get_if<OutputErrorState>(&_state)) {
		showError(*error);
	} else if (_state.is<FinishedState>()) {
		_panel->setTitle(Lang::Viewer(lng_export_title));
		_panel->setHideOnDeactivate(false);
	} else if (_state.is<CancelledState>()) {
		LOG(("Export Info: Stop Panel After Cancel."));
		stopExport();
	}
}

void PanelController::saveSettings() const {
	const auto check = [](const QString &value) {
		const auto result = value.endsWith('/')
			? value.mid(0, value.size() - 1)
			: value;
		return (cPlatform() == dbipWindows) ? result.toLower() : result;
	};
	auto settings = *_settings;
	if (check(settings.path) == check(File::DefaultDownloadPath())) {
		settings.path = QString();
	}
	Local::WriteExportSettings(settings);
}

PanelController::~PanelController() {
	if (_saveSettingsTimer.isActive()) {
		saveSettings();
	}
}

} // namespace View
} // namespace Export
