/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * SDL Client
 *
 * Copyright 2022 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 <sstream>
#include <iomanip>
#include <random>

#include "sdl_utils.hpp"

#include "sdl_freerdp.hpp"

#include <SDL3/SDL.h>

#include <freerdp/version.h>

#define STR(x) #x
#define EV_CASE_STR(x) \
	case x:            \
		return STR(x)

const char* sdl_event_type_str(Uint32 type)
{
	switch (type)
	{
		EV_CASE_STR(SDL_EVENT_FIRST);
		EV_CASE_STR(SDL_EVENT_QUIT);
		EV_CASE_STR(SDL_EVENT_TERMINATING);
		EV_CASE_STR(SDL_EVENT_LOW_MEMORY);
		EV_CASE_STR(SDL_EVENT_WILL_ENTER_BACKGROUND);
		EV_CASE_STR(SDL_EVENT_DID_ENTER_BACKGROUND);
		EV_CASE_STR(SDL_EVENT_WILL_ENTER_FOREGROUND);
		EV_CASE_STR(SDL_EVENT_DID_ENTER_FOREGROUND);
		EV_CASE_STR(SDL_EVENT_LOCALE_CHANGED);
		EV_CASE_STR(SDL_EVENT_SYSTEM_THEME_CHANGED);
		EV_CASE_STR(SDL_EVENT_DISPLAY_ORIENTATION);
		EV_CASE_STR(SDL_EVENT_DISPLAY_ADDED);
		EV_CASE_STR(SDL_EVENT_DISPLAY_REMOVED);
		EV_CASE_STR(SDL_EVENT_DISPLAY_MOVED);
		EV_CASE_STR(SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED);
		EV_CASE_STR(SDL_EVENT_WINDOW_SHOWN);
		EV_CASE_STR(SDL_EVENT_WINDOW_HIDDEN);
		EV_CASE_STR(SDL_EVENT_WINDOW_EXPOSED);
		EV_CASE_STR(SDL_EVENT_WINDOW_MOVED);
		EV_CASE_STR(SDL_EVENT_WINDOW_RESIZED);
		EV_CASE_STR(SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED);
		EV_CASE_STR(SDL_EVENT_WINDOW_MINIMIZED);
		EV_CASE_STR(SDL_EVENT_WINDOW_MAXIMIZED);
		EV_CASE_STR(SDL_EVENT_WINDOW_RESTORED);
		EV_CASE_STR(SDL_EVENT_WINDOW_MOUSE_ENTER);
		EV_CASE_STR(SDL_EVENT_WINDOW_MOUSE_LEAVE);
		EV_CASE_STR(SDL_EVENT_WINDOW_FOCUS_GAINED);
		EV_CASE_STR(SDL_EVENT_WINDOW_FOCUS_LOST);
		EV_CASE_STR(SDL_EVENT_WINDOW_CLOSE_REQUESTED);
		EV_CASE_STR(SDL_EVENT_WINDOW_HIT_TEST);
		EV_CASE_STR(SDL_EVENT_WINDOW_ICCPROF_CHANGED);
		EV_CASE_STR(SDL_EVENT_WINDOW_DISPLAY_CHANGED);
		EV_CASE_STR(SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED);
		EV_CASE_STR(SDL_EVENT_WINDOW_OCCLUDED);
		EV_CASE_STR(SDL_EVENT_WINDOW_ENTER_FULLSCREEN);
		EV_CASE_STR(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN);
		EV_CASE_STR(SDL_EVENT_WINDOW_DESTROYED);

		EV_CASE_STR(SDL_EVENT_KEY_DOWN);
		EV_CASE_STR(SDL_EVENT_KEY_UP);
		EV_CASE_STR(SDL_EVENT_TEXT_EDITING);
		EV_CASE_STR(SDL_EVENT_TEXT_INPUT);
		EV_CASE_STR(SDL_EVENT_KEYMAP_CHANGED);
		EV_CASE_STR(SDL_EVENT_KEYBOARD_ADDED);
		EV_CASE_STR(SDL_EVENT_KEYBOARD_REMOVED);

		EV_CASE_STR(SDL_EVENT_MOUSE_MOTION);
		EV_CASE_STR(SDL_EVENT_MOUSE_BUTTON_DOWN);
		EV_CASE_STR(SDL_EVENT_MOUSE_BUTTON_UP);
		EV_CASE_STR(SDL_EVENT_MOUSE_WHEEL);
		EV_CASE_STR(SDL_EVENT_MOUSE_ADDED);
		EV_CASE_STR(SDL_EVENT_MOUSE_REMOVED);

		EV_CASE_STR(SDL_EVENT_JOYSTICK_AXIS_MOTION);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_BALL_MOTION);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_HAT_MOTION);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_BUTTON_DOWN);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_BUTTON_UP);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_ADDED);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_REMOVED);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_BATTERY_UPDATED);
		EV_CASE_STR(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE);

		EV_CASE_STR(SDL_EVENT_GAMEPAD_AXIS_MOTION);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_BUTTON_DOWN);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_BUTTON_UP);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_ADDED);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_REMOVED);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_REMAPPED);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_TOUCHPAD_UP);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_SENSOR_UPDATE);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_UPDATE_COMPLETE);
		EV_CASE_STR(SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED);

		EV_CASE_STR(SDL_EVENT_FINGER_DOWN);
		EV_CASE_STR(SDL_EVENT_FINGER_UP);
		EV_CASE_STR(SDL_EVENT_FINGER_MOTION);

		EV_CASE_STR(SDL_EVENT_CLIPBOARD_UPDATE);

		EV_CASE_STR(SDL_EVENT_DROP_FILE);
		EV_CASE_STR(SDL_EVENT_DROP_TEXT);
		EV_CASE_STR(SDL_EVENT_DROP_BEGIN);
		EV_CASE_STR(SDL_EVENT_DROP_COMPLETE);
		EV_CASE_STR(SDL_EVENT_DROP_POSITION);

		EV_CASE_STR(SDL_EVENT_AUDIO_DEVICE_ADDED);
		EV_CASE_STR(SDL_EVENT_AUDIO_DEVICE_REMOVED);
		EV_CASE_STR(SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED);

		EV_CASE_STR(SDL_EVENT_SENSOR_UPDATE);

		EV_CASE_STR(SDL_EVENT_PEN_DOWN);
		EV_CASE_STR(SDL_EVENT_PEN_UP);
		EV_CASE_STR(SDL_EVENT_PEN_MOTION);
		EV_CASE_STR(SDL_EVENT_PEN_BUTTON_DOWN);
		EV_CASE_STR(SDL_EVENT_PEN_BUTTON_UP);
		EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_ADDED);
		EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_REMOVED);
		EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_APPROVED);
		EV_CASE_STR(SDL_EVENT_CAMERA_DEVICE_DENIED);

		EV_CASE_STR(SDL_EVENT_RENDER_TARGETS_RESET);
		EV_CASE_STR(SDL_EVENT_RENDER_DEVICE_RESET);
		EV_CASE_STR(SDL_EVENT_POLL_SENTINEL);

		EV_CASE_STR(SDL_EVENT_USER);

		EV_CASE_STR(SDL_EVENT_USER_CERT_DIALOG);
		EV_CASE_STR(SDL_EVENT_USER_CERT_RESULT);
		EV_CASE_STR(SDL_EVENT_USER_SHOW_DIALOG);
		EV_CASE_STR(SDL_EVENT_USER_SHOW_RESULT);
		EV_CASE_STR(SDL_EVENT_USER_AUTH_DIALOG);
		EV_CASE_STR(SDL_EVENT_USER_AUTH_RESULT);
		EV_CASE_STR(SDL_EVENT_USER_SCARD_DIALOG);
		EV_CASE_STR(SDL_EVENT_USER_RETRY_DIALOG);
		EV_CASE_STR(SDL_EVENT_USER_SCARD_RESULT);
		EV_CASE_STR(SDL_EVENT_USER_UPDATE);
		EV_CASE_STR(SDL_EVENT_USER_CREATE_WINDOWS);
		EV_CASE_STR(SDL_EVENT_USER_WINDOW_RESIZEABLE);
		EV_CASE_STR(SDL_EVENT_USER_WINDOW_FULLSCREEN);
		EV_CASE_STR(SDL_EVENT_USER_WINDOW_MINIMIZE);
		EV_CASE_STR(SDL_EVENT_USER_POINTER_NULL);
		EV_CASE_STR(SDL_EVENT_USER_POINTER_DEFAULT);
		EV_CASE_STR(SDL_EVENT_USER_POINTER_POSITION);
		EV_CASE_STR(SDL_EVENT_USER_POINTER_SET);
		EV_CASE_STR(SDL_EVENT_USER_QUIT);

		EV_CASE_STR(SDL_EVENT_LAST);
		default:
			return "SDL_UNKNOWNEVENT";
	}
}

