/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * SDL Client helper dialogs
 *
 * Copyright 2023 Armin Novak <armin.novak@thincast.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <cassert>
#include <thread>

#include "sdl_connection_dialog.hpp"
#include "../sdl_utils.hpp"
#include "../sdl_context.hpp"
#include "res/sdl3_resource_manager.hpp"

static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff };
static const SDL_Color infocolor = { 0x43, 0xe0, 0x0f, 0x60 };
static const SDL_Color warncolor = { 0xcd, 0xca, 0x35, 0x60 };
static const SDL_Color errorcolor = { 0xf7, 0x22, 0x30, 0x60 };

static const Uint32 vpadding = 5;
static const Uint32 hpadding = 5;

SDLConnectionDialog::SDLConnectionDialog(rdpContext* context) : _context(context)
{
	std::ignore = hide();
}

SDLConnectionDialog::~SDLConnectionDialog()
{
	resetTimer();
	destroyWindow();
}

bool SDLConnectionDialog::setTitle(const char* fmt, ...)
{
	std::scoped_lock lock(_mux);
	va_list ap = {};
	va_start(ap, fmt);
	_title = print(fmt, ap);
	va_end(ap);

	return show(SdlConnectionDialogWrapper::MSG_NONE);
}

bool SDLConnectionDialog::showInfo(const char* fmt, ...)
{
	va_list ap = {};
	va_start(ap, fmt);
	auto rc = show(SdlConnectionDialogWrapper::MSG_INFO, fmt, ap);
	va_end(ap);
	return rc;
}

bool SDLConnectionDialog::showWarn(const char* fmt, ...)
{
	va_list ap = {};
	va_start(ap, fmt);
	auto rc = show(SdlConnectionDialogWrapper::MSG_WARN, fmt, ap);
	va_end(ap);
	return rc;
}

bool SDLConnectionDialog::showError(const char* fmt, ...)
{
	va_list ap = {};
	va_start(ap, fmt);
	auto rc = show(SdlConnectionDialogWrapper::MSG_ERROR, fmt, ap);
	va_end(ap);
	if (!rc)
		return rc;
	return setTimer();
}

bool SDLConnectionDialog::show()
{
	std::scoped_lock lock(_mux);
	return show(_type_active);
}

bool SDLConnectionDialog::hide()
{
	std::scoped_lock lock(_mux);
	return show(SdlConnectionDialogWrapper::MSG_DISCARD);
}

bool SDLConnectionDialog::running() const
{
	std::scoped_lock lock(_mux);
	return _running;
}

bool SDLConnectionDialog::updateMsg(SdlConnectionDialogWrapper::MsgType type)
{
	switch (type)
	{
		case SdlConnectionDialogWrapper::MSG_INFO:
		case SdlConnectionDialogWrapper::MSG_WARN:
		case SdlConnectionDialogWrapper::MSG_ERROR:
			_type_active = type;
			if (!createWindow())
				return false;
			break;
		case SdlConnectionDialogWrapper::MSG_DISCARD:
			resetTimer();
			destroyWindow();
			break;
		default:
			if (_window)
			{
				SDL_SetWindowTitle(_window.get(), _title.c_str());
			}
			break;
	}
	return true;
}

bool SDLConnectionDialog::setModal()
{
	if (_window)
	{
		auto sdl = get_context(_context);
		auto parent = sdl->getFirstWindow();
		if (!parent)
			return true;

		if (!SDL_SetWindowParent(_window.get(), parent->window()))
			return false;
		if (!SDL_SetWindowModal(_window.get(), true))
			return false;
		if (!SDL_RaiseWindow(_window.get()))
			return false;
	}
	return true;
}

bool SDLConnectionDialog::updateInternal()
{
	std::scoped_lock lock(_mux);
	for (auto& btn : _list)
	{
		if (!btn.widget.update_text(_msg))
			return false;
	}

	return true;
}

bool SDLConnectionDialog::wait(bool ignoreRdpContext)
{
	while (running())
	{
		if (!ignoreRdpContext)
		{
			if (freerdp_shall_disconnect_context(_context))
				return false;
		}
		std::this_thread::yield();
	}
	return true;
}

