#include "WindowServer.h"

#include <BAN/Debug.h>

#include <LibGUI/Window.h>
#include <LibInput/KeyboardLayout.h>

#include <fcntl.h>
#include <stdlib.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>

int open_server_fd()
{
	struct stat st;
	if (stat(LibGUI::s_window_server_socket.data(), &st) != -1)
		unlink(LibGUI::s_window_server_socket.data());

	int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
	if (server_fd == -1)
	{
		perror("socket");
		exit(1);
	}

	sockaddr_un server_addr;
	server_addr.sun_family = AF_UNIX;
	strcpy(server_addr.sun_path, LibGUI::s_window_server_socket.data());
	if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1)
	{
		perror("bind");
		exit(1);
	}

	if (chmod("/tmp/resolver.sock", 0777) == -1)
	{
		perror("chmod");
		exit(1);
	}

	if (listen(server_fd, SOMAXCONN) == -1)
	{
		perror("listen");
		exit(1);
	}

	return server_fd;
}

int main()
{
	srand(time(nullptr));

	int server_fd = open_server_fd();
	auto framebuffer = open_framebuffer();
	if (framebuffer.bpp != 32)
	{
		dwarnln("Window server requires 32 bpp");
		return 1;
	}

	if (tty_ctrl(STDIN_FILENO, TTY_CMD_UNSET, TTY_FLAG_ENABLE_INPUT) == -1)
	{
		perror("tty_ctrl");
		exit(1);
	}

	atexit([]() { tty_ctrl(STDIN_FILENO, TTY_CMD_SET, TTY_FLAG_ENABLE_INPUT); });

	MUST(LibInput::KeyboardLayout::initialize());
	MUST(LibInput::KeyboardLayout::get().load_from_file("/usr/share/keymaps/us.keymap"sv));

	int keyboard_fd = open("/dev/input0", O_RDONLY);
	if (keyboard_fd == -1)
		perror("open");

	int mouse_fd = open("/dev/input1", O_RDONLY);
	if (mouse_fd == -1)
		perror("open");

	dprintln("Window server started");

	for (int i = 0; i < 2; i++)
	{
		if (fork() == 0)
		{
			execl("/bin/test-window", "test-window", NULL);
			exit(1);
		}
	}

	WindowServer window_server(framebuffer);
	for (;;)
	{
		int max_socket = server_fd;

		fd_set fds;
		FD_ZERO(&fds);
		FD_SET(server_fd, &fds);
		if (keyboard_fd != -1)
		{
			FD_SET(keyboard_fd, &fds);
			max_socket = BAN::Math::max(max_socket, keyboard_fd);
		}
		if (mouse_fd != -1)
		{
			FD_SET(mouse_fd, &fds);
			max_socket = BAN::Math::max(max_socket, mouse_fd);
		}
		window_server.for_each_window(
			[&](int fd, Window&) -> BAN::Iteration
			{
				FD_SET(fd, &fds);
				max_socket = BAN::Math::max(max_socket, fd);
				return BAN::Iteration::Continue;
			}
		);

		if (select(max_socket + 1, &fds, nullptr, nullptr, nullptr) == -1)
		{
			dwarnln("select: {}", strerror(errno));
			break;
		}

		if (FD_ISSET(server_fd, &fds))
		{
			int window_fd = accept(server_fd, nullptr, nullptr);
			if (window_fd == -1)
			{
				dwarnln("accept: {}", strerror(errno));
				continue;
			}
			auto window = MUST(BAN::RefPtr<Window>::create(window_fd));
			window_server.add_window(window_fd, window);
		}

		if (keyboard_fd != -1 && FD_ISSET(keyboard_fd, &fds))
		{
			LibInput::RawKeyEvent event;
			if (read(keyboard_fd, &event, sizeof(event)) == -1)
			{
				perror("read");
				continue;
			}
			window_server.on_key_event(LibInput::KeyboardLayout::get().key_event_from_raw(event));
		}

		if (mouse_fd != -1 && FD_ISSET(mouse_fd, &fds))
		{
			LibInput::MouseEvent event;
			if (read(mouse_fd, &event, sizeof(event)) == -1)
			{
				perror("read");
				continue;
			}
			switch (event.type)
			{
				case LibInput::MouseEventType::MouseButtonEvent:
					window_server.on_mouse_button(event.button_event);
					break;
				case LibInput::MouseEventType::MouseMoveEvent:
					window_server.on_mouse_move(event.move_event);
					break;
				case LibInput::MouseEventType::MouseScrollEvent:
					window_server.on_mouse_scroll(event.scroll_event);
					break;
			}
		}

		window_server.for_each_window(
			[&](int fd, Window& window) -> BAN::Iteration
			{
				if (!FD_ISSET(fd, &fds))
					return BAN::Iteration::Continue;

				LibGUI::WindowPacket packet;
				ssize_t nrecv = recv(fd, &packet, sizeof(packet), 0);
				if (nrecv < 0)
					dwarnln("recv: {}", strerror(errno));
				if (nrecv <= 0)
				{
					window.mark_deleted();
					return BAN::Iteration::Continue;
				}

				switch (packet.type)
				{
					case LibGUI::WindowPacketType::CreateWindow:
					{
						if (nrecv != sizeof(LibGUI::WindowCreatePacket))
						{
							dwarnln("Invalid WindowCreate packet size");
							break;
						}

						const size_t window_fb_bytes = packet.create.width * packet.create.height * 4;

						long smo_key = smo_create(window_fb_bytes, PROT_READ | PROT_WRITE);
						if (smo_key == -1)
						{
							dwarnln("smo_create: {}", strerror(errno));
							break;
						}

						void* smo_address = smo_map(smo_key);
						if (smo_address == nullptr)
						{
							dwarnln("smo_map: {}", strerror(errno));
							break;
						}
						memset(smo_address, 0, window_fb_bytes);

						LibGUI::WindowCreateResponse response;
						response.framebuffer_smo_key = smo_key;
						if (send(fd, &response, sizeof(response), 0) != sizeof(response))
						{
							dwarnln("send: {}", strerror(errno));
							break;
						}

						window.set_size({
							static_cast<int32_t>(packet.create.width),
							static_cast<int32_t>(packet.create.height)
						}, reinterpret_cast<uint32_t*>(smo_address));
						window.set_position({
							static_cast<int32_t>(window.width() / 2),
							static_cast<int32_t>(window.height() / 2)
						});

						break;
					}
					case LibGUI::WindowPacketType::Invalidate:
					{
						if (nrecv != sizeof(LibGUI::WindowInvalidatePacket))
						{
							dwarnln("Invalid Invalidate packet size");
							break;
						}
						if (packet.invalidate.x + packet.invalidate.width > window.width() || packet.invalidate.y + packet.invalidate.height > window.height())
						{
							dwarnln("Invalid Invalidate packet parameters");
							break;
						}

						window_server.invalidate({
							window.x() + static_cast<int32_t>(packet.invalidate.x),
							window.y() + static_cast<int32_t>(packet.invalidate.y),
							static_cast<int32_t>(packet.invalidate.width),
							static_cast<int32_t>(packet.invalidate.height),
						});

						break;
					}
					default:
						dwarnln("Invalid window packet from {}", fd);
				}

				return BAN::Iteration::Continue;
			}
		);
	}
}