#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <unistd.h>
#include <libgen.h>
#include <dirent.h>
#include <ctype.h>
#include <stdarg.h>

#include "tomlc99/toml.h"

// FIXME: This is hardcoded, fix it.
char *python_interpreter_path = "/usr/local/bin/python3";

bool can_run_command(const char *cmd) {
    if(strchr(cmd, '/')) {
        return access(cmd, X_OK)==0;
    }
    const char *path = getenv("PATH");
    if(!path) return false;
    char *buf = malloc(strlen(path)+strlen(cmd)+3);
    if(!buf) return false;
    for(; *path; ++path) {
        char *p = buf;
        for(; *path && *path!=':'; ++path,++p)
            *p = *path;
        if(p==buf) *p++='.';
        if(p[-1]!='/') *p++='/';
        strcpy(p, cmd);
        if(access(buf, X_OK)==0) {
            free(buf);
            return true;
        }
        if(!*path) break;
    }
    free(buf);
    return false;
}

#define BUFFER_SIZE 1024
bool copy_directory(const char *src_path, const char *dest_path) {
    DIR *src_dir = opendir(src_path);
    if (src_dir == NULL) {
        // Failed to open source directory
        return false;
    }

    // Create destination directory if it doesn't exist
    mkdir(dest_path, 0755);

    // Copy files and directories from source to destination
    struct dirent *entry;
    while ((entry = readdir(src_dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            // Skip "." and ".." directories
            continue;
        }

        char src_file_path[BUFFER_SIZE];
        snprintf(src_file_path, BUFFER_SIZE, "%s/%s", src_path, entry->d_name);

        char dest_file_path[BUFFER_SIZE];
        snprintf(dest_file_path, BUFFER_SIZE, "%s/%s", dest_path, entry->d_name);

        struct stat src_file_stat;
        stat(src_file_path, &src_file_stat);

        if (S_ISDIR(src_file_stat.st_mode)) {
            // Recursively copy directories
            if (!copy_directory(src_file_path, dest_file_path)) {
                return false;
            }
        } else if (S_ISREG(src_file_stat.st_mode)) {
            // Copy regular files
            FILE *src_file = fopen(src_file_path, "rb");
            if (src_file == NULL) {
                // Failed to open source file
                return false;
            }

            FILE *dest_file = fopen(dest_file_path, "wb");
            if (dest_file == NULL) {
                // Failed to open destination file
                fclose(src_file);
                return false;
            }

            // Copy file contents
            char buffer[BUFFER_SIZE];
            size_t bytes_read;
            while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src_file)) > 0) {
                fwrite(buffer, 1, bytes_read, dest_file);
            }

            fclose(src_file);
            fclose(dest_file);
        } else {
            // Skip other file types
            continue;
        }
    }

    closedir(src_dir);
    return true;
}

void clear_directory(char const *path) {
  DIR *dir = opendir(path);
  if (dir == NULL) {
    // Unable to open the directory
    perror("opendir");
    return;
  }

  struct dirent *entry;
  while ((entry = readdir(dir)) != NULL) {
    // Skip "." and ".." entries
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
      continue;
    }

    // Get the full path of the entry
    char entry_path[1024];
    snprintf(entry_path, sizeof(entry_path), "%s/%s", path, entry->d_name);

    // Check if the entry is a directory
    struct stat entry_stat;
    if (stat(entry_path, &entry_stat) != 0) {
      perror("stat");
      continue;
    }
    if (S_ISDIR(entry_stat.st_mode)) {
      // Remove the directory recursively
      clear_directory(entry_path);
      if (rmdir(entry_path) != 0) {
        perror("rmdir");
      }
    } else {
      // Remove the entry
      if (unlink(entry_path) != 0) {
        perror("unlink");
      }
    }
  }

  closedir(dir);
}

#define MANIFEST_FNAME "tos_project.toml"

