/**
  \file makelists.c
  \author FC
  \date 2026
  \brief "Make" script.

  This script should be executed before compiling as it provides/corrects some macros in
  config/config.h and generates config/.autogen_macros.h from config/musiclist.txt and config/charlist.txt.
  
  This is done in order to have precise static buffer sizes and have custom simplified formats to load
  the character and music list.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define CHARLIST_FILE  "../config/charlist.txt"
#define MUSICLIST_FILE "../config/musiclist.txt"
#define AREALIST_FILE  "../config/arealist.h"
#define AUTOGEN_FILE   "../config/.autogen_macros.h"
#define CONFIG_FILE    "../config/config.h"
#define CONFIG_TMP     "../config/config.tmp"

#define LINE_BUFFER 4096

int max_found_musiclist_length = 0;
int max_found_charlist_length = 0;

/* -------------------- Utils -------------------- */

void trim(char *str) {
    char *start = str;
    char *end;

    while (*start && isspace((unsigned char)*start)) start++;
    if (*start == '\0') {
        str[0] = '\0';
        return;
    }

    end = start + strlen(start) - 1;
    while (end > start && isspace((unsigned char)*end)) end--;

    end[1] = '\0';
    memmove(str, start, end - start + 1);
}

void sanitize(char *str) {
    char buffer[LINE_BUFFER];
    char *p = buffer;
    char *orig = str;

    while (*str) {
        switch (*str) {
            case '&': strcpy(p, "<and>"); p += 5; break;
            case '%': strcpy(p, "<percent>"); p += 9; break;
            case '#': strcpy(p, "<num>"); p += 5; break;
            case '$': strcpy(p, "<dollar>"); p += 8; break;
            default: *p++ = *str; break;
        }
        str++;
    }
    *p = '\0';

    strcpy(orig, buffer);
}

void update_max_length(const char *str, int *var) {
    int len = strlen(str);
    if (len > *var) *var = len;
}

/* -------------------- Config -------------------- */

int read_max_length(const char *macro) {
    FILE *f = fopen(CONFIG_FILE, "r");
    char line[LINE_BUFFER];
    int value = 128;

    if (!f) return value;

    while (fgets(line, LINE_BUFFER, f)) {
        if (strstr(line, macro)) {
            sscanf(line, "#define %*s %d", &value);
            break;
        }
    }

    fclose(f);
    return value;
}

void update_config_file(int new_length, const char *macro) {
    FILE *fin = fopen(CONFIG_FILE, "r");
    FILE *fout = fopen(CONFIG_TMP, "w");
    char line[LINE_BUFFER];

    if (!fin || !fout) return;

    while (fgets(line, LINE_BUFFER, fin)) {
        if (strstr(line, macro)) {
            fprintf(fout, "#define %s %d\n", macro, new_length);
        } else {
            fputs(line, fout);
        }
    }

    fclose(fin);
    fclose(fout);

    remove(CONFIG_FILE);
    rename(CONFIG_TMP, CONFIG_FILE);
}

/* -------------------- Generators -------------------- */

void write_charlist_macros(FILE *out) {
    FILE *fin = fopen(CHARLIST_FILE, "r");
    char line[LINE_BUFFER];

    if (!fin) return;
    int counter = 0;
    while (fgets(line, LINE_BUFFER, fin)) {
        trim(line);
        if (*line == '\0') continue;

        sanitize(line);
        update_max_length(line, &max_found_charlist_length);
        counter++;
        fprintf(out, "    X(\"%s\") \\\n", line);
    }

    fprintf(out, "\n#define CHAR_LIST_COUNT %u\n", counter);

    fprintf(out, "\n");
    fclose(fin);
}

