#include <cinttypes>
#include <cmath>

#include "Vapp.hpp"

#include <rlImGui.h>
#include <imgui.h>
#include <raylib.h>

const char select_user_id[] = "SELECT USER_ID FROM user_table GROUP BY USER_ID;";

constexpr int sizeOfCanvas = 1000;

void Vapp::init()
{
  bigTexture = LoadRenderTexture(sizeOfCanvas * 4, sizeOfCanvas * 4);
  treeTexture = LoadRenderTexture(sizeOfCanvas, sizeOfCanvas);
  canvas.init(sizeOfCanvas);

  DnaManager::setUp(&manager, 0);

  sql::init();
  sql::open(DB_NAME, &db);

  sqlite3_stmt *stmt;
  sql::prepare_v2(db, select_user_id, -1, &stmt, NULL);

  while (sql::step(stmt) != SQL_DONE)
  {
    int type = sql::column_type(stmt, 0);
    if (type != SQL_NULL)
    {
      int64_t user_id = sql::column_int64(stmt, 0);
      ids.push_back(user_id);
    }
  }
  sql::prepare_v2(db, max_gen, -1, &get_gen_num, nullptr);
}

void Vapp::update()
{
  if (update_gen && selected_id_index >= 0)
  {
    sql::bind_int64(get_gen_num, 1, ids[selected_id_index]);
    while (sql::step(get_gen_num) != SQL_DONE)
    {
      int type = sql::column_type(get_gen_num, 0);
      if (type != SQL_NULL)
      {
        gens = sql::column_int64(get_gen_num, 0);
      }
    }
    sql::reset(get_gen_num);

    update_gen = false;
  }

  switch (stageOfDrawing)
  {
  case DrawingStage::setup:
    setUpManager();
    stageOfDrawing = DrawingStage::drawTree;
    drawTreeIndex = 0;
    drawX = 0;
    drawY = 0;
    canvas.newGen(treeTexture, &manager.vector[drawTreeIndex]);
    break;
  case DrawingStage::drawTree:
    if (canvas.tick(treeTexture))
    {
      stageOfDrawing = DrawingStage::drawBig;
    }
    break;
  case DrawingStage::drawBig:
    BeginTextureMode(bigTexture);
    DrawTexture(treeTexture.texture, drawX * sizeOfCanvas, drawY * sizeOfCanvas, WHITE);
    EndTextureMode();

    drawTreeIndex++;
    if (drawTreeIndex < NUM_PER_GEN)
    {
      canvas.newGen(treeTexture, &manager.vector[drawTreeIndex]);
      stageOfDrawing = DrawingStage::drawTree;
    }
    else
    {
      stageOfDrawing = DrawingStage::calSim;
    }

    drawX++;
    if (drawX == 4)
    {
      drawY++;
      drawX = 0;
    }
    if (drawY == 4)
    {
      drawY = 0;
    }
    break;
  case DrawingStage::calSim:

    simil[0] = Similarity::calc_similarity(manager.vector, Similarity::cosine_similarity);
    simil[1] = Similarity::calc_similarity(manager.vector, Similarity::hamming_distance);
    simil[2] = Similarity::calc_similarity(manager.vector, Similarity::cosine_similarity_int);
    stageOfDrawing = DrawingStage::done;
    break;

  case DrawingStage::done:
    enableAll = true;
    break;
  default:
    break;
  }
}