char* find_project_root(void)
{
  char* result = NULL;

  // Get the current working directory
  char cwd[1024];
  if (getcwd(cwd, sizeof(cwd)) == NULL) {
    return NULL;
  }

  // Check if the current working directory contains the file "tos_project.toml"
  size_t file_path_len = strlen(cwd) + strlen("/tos_project.toml") + 1;
  char file_path[file_path_len];
  snprintf(file_path, file_path_len, "%s/tos_project.toml", cwd);

  if (access(file_path, F_OK) == 0) {
    // The file exists, so we can return the path to the directory containing it
    result = malloc(strlen(cwd) + 1);
    strncpy(result, cwd, strlen(cwd) + 1);
    return result;
  }

  // The file does not exist in the current working directory, so we need to search backwards
  size_t len = strlen(cwd);
  for (int i = len - 1; i >= 0; i--) {
    if (cwd[i] == '/') {
      // We found a directory separator, so we can check if the parent directory contains the file
      cwd[i] = '\0';
      snprintf(file_path, file_path_len, "%s/tos_project.toml", cwd);

      if (access(file_path, F_OK) == 0) {
        // The file exists, so we can return the path to the parent directory containing it
        result = malloc(strlen(cwd) + 1);
        strncpy(result, cwd, strlen(cwd) + 1);
        break;
      }
    }
  }

  return result;
}

#define SAMPLE_MANIFEST "[General]\n" \
  "Name=\"%s\"\n" \
  "Author=\"%s\"\n" \
  "Version=\"0.1\"\n" \
  "\n" \
  "[Dependencies]\n"

typedef struct {
  char *name, *uri;
} dependency;

#define MAX_DEP 20
typedef struct project_manifest {
  char *name, *author, *version;
  dependency dependencies[MAX_DEP];
  size_t dependencies_amount;
} project_manifest;

void free_manifest(project_manifest *manifest) {
  int i;

  free(manifest->name);
  free(manifest->author);
  free(manifest->version);

  for (i = 0; i < manifest->dependencies_amount; i++) {
    free(manifest->dependencies[i].name);
    free(manifest->dependencies[i].uri);
  }
}

project_manifest* load_manifest(char const *path) {
  FILE* fp;
  int i;
  char errbuf[200];

  fp = fopen(path, "r");
  if (!fp) {
    fputs("Error: Cannot load manifest file: Cannot open file.\n", stderr);
    return NULL;
  }

  project_manifest *new = calloc(1, sizeof(project_manifest));

  toml_table_t *conf = toml_parse_file(fp, errbuf, sizeof(errbuf));
  fclose(fp);
  if (!conf) {
    fprintf(stderr, "Error: Cannot load manifest file: Cannot parse file: %s\n", errbuf);
    return NULL;
  }

  toml_table_t *general = toml_table_in(conf, "General");
  if (!general) {
    fprintf(stderr, "Error: Cannot load manifest file: Cannot find [General] table.\n");
    return NULL;
  }

  toml_datum_t name = toml_string_in(general, "Name");
  if (!name.ok) {
    fprintf(stderr, "Error: Cannot load manifest file: Cannot find Name field.\n");
    return NULL;
  }

  toml_datum_t author = toml_string_in(general, "Author");
  if (!author.ok) {
    fprintf(stderr, "Error: Cannot load manifest file: Cannot find Author field.\n");
    return NULL;
  }

  toml_datum_t version = toml_string_in(general, "Version");
  if (!version.ok) {
    fprintf(stderr, "Error: Cannot load manifest file: Cannot find Version field.\n");
    return NULL;
  }

  new->name = name.u.s;
  new->author = author.u.s;
  new->version = version.u.s;

  new->dependencies_amount = 0;

  // FIXME: Add dependencies.
  toml_table_t *dependencies = toml_table_in(conf, "Dependencies");
  if (!dependencies) {
    fprintf(stderr, "Error: Cannot load manifest file: Cannot find [Dependencies] table.\n");
    return NULL;
  }

  for (i = 0; ; i++) {
    int j;

    char const *key = toml_key_in(dependencies, i);
    if (!key) break;

    toml_datum_t dep_uri = toml_string_in(dependencies, key);
    new->dependencies_amount++;
    char *name = calloc(1, sizeof(char) * (strlen(key) + 1));
    strcpy(name, key);
    char *url = calloc(1, sizeof(char) * (strlen(dep_uri.u.s) + 1));
    strcpy(url, dep_uri.u.s);

    // HACK: Just replace the system command...
    for (j = 0; j < strlen(name); j++)
      if (name[j] == '\'')
        name[j] = ' ';

    for (j = 0; j < strlen(url); j++)
      if (url[j] == '\'')
        url[j] = ' ';

    dependency dep = {
      name, url
    };

    new->dependencies[i] = dep;
  }

  toml_free(conf);
  return new;
}