const char* sdl_error_string(Sint32 res)
{
	if (res == 0)
		return nullptr;

	return SDL_GetError();
}

BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line,
                      const char* fkt)
{
	const char* msg = sdl_error_string(res);

	WINPR_UNUSED(file);

	if (!msg)
		return FALSE;

	WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, msg);
	return TRUE;
}

bool sdl_push_user_event(Uint32 type, ...)
{
	SDL_Event ev = {};
	SDL_UserEvent* event = &ev.user;

	va_list ap = {};
	va_start(ap, type);
	event->type = type;
	switch (type)
	{
		case SDL_EVENT_USER_AUTH_RESULT:
		{
			auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);
			arg->user = va_arg(ap, char*);
			arg->domain = va_arg(ap, char*);
			arg->password = va_arg(ap, char*);
			arg->result = va_arg(ap, Sint32);
		}
		break;
		case SDL_EVENT_USER_AUTH_DIALOG:
		{
			auto arg = reinterpret_cast<SDL_UserAuthArg*>(ev.padding);

			arg->title = va_arg(ap, char*);
			arg->user = va_arg(ap, char*);
			arg->domain = va_arg(ap, char*);
			arg->password = va_arg(ap, char*);
			arg->result = va_arg(ap, Sint32);
		}
		break;
		case SDL_EVENT_USER_SCARD_DIALOG:
		{
			event->data1 = va_arg(ap, char*);
			event->data2 = va_arg(ap, char**);
			event->code = va_arg(ap, Sint32);
		}
		break;
		case SDL_EVENT_USER_RETRY_DIALOG:
			event->code = va_arg(ap, Sint32);
			break;
		case SDL_EVENT_USER_SCARD_RESULT:
		case SDL_EVENT_USER_SHOW_RESULT:
		case SDL_EVENT_USER_CERT_RESULT:
			event->code = va_arg(ap, Sint32);
			break;

		case SDL_EVENT_USER_SHOW_DIALOG:
			event->data1 = va_arg(ap, char*);
			event->data2 = va_arg(ap, char*);
			event->code = va_arg(ap, Sint32);
			break;
		case SDL_EVENT_USER_CERT_DIALOG:
			event->data1 = va_arg(ap, char*);
			event->data2 = va_arg(ap, char*);
			break;
		case SDL_EVENT_USER_UPDATE:
			event->data1 = va_arg(ap, void*);
			break;
		case SDL_EVENT_USER_POINTER_POSITION:
			event->data1 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
			event->data2 = reinterpret_cast<void*>(static_cast<uintptr_t>(va_arg(ap, UINT32)));
			break;
		case SDL_EVENT_USER_POINTER_SET:
			event->data1 = va_arg(ap, void*);
			event->data2 = va_arg(ap, void*);
			break;
		case SDL_EVENT_USER_CREATE_WINDOWS:
			event->data1 = va_arg(ap, void*);
			break;
		case SDL_EVENT_USER_WINDOW_FULLSCREEN:
		case SDL_EVENT_USER_WINDOW_RESIZEABLE:
			event->data1 = va_arg(ap, void*);
			event->code = va_arg(ap, int);
			break;
		case SDL_EVENT_USER_WINDOW_MINIMIZE:
		case SDL_EVENT_USER_QUIT:
		case SDL_EVENT_USER_POINTER_NULL:
		case SDL_EVENT_USER_POINTER_DEFAULT:
		case SDL_EVENT_CLIPBOARD_UPDATE:
			break;
		default:
			va_end(ap);
			SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[%s] unsupported type %u", __func__, type);
			return false;
	}
	va_end(ap);
	return SDL_PushEvent(&ev) == 1;
}

