/**
 * @file main.c
 * @brief Converts files to a C file that can be used by llfs (linked list file system).
 * @version 0.2.1
 * @author Lorenz C.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tinydir.h"

#define VERSION      "0.2.1"
#define LLFS_VERSION "0.1.1"
#define MAX_PATH_LEN 256

static void file_name_to_llfs_name(char* llfs_name, const char* file_name);
static void print_help(void);
static void print_version(void);

int main(int argc, char** argv) {
    char src_path[MAX_PATH_LEN];
    char llfs_name[MAX_PATH_LEN];
    char prev_llfs_name[MAX_PATH_LEN] = "NULL";
    FILE* out_file;
    FILE* src_file;
    tinydir_dir dir;
    int file_count = 0;

    // Check if the help or version flag is set
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            print_help();
            return 0;
        } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
            print_version();
            return 0;
        }
    }

    // Check if at least 2 arguments are given (directory and output file)
    if (argc < 3) {
        fprintf(stderr, "Error: Not enough arguments.\r\n");
        print_help();
        goto fail_init;
    }

    // Open the output file
    out_file = fopen(argv[argc - 1], "w");
    if (out_file == NULL) {
        fprintf(stderr, "Error: Could not open output file\r\n");
        goto fail_init;
    }

    // Open the source directory
    strcpy(src_path, argv[argc - 2]);
    if (tinydir_open(&dir, src_path) == -1) {
        fprintf(stderr, "Error: Could not open directory\r\n");
        goto fail_open_dir;
    }

    // Write the header of the output file
    fprintf(out_file, "/**\n"
                      " * @file %s\n"
                      " * @brief Linked list file system (llfs) data file.\n"
                      " * This file was generated by mkllfs %s.\n"
                      " * @date %s\n */\n\n",
            argv[argc - 1], VERSION, __DATE__);

    // Write the headers of the output file
    fprintf(out_file, "#include <stddef.h>\n");
    fprintf(out_file, "#include \"llfs.h\"\n");
    fprintf(out_file, "\n");

    // Iterate over all files in the directory
    while (dir.has_next) {
        tinydir_file file;
        size_t file_size = 0;

        // Get file info
        if (tinydir_readfile(&dir, &file) == -1) {
            fprintf(stderr, "Error: Could not read file\r\n");
            continue;
        }
        if (file.is_dir) {
            tinydir_next(&dir);
            continue;
        }

        // Open the source file
        src_file = fopen(file.path, "rb");
        if (src_file == NULL) {
            printf("Error: Could not open source file: %s\r\n", file.path);
            continue;
        }

        // Convert the file name to a valid llfs name
        file_name_to_llfs_name(llfs_name, file.name);

        // Write the file data to the output file
        fprintf(out_file, "// File: %s\n", file.name);
        fprintf(out_file, "__attribute__((section(\".ext_qspi_flash\")))\n");
        fprintf(out_file, "const uint8_t %s_data[] = {\n", llfs_name);
        for (int i = 0, c; (c = fgetc(src_file)) != EOF; i++) {
            file_size++;
            fprintf(out_file, "0x%02X, ", c);
            if (i % 16 == 15) {
                fprintf(out_file, "\n");
            }
        }
        fprintf(out_file, "\n};\n");

        // Write the file info to the output file
        fprintf(out_file,
                "const struct llfs_data_file %s = {\n    .data = %s_data,\n    .name = \"%s\",\n    .len = %zu,\n    "
                ".next =%s%s\n};\n\n",
                llfs_name, llfs_name, file.name, file_size, strcmp(prev_llfs_name, "NULL") == 0 ? " " : " &",
                prev_llfs_name);

        file_count++;
        tinydir_next(&dir);
        strcpy(prev_llfs_name, llfs_name);
        fclose(src_file);
    }

    // Make the last file the root file of the llfs
    fprintf(out_file, "\n");
    fprintf(out_file, "const struct llfs_data_file *llfs_root =%s%s;\n", (strcmp(prev_llfs_name, "NULL") == 0 ? " " : " &"), prev_llfs_name);

    // Print the number of files
    printf("Successfully converted %d files.\r\n", file_count);

fail:
    tinydir_close(&dir);
fail_open_dir:
    fclose(out_file);
fail_init:
    return 0;
}

/**
 * @brief Converts a file name to a valid llfs name, so that it can be used as a variable name in the generated C file.
 * The length of the generated llfs name is the same as the length of the original file name.
 *
 * @param[out] llfs_name The output llfs name. Must be at least as long as the file name.
 * @param[in] file_name The input file name as a null terminated string.
 */
static void file_name_to_llfs_name(char* llfs_name, const char* file_name) {
    size_t len = strlen(file_name);

    // Replace all invalid characters with '_'
    for (int i = 0; i < len; i++) {
        if (file_name[i] == '.' || file_name[i] == '-' || file_name[i] == ' ') {
            llfs_name[i] = '_';
        } else {
            llfs_name[i] = file_name[i];
        }
    }

    // Replace the first character if it is a number
    if (llfs_name[0] >= '0' && llfs_name[0] <= '9') {
        llfs_name[0] = '_';
    }

    llfs_name[len] = '\0';
}

/**
 * @brief Prints the help message.
 */
static void print_help(void) {
    printf("Make Linked list file system (mkllfs)\r\n");
    printf("Converts files into a linked list file system.\r\n");
    printf("By Lorenz C.\r\n");
    printf("\r\n");
    printf("Usage: mkllfs [options] <directory> <output file>\r\n");
    printf("Options:\r\n");
    printf("    -h, --help: Print this help message\r\n");
    printf("    -v, --version: Print the version\r\n");
}

/**
 * @brief Prints the version.
 */
static void print_version(void) {
    printf("mkllfs version %s\r\n", VERSION);
    printf("llfs version %s\r\n", LLFS_VERSION);
}