char buffer_text_format[2048];
char *text_format(char const *format, ...) {
  va_list args;
  va_start(args, format);
  vsprintf(buffer_text_format, format, args);
  va_end(args);
  return buffer_text_format;
}

char *get_username(void) {
#if defined(_WIN32)
  // FIXME: This leaks memory.

  TCHAR infoBuf[105];
  DWORD bufCharCount = 105;
  if( !GetUserName( infoBuf, &bufCharCount ) )
    printError( TEXT("GetUserName") ); 

  char *c_szText[105];
  wcstombs(c_szText, infoBuf, wcslen(infoBuf) + 1);
  return c_szText;
#else
  uid_t uid = geteuid();
  struct passwd *pw = getpwuid(uid);
  if (pw == NULL)
    return NULL;
  return pw->pw_name;
#endif
}

bool create_manifest_file(char const *project_path) {
  int i;
  char *name;
  char cwd[4096];
  if (strlen(project_path) == 1 && project_path[0] == '.') {
    if (getcwd(cwd, sizeof(cwd)) == NULL) {
      fputs("Error: Cannot create manifest: Cannot get current working directory.\n", stderr);
      return false;
    } else {
      name = malloc((strlen(cwd)+1)*sizeof(char));
      strcpy(name, cwd);
    }
  } else {
    name = basename((char *)project_path);
  }

  char *fname;
  for (i = strlen(name) - 1; i >= 0; i--) {
    if (name[i] == '/') {
      fname = name + i + 1;
      break;
    }
  }

  char *uname = get_username();

  FILE *fd = fopen(text_format("%s/" MANIFEST_FNAME, project_path), "w+");
  if (fd == NULL) {
    fputs("Error: Cannot create manifest: Cannot open manifest file.\n", stderr);
    return false;
  }

  fprintf(fd, SAMPLE_MANIFEST, fname, uname);

  fclose(fd);

  return true;
}

void print_help(char **argv) {
  printf("Usage: %s [command]\n", argv[0]);
  fputs("\nCommands:\n", stderr);
  fputs(" * init|i [path=.] - Setup a new project.\n", stderr);
  fputs(" * build|. [--zeal|-z|-Z] - Build project in current working directory.\n", stderr);
  fputs(" * clean - Clean output code in current working directory.\n", stderr);
  fputs("\nTo see manifest file usage, check out man tbuild(1)\n", stderr);
}

bool file_exists(char const *path) {
  struct stat st = {0};
  return (stat(path, &st) != -1);
}

// FIXME: Implement for Windows.
bool makedir(char const *path) {
  struct stat st = {0};

  if (stat(path, &st) == -1) {
    mkdir(path, 0700);
  } else {
    fprintf(stderr, "Warning: File already exists. Continuing anyway.");
    return false;
  }
  return true;
}

bool makedir_parenting(char const *path) {
  char *p;
  struct stat st = {0};

  for(p=strchr(path+1, '/'); p; p=strchr(p+1, '/')){
    *p = 0;
    if(stat(path, &st) == -1 && makedir(path) == true)
      return true;
    *p = '/';
  }

  if(stat(path, &st) == -1)
    makedir(path);

  return true;
}

