#include "build.hpp"
#include <unordered_set>

std::filesystem::path OBJ_DIR = "obj";
std::filesystem::path INC_DIR = "inc";
std::filesystem::path SRC_DIR = "src";
std::filesystem::path RAYLIB_DIR = "raylib";
std::filesystem::path BUILD_FILE = "treender";

bool build = false;
bool clear = false;
bool run = false;

std::string cpp_compiler = "g++";
std::string c_compiler = "gcc";
std::string opt_flags = "-ggdb";
std::vector<std::string> command;
std::vector<std::string> RAYINCLUDE = {"-Iraylib/src", "-Iraylib/src/external/glfw/include", "-Iraylib/src/external/glfw/deps/mingw"};

void clear_build_dir(bool src, bool raylib)
{
  if (src)
  {
    std::filesystem::remove_all(OBJ_DIR / SRC_DIR);
  }
  if (raylib)
  {
    std::filesystem::remove_all(OBJ_DIR / RAYLIB_DIR);
  }
  if (src && raylib)
  {
    if (std::filesystem::exists(OBJ_DIR))
      std::filesystem::remove_all(OBJ_DIR);
  }
  std::filesystem::remove(BUILD_FILE);
}

void compile_raylib_dir()
{
  std::filesystem::path out_dir = OBJ_DIR / RAYLIB_DIR;
  std::filesystem::path ray_src_dir = RAYLIB_DIR / "src";
  std::filesystem::create_directory(out_dir);
  std::vector<std::filesystem::path> ray_srcs = {"rcore.c", "rshapes.c", "rtextures.c", "rtext.c", "utils.c", "rmodels.c", "raudio.c", "rglfw.c"};
  std::vector<std::string> ray_flags = {"-D_GNU_SOURCE", "-DPLATFORM_DESKTOP", "-DGRAPHICS_API_OPENGL_33"};

  command.clear();
  command.push_back(c_compiler);
  command.push_back("-c");
  int src_loc = command.size();
  command.push_back("");
  command.push_back("-o");
  int obj_loc = command.size();
  command.push_back("");

  command.insert(command.end(), ray_flags.begin(), ray_flags.end());
  command.push_back(opt_flags);
  command.insert(command.end(), RAYINCLUDE.begin(), RAYINCLUDE.end());

  std::filesystem::path out, src;

  for (size_t i = 0; i < ray_srcs.size(); i++)
  {
    src = ray_src_dir / ray_srcs[i];
    out = out_dir / ray_srcs[i].replace_extension(".o");
    if (check_if_rebuild(src, out))
    {
      command[src_loc] = src;
      command[obj_loc] = out;
      nob_cmd_run_sync(command);
    }
  }
}

int compile_src_dir()
{
  nob_directory src_dir = get_all_files_in_dir(SRC_DIR);
  nob_directory obj_dir;

  for (auto &&i : src_dir.dirs)
  {
    std::filesystem::create_directory(OBJ_DIR / i);
  }

  std::unordered_set<std::string> modified_heders;

  nob_directory inc_dir = get_all_files_in_dir(INC_DIR);
  for (size_t i = 0; i < inc_dir.files.size(); i++)
  {
    if (check_if_rebuild(inc_dir.files[i], BUILD_FILE))
    {
      std::string heder = inc_dir.files[i].string().substr(4);
      modified_heders.insert(heder);
    }
  }

  command.clear();
  command.push_back(cpp_compiler);
  command.push_back("-c");
  int src_loc = command.size();
  command.push_back("");
  command.push_back("-o");
  int obj_loc = command.size();
  command.push_back("");
  command.push_back(opt_flags);
  command.insert(command.end(), RAYINCLUDE.begin(), RAYINCLUDE.end());
  command.push_back("-Iinc");

  command.push_back("-std=c++23");
  command.push_back("-Wall");
  command.push_back("-Werror");

  int build = 0;
  for (auto &&i : src_dir.files)
  {
    std::filesystem::path tmp = i;
    std::filesystem::path out = OBJ_DIR / tmp.replace_extension(".o");
    bool reb = false;
    if (check_if_rebuild(i, out))
      reb = true;

    if (!reb)
    {
      std::vector<std::string> incudes = get_includes(i);
      for (auto &&j : incudes)
      {
        if (modified_heders.find(j) != modified_heders.end())
        {
          reb = true;
          break;
        }
      }
    }

    if (reb)
    {
      command[src_loc] = i;
      command[obj_loc] = out;
      if (!nob_cmd_run_sync(command))
        return -1;

      build = 1;
    }
  }
  return build;
}

void compile_obj_dir()
{
  nob_directory obj_dir = get_all_files_in_dir(OBJ_DIR);

  command.clear();
  command.push_back(cpp_compiler);

  for (auto &&i : obj_dir.files)
  {
    command.push_back(i);
  }

  command.push_back("-o");
  command.push_back(BUILD_FILE);

  nob_cmd_run_sync(command);
}

int main(int argc, char const *argv[])
{
  if (rebuild_my_self(__FILE__, argc, argv))
    return 0;

  for (int i = 1; i < argc; ++i)
  {
    if (!std::strcmp(argv[i], "opt"))
      opt_flags = "-O3";

    if (!std::strcmp(argv[i], "build"))
      build = true;

    if (!std::strcmp(argv[i], "run"))
      run = true;

    if (!std::strcmp(argv[i], "clear"))
    {
      clear_build_dir(true, true);
      clear = true;
    }
  }

  if (clear && !build)
  {
    return 0;
  }

  if (!std::filesystem::is_directory(RAYLIB_DIR))
  {
    command = {"git", "clone", "--depth", "1", "--branch", "5.0", "https://github.com/raysan5/raylib.git"};
    nob_cmd_run_sync(command);
    std::filesystem::remove_all("raylib/.git");
  }

  if (!std::filesystem::is_directory(RAYLIB_DIR))
    return 0;

  std::filesystem::create_directory(OBJ_DIR);

  compile_raylib_dir();

  int res = compile_src_dir();
  switch (res)
  {
  case 1:
    compile_obj_dir();
    break;
  case -1:
    return 0;
  }

  if (!std::filesystem::exists(BUILD_FILE))
    compile_obj_dir();

  if (run)
  {
    command = {"./" + BUILD_FILE.string()};
    nob_cmd_run_sync(command);
  }
  return 0;
}