bool SDLConnectionDialog::handle(const SDL_Event& event)
{
	Uint32 windowID = 0;
	if (_window)
	{
		windowID = SDL_GetWindowID(_window.get());
	}

	switch (event.type)
	{
		case SDL_EVENT_USER_RETRY_DIALOG:
		{
			std::scoped_lock lock(_mux);
			auto type = static_cast<SdlConnectionDialogWrapper::MsgType>(event.user.code);
			return updateMsg(type);
		}
		case SDL_EVENT_QUIT:
			resetTimer();
			destroyWindow();
			return false;
		case SDL_EVENT_KEY_DOWN:
		case SDL_EVENT_KEY_UP:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
				if (!update())
					return false;
				switch (event.key.key)
				{
					case SDLK_RETURN:
					case SDLK_RETURN2:
					case SDLK_ESCAPE:
					case SDLK_KP_ENTER:
						if (event.type == SDL_EVENT_KEY_UP)
						{
							freerdp_abort_connect_context(_context);
							std::ignore = sdl_push_quit();
						}
						break;
					case SDLK_TAB:
						if (!_buttons.set_highlight_next())
							return false;
						break;
					default:
						break;
				}

				return windowID == ev.windowID;
			}
			return false;
		case SDL_EVENT_MOUSE_MOTION:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_MouseMotionEvent&>(event);

				_buttons.set_mouseover(event.button.x, event.button.y);
				if (!update())
					return false;
				return windowID == ev.windowID;
			}
			return false;
		case SDL_EVENT_MOUSE_BUTTON_DOWN:
		case SDL_EVENT_MOUSE_BUTTON_UP:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_MouseButtonEvent&>(event);
				if (!update())
					return false;

				auto button = _buttons.get_selected(event.button);
				if (button)
				{
					if (event.type == SDL_EVENT_MOUSE_BUTTON_UP)
					{
						freerdp_abort_connect_context(_context);
						std::ignore = sdl_push_quit();
					}
				}

				return windowID == ev.windowID;
			}
			return false;
		case SDL_EVENT_MOUSE_WHEEL:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
				if (!update())
					return false;
				return windowID == ev.windowID;
			}
			return false;
		case SDL_EVENT_FINGER_UP:
		case SDL_EVENT_FINGER_DOWN:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
				if (!update())
					return false;
				return windowID == ev.windowID;
			}
			return false;
		default:
			if ((event.type >= SDL_EVENT_WINDOW_FIRST) && (event.type <= SDL_EVENT_WINDOW_LAST))
			{
				auto& ev = reinterpret_cast<const SDL_WindowEvent&>(event);
				switch (ev.type)
				{
					case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
						if (windowID == ev.windowID)
						{
							freerdp_abort_connect_context(_context);
							std::ignore = sdl_push_quit();
						}
						break;
					default:
						if (!update())
							return false;
						if (!setModal())
							return false;
						break;
				}

				return windowID == ev.windowID;
			}
			return false;
	}
}

bool SDLConnectionDialog::visible() const
{
	std::scoped_lock lock(_mux);
	return SdlWidgetList::visible();
}

