#pragma once

#include <SDL2/SDL.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_video.h>
#include <array>
#include <cmath>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>

#include "api/Camera.h"
#include "api/Color.h"
#include "api/KeyCodes.h"
#include "api/Sprite.h"
#include "api/Transform.h"

#include "types.h"

namespace crepe {

class Texture;
class Mediator;

/**
 * \brief Facade for the SDL library
 *
 * SDLContext is a singleton that handles the SDL window and renderer, provides methods for
 * event handling, and rendering to the screen. It is never used directly by the user
 */
class SDLContext {
public:
	//! data that the camera component cannot hold
	struct CameraAuxiliaryData {

		//! zoomed in viewport in game_units
		vec2 zoomed_viewport;

		/**
		 * \brief scaling factor
		 *
		 * depending on the black bars type will the scaling be different.
		 * - letterboxing --> scaling on the y-as
		 * - pillarboxing --> scaling on the x-as
		 */
		vec2 render_scale;

		/**
		 * \brief size of calculated black bars
		 *
		 * depending on the black bars type will the size be different
		 * - lettorboxing --> {0, bar_height}
		 * - pillarboxing --> {bar_width , 0}
		 */
		vec2 bar_size;

		//! Calculated camera position
		vec2 cam_pos;
	};

	//! rendering data needed to render on screen
	struct RenderContext {
		const Sprite & sprite;
		const Texture & texture;
		const vec2 & pos;
		const double & angle;
		const double & scale;
	};

public:
	//! EventType enum for passing eventType
	enum EventType {
		NONE = 0,
		MOUSEDOWN,
		MOUSEUP,
		MOUSEMOVE,
		MOUSEWHEEL,
		KEYUP,
		KEYDOWN,
		SHUTDOWN,
		WINDOW_MINIMIZE,
		WINDOW_MAXIMIZE,
		WINDOW_FOCUS_GAIN,
		WINDOW_FOCUS_LOST,
		WINDOW_MOVE,
		WINDOW_RESIZE,
		WINDOW_EXPOSE,
	};
	struct KeyData {
		Keycode key = Keycode::NONE;
		bool key_repeat = false;
	};
	struct MouseData {
		MouseButton mouse_button = MouseButton::NONE;
		ivec2 mouse_position = {-1, -1};
		int scroll_direction = -1;
		float scroll_delta = INFINITY;
		ivec2 rel_mouse_move = {-1, -1};
	};
	struct WindowData {
		ivec2 move_delta;
		ivec2 resize_dimension;
	};
	//! EventData struct for passing event data from facade
	struct EventData {
    SDLContext::EventType event_type = SDLContext::EventType::NONE;

    union EventDataUnion {
        KeyData key_data;
        MouseData mouse_data;
        WindowData window_data;

        EventDataUnion() {}
        ~EventDataUnion() {}
    } data;
    
    // Helper functions
    // bool isKeyEvent() const { return event_type == SDLContext::EventType::KEYDOWN || event_type == SDLContext::EventType::KEYUP; }
    // bool isMouseEvent() const { return event_type == SDLContext::EventType::MOUSEDOWN || event_type == SDLContext::EventType::MOUSEUP || event_type == SDLContext::EventType::MOUSEMOVE; }
    // bool isWindowEvent() const { return event_type == SDLContext::EventType::WINDOW_MINIMIZE || event_type == SDLContext::EventType::WINDOW_RESIZE; }
	};
	/**
	 * \brief Retrieves the current state of the keyboard.
	 *
	 * This method updates the state of all keys on the keyboard. Each element of the unordered map corresponds to a
	 * specific key defined in the `Keycode` enum, and the value indicates whether
	 * the key is currently pressed (true) or not pressed (false).
	 * 
	 */
	void update_keyboard_state();
	/**
	 * \brief Gets the singleton instance of SDLContext.
	 * \return Reference to the SDLContext instance.
	 */
	static SDLContext & get_instance();

public:
	SDLContext(const SDLContext &) = delete;
	SDLContext(SDLContext &&) = delete;
	SDLContext & operator=(const SDLContext &) = delete;
	SDLContext & operator=(SDLContext &&) = delete;

public:
	/**
	 * \brief Constructs an SDLContext instance.
	 * Initializes SDL, creates a window and renderer.
	 */
	SDLContext(Mediator & mediator);

