#include "renderer.hpp"

renderer::renderer(
  daguerre::image<daguerre::hilbert_color> &&fb,
  daguerre::image<daguerre::hilbert_color> &&bg,
  daguerre::hilbert_color bgc,
  daguerre::image<daguerre::hilbert_color> &&c,
  daguerre::hilbert_color cbg)

: framebuffer(std::move(fb)),
  double_buffer(framebuffer.width, framebuffer.height),
  background(std::move(bg)), cursor(std::move(c)), cursor_background(cbg),
  cursor_x(framebuffer.width / 2), cursor_y(framebuffer.height / 2) {

  euler::syscall::create_private_socket(dispatcher_handle_1, dispatcher_handle_2);

  if (background.width != framebuffer.width ||
      background.height != framebuffer.height) {

    daguerre::image<daguerre::hilbert_color>
      new_background(framebuffer.width, framebuffer.height);

    new_background.fill(bgc);

    int from_x = 0;
    int from_y = 0;
    int to_x = framebuffer.width / 2 - background.width / 2;
    int to_y = framebuffer.height / 2 - background.height / 2;
    int width = background.width;
    int height = background.height;

    daguerre::make_safe(to_x, from_x, width, 0, framebuffer.width);
    daguerre::make_safe(to_y, from_y, height, 0, framebuffer.height);

    new_background.copy_from(background, to_x, to_y, from_x, from_y, width, height);
    background = std::move(new_background);

  }

}

template <class color_t>
void overlay_trans(color_t &to, const color_t &from, const color_t &param) {
  if (from != param)
    to = from;
}

void renderer::do_render() {

  double_buffer.copy_from(background, 0, 0);

  for (auto it = windows.begin(); it != windows.end(); ++it) {
    int tx = (*it)->x, ty = (*it)->y, fx = 0, fy = 0;
    int w = (*it)->contents_with_decorations.width;
    int h = (*it)->contents_with_decorations.height;
    daguerre::make_safe(tx, fx, w, 0, double_buffer.width);
    daguerre::make_safe(ty, fy, h, 0, double_buffer.height);
    double_buffer.copy_from(
      (*it)->contents_with_decorations, tx, ty, fx, fy, w, h);
  }

  double_buffer.convert_from(
    cursor_background, cursor, cursor_x, cursor_y, 0, 0,
    std::min(cursor.width, framebuffer.width - cursor_x),
    std::min(cursor.height, framebuffer.height - cursor_y),
    &overlay_trans);

}

[[noreturn]] void renderer::render_thread_main() {
  euler::syscall::set_thread_name("render thread");
  while (true) {
    uint8_t byte;
    euler::syscall::read_from_stream(dispatcher_handle_2, 1, &byte);
    mut.lock();
    euler::syscall::clear_socket_read_queue(dispatcher_handle_2);
    do_render();
    mut.unlock();
    framebuffer.copy_from(double_buffer, 0, 0);
  }
}

void renderer::get_cursor(int &x_out, int &y_out) {
  x_out = cursor_x;
  y_out = cursor_y;
}

void renderer::bump_cursor(int x_offset, int y_offset) {

  cursor_x += x_offset;
  if (cursor_x < 0)
    cursor_x = 0;
  else if (cursor_x >= framebuffer.width)
    cursor_x = framebuffer.width - 1;

  cursor_y += y_offset;
  if (cursor_y < 0)
    cursor_y = 0;
  else if (cursor_y >= framebuffer.height)
    cursor_y = framebuffer.height - 1;

}

void renderer::move_window_to_front(std::list<window *>::iterator w) {
  window *wp = *w;
  windows.erase(w);
  if (windows.size() != 0)
    windows.back()->draw_decorations(false);
  windows.push_back(wp);
  wp->draw_decorations(true);
}

void renderer::add_window(window *w) {
  if (windows.size() != 0)
    windows.back()->draw_decorations(false);
  w->draw_decorations(true);
  windows.push_back(w);
}

void renderer::remove_window(window *w) {
  windows.remove(w);
  if (windows.size() != 0)
    windows.back()->draw_decorations(true);
}