bool sdl_push_quit()
{
	SDL_Event ev = {};
	ev.type = SDL_EVENT_QUIT;
	SDL_PushEvent(&ev);
	return true;
}

std::string sdl_window_event_str(Uint32 ev)
{
	if ((ev >= SDL_EVENT_WINDOW_FIRST) && (ev <= SDL_EVENT_WINDOW_LAST))
		return sdl_event_type_str(ev);

	return "SDL_EVENT_WINDOW_UNKNOWN";
}

UINT32 sdl::utils::orientaion_to_rdp(SDL_DisplayOrientation orientation)
{
	switch (orientation)
	{
		case SDL_ORIENTATION_LANDSCAPE:
			return ORIENTATION_LANDSCAPE;
		case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
			return ORIENTATION_LANDSCAPE_FLIPPED;
		case SDL_ORIENTATION_PORTRAIT_FLIPPED:
			return ORIENTATION_PORTRAIT_FLIPPED;
		case SDL_ORIENTATION_PORTRAIT:
		default:
			return ORIENTATION_PORTRAIT;
	}
}

std::string sdl::utils::sdl_orientation_to_str(SDL_DisplayOrientation orientation)
{
	switch (orientation)
	{
		case SDL_ORIENTATION_LANDSCAPE:
			return "SDL_ORIENTATION_LANDSCAPE";
		case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
			return "SDL_ORIENTATION_LANDSCAPE_FLIPPED";
		case SDL_ORIENTATION_PORTRAIT_FLIPPED:
			return "SDL_ORIENTATION_PORTRAIT_FLIPPED";
		case SDL_ORIENTATION_PORTRAIT:
			return "SDL_ORIENTATION_PORTRAIT";
		default:
			return "SDL_ORIENTATION_UNKNOWN";
	}
}

