/*
PolyGun

Copyright (c) 2023 mrkubax10 <mrkubax10@onet.pl>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#ifndef POLYGUN_WINDOW_WAYLAND_WINDOW_HPP
#define POLYGUN_WINDOW_WAYLAND_WINDOW_HPP

#include "game/window/window.hpp"

#include <stdexcept>
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <wayland-relative-pointer-unstable-v1.h>
#include <wayland-pointer-constraints-unstable-v1.h>
#include <libdecor-0/libdecor.h>
#include <xkbcommon/xkbcommon.h>
#if defined(RENDERER_GL)
#include <wayland-egl.h>
#include "game/renderer/gl/egl_gl_context.hpp"
#endif

namespace polygun::window {
	class WaylandWindow final : public Window {
	public:
		WaylandWindow(const std::string& title, unsigned width, unsigned height, renderer::RendererType renderer_type);
		~WaylandWindow() override;

		struct wl_display* get_display() { return m_display; }

		virtual size_t poll_events(Event& event) override;
		virtual void finish_frame() override;
		virtual void grab_mouse(bool grab) override;
#if defined(RENDERER_GL)
		virtual bool window_creates_gl_context() const override { return true; }
		virtual renderer::GLContext* get_window_gl_context() override { return m_egl_context; }
#endif

	private:
		unsigned m_width;
		unsigned m_height;
#if defined(RENDERER_GL)
		renderer::EGLGLContext* m_egl_context;
		struct wl_egl_window* m_egl_window;
#endif
		struct wl_display* m_display;
		struct wl_registry* m_registry;
		struct wl_compositor* m_compositor;
		struct wl_surface* m_surface;
		struct wl_seat* m_seat;
		struct wl_pointer* m_mouse;
		struct wl_keyboard* m_keyboard;
		struct wl_shm* m_shm;
		struct wl_cursor_theme* m_mouse_theme;
		struct wl_cursor* m_default_mouse;
		struct wl_surface* m_mouse_surface;
		struct zwp_relative_pointer_manager_v1* m_relative_mouse_manager;
		struct zwp_relative_pointer_v1* m_relative_mouse;
		struct zwp_pointer_constraints_v1* m_mouse_constraints;
		struct zwp_locked_pointer_v1* m_locked_mouse;
		bool m_configured;
		bool m_mouse_inside_surface;
		bool m_was_mouse_grabbed;
		uint32_t m_mouse_serial;
		struct libdecor* m_libdecor_context;
		struct libdecor_frame* m_frame;
		struct xkb_keymap* m_keymap;
		struct xkb_state* m_xkb_state;

	private:
		void apply_mouse_theme();
		static void handle_registry(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version);
		static void handle_remove_registry(void* data, struct wl_registry* registry, uint32_t name);
		static void handle_seat_capabilities(void* data, struct wl_seat* seat, uint32_t capability);
		static void handle_keyboard_keymap(void* data, struct wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size);
		static void handle_keyboard_enter(void* data, struct wl_keyboard* keyboard, uint32_t serial, struct wl_surface* surface, struct wl_array* keys);
		static void handle_keyboard_leave(void* data, struct wl_keyboard* keyboard, uint32_t serial, struct wl_surface* surface);
		static void handle_keyboard_key(void* data, struct wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
		static void handle_keyboard_modifiers(void* data, struct wl_keyboard* keyboard, uint32_t serial, uint32_t mods_unpressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group);
		static void handle_pointer_enter(void* data, struct wl_pointer* mouse, uint32_t serial, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y);
		static void handle_pointer_leave(void* data, struct wl_pointer* mouse, uint32_t serial, struct wl_surface* surface);
		static void handle_pointer_motion(void* data, struct wl_pointer* mouse, uint32_t time, wl_fixed_t x, wl_fixed_t y);
		static void handle_pointer_button(void* data, struct wl_pointer* mouse, uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
		static void handle_pointer_axis(void* data, struct wl_pointer* mouse, uint32_t time, uint32_t axis, wl_fixed_t value);
		static void handle_relative_pointer_motion(void* data, struct zwp_relative_pointer_v1* relative_mouse, uint32_t time_hi, uint32_t time_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t acc_x, wl_fixed_t acc_y);
		static void handle_libdecor_error(struct libdecor* context, enum libdecor_error err, const char* message);
		static void configure_frame(struct libdecor_frame* frame, struct libdecor_configuration* config, void* data);
		static void close_frame(struct libdecor_frame* frame, void* data);
		static void commit_frame(struct libdecor_frame* frame, void* data);
	};
}

#endif // POLYGUN_WINDOW_WAYLAND_WINDOW_HPP