void Vapp::draw()
{
  ClearBackground(RAYWHITE);

  ImGui::DockSpaceOverViewport(0, NULL, ImGuiDockNodeFlags_PassthruCentralNode);

  if (ImGui::BeginMainMenuBar())
  {
    ImGui::MenuItem("Selection", nullptr, &showSelection, enableAll);
    ImGui::MenuItem("Stats", nullptr, &showStats, enableAll);
    if (ImGui::MenuItem("Draw", nullptr, false, enableAll))
    {
      enableAll = false;
      stageOfDrawing = DrawingStage::setup;
    }
    if (ImGui::MenuItem("CalcTable", nullptr, false, enableAll))
    {
      setUpTable();
    }
    ImGui::EndMainMenuBar();
  }

  if (showSelection)
  {
    ImGui::Begin("Selection", &showSelection);
    if (!enableAll)
      ImGui::BeginDisabled();
    if (ImGui::BeginListBox("##list_id", ImVec2(ImGui::GetContentRegionAvail().x * 0.5f, -FLT_MIN)))
    {
      for (int n = 0; n < ids.size(); n++)
      {
        const bool is_selected = (selected_id_index == n);
        if (ImGui::Selectable(TextFormat("%d", ids[n]), is_selected))
        {
          selected_id_index = n;
          gens = -1;
          update_gen = true;
        }
      }
      ImGui::EndListBox();
    }
    ImGui::SameLine();
    if (ImGui::BeginListBox("##list_gen", ImVec2(-FLT_MIN, -FLT_MIN)))
    {
      for (int n = 0; n <= gens; n++)
      {
        const bool is_selected = (selected_gen == n);
        if (ImGui::Selectable(TextFormat("%d", n), is_selected))
          selected_gen = n;
      }
      ImGui::EndListBox();
    }
    if (!enableAll)
      ImGui::EndDisabled();
    ImGui::End();
  }

  if (showStats)
  {
    ImGui::Begin("Status", &showStats);
    ImGui::LabelText("##sim1", "cosine_similarity: %f", simil[0]);
    ImGui::LabelText("##sim2", "hamming_distance: %f", simil[1]);
    ImGui::LabelText("##sim3", "cosine_similarity_int: %f", simil[2]);

    const ImGuiTableFlags flags = ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody;
    const int columns = 4;
    if (ImGui::BeginTable("table1", columns, flags))
    {
      for (int row = 0; row < similTable.size(); row++)
      {
        ImGui::TableNextRow();
        for (int column = 0; column < columns; column++)
        {
          ImGui::TableSetColumnIndex(column);
          if (column == 0)
          {
            ImGui::Text("%d", row);
            continue;
          }
          ImGui::Text("%f", similTable[row][column - 1]);
        }
      }
      ImGui::EndTable();
    }

    ImGui::End();
  }

  ImGui::Begin("Trees", nullptr);
  rlImGuiImageRenderTextureFit(&bigTexture, true);
  ImGui::End();
}

void Vapp::deinit()
{
  UnloadRenderTexture(treeTexture);
  UnloadRenderTexture(bigTexture);
  canvas.deinit();
  sql::close(db);
  sql::shutdown();
}

void Vapp::setUpManager()
{

  sqlite3_stmt *get_gen_stmt;
  sql::prepare_v2(db, get_gen, -1, &get_gen_stmt, NULL);

  DnaManager::cleanUp(&manager, ids[selected_id_index]);
  int lodedGen = -1;

  while (lodedGen < selected_gen)
  {
    sql::bind_int64(get_gen_stmt, 1, manager.id);
    sql::bind_int64(get_gen_stmt, 2, manager.generation);

    bool new_gen = false;
    while (sql::step(get_gen_stmt) != SQL_DONE)
    {
      int64_t pos = sql::column_int64(get_gen_stmt, 1);
      int64_t liked = sql::column_int64(get_gen_stmt, 2);
      UiUnit unit = DnaManager::next(&manager);
      if ((unit.index != pos))
      {
        // DOTO: SET ERROR
        TraceLog(LOG_ERROR, "LOADING DNA");
        sql::finalize(get_gen_stmt);
        return;
      }
      unit.liked = (Liked)liked;
      new_gen = DnaManager::like(unit, &manager);
    }

    if (manager.generation < selected_gen)
    {
      DnaManager::newGen(&manager);
    }

    sql::reset(get_gen_stmt);
    lodedGen++;
  }

  sql::finalize(get_gen_stmt);
}

void Vapp::setUpTable()
{
  sqlite3_stmt *get_gen_stmt;
  sql::prepare_v2(db, get_gen, -1, &get_gen_stmt, NULL);

  DnaManager::cleanUp(&manager, ids[selected_id_index]);
  similTable.clear();
  while (true)
  {
    sql::bind_int64(get_gen_stmt, 1, manager.id);
    sql::bind_int64(get_gen_stmt, 2, manager.generation);

    bool new_gen = false;
    while (sql::step(get_gen_stmt) != SQL_DONE)
    {
      int64_t pos = sql::column_int64(get_gen_stmt, 1);
      int64_t liked = sql::column_int64(get_gen_stmt, 2);
      UiUnit unit = DnaManager::next(&manager);
      if ((unit.index != pos))
      {
        // DOTO: SET ERROR
        TraceLog(LOG_ERROR, "LOADING DNA");
        sql::finalize(get_gen_stmt);
        return;
      }
      unit.liked = (Liked)liked;
      new_gen = DnaManager::like(unit, &manager);
    }

    if (new_gen)
    {
      similTable.emplace_back();
      int s = similTable.size() - 1;
      similTable[s][0] = Similarity::calc_similarity(manager.vector, Similarity::cosine_similarity);
      similTable[s][1] = Similarity::calc_similarity(manager.vector, Similarity::hamming_distance);
      similTable[s][2] = Similarity::calc_similarity(manager.vector, Similarity::cosine_similarity_int);

      DnaManager::newGen(&manager);
    }
    else
    {
      break;
    }

    sql::reset(get_gen_stmt);
  }

  sql::finalize(get_gen_stmt);
}