void write_musiclist_macros(FILE *out) {
    FILE *fin = fopen(MUSICLIST_FILE, "r");
    char line[LINE_BUFFER];

    if (!fin) return;

    while (fgets(line, LINE_BUFFER, fin)) {
        char *sep, *name, *url = NULL;
        trim(line);
        if (*line == '\0') continue;

        sep = strstr(line, " | ");
        name = line;
        if (sep) {
            *sep = '\0';
            url = sep + 3;
        }

        trim(name);
        sanitize(name);
        update_max_length(name, &max_found_musiclist_length);

        if (url) {
            trim(url);
            sanitize(url);
            update_max_length(url, &max_found_musiclist_length);
        }

        if (url && *url) {
            fprintf(out, "    X_STREAM_SONG(\"%s\", \"%s\") \\\n", name, url);
        } else {
            fprintf(out, "    X(\"%s\") \\\n", name);
        }
    }

    fprintf(out, "\n");
    fclose(fin);
}

/* -------------------- Area List Stats -------------------- */

int compute_area_list_max_length(void) {
    FILE *fin = fopen(AREALIST_FILE, "r");
    char line[LINE_BUFFER];
    int max_len = 0;

    if (!fin) return 0;

    while (fgets(line, LINE_BUFFER, fin)) {
        char *start, *end;
        char name[LINE_BUFFER];
        trim(line);
        if (!strstr(line, ".name")) continue;

        start = strchr(line, '"');
        if (!start) continue;
        start++;
        end = strchr(start, '"');
        if (!end) continue;

        strncpy(name, start, end - start);
        name[end - start] = '\0';
        trim(name);

        {
            int len = 0;
            char *p = name;
            while (*p) {
                switch (*p) {
                    case '&': len += 5; break;
                    case '%': len += 9; break;
                    case '#': len += 5; break;
                    case '$': len += 8; break;
                    default: len++; break;
                }
                p++;
            }
            if (len > max_len) max_len = len;
        }
    }

    fclose(fin);
    return max_len;
}

int compute_area_list_total_count(void) {
    FILE *fin = fopen(AREALIST_FILE, "r");
    char line[LINE_BUFFER];
    int count = 0;

    if (!fin) return 0;

    while (fgets(line, LINE_BUFFER, fin)) {
        char *p;
        trim(line);
        p = strstr(line, ".name = ");
        if (p) {
            p = strchr(p, '"') + 1;
            while (*p && *p != '"') {
                count++;
                p++;
            }
        }
    }

    fclose(fin);
    return count;
}

/* -------------------- Main -------------------- */

int main(void) {
    FILE *fout;
    int current_max_musiclist_length, current_max_char_name;
    int area_max_length = compute_area_list_max_length();
    int area_total_count = compute_area_list_total_count();

    fout = fopen(AUTOGEN_FILE, "w");
    if (!fout) {
        perror("Cannot open .autogen_macros.h");
        return 1;
    }

    current_max_musiclist_length = read_max_length("SONG_NAME_MAX_LENGTH");
    current_max_char_name = read_max_length("CHAR_NAME_MAX_LENGTH");

    fprintf(fout, "#define CHARLIST \\\n");
    write_charlist_macros(fout);

    fprintf(fout, "#define MUSIC_LIST \\\n");
    write_musiclist_macros(fout);

    fprintf(fout, "#define AREA_NAME_MAX_LENGTH %d\n", area_max_length);
    fprintf(fout, "#define AREA_LIST_TOTAL_STRING_COUNT %d\n\n", area_total_count);

    fclose(fout);

    if (max_found_musiclist_length > current_max_musiclist_length) {
        printf("Updating SONG_NAME_MAX_LENGTH: %d -> %d\n", current_max_musiclist_length, max_found_musiclist_length);
        update_config_file(max_found_musiclist_length, "SONG_NAME_MAX_LENGTH");
    }
    if (max_found_charlist_length > current_max_char_name) {
        printf("Updating CHAR_NAME_MAX_LENGTH: %d -> %d\n", current_max_char_name, max_found_charlist_length);
        update_config_file(max_found_charlist_length, "CHAR_NAME_MAX_LENGTH");
    }

    return 0;
}