#include "ehs/io/Window_Way.h"

#include <unistd.h>
#include <sys/mman.h>

#include "ehs/io/Console.h"
#include "ehs/io/xdg-shell-client-protocol.h"

namespace ehs
{
	void Window::SurfaceConfigEvent(void* data, xdg_surface* xdg_surface, UInt_32 serial)
	{
		xdg_surface_ack_configure(xdg_surface, serial);

		//Window* win = (Window *)data;

		//wl_surface_commit(win->wlSurface);
	}

	void Window::ShellPing(void* data, xdg_wm_base* shell, UInt_32 serial)
	{
		xdg_wm_base_pong(shell, serial);
	}

	void Window::RegistryHandler(void* data, wl_registry* registry, UInt_32 id, const char* interface, UInt_32 version)
	{
		Window *win = (Window *)data;

		if (Str_8::Cmp(interface, wl_compositor_interface.name))
			win->compositor = (wl_compositor*)wl_registry_bind(registry, id, &wl_compositor_interface, 1);
		else if (Str_8::Cmp(interface, xdg_wm_base_interface.name))
			win->xdgShell = (xdg_wm_base*)wl_registry_bind(registry, id, &xdg_wm_base_interface, 1);
		else if (Str_8::Cmp(interface, zxdg_decoration_manager_v1_interface.name))
			win->decManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1);
		else if (Str_8::Cmp(interface, wl_seat_interface.name))
			win->seat = (wl_seat *)wl_registry_bind(registry, id, &wl_seat_interface, 1);
	}

	void Window::RegistryRemoved(void* data, wl_registry* registry, UInt_32 id)
	{
	}

	void Window::ResizeEvent(void *data, struct xdg_toplevel *xdg_toplevel, Int_32 width, Int_32 height, wl_array *states)
	{
		if (!width && !height)
			return;

		Window *win = (Window *)data;

		win->scale = {(UInt_32)width, (UInt_32)height};
	}

	void Window::CloseEvent(void *data, xdg_toplevel *xdg_toplevel)
	{
		Window *win = (Window *)data;
		win->Close();
	}

	void Window::SeatCapabilitiesEvent(void *data, wl_seat *seat, UInt_32 capabilities)
	{
		Window *win = (Window *)data;

		if (capabilities & WL_SEAT_CAPABILITY_POINTER)
		{
			win->pointer = wl_seat_get_pointer(seat);

			static const wl_pointer_listener pointer_listener = {
				.motion = PointerMotionEvent
			};

			wl_pointer_add_listener(win->pointer, &pointer_listener, win);
		}
	}

	void Window::PointerMotionEvent(void *data, wl_pointer *pointer, UInt_32 time, wl_fixed_t sx, wl_fixed_t sy)
	{
		EHS_LOG_INT(LogType::INFO, 0, "Pointer Pos: <" + Str_8::FromNum(wl_fixed_to_double(sx)) + ", " + Str_8::FromNum(wl_fixed_to_double(sy)) + ">");
	}

	Window::~Window()
	{
		if (!created)
			return;

		zxdg_toplevel_decoration_v1_destroy(dec);
		zxdg_decoration_manager_v1_destroy(decManager);
		xdg_toplevel_destroy(xdgToplevel);
		xdg_surface_destroy(xdgSurface);
		xdg_wm_base_destroy(xdgShell);
		wl_surface_destroy(wlSurface);
		wl_compositor_destroy(compositor);
		wl_registry_destroy(registry);
		wl_display_disconnect(display);
	}

	Window::Window()
		: display(nullptr), registry(nullptr), compositor(nullptr), wlSurface(nullptr), xdgShell(nullptr),
		xdgSurface(nullptr), xdgToplevel(nullptr), decManager(nullptr), dec(nullptr), seat(nullptr), pointer(nullptr)
	{
	}

	Window::Window(Window &&win) noexcept
		: BaseWindow(win), display(win.display), registry(win.registry), compositor(win.compositor),
		wlSurface(win.wlSurface), xdgShell(win.xdgShell), xdgSurface(win.xdgSurface), xdgToplevel(win.xdgToplevel),
		decManager(win.decManager), dec(win.dec), seat(win.seat), pointer(win.pointer)
	{
		win.display = nullptr;
		win.registry = nullptr;
		win.compositor = nullptr;
		win.wlSurface = nullptr;
		win.xdgShell = nullptr;
		win.xdgSurface = nullptr;
		win.xdgToplevel = nullptr;
		win.decManager = nullptr;
		win.dec = nullptr;
		win.seat = nullptr;
		win.pointer = nullptr;
	}

	Window::Window(const Window &win)
		: BaseWindow(win), display(nullptr), registry(nullptr), compositor(nullptr), wlSurface(nullptr),
		xdgShell(nullptr), xdgSurface(nullptr), xdgToplevel(nullptr), decManager(nullptr), dec(nullptr), seat(nullptr),
		pointer(nullptr)
	{
	}

	Window & Window::operator=(Window &&win) noexcept
	{
		if (this == &win)
			return *this;

		BaseWindow::operator=(win);

		display = win.display;
		registry = win.registry;
		compositor = win.compositor;
		wlSurface = win.wlSurface;
		xdgShell = win.xdgShell;
		xdgSurface = win.xdgSurface;
		xdgToplevel = win.xdgToplevel;
		decManager = win.decManager;
		dec = win.dec;
		seat = win.seat;
		pointer = win.pointer;

		win.display = nullptr;
		win.registry = nullptr;
		win.compositor = nullptr;
		win.wlSurface = nullptr;
		win.xdgShell = nullptr;
		win.xdgSurface = nullptr;
		win.xdgToplevel = nullptr;
		win.decManager = nullptr;
		win.dec = nullptr;
		win.seat = nullptr;
		win.pointer = nullptr;

		return *this;
	}

	Window & Window::operator=(const Window &win)
	{
		if (this == &win)
			return *this;

		BaseWindow::operator=(win);

		display = nullptr;
		registry = nullptr;
		compositor = nullptr;
		wlSurface = nullptr;
		xdgShell = nullptr;
		xdgSurface = nullptr;
		xdgToplevel = nullptr;
		decManager = nullptr;
		dec = nullptr;
		seat = nullptr;
		pointer = nullptr;

		return *this;
	}

	void Window::Create_32(const Str_32& title, const Vec2_s32& pos, const Vec2_u32 scale)
	{
		Create_8(UTF::To_8(title), pos, scale);
	}

	void Window::Create_16(const Str_16& title, const Vec2_s32& pos, const Vec2_u32 scale)
	{
		Create_8(UTF::To_8(title), pos, scale);
	}

	void Window::Create_8(const Str_8& title, const Vec2_s32& pos, const Vec2_u32 scale)
	{
		if (created)
			return;

		display = wl_display_connect(nullptr);
		if (!display)
		{
			EHS_LOG_INT(LogType::ERR, 0, "Failed to connect to display server.");
			return;
		}

		static constexpr wl_registry_listener registry_listener = {
			.global = RegistryHandler,
			.global_remove = RegistryRemoved
		};

		registry = wl_display_get_registry(display);
		wl_registry_add_listener(registry, &registry_listener, this);

		wl_display_dispatch(display);
		wl_display_roundtrip(display);

		if (!compositor || !xdgShell)
		{
			EHS_LOG_INT(LogType::ERR, 1, "Can't find required interfaces.");
			return;
		}

		static constexpr xdg_wm_base_listener xdg_shell_listener = {
			.ping = ShellPing
		};

		xdg_wm_base_add_listener(xdgShell, &xdg_shell_listener, nullptr);

		wl_display_roundtrip(display);

		wlSurface = wl_compositor_create_surface(compositor);
		if (!wlSurface)
		{
			EHS_LOG_INT(LogType::ERR, 2, "Can't create surface.");
			return;
		}

		xdgSurface = xdg_wm_base_get_xdg_surface(xdgShell, wlSurface);

		static constexpr xdg_surface_listener surfaceListener = {
			.configure = SurfaceConfigEvent
		};

		xdg_surface_add_listener(xdgSurface, &surfaceListener, this);

		wl_display_roundtrip(display);

		xdgToplevel = xdg_surface_get_toplevel(xdgSurface);

		static constexpr xdg_toplevel_listener topLevelListener = {
			.configure = ResizeEvent,
			.close = CloseEvent
		};

		xdg_toplevel_add_listener(xdgToplevel, &topLevelListener, this);

		wl_display_roundtrip(display);

		xdg_toplevel_set_title(xdgToplevel, title);
		xdg_toplevel_set_app_id(xdgToplevel, title);

		wl_display_roundtrip(display);

		dec = zxdg_decoration_manager_v1_get_toplevel_decoration(decManager, xdgToplevel);
		zxdg_toplevel_decoration_v1_set_mode(dec, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);

		wl_display_roundtrip(display);

		static constexpr wl_seat_listener seat_listener = {
			.capabilities = SeatCapabilitiesEvent
		};

		wl_seat_add_listener(seat, &seat_listener, this);

		wl_display_roundtrip(display);

		wl_surface_commit(wlSurface);

		wl_display_roundtrip(display);

		this->scale = scale;
		created = true;

		OnCreated();
	}

	void Window::OnCreated()
	{
	}

	void Window::Close()
	{
		if (!created)
			return;

		zxdg_toplevel_decoration_v1_destroy(dec);
		dec = nullptr;

		zxdg_decoration_manager_v1_destroy(decManager);
		decManager = nullptr;

		xdg_toplevel_destroy(xdgToplevel);
		xdgToplevel = nullptr;

		xdg_surface_destroy(xdgSurface);
		xdgSurface = nullptr;

		xdg_wm_base_destroy(xdgShell);
		xdgShell = nullptr;

		wl_surface_destroy(wlSurface);
		wlSurface = nullptr;

		wl_compositor_destroy(compositor);
		compositor = nullptr;

		wl_registry_destroy(registry);
		registry = nullptr;

		wl_display_disconnect(display);
		display = nullptr;

		created = false;
	}

	void Window::Show()
	{
	}

	void Window::Hide()
	{
	}

	bool Window::Poll()
	{
		if (wl_display_dispatch_pending(display) == -1)
			return false;

		if (!created)
			return false;

		return true;
	}

	void Window::ShowCursor(bool toggle)
	{
	}

	void Window::ConstrainCursor(const bool constrain)
	{
	}

	void Window::SetTitle_32(const Str_32& newTitle)
	{
	}

	Str_32 Window::GetTitle_32() const
	{
		return {};
	}

	void Window::SetTitle_16(const Str_16& newTitle)
	{
	}

	Str_16 Window::GetTitle_16() const
	{
		return {};
	}

	void Window::SetTitle_8(const Str_8& newTitle)
	{
	}

	Str_8 Window::GetTitle_8() const
	{
		return {};
	}

	void Window::SetPos(const Vec2_s32& newPos)
	{
	}

	Vec2_s32 Window::GetPos() const
	{
		return {};
	}

	void Window::SetScale(const Vec2_u32& newScale)
	{
	}

	Vec2_u32 Window::GetScale() const
	{
		return scale;
	}

	Serializer<UInt_64> Window::GetClipboard()
	{
		return {};
	}

	void Window::SetClipboard(Serializer<UInt_64> data)
	{
	}

	void Window::SetCursorImg(const CursorImg img)
	{
	}
}