#include <daguerre/image.hpp>
#include "socket.hpp"
#include "window.hpp"
#include "main.hpp"
#include <memory>
#include <vector>

struct socket_state {

  euler::syscall::stream_handle socket;
  window *w;

  bool try_show_window() {

    if (w->is_shown)
      return false;

    r->lock();
    r->add_window(w);
    r->unlock();
    r->dispatch_render();
    return true;

  }

  bool try_hide_window() {

    if (!w->is_shown)
      return false;

    r->lock();
    r->remove_window(w);
    r->unlock();
    r->dispatch_render();
    return true;

  }

  bool try_set_window_dimensions() {

    struct [[gnu::packed]] { uint32_t width; uint32_t height; } packet;
    if (euler::syscall::read_from_stream(socket, sizeof(packet), &packet) !=
        euler::syscall::stream_result::success)
      return false;

    if (packet. width > __INT_MAX__ - window::decorations_extra_width ||
        packet.height > __INT_MAX__ - window::decorations_extra_height)
      return false;

    r->lock();
    w->set_size(packet.width, packet.height);
    w->draw_decorations(r->is_top(w));
    r->unlock();
    return true;

  }

  bool try_set_window_title() {

    uint32_t length;
    if (euler::syscall::read_from_stream(socket, 4, &length) !=
        euler::syscall::stream_result::success)
      return false;

    std::string title;
    title.resize(length);
    if (euler::syscall::read_from_stream(socket, length, title.data()) !=
        euler::syscall::stream_result::success)
      return false;

    r->lock();
    w->title = std::move(title);
    w->draw_decorations(r->is_top(w));
    r->unlock();
    r->dispatch_render();
    return true;

  }

  bool try_update_window_region() {

    struct [[gnu::packed]] {
      uint32_t start_x; uint32_t start_y; uint32_t width; uint32_t height;
    } packet;
    if (euler::syscall::read_from_stream(socket, sizeof(packet), &packet) !=
        euler::syscall::stream_result::success)
      return false;

    static_assert(__INT_MAX__ <= __UINT64_MAX__);
    if ((uint64_t)packet.start_x + packet. width > (uint64_t)w->contents. width ||
        (uint64_t)packet.start_y + packet.height > (uint64_t)w->contents.height)
      return false;

    daguerre::image<daguerre::hilbert_color> content(packet.width, packet.height);
    if (euler::syscall::read_from_stream(
          socket, packet.width * packet.height * 4, content.buffer) !=
        euler::syscall::stream_result::success)
      return false;

    r->lock();
    w->contents.copy_from(content, packet.start_x, packet.start_y);
    r->unlock();
    r->dispatch_render();
    return true;

  }

  bool try_process_request() {

    uint8_t type;
    if (euler::syscall::read_from_stream(socket, 1, &type) !=
        euler::syscall::stream_result::success)
      return false;

    switch (type) {
    case 0x00: return try_show_window();
    case 0x01: return try_hide_window();
    case 0x02: return try_set_window_dimensions();
    case 0x03: return try_set_window_title();
    case 0x04: return try_update_window_region();
    default: return false;
    }

  }

};

[[noreturn]] void socket_thread_main(euler::syscall::stream_handle socket) {

  euler::syscall::set_thread_name("socket thread");

  int x, y;
  r->get_cursor(x, y);

  window *w = new window(x, y);
  socket_state *state = new socket_state {
    .socket = socket, .w = w };

  while (state->try_process_request()) ;

  if (w->is_shown) {
    r->lock();
    r->remove_window(w);
    r->unlock();
  }

  delete state;
  delete w;
  euler::syscall::close_stream(socket);
  euler::syscall::end_this_thread(0);

}
