// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)

#include <opencv2/core/core.hpp>

#include "imgui.h"
#include "imgui_impl_opengl3.h"
#include "imgui_impl_sdl2.h"
#include <SDL.h>
#include <SDL_opengl.h>

#include "cxxopts.hpp"
#include "spdlog/spdlog.h"

#include "camera.h"
#include "common.h"
#include "minus_renderer.h"

int main(int argc, char *argv[]) {
  spdlog::set_level(spdlog::level::trace);

  cxxopts::Options options("Minus Renderer",
                           "A minus soft rasterize renderer.");

  options.add_options()("o,obj", "Object file path",
                        cxxopts::value<std::string>())(
      "d,diffuse_map", "Diffuse map file path",
      cxxopts::value<std::string>())("h,help", "Print usage");

  auto result = options.parse(argc, argv);
  if (result.count("help")) {
    spdlog::info("{}", options.help());
    exit(0);
  }

  const std::string obj_path{result["obj"].as<std::string>()};
  const std::string diffuse_map_path{result["diffuse_map"].as<std::string>()};

  const int resolution_width = 640;
  const int resolution_height = 320;
  const int far = -100;
  const int near = -1;
  const float fov = 120. / 180. * M_PI;
  const float aspect_ratio = static_cast<float>(resolution_width) /
                             static_cast<float>(resolution_height);

  const std::string window_name{"Minor Render"};

  // Setup SDL
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
    printf("Error: %s\n", SDL_GetError());
    return -1;
  }

  // Decide GL+GLSL versions
#if defined(__APPLE__)
  // GL 3.2 Core + GLSL 150
  const char *glsl_version = "#version 150";
  SDL_GL_SetAttribute(
      SDL_GL_CONTEXT_FLAGS,
      SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
  // GL 3.0 + GLSL 130
  const char *glsl_version = "#version 130";
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif

  // Create window with graphics context
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
  SDL_WindowFlags window_flags =
      (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI);
  SDL_Window *window = SDL_CreateWindow(
      window_name.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
      static_cast<float>(resolution_width),
      static_cast<float>(resolution_height) * 1.2, window_flags);
  if (window == nullptr) {
    printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
    return -1;
  }

  SDL_GLContext gl_context = SDL_GL_CreateContext(window);
  SDL_GL_MakeCurrent(window, gl_context);
  SDL_GL_SetSwapInterval(1); // Enable vsync

  // Setup Dear ImGui context
  IMGUI_CHECKVERSION();
  ImGui::CreateContext();
  ImGuiIO &io = ImGui::GetIO();
  io.ConfigFlags |=
      ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls

  // Setup Dear ImGui style
  ImGui::StyleColorsDark();

  // Setup Platform backends
  ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
  ImGui_ImplOpenGL3_Init(glsl_version);

  // Our state
  ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

  Camera camera{Vector3d{0, 1, 0}};
  MinusRenderer renderer{near, far, fov, aspect_ratio};
  renderer.set_meshes(
      Mesh::load_mesh(obj_path, {{Texture::DiffuseMap, diffuse_map_path}}));
  TransformMatrix translate{
      {1., 0., 0., 3.}, {0., 1., 0., 3.}, {0., 0., 1., -4.}, {0., 0., 0., 1.}};
  renderer.model_transform(translate);

  // Main loop
  for (bool done = false; !done;) {
    // Poll and handle events (inputs, window resize, etc.)
    // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to
    // tell if dear imgui wants to use your inputs.
    // - When io.WantCaptureMouse is true, do not dispatch mouse input data to
    // your main application, or clear/overwrite your copy of the mouse data.
    // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input
    // data to your main application, or clear/overwrite your copy of the
    // keyboard data. Generally you may always pass all inputs to dear imgui,
    // and hide them from your application based on those two flags.
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
      ImGui_ImplSDL2_ProcessEvent(&event);
      if (event.type == SDL_QUIT)
        done = true;
      if (event.type == SDL_WINDOWEVENT &&
          event.window.event == SDL_WINDOWEVENT_CLOSE &&
          event.window.windowID == SDL_GetWindowID(window))
        done = true;
    }

    // Start the Dear ImGui frame
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplSDL2_NewFrame();
    ImGui::NewFrame();

    // 2. Show a simple window that we create ourselves. We use a Begin/End pair
    // to create a named window.
    {
      TransformMatrix move2origin{
          {1, 0, 0, 0}, {0, 1, 0, -3}, {0, 0, 1, 4}, {0, 0, 0, 1}};
      TransformMatrix rotation{
          Eigen::Transform<double, 3, Eigen::Affine>(
              Eigen::AngleAxisd(0.0625 * M_PI, Eigen::Vector3d::UnitX())
                  .toRotationMatrix())
              .matrix()};
      TransformMatrix move_back{
          {1, 0, 0, 0}, {0, 1, 0, 3}, {0, 0, 1, -4}, {0, 0, 0, 1}};
      renderer.model_transform(move_back * rotation * move2origin);

      camera.set_position(Point3d{0, 0, 0});
      camera.set_gaze(Vector3d{0, 0, -1});
      renderer.view_transform(camera.get_view_transform());

      const auto &image = renderer.render(resolution_width, resolution_height);

      ImGui::Begin(window_name.c_str()); // Create a window

      GLuint texture;
      glGenTextures(1, &texture);
      glBindTexture(GL_TEXTURE_2D, texture);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      // // Texture must be bound first
      // GLint swizzle[4] = {
      //     GL_RED, // Shader Red   channel source = Texture Red
      //     GL_RED, // Shader Green channel source = Texture Red
      //     GL_RED, // Shader Blue  channel source = Texture Red
      //     GL_ONE  // Shader Alpha channel source = One
      // };
      // glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0, GL_BGR,
                   GL_UNSIGNED_BYTE, image.data);
      ImGui::Image(reinterpret_cast<void *>(static_cast<intptr_t>(texture)),
                   ImVec2(image.cols, image.rows));

      ImGui::End();
    }

    // Rendering
    ImGui::Render();
    glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
    glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w,
                 clear_color.z * clear_color.w, clear_color.w);
    glClear(GL_COLOR_BUFFER_BIT);
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
    SDL_GL_SwapWindow(window);
  }

  // Cleanup
  ImGui_ImplOpenGL3_Shutdown();
  ImGui_ImplSDL2_Shutdown();
  ImGui::DestroyContext();

  SDL_GL_DeleteContext(gl_context);
  SDL_DestroyWindow(window);
  SDL_Quit();

  return 0;
}