	/**
	 * \brief Destroys the SDLContext instance.
	 * Cleans up SDL resources, including the window and renderer.
	 */
	~SDLContext();

public:
	/**
	 * \brief Retrieves a list of all events from the SDL context.
	 *
	 * This method retrieves all the events from the SDL context that are currently
	 * available. It is primarily used by the InputSystem to process various
	 * input events such as mouse clicks, mouse movements, and keyboard presses.
	 *
	 * \return Events that occurred since last call to `get_events()`
	 */
	std::vector<SDLContext::EventData> get_events();
	/**
	 * \brief Fills event_list with triggered window events
	 *
	 * This method checks if any window events are triggered and adds them to the event_list.
	 *
	 */
	void handle_window_event(const SDL_WindowEvent & window_event,
							 std::vector<SDLContext::EventData> & event_list);
	/**
	 * \brief Converts an SDL scan code to the custom Keycode type.
	 *
	 * This method maps an SDL scan code to the corresponding `Keycode` enum value,
	 * which is used internally by the system to identify the keys.
	 *
	 * \param sdl_key The SDL scan code to convert.
	 * \return The corresponding `Keycode` value or `Keycode::NONE` if the key is unrecognized.
	 */
	Keycode sdl_to_keycode(SDL_Scancode sdl_key);

	/**
	 * \brief Converts an SDL mouse button code to the custom MouseButton type.
	 *
	 * This method maps an SDL mouse button code to the corresponding `MouseButton`
	 * enum value, which is used internally by the system to identify mouse buttons.
	 *
	 * \param sdl_button The SDL mouse button code to convert.
	 * \return The corresponding `MouseButton` value or `MouseButton::NONE` if the key is unrecognized
	 */
	MouseButton sdl_to_mousebutton(Uint8 sdl_button);

public:
	/**
	 * \brief Gets the current SDL ticks since the program started.
	 * \return Current ticks in milliseconds as a constant uint64_t.
	 */
	uint64_t get_ticks() const;
	/**
	 * \brief Pauses the execution for a specified duration.
	 *
	 * This function uses SDL's delay function to halt the program execution for a given number
	 * of milliseconds, allowing for frame rate control or other timing-related functionality.
	 *
	 * \param ms Duration of the delay in milliseconds.
	 */
	void delay(int ms) const;

public:
	/**
	 * \brief Loads a texture from a file path.
	 * \param path Path to the image file.
	 * \return Pointer to the created SDL_Texture.
	 */
	std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>>
	texture_from_path(const std::string & path);
	/**
	 * \brief Gets the size of a texture.
	 * \param texture Reference to the Texture object.
	 * \return Width and height of the texture as an integer in pixels.
	 */
	ivec2 get_size(const Texture & ctx);

public:
	/**
	 * \brief Draws a sprite to the screen using the specified transform and camera.
	 * \param RenderContext Reference to rendering data to draw
	 */
	void draw(const RenderContext & ctx);

	//! Clears the screen, preparing for a new frame.
	void clear_screen();

	//! Presents the rendered frame to the screen.
	void present_screen();

	/**
	 * \brief calculates camera view settings. such as black_bars, zoomed_viewport, scaling and
	 * adjusting window size.
	 *
	 * \note only supports windowed mode.
	 * \param camera Reference to the current Camera object in the scene.
	 * \param new_pos new camera position from transform and offset
	 */
	void update_camera_view(const Camera & camera, const vec2 & new_pos);

public:
	//! the data needed to construct a sdl dst rectangle
	struct DestinationRectangleData {
		const Sprite & sprite;
		const Texture & texture;
		const vec2 & pos;
		const double & img_scale;
	};

