#include "core_widget.hpp"

core_widget::core_widget() {
  clear_all();
}

void core_widget::clear_all() {
  for (int i = 0; i < LIB94_CORE_SIZE; ++i) {

    if (write_values[i] != 0)
      to_draw_write.insert(i);
    write_values[i] = 0;

    if (read_values[i] != 0)
      to_draw_read.insert(i);
    read_values[i] = 0;

    if (execute_values[i] != 0)
      to_draw_execute.insert(i);
    execute_values[i] = 0;

  }
}

void core_widget::age_all() {
  for (int i = 0; i < LIB94_CORE_SIZE; ++i) {

    if (write_values[i] != 0) {
      to_draw_write.insert(i);
      write_values[i] = (uint8_t)((float)write_values[i] * age_scale);
    }

    if (read_values[i] != 0) {
      to_draw_read.insert(i);
      read_values[i] = (uint8_t)((float)read_values[i] * age_scale);
    }

    if (execute_values[i] != 0) {
      to_draw_execute.insert(i);
      execute_values[i] = (uint8_t)((float)execute_values[i] * age_scale);
    }

  }
}

void core_widget::add_new_writes(const std::set<lib94::number_t> &writes) {
  for (lib94::number_t n : writes) {
    write_values[n] = 255;
    to_draw_write.insert(n);
  }
}

void core_widget::add_new_reads(const std::set<lib94::number_t> &reads) {
  for (lib94::number_t n : reads) {
    read_values[n] = 255;
    to_draw_read.insert(n);
  }
}

void core_widget::add_new_executions(const std::set<lib94::number_t> &executions) {
  for (lib94::number_t n : executions) {
    execute_values[n] = 255;
    to_draw_execute.insert(n);
  }
}

void core_widget::measure_vfunc(Gtk::Orientation, int for_size, int &minimum, int &natural, int &minimum_baseline, int &natural_baseline) const {
  minimum = (LIB94_CORE_SIZE - 1) / for_size + 1;
  natural = (LIB94_CORE_SIZE - 1) / for_size + 1;
  minimum_baseline = -1;
  natural_baseline = -1;
}

void core_widget::draw(int width, int height) {
  mut.lock();

  if (width == last_width && height == last_height) {

    uint8_t *buffer = (uint8_t *)pixbuf->property_pixels().get_value();

    for (lib94::number_t i : to_draw_write) {
      int cy = i / cpr * scale + ypad;
      int cx = i % cpr * scale + xpad;
      for (int dy = 0; dy < scale; ++dy)
        for (int dx = 0; dx < scale; ++dx)
          buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4] = write_values[i];
    }

    for (lib94::number_t i : to_draw_read) {
      int cy = i / cpr * scale + ypad;
      int cx = i % cpr * scale + xpad;
      for (int dy = 0; dy < scale; ++dy)
        for (int dx = 0; dx < scale; ++dx)
          buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 2] = read_values[i];
    }

    for (lib94::number_t i : to_draw_execute) {
      int cy = i / cpr * scale + ypad;
      int cx = i % cpr * scale + xpad;
      for (int dy = 0; dy < scale; ++dy)
        for (int dx = 0; dx < scale; ++dx)
          buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 1] = execute_values[i];
    }

  }

  else {

    pixbuf = Gdk::Pixbuf::create(Gdk::Colorspace::RGB, true, 8, width, height);
    last_width = width;
    last_height = height;

    scale = 1;
    while (true) {
      ++scale;
      if ((width / scale) * (height / scale) < LIB94_CORE_SIZE) {
        --scale;
        break;
      }
    }

    cpr = width / scale + 1;
    xpad = INT_MAX;
    ypad = 0;

    while (cpr >= 1) {
      --cpr;

      int rows = (LIB94_CORE_SIZE - 1) / cpr + 1;
      int new_xpad = (width - cpr * scale) / 2;
      int new_ypad = (height - rows * scale) / 2;

      if (abs(new_xpad - new_ypad) < abs(xpad - ypad)) {
        xpad = new_xpad;
        ypad = new_ypad;
      }

      else {
        ++cpr;
        break;
      }
    }

    uint8_t *buffer = (uint8_t *)pixbuf->property_pixels().get_value();

    for (lib94::number_t i = 0; i < LIB94_CORE_SIZE; ++i) {
      int cy = i / cpr * scale + ypad;
      int cx = i % cpr * scale + xpad;
      for (int dy = 0; dy < scale; ++dy)
        for (int dx = 0; dx < scale; ++dx) {
          buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4] = write_values[i];
          buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 2] = read_values[i];
          buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 1] = execute_values[i];
          buffer[(cy + dy) * pixbuf->property_rowstride() + (cx + dx) * 4 + 3] = 255;
        }
    }

  }

  to_draw_write.clear();
  to_draw_read.clear();
  to_draw_execute.clear();

  mut.unlock();
}

void core_widget::snapshot_vfunc(const Glib::RefPtr<Gtk::Snapshot> &snapshot) {
  auto start_time = std::chrono::system_clock::now();

  Gtk::Allocation allocation = get_allocation();
  Gdk::Rectangle allocation_rect(0, 0, allocation.get_width(), allocation.get_height());

  draw(allocation.get_width(), allocation.get_height());

  auto texture = Gdk::Texture::create_for_pixbuf(pixbuf);
  snapshot->append_texture(texture, allocation_rect);

  auto end_time = std::chrono::system_clock::now();
  last_draw_time = end_time - start_time;
}