void replace_in_file(char const *file, char const *text_to_find, char const *text_to_replace) {
  FILE *input = fopen(file, "r");
  FILE *output = fopen("temp.txt", "w");

  char buffer[512];
  while (fgets(buffer, sizeof(buffer), input) != NULL) {
    char *pos = strstr(buffer, text_to_find);

    if (pos == NULL) {
      fputs(buffer, output);
      continue;
    }

    char *temp = calloc(strlen(buffer) - strlen(text_to_find) + strlen(text_to_replace) + 1, 1);

    memcpy(temp, buffer, pos - buffer);
    memcpy(temp + (pos - buffer), text_to_replace, strlen(text_to_replace));
    memcpy(temp + (pos - buffer) + strlen(text_to_replace),
           pos + strlen(text_to_find),
           1 + strlen(buffer) - ((pos - buffer) + strlen(text_to_find)));

    fputs(temp, output);
    free(temp);
  }
  fclose(output);
  fclose(input);

  rename("temp.txt", file);
}

void convert_to_zealos(char const *path) {
  DIR *dir;
  struct dirent *entry;
  struct stat s;

  if (!(dir = opendir(path)))
    return;

  if (!(entry = readdir(dir)))
    return;

  do {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
      continue;

    char fpath[1024];
    int len = snprintf(fpath, sizeof(fpath)-1, "%s/%s", path, entry->d_name);
    fpath[len] = 0;

    if (lstat(fpath, &s) == 0 && S_ISDIR(s.st_mode)) { // Is directory?
      convert_to_zealos(fpath);
      continue;
    } 

    if (fpath[len-2] != 'H' || fpath[len-3] != '.')
      continue;

    printf("Converting %s\n", entry->d_name);

    replace_in_file(fpath, "MemCpy", "MemCopy");
    replace_in_file(fpath, "MemCpy", "MemCopy");
    replace_in_file(fpath, "MemCmp", "MemCompare");
    replace_in_file(fpath, "StrCpy", "StrCopy");
    replace_in_file(fpath, "StrCmp", "StrCompare");
    replace_in_file(fpath, "StrICmp", "StrICompare");
    replace_in_file(fpath, "StrNCmp", "StrNCompare");
    replace_in_file(fpath, "StrNICmp", "StrNICompare");
    replace_in_file(fpath, "BEqu", "BEqual");
    replace_in_file(fpath, "LBEqu", "LBEqual");
    replace_in_file(fpath, "ms", "mouse");
    replace_in_file(fpath, "Snd", "Sound");
    replace_in_file(fpath, "SndTaskEndCB", "SoundTaskEndCB");
    replace_in_file(fpath, "mp_cnt", "mp_count");
    replace_in_file(fpath, "QueIns", "QueueInsert");
    replace_in_file(fpath, "QueInit", "QueueInit");
    replace_in_file(fpath, "QueRem", "QueueRemove");
    replace_in_file(fpath, "QueDel", "QueueDel");
    replace_in_file(fpath, "MsSet", "MouseSet");
    replace_in_file(fpath, "UnusedStk", "UnusedStack");
    replace_in_file(fpath, "word_lst", "word_list");
    replace_in_file(fpath, "FileExtRem", "FileExtRemove");
    replace_in_file(fpath, "cnts", "counts");
    replace_in_file(fpath, "PostMsg", "MessagePost");
    replace_in_file(fpath, "PostMsgWait", "MessagePostWait");
    replace_in_file(fpath, "QSort", "QuickSort");
    replace_in_file(fpath, "QSortI64", "QuickSortI64");
    replace_in_file(fpath, "ScanMsg", "MessageScan");
    replace_in_file(fpath, "cnts", "counts");
    replace_in_file(fpath, "Dsk", "Disk");
    replace_in_file(fpath, "collision_cnt", "collision_count");
    replace_in_file(fpath, "Drv", "Drive");
    replace_in_file(fpath, "DrvRep", "DriveRep");
    replace_in_file(fpath, "Drv2Let", "Drive2Letter");
    replace_in_file(fpath, "LstSub", "ListSub");
    replace_in_file(fpath, "LstMatch", "ListMatch");
    replace_in_file(fpath, "DefineLstLoad", "DefineListLoad");
    replace_in_file(fpath, "ExtDft", "ExtDefault");
    replace_in_file(fpath, "ExtChg", "ExtChange");
    replace_in_file(fpath, "RegDft", "RegDefault");
    replace_in_file(fpath, "\"HC\"", "\"CC\"");
    replace_in_file(fpath, "CDrv", "CDrive");
    replace_in_file(fpath, "CDbgInfo", "CDebugInfo");
    replace_in_file(fpath, "dbg_info", "debug_info");
    replace_in_file(fpath, "StrFirstRem", "StrFirstRemove");
    replace_in_file(fpath, "StrLastRem", "StrLastRemove");
    replace_in_file(fpath, "TempleOS/Apps", "/Apps");
    replace_in_file(fpath, "adam_task", "sys_task");
    replace_in_file(fpath, "JobQue", "JobQueue");
    replace_in_file(fpath, "MSG_", "MESSAGE_");
    replace_in_file(fpath, ".HC", ".ZC");
    replace_in_file(fpath, "Msg", "Message");
    replace_in_file(fpath, "MusicSettingsRst", "MusicSettingsReset");
    replace_in_file(fpath, "hndlr", "handler");
    replace_in_file(fpath, "FifoU8Rem", "FifoU8Remove");
    replace_in_file(fpath, "GodBitsIns", "GodBitsInsert");
    replace_in_file(fpath, "fp_draw_ms", "fp_draw_mouse");
    replace_in_file(fpath, "DrawStdMs", "DrawStdMouse");
    replace_in_file(fpath, "WIG_TASK_DFT", "WIG_TASK_DEFAULT");
    replace_in_file(fpath, "DirMk", "DirMake");
    replace_in_file(fpath, "GetI64", "I64Get");
    replace_in_file(fpath, "GetF64", "F64Get");
    replace_in_file(fpath, "GetStr", "StrGet");
    replace_in_file(fpath, "GetChar", "CharGet");

    // Added from Anfintony's Insecticide November 24 2022
    replace_in_file(fpath, "GetMsg", "MessageGet");
    replace_in_file(fpath, "DRV_SIGNATURE_VAL", "DRIVE_SIGNATURE_VAL");
    replace_in_file(fpath, "dv_signature", "drive_signature");
    replace_in_file(fpath, "DrvTextAttrGet", "DriveTextAttrGet");
    replace_in_file(fpath, "DrvIsWritable", "DriveIsWritable");
    replace_in_file(fpath, "gr_palette_std", "gr32_palette_std");
    replace_in_file(fpath, "GetKey", "KeyGet");
    replace_in_file(fpath, "STD_DISTRO_DVD_CFG", "STD_DISTRO_DVD_CONFIG");
    replace_in_file(fpath, "CBGR48", "CBGR24");
    replace_in_file(fpath, "CFreeLst", "CFreeList");
    replace_in_file(fpath, "DrvLock", "DriveLock");
    replace_in_file(fpath, "DrvUnlock", "DriveUnlock");
    replace_in_file(fpath, "DrvChk", "DriveCheck");
    replace_in_file(fpath, "AMAlloc", "SysMAlloc");
    replace_in_file(fpath, "Let2Drv", "Letter2Drive");
    replace_in_file(fpath, "Let2Let", "Letter2Letter");
    replace_in_file(fpath, "Let2BlkDev", "Letter2BlkDev");
    replace_in_file(fpath, "first_drv_let", "first_drive_let");

    // Added by Doodguy and Anfintony November 25 2022
    replace_in_file(fpath, "ScanKey", "KeyScan");
    replace_in_file(fpath, "ScanChar", "CharScan");
    replace_in_file(fpath, "fp_final_scrn_update", "fp_final_screen_update");

    entry->d_name[strlen(entry->d_name)-2] = 'Z';
    rename(fpath, text_format("%s/%s", path, entry->d_name));

  } while ((entry = readdir(dir)));
  closedir(dir);
}