	/**
	 * \brief calculates the sqaure size of the image for destination
	 *
	 * \param data needed to calculate a destination rectangle
	 * \return sdl rectangle to draw a dst image to draw on the screen
	 */
	SDL_FRect get_dst_rect(const DestinationRectangleData & data) const;
	/**
	 * \brief Set an additional color value multiplied into render copy operations.
	 *
	 * \param  texture the given texture to adjust
	 * \param  color the color data for the texture
	 */
	void set_color_texture(const Texture & texture, const Color & color);

private:
	//! sdl Window
	std::unique_ptr<SDL_Window, std::function<void(SDL_Window *)>> game_window;

	//! renderer for the crepe engine
	std::unique_ptr<SDL_Renderer, std::function<void(SDL_Renderer *)>> game_renderer;

	//! black bars rectangle to draw
	SDL_FRect black_bars[2] = {};

	/**
	 * \cam_aux_data extra data that the component cannot hold.
	 *
	 * - this is defined in this class because get_events() needs this information aswell
	 */
	CameraAuxiliaryData cam_aux_data;
private: 
	std::unordered_map<Keycode, bool> keyboard_state;
	const std::unordered_map<SDL_Scancode, Keycode> LOOKUP_TABLE = {
        {SDL_SCANCODE_SPACE, Keycode::SPACE},
        {SDL_SCANCODE_APOSTROPHE, Keycode::APOSTROPHE},
        {SDL_SCANCODE_COMMA, Keycode::COMMA},
        {SDL_SCANCODE_MINUS, Keycode::MINUS},
        {SDL_SCANCODE_PERIOD, Keycode::PERIOD},
        {SDL_SCANCODE_SLASH, Keycode::SLASH},
        {SDL_SCANCODE_0, Keycode::D0},
        {SDL_SCANCODE_1, Keycode::D1},
        {SDL_SCANCODE_2, Keycode::D2},
        {SDL_SCANCODE_3, Keycode::D3},
        {SDL_SCANCODE_4, Keycode::D4},
        {SDL_SCANCODE_5, Keycode::D5},
        {SDL_SCANCODE_6, Keycode::D6},
        {SDL_SCANCODE_7, Keycode::D7},
        {SDL_SCANCODE_8, Keycode::D8},
        {SDL_SCANCODE_9, Keycode::D9},
        {SDL_SCANCODE_SEMICOLON, Keycode::SEMICOLON},
        {SDL_SCANCODE_EQUALS, Keycode::EQUAL},
        {SDL_SCANCODE_A, Keycode::A},
        {SDL_SCANCODE_B, Keycode::B},
        {SDL_SCANCODE_C, Keycode::C},
        {SDL_SCANCODE_D, Keycode::D},
        {SDL_SCANCODE_E, Keycode::E},
        {SDL_SCANCODE_F, Keycode::F},
        {SDL_SCANCODE_G, Keycode::G},
        {SDL_SCANCODE_H, Keycode::H},
        {SDL_SCANCODE_I, Keycode::I},
        {SDL_SCANCODE_J, Keycode::J},
        {SDL_SCANCODE_K, Keycode::K},
        {SDL_SCANCODE_L, Keycode::L},
        {SDL_SCANCODE_M, Keycode::M},
        {SDL_SCANCODE_N, Keycode::N},
        {SDL_SCANCODE_O, Keycode::O},
        {SDL_SCANCODE_P, Keycode::P},
        {SDL_SCANCODE_Q, Keycode::Q},
        {SDL_SCANCODE_R, Keycode::R},
        {SDL_SCANCODE_S, Keycode::S},
        {SDL_SCANCODE_T, Keycode::T},
        {SDL_SCANCODE_U, Keycode::U},
        {SDL_SCANCODE_V, Keycode::V},
        {SDL_SCANCODE_W, Keycode::W},
        {SDL_SCANCODE_X, Keycode::X},
        {SDL_SCANCODE_Y, Keycode::Y},
        {SDL_SCANCODE_Z, Keycode::Z},
        {SDL_SCANCODE_LEFTBRACKET, Keycode::LEFT_BRACKET},
        {SDL_SCANCODE_BACKSLASH, Keycode::BACKSLASH},
        {SDL_SCANCODE_RIGHTBRACKET, Keycode::RIGHT_BRACKET},
        {SDL_SCANCODE_GRAVE, Keycode::GRAVE_ACCENT},
        {SDL_SCANCODE_ESCAPE, Keycode::ESCAPE},
        {SDL_SCANCODE_RETURN, Keycode::ENTER},
        {SDL_SCANCODE_TAB, Keycode::TAB},
        {SDL_SCANCODE_BACKSPACE, Keycode::BACKSPACE},
        {SDL_SCANCODE_INSERT, Keycode::INSERT},
        {SDL_SCANCODE_DELETE, Keycode::DELETE},
        {SDL_SCANCODE_RIGHT, Keycode::RIGHT},
        {SDL_SCANCODE_LEFT, Keycode::LEFT},
        {SDL_SCANCODE_DOWN, Keycode::DOWN},
        {SDL_SCANCODE_UP, Keycode::UP},
        {SDL_SCANCODE_PAGEUP, Keycode::PAGE_UP},
        {SDL_SCANCODE_PAGEDOWN, Keycode::PAGE_DOWN},
        {SDL_SCANCODE_HOME, Keycode::HOME},
        {SDL_SCANCODE_END, Keycode::END},
        {SDL_SCANCODE_CAPSLOCK, Keycode::CAPS_LOCK},
        {SDL_SCANCODE_SCROLLLOCK, Keycode::SCROLL_LOCK},
        {SDL_SCANCODE_NUMLOCKCLEAR, Keycode::NUM_LOCK},
        {SDL_SCANCODE_PRINTSCREEN, Keycode::PRINT_SCREEN},
        {SDL_SCANCODE_PAUSE, Keycode::PAUSE},
        {SDL_SCANCODE_F1, Keycode::F1},
        {SDL_SCANCODE_F2, Keycode::F2},
        {SDL_SCANCODE_F3, Keycode::F3},
        {SDL_SCANCODE_F4, Keycode::F4},
        {SDL_SCANCODE_F5, Keycode::F5},
        {SDL_SCANCODE_F6, Keycode::F6},
        {SDL_SCANCODE_F7, Keycode::F7},
        {SDL_SCANCODE_F8, Keycode::F8},
        {SDL_SCANCODE_F9, Keycode::F9},
        {SDL_SCANCODE_F10, Keycode::F10},
        {SDL_SCANCODE_F11, Keycode::F11},
        {SDL_SCANCODE_F12, Keycode::F12},
        {SDL_SCANCODE_KP_0, Keycode::KP0},
        {SDL_SCANCODE_KP_1, Keycode::KP1},
        {SDL_SCANCODE_KP_2, Keycode::KP2},
        {SDL_SCANCODE_KP_3, Keycode::KP3},
        {SDL_SCANCODE_KP_4, Keycode::KP4},
        {SDL_SCANCODE_KP_5, Keycode::KP5},
        {SDL_SCANCODE_KP_6, Keycode::KP6},
        {SDL_SCANCODE_KP_7, Keycode::KP7},
        {SDL_SCANCODE_KP_8, Keycode::KP8},
        {SDL_SCANCODE_KP_9, Keycode::KP9},
        {SDL_SCANCODE_LSHIFT, Keycode::LEFT_SHIFT},
        {SDL_SCANCODE_LCTRL, Keycode::LEFT_CONTROL},
        {SDL_SCANCODE_LALT, Keycode::LEFT_ALT},
        {SDL_SCANCODE_LGUI, Keycode::LEFT_SUPER},
        {SDL_SCANCODE_RSHIFT, Keycode::RIGHT_SHIFT},
        {SDL_SCANCODE_RCTRL, Keycode::RIGHT_CONTROL},
        {SDL_SCANCODE_RALT, Keycode::RIGHT_ALT},
        {SDL_SCANCODE_RGUI, Keycode::RIGHT_SUPER},
        {SDL_SCANCODE_MENU, Keycode::MENU}
    };
};

} // namespace crepe