bool SDLConnectionDialog::createWindow()
{
	destroyWindow();

	const size_t widget_height = 50;
	const size_t widget_width = 600;
	const size_t total_height = 300;

	if (!reset(_title, widget_width, total_height))
		return false;

	if (!setModal())
		return false;

	SDL_Color res_bgcolor;
	switch (_type_active)
	{
		case SdlConnectionDialogWrapper::MSG_INFO:
			res_bgcolor = infocolor;
			break;
		case SdlConnectionDialogWrapper::MSG_WARN:
			res_bgcolor = warncolor;
			break;
		case SdlConnectionDialogWrapper::MSG_ERROR:
			res_bgcolor = errorcolor;
			break;
		case SdlConnectionDialogWrapper::MSG_DISCARD:
		default:
			res_bgcolor = _backgroundcolor;
			break;
	}

#if defined(WITH_SDL_IMAGE_DIALOGS)
	std::string res_name;
	switch (_type_active)
	{
		case SdlConnectionDialogWrapper::MSG_INFO:
			res_name = "icon_info.svg";
			break;
		case SdlConnectionDialogWrapper::MSG_WARN:
			res_name = "icon_warning.svg";
			break;
		case SdlConnectionDialogWrapper::MSG_ERROR:
			res_name = "icon_error.svg";
			break;
		case SdlConnectionDialogWrapper::MSG_DISCARD:
		default:
			res_name = "";
			break;
	}

	const auto height = (total_height - 3.0f * vpadding) / 2.0f;
	SDL_FRect iconRect{ hpadding, vpadding, widget_width / 4.0f - 2.0f * hpadding, height };
	widget_cfg_t icon{ textcolor,
		               res_bgcolor,
		               { _renderer, iconRect,
		                 SDL3ResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
	_list.emplace_back(std::move(icon));

	iconRect.y += height;

	widget_cfg_t logo{ textcolor,
		               _backgroundcolor,
		               { _renderer, iconRect,
		                 SDL3ResourceManager::get(SDLResourceManager::typeImages(),
		                                          "FreeRDP_Icon.svg") } };
	_list.emplace_back(std::move(logo));

	SDL_FRect rect = { widget_width / 4.0f, vpadding, widget_width * 3.0f / 4.0f,
		               total_height - 3ul * vpadding - widget_height };
#else
	SDL_FRect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
		               total_height - 2ul * vpadding };
#endif

	widget_cfg_t w{ textcolor, _backgroundcolor, { _renderer, rect } };
	if (!w.widget.set_wrap(true, widget_width))
		return false;
	_list.emplace_back(std::move(w));
	rect.y += widget_height + vpadding;

	const std::vector<int> buttonids = { 1 };
	const std::vector<std::string> buttonlabels = { "cancel" };
	if (!_buttons.populate(_renderer, buttonlabels, buttonids, widget_width,
	                       total_height - widget_height - vpadding,
	                       static_cast<Sint32>(widget_width / 2),
	                       static_cast<Sint32>(widget_height)))
		return false;
	if (!_buttons.set_highlight(0))
		return false;

	if (!SDL_ShowWindow(_window.get()))
		return false;
	if (!SDL_RaiseWindow(_window.get()))
		return false;

	return true;
}

void SDLConnectionDialog::destroyWindow()
{
	_buttons.clear();
	_list.clear();
	_renderer = nullptr;
	_window = nullptr;
}

bool SDLConnectionDialog::show(SdlConnectionDialogWrapper::MsgType type, const char* fmt,
                               va_list ap)
{
	std::scoped_lock lock(_mux);
	_msg = print(fmt, ap);
	return show(type);
}

bool SDLConnectionDialog::show(SdlConnectionDialogWrapper::MsgType type)
{
	if (SDL_IsMainThread())
		return updateMsg(type);
	else
		return sdl_push_user_event(SDL_EVENT_USER_RETRY_DIALOG, type);
}

std::string SDLConnectionDialog::print(const char* fmt, va_list ap)
{
	int size = -1;
	std::string res;

	do
	{
		res.resize(128);
		if (size > 0)
			res.resize(WINPR_ASSERTING_INT_CAST(uint32_t, size));

		va_list copy = {};
		va_copy(copy, ap);
		WINPR_PRAGMA_DIAG_PUSH
		WINPR_PRAGMA_DIAG_IGNORED_FORMAT_NONLITERAL
		size = vsnprintf(res.data(), res.size(), fmt, copy);
		WINPR_PRAGMA_DIAG_POP
		va_end(copy);

	} while ((size > 0) && (static_cast<size_t>(size) > res.size()));

	return res;
}

bool SDLConnectionDialog::setTimer(Uint32 timeoutMS)
{
	std::scoped_lock lock(_mux);
	resetTimer();

	_timer = SDL_AddTimer(timeoutMS, &SDLConnectionDialog::timeout, this);
	_running = true;
	return true;
}

void SDLConnectionDialog::resetTimer()
{
	if (_running)
		SDL_RemoveTimer(_timer);
	_running = false;
}

Uint32 SDLConnectionDialog::timeout(void* pvthis, [[maybe_unused]] SDL_TimerID timerID,
                                    [[maybe_unused]] Uint32 intervalMS)
{
	auto self = static_cast<SDLConnectionDialog*>(pvthis);
	std::ignore = self->hide();
	self->_running = false;
	return 0;
}