bool run_scripts(char const *path) {
  DIR *dir;
  struct dirent *entry;
  struct stat s;

  if (!(dir = opendir(path)))
    return false;

  if (!(entry = readdir(dir)))
    return false;

  do {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
      continue;

    char fpath[1024];
    int len = snprintf(fpath, sizeof(fpath)-1, "%s/%s", path, entry->d_name);
    fpath[len] = 0;

    if (lstat(fpath, &s) == 0 && S_ISDIR(s.st_mode)) { // Is directory?
      run_scripts(fpath);
      continue;
    } 

    if (fpath[len-3] != 'p' || fpath[len-2] != 'y' || fpath[len-3] != '.')
      continue;

    printf("Running %s\n", entry->d_name);
    char *argv[] = {
      fpath,
      NULL
    };

    execve(python_interpreter_path, argv, NULL);

  } while ((entry = readdir(dir)));
  closedir(dir);
  
  return true;
}

int main(int argc, char **argv) {
  if (argc < 2) {
    puts("No arguments provided.\n");
    print_help(argv);
    return 0;
  }

  char path_save[PATH_MAX];
  char abs_exe_path[PATH_MAX];
  char *p;

  if(!(p = strrchr(argv[0], '/')))
    getcwd(abs_exe_path, sizeof(abs_exe_path));
  else {
    *p = '\0';
    getcwd(path_save, sizeof(path_save));
    chdir(argv[0]);
    getcwd(abs_exe_path, sizeof(abs_exe_path));
    chdir(path_save);
  }

  char *project_path = ".";
  char cmd = tolower(argv[1][0]);
  if (cmd == 'i') {
    if (argc > 2) {
      project_path = calloc(1, (strlen(argv[2])+1)*sizeof(char));
      strcpy(project_path, argv[2]);
    }

    fprintf(stderr, "Initializing project in `%s`.\n", project_path);

    bool ret = true;
    if (!(strlen(project_path) == 1 && project_path[0] == '.'))
      ret = makedir_parenting(project_path);

    if (!ret) {
      fputs("Error: Cannot create project: Cannot create directories.\n", stderr);
      return -1;
    }

    // Check if manifest file exists.
    if (file_exists(text_format("%s/" MANIFEST_FNAME, project_path))) {
      fputs("Error: Cannot create project: Project already exists!\n", stderr);
      return -1;
    }

    create_manifest_file(project_path);

    FILE *fp = fopen(text_format("%s/.gitignore", project_path), "w+");
    if (fp) {
      fputs("build\nout\n", fp);
      fclose(fp);
    } else {
      fputs("Warning: Cannot open .gitignore!\n", stderr);
    }

    if (can_run_command("git"))
      system(text_format("git init %s", project_path));

    // Free only if alloc'ed, "." doesn't count since it is embedded in the program itself.
    // Trying to free it if it's "." would cause a crash cause of this.
    if (argc > 2)
      free(project_path);
  } else if (cmd == 'b' || cmd == '.') {
    int i;

    struct {
      bool zeal_build;
    } options = { 0 };

    for (int i = 2; i < argc; i++) {
      if (argv[i][0] != '-') 
        continue;

      if (argv[i][1] == '-') {
        if (strcmp(argv[i], "--zeal"))
          options.zeal_build = true;

        continue;
      } 

      for (int j = 1; j < strlen(argv[i]); j++) {
        switch (argv[i][j]) {
          case 'z':
          case 'Z':
            options.zeal_build = true;
            break;
        }
      }
    }

    // Find project root.
    char cwd[4096];
    if (getcwd(cwd, sizeof(cwd)) == NULL) {
       fputs("Error: Cannot build project: Cannot get current working directory.\n", stderr);
       return 1;
    }

    project_path = find_project_root();
    if (project_path == NULL) {
      fputs("Error: Cannot build project: Not in a project directory.\n", stderr);
      return 1;
    }

    project_manifest* manifest = load_manifest(text_format("%s/" MANIFEST_FNAME, project_path));
    if (!manifest) {
      fputs("Error: Cannot build project: Failed to open manifest.\n", stderr);
      return 1;
    }
    puts(buffer_text_format);

    text_format("%s/lib", project_path);
    char *lib_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
    strcpy(lib_dir, buffer_text_format);
    if (!file_exists(lib_dir)) {
      bool status = makedir(lib_dir);
      if (!status) {
        fputs("Error: Cannot build project: Cannot create lib directory.\n", stderr);
        free_manifest(manifest);
        return 1;
      }
    }

    for (i = 0; i < manifest->dependencies_amount; i++) {
      dependency dep = manifest->dependencies[i];
      printf("%s -> %s (%i)\n", dep.name, dep.uri, i);

      puts(text_format("%s/%s", lib_dir, dep.name));
      if (!file_exists(text_format("%s/%s", lib_dir, dep.name)))
        system(text_format("git clone --depth 1 --recursive --shallow-submodules '%s' '%s/%s'", dep.uri, lib_dir, dep.name));
      else
        // FIXME: Make this cross platform.
        system(text_format("cd '%s/%s' && git pull", lib_dir, dep.name));
    }

    // Create build directory
    text_format("%s/build", project_path);
    char *build_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
    strcpy(build_dir, buffer_text_format);
    text_format("%s/src", project_path);
    char *src_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
    strcpy(src_dir, buffer_text_format);
    if (!file_exists(build_dir)) {
      bool status = makedir(build_dir);
      if (!status) {
        fputs("Error: Cannot build project: Cannot create build directory.\n", stderr);
        free_manifest(manifest);
        return 1;
      }
    }

    // Clear and populate.
    puts("Populating build directory...");
    clear_directory(build_dir);
    copy_directory(src_dir, build_dir);
    if (file_exists(lib_dir))
      copy_directory(lib_dir, build_dir);

    // Run scripts (if any)
    puts("Running scripts...");
    // FIXME: This is incredibly hacky.
    text_format("%s/scripts", project_path);
    char *scripts_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
    strcpy(scripts_dir, buffer_text_format);
    if (file_exists(scripts_dir)) {
      if (!run_scripts(scripts_dir)) {
        fputs("Failed running script!\b", stderr);
        return -1;
      }
    }

    if (options.zeal_build) {
      puts("Converting to ZealOS...");
      convert_to_zealos(build_dir);
    }

    // TODO: Obfuscate code if enabled.

    text_format("%s/output", project_path);
    char *out_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
    strcpy(out_dir, buffer_text_format);
    if (!file_exists(out_dir)) {
      bool status = makedir(out_dir);
      if (!status) {
        fputs("Error: Cannot build project: Cannot create output directory.\n", stderr);
        free_manifest(manifest);
        return 1;
      }
    }

    if (options.zeal_build)
      text_format("%s/output/%s-%s.zeal.ISO.C", project_path, manifest->name, manifest->version);
    else
      text_format("%s/output/%s-%s.ISO.C", project_path, manifest->name, manifest->version);
    char *iso_c = malloc((strlen(buffer_text_format)+1)*sizeof(char));
    strcpy(iso_c, buffer_text_format);
#if defined(_WIN32)
    system(text_format("%s/RedSeaGen.exe '%s' '%s'", abs_exe_path, build_dir, iso_c));
#else
    system(text_format("%s/RedSeaGen '%s' '%s'", abs_exe_path, build_dir, iso_c));
#endif

    free(build_dir);
    free(out_dir);
    free(scripts_dir);
    free(lib_dir);
    free(project_path);
    free_manifest(manifest);
  }
}

