/**
 * 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_freerdp.hpp"
#include "res/sdl2_resource_manager.hpp"

static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff };
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)
{
	SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);
	hide();
}

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

bool SDLConnectionDialog::visible() const
{
	if (!_window || !_renderer)
		return false;

	auto flags = SDL_GetWindowFlags(_window);
	return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
}

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(MSG_NONE);
}

bool SDLConnectionDialog::showInfo(const char* fmt, ...)
{
	va_list ap = {};
	va_start(ap, fmt);
	auto rc = show(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(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(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(MSG_DISCARD);
}

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

bool SDLConnectionDialog::update()
{
	std::scoped_lock lock(_mux);
	switch (_type)
	{
		case MSG_INFO:
		case MSG_WARN:
		case MSG_ERROR:
			_type_active = _type;
			createWindow();
			break;
		case MSG_DISCARD:
			resetTimer();
			destroyWindow();
			break;
		default:
			if (_window)
			{
				SDL_SetWindowTitle(_window, _title.c_str());
			}
			break;
	}
	_type = MSG_NONE;
	return true;
}

bool SDLConnectionDialog::setModal()
{
	if (_window)
	{
		auto sdl = get_context(_context);
		if (sdl->windows.empty())
			return true;

		auto parent = sdl->windows.begin()->second.window();
		SDL_SetWindowModalFor(_window, parent);
		SDL_RaiseWindow(_window);
	}
	return true;
}

bool SDLConnectionDialog::clearWindow(SDL_Renderer* renderer)
{
	assert(renderer);

	const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g,
	                                       backgroundcolor.b, backgroundcolor.a);
	if (widget_log_error(drc, "SDL_SetRenderDrawColor"))
		return false;

	const int rcls = SDL_RenderClear(renderer);
	return !widget_log_error(rcls, "SDL_RenderClear");
}

bool SDLConnectionDialog::update(SDL_Renderer* renderer)
{
	std::scoped_lock lock(_mux);
	if (!renderer)
		return false;

	if (!clearWindow(renderer))
		return false;

	for (auto& btn : _list)
	{
		if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor))
			return false;
	}

	if (!_buttons.update(renderer))
		return false;

	SDL_RenderPresent(renderer);
	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);
	}

	switch (event.type)
	{
		case SDL_USEREVENT_RETRY_DIALOG:
			return update();
		case SDL_QUIT:
			resetTimer();
			destroyWindow();
			return false;
		case SDL_KEYDOWN:
		case SDL_KEYUP:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_KeyboardEvent&>(event);
				update(_renderer);
				switch (event.key.keysym.sym)
				{
					case SDLK_RETURN:
					case SDLK_RETURN2:
					case SDLK_ESCAPE:
					case SDLK_KP_ENTER:
						if (event.type == SDL_KEYUP)
						{
							freerdp_abort_connect_context(_context);
							sdl_push_quit();
						}
						break;
					case SDLK_TAB:
						_buttons.set_highlight_next();
						break;
					default:
						break;
				}

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

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

				auto button = _buttons.get_selected(event.button);
				if (button)
				{
					if (event.type == SDL_MOUSEBUTTONUP)
					{
						freerdp_abort_connect_context(_context);
						sdl_push_quit();
					}
				}

				return windowID == ev.windowID;
			}
			return false;
		case SDL_MOUSEWHEEL:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_MouseWheelEvent&>(event);
				update(_renderer);
				return windowID == ev.windowID;
			}
			return false;
		case SDL_FINGERUP:
		case SDL_FINGERDOWN:
			if (visible())
			{
				auto& ev = reinterpret_cast<const SDL_TouchFingerEvent&>(event);
				update(_renderer);
#if SDL_VERSION_ATLEAST(2, 0, 18)
				return windowID == ev.windowID;
#else
				return false;
#endif
			}
			return false;
		case SDL_WINDOWEVENT:
		{
			auto& ev = reinterpret_cast<const SDL_WindowEvent&>(event);
			switch (ev.event)
			{
				case SDL_WINDOWEVENT_CLOSE:
					if (windowID == ev.windowID)
					{
						freerdp_abort_connect_context(_context);
						sdl_push_quit();
					}
					break;
				default:
					update(_renderer);
					setModal();
					break;
			}

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

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

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

	auto flags = WINPR_ASSERTING_INT_CAST(
	    uint32_t, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS);
	auto rc = SDL_CreateWindowAndRenderer(widget_width, total_height, flags, &_window, &_renderer);
	if (rc != 0)
	{
		widget_log_error(rc, "SDL_CreateWindowAndRenderer");
		return false;
	}
	SDL_SetWindowTitle(_window, _title.c_str());
	setModal();

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

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

	int height = (total_height - 3ul * vpadding) / 2ul;
	SDL_Rect iconRect{ hpadding, vpadding, widget_width / 4ul - 2ul * hpadding, height };
	widget_cfg_t icon{ textcolor,
		               res_bgcolor,
		               { _renderer, iconRect,
		                 SDL2ResourceManager::get(SDLResourceManager::typeImages(), res_name) } };
	_list.emplace_back(std::move(icon));

	iconRect.y += height;

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

	SDL_Rect rect = { widget_width / 4ul, vpadding, widget_width * 3ul / 4ul,
		              total_height - 3ul * vpadding - widget_height };
#else
	SDL_Rect rect = { hpadding, vpadding, widget_width - 2ul * hpadding,
		              total_height - 2ul * vpadding };
#endif

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

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

	SDL_ShowWindow(_window);
	SDL_RaiseWindow(_window);

	return true;
}

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

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

bool SDLConnectionDialog::show(MsgType type)
{
	_type = type;
	return sdl_push_user_event(SDL_USEREVENT_RETRY_DIALOG);
}

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(size_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([[maybe_unused]] Uint32 intervalMS, void* pvthis)
{
	auto self = static_cast<SDLConnectionDialog*>(pvthis);
	self->hide();
	self->_running = false;
	return 0;
}

SDLConnectionDialogHider::SDLConnectionDialogHider(freerdp* instance)
    : SDLConnectionDialogHider(get(instance))
{
}

SDLConnectionDialogHider::SDLConnectionDialogHider(rdpContext* context)
    : SDLConnectionDialogHider(get(context))
{
}

SDLConnectionDialogHider::SDLConnectionDialogHider(SDLConnectionDialog* dialog) : _dialog(dialog)
{
	if (_dialog)
	{
		_visible = _dialog->visible();
		if (_visible)
		{
			_dialog->hide();
		}
	}
}

SDLConnectionDialogHider::~SDLConnectionDialogHider()
{
	if (_dialog && _visible)
	{
		_dialog->show();
	}
}

SDLConnectionDialog* SDLConnectionDialogHider::get(freerdp* instance)
{
	if (!instance)
		return nullptr;
	return get(instance->context);
}

SDLConnectionDialog* SDLConnectionDialogHider::get(rdpContext* context)
{
	auto sdl = get_context(context);
	if (!sdl)
		return nullptr;
	return sdl->connection_dialog.get();
}