std::string sdl::utils::rdp_orientation_to_str(uint32_t orientation)
{
	switch (orientation)
	{
		case ORIENTATION_LANDSCAPE:
			return "ORIENTATION_LANDSCAPE";
		case ORIENTATION_LANDSCAPE_FLIPPED:
			return "ORIENTATION_LANDSCAPE_FLIPPED";
		case ORIENTATION_PORTRAIT_FLIPPED:
			return "ORIENTATION_PORTRAIT_FLIPPED";
		case ORIENTATION_PORTRAIT:
			return "ORIENTATION_PORTRAIT";
		default:
		{
			std::stringstream ss;
			ss << "ORIENTATION_UNKNOWN_" << std::hex << std::setfill('0') << std::setw(8)
			   << orientation;
			return ss.str();
		}
	}
}

std::string sdl::utils::generate_uuid_v4()
{
	static std::random_device rd;
	static std::mt19937 gen(rd());
	static std::uniform_int_distribution<> dis(0, 255);
	std::stringstream ss;
	ss << std::hex << std::setfill('0') << std::setw(2);
	for (int i = 0; i < 4; i++)
	{
		ss << dis(gen);
	}
	ss << "-";
	for (int i = 0; i < 2; i++)
	{
		ss << dis(gen);
	}
	ss << "-";
	for (int i = 0; i < 2; i++)
	{
		ss << dis(gen);
	}
	ss << "-";
	for (int i = 0; i < 2; i++)
	{
		ss << dis(gen);
	}
	ss << "-";
	for (int i = 0; i < 6; i++)
	{
		ss << dis(gen);
	}
	return ss.str();
}
