/*

Copyright:
    (C) 2022-2023 Daniele 'dzonerzy' Linguaglossa - http://libdzonerzy.so

    This file is part of libdzonerzy.so.

    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

*/

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <ctype.h>

#define VERSION "0.0.1"

typedef enum _format
{
    FORMAT_C,
    FORMAT_H,
} format_t;

typedef enum _type
{
    TYPE_EMBED,
    TYPE_ADVISORY,
    TYPE_ARTICLE,
    TYPE_PAGE,
} type_t;

typedef struct _embed
{
    char *input;
    char *output;
    format_t format;
    type_t type;
    int verbose;
    int quiet;
    int autowrap;
} embed_t;

static void usage(char **argv);
static void version(char **argv);
static int embed(embed_t *args);
static char *clean_filename(char *filename);

int main(int argc, char **argv)
{
    int c = 0;
    embed_t args = {0};

    while ((c = getopt(argc, argv, "hvi:o:f:t:Vqw")) != -1)
    {
        switch (c)
        {
        case 'h':
            usage(argv);
            exit(EXIT_SUCCESS);
            break;
        case 'v':
            version(argv);
            exit(EXIT_SUCCESS);
            break;
        case 'i':
            args.input = optarg;
            break;
        case 'o':
            args.output = optarg;
            break;
        case 'f':
            // check -f=c -f=h handle equal sign and skip it
            if (optarg[0] == '=')
            {
                optarg++;
            }

            switch (optarg[0])
            {
            case 'c':
                args.format = FORMAT_C;
                break;
            case 'h':

                args.format = FORMAT_H;
                break;
            default:
                usage(argv);
                exit(EXIT_FAILURE);
                break;
            }
            break;
        case 't':
            // check -t=embed -t=advisory handle equal sign and skip it
            if (optarg[0] == '=')
            {
                optarg++;
            }

            switch (optarg[0])
            {
            case 'e':
                args.type = TYPE_EMBED;
                break;
            case 'a':
                args.type = TYPE_ADVISORY;
                break;
            case 'p':
                args.type = TYPE_PAGE;
                break;
            case 'A':
                args.type = TYPE_ARTICLE;
                break;
            default:
                fprintf(stderr, "Unknown type: %s defaulting to embed\n", optarg);
                args.type = TYPE_EMBED;
                break;
            }
            break;
        case 'V':
            args.verbose = 1;
            break;
        case 'q':
            args.quiet = 1;
            break;
        case 'w':
            args.autowrap = 1;
            break;
        default:
            usage(argv);
            exit(EXIT_FAILURE);
            break;
        }
    }

    return embed(&args);
}

void usage(char **argv)
{
    char *name = strrchr(argv[0], '/');
    if (name == NULL)
    {
        name = argv[0];
    }
    else
    {
        name++;
    }

    printf("Usage: %s [OPTIONS]\n", name);
    printf("Embed a file into a C source file.\n");
    printf("\n");
    printf("Options:\n");
    printf("  -h, --help              Print this help and exit\n");
    printf("  -v, --version           Print version and exit\n");
    printf("  -i, --input=FILE        Input file\n");
    printf("  -o, --output=FILE       Output file\n");
    printf("  -f, --format=FORMAT     Output format (c, h)\n");
    printf("  -t, --type=TYPE         Output type (e,a,p,A - embed, advisory, page, article)\n");
    printf("  -w, --autowrap          Autowrap output\n");
    printf("  -V, --verbose           Verbose output\n");
    printf("  -q, --quiet             Quiet output\n");
    printf("\n");
}

void version(char **argv)
{
    char *name = strrchr(argv[0], '/');
    if (name == NULL)
    {
        name = argv[0];
    }
    else
    {
        name++;
    }

    printf("%s version %s\n", name, VERSION);
    printf("\n");
}

char *clean_filename(char *filename)
{
    char *p = filename;
    while (*p)
    {
        // replace <space> with _
        // replace symbols with _
        // replace - with _
        // replace . with _
        // all characters to uppercase

        switch (*p)
        {
        case ' ':
        case '-':
        case '.':
        case '/':
        case '\\':
        case ':':
        case ';':
        case ',':
        case '\'':
        case '"':
        case '[':
        case ']':
        case '{':
        case '}':
        case '(':
        case ')':
        case '<':
        case '>':
        case '?':
        case '!':
        case '@':
        case '#':
        case '$':
        case '%':
        case '^':
        case '&':
        case '*':
        case '+':
        case '=':
        case '|':
        case '`':
        case '~':
            *p = '_';
            break;
        default:
            *p = toupper(*p);
            break;
        }

        p++;
    }

    return filename;
}

unsigned char *read_file_data(FILE *f, size_t *size)
{
    fseek(f, 0, SEEK_END);
    *size = ftell(f);
    fseek(f, 0, SEEK_SET);

    unsigned char *data = malloc(*size + 1);
    if (data == NULL)
    {
        return NULL;
    }

    fread(data, 1, *size, f);
    data[*size] = '\0';  // null-terminate for string operations

    return data;
}

unsigned char *image_add_extension(unsigned char *buff, const char *add_ext)
{
    if (buff == NULL || add_ext == NULL)
    {
        return NULL;
    }

    // Find the size of the buffer
    size_t len = strlen((char *)buff);

    // Allocate a new buffer for the modified content
    unsigned char *new_buff = malloc(len * 2); // Allocate double the size for safety
    if (new_buff == NULL)
    {
        return NULL;
    }

    unsigned char *src = buff;
    unsigned char *dst = new_buff;

    // Loop through the buffer to find markdown image patterns
    while (*src)
    {
        if (strncmp((char *)src, "![", 2) == 0)
        {
            // Copy the "!["
            *dst++ = *src++;
            *dst++ = *src++;

            // Skip everything inside the square brackets until the closing bracket ']'
            while (*src && *src != ']')
            {
                *dst++ = *src++;
            }

            // Copy the closing bracket if it's there
            if (*src == ']')
            {
                *dst++ = *src++;
            }

            // Now check for the '(' indicating the start of the image URL
            if (*src == '(')
            {
                *dst++ = *src++;
                unsigned char *start = src;

                // Find the end of the image URL which is marked by ')'
                while (*src && *src != ')')
                {
                    src++;
                }

                // Copy the image URL
                strncpy((char *)dst, (char *)start, src - start);
                dst += src - start;

                // Add the extension
                strcpy((char *)dst, add_ext);
                dst += strlen(add_ext);

                // Copy the closing parenthesis
                if (*src == ')')
                {
                    *dst++ = *src++;
                }
            }
        }
        else
        {
            // Copy any other character
            *dst++ = *src++;
        }
    }

    // Null-terminate the new buffer
    *dst = '\0';

    return new_buff;
}

int embed(embed_t *args)
{
    if (args->input == NULL)
    {
        fprintf(stderr, "Input file not specified\n");
        return EXIT_FAILURE;
    }

    char *formats[] = {"c", "h"};

    if (args->verbose)
    {
        printf("Input file: %s\n", args->input);
        printf("Output file: %s\n", args->output);
        printf("Output format: %s\n", formats[args->format]);
    }

    FILE *input = fopen(args->input, "rb");

    if (input == NULL)
    {
        if (!args->quiet)
            fprintf(stderr, "Unable to open input file: %s\n", args->input);
        return EXIT_FAILURE;
    }

    FILE *output = NULL;

    if (args->output != NULL)
    {
        output = fopen(args->output, "w");
    }
    else
    {
        output = stdout;
    }

    char *name_only = strdup(args->input);
    name_only = strrchr(name_only, '/');
    if (name_only == NULL)
    {
        name_only = args->input;
    }
    else
    {
        name_only++;
    }

    char *input_name = strdup(args->input);
    input_name = strrchr(input_name, '/');
    if (input_name == NULL)
    {
        input_name = args->input;
    }
    else
    {
        input_name++;
    }

    input_name = clean_filename(input_name);

    fprintf(output, "/*\n");
    fprintf(output, " * This file was automatically generated by embed - DO NOT EDIT\n");
    fprintf(output, " * https://libdzonerzy.so\n");
    fprintf(output, " */\n");
    fprintf(output, "\n");

    char *typ;

    switch (args->type)
    {
    case TYPE_EMBED:
        typ = "embed";
        break;
    case TYPE_ADVISORY:
        typ = "advisory";
        break;
    case TYPE_PAGE:
        typ = "pages";
        break;
    case TYPE_ARTICLE:
        typ = "articles";
        break;
    default:
        typ = "unknown";
        break;
    }

    if (args->format == FORMAT_C)
    {
        fprintf(output, "__attribute__((section(\"%s/%s\"))) const unsigned char %s[] = {", typ, name_only, input_name);

        if (args->autowrap)
        {
            fprintf(output, "\n");
        }
    }
    else if (args->format == FORMAT_H)
    {
        fprintf(output, "#pragma once\n");
        fprintf(output, "#ifndef %s_H\n", input_name);
        fprintf(output, "__attribute__((section(\"%s/%s\"))) const unsigned char %s[] = {", typ, name_only, input_name);

        if (args->autowrap)
        {
            fprintf(output, "\n");
        }
    }

    size_t i = 0;

    size_t size = 0;
    unsigned char *data = read_file_data(input, &size);
    // update the image URL with the extension
    if (args->type == TYPE_ARTICLE || args->type == TYPE_PAGE)
    {
        unsigned char *data_updated = image_add_extension(data, ".webp");
        // free the original data
        free(data);
        size = strlen((const char *)data_updated);
        data = data_updated;
    }

    // Prepend 4-byte size (little-endian) for non-embed types
    int has_size_prefix = (args->type != TYPE_EMBED);

    if (args->autowrap)
    {
        fprintf(output, "    ");

        // Write 4-byte size prefix (little-endian)
        if (has_size_prefix)
        {
            fprintf(output, "0x%02X, 0x%02X, 0x%02X, 0x%02X, ",
                    (unsigned char)(size & 0xFF),
                    (unsigned char)((size >> 8) & 0xFF),
                    (unsigned char)((size >> 16) & 0xFF),
                    (unsigned char)((size >> 24) & 0xFF));
        }

        while (i < size)
        {
            fprintf(output, "0x%02X", data[i]);

            if (i % 16 == 15)
            {
                fprintf(output, ",\n");
                fprintf(output, "    ");
            }
            else
            {
                fprintf(output, ", ");
            }

            i++;
        }

        if (i > 0)
        {
            fprintf(output, "\n");
        }

        fprintf(output, "};\n");
    }
    else
    {
        // Write 4-byte size prefix (little-endian)
        if (has_size_prefix)
        {
            fprintf(output, "0x%02X, 0x%02X, 0x%02X, 0x%02X, ",
                    (unsigned char)(size & 0xFF),
                    (unsigned char)((size >> 8) & 0xFF),
                    (unsigned char)((size >> 16) & 0xFF),
                    (unsigned char)((size >> 24) & 0xFF));
        }

        while (i < size)
        {
            // no wrap
            if (i == 0)
            {
                fprintf(output, "0x%02X", data[i]);
            }
            else
            {
                fprintf(output, ", 0x%02X", data[i]);
            }

            i++;
        }

        fprintf(output, "};\n\n");
    }

    if (args->format == FORMAT_H)
    {
        fprintf(output, "#endif /* %s_H */\n", input_name);
    }

    fclose(input);
    fclose(output);

    if (!args->quiet)
        printf("Generated resource '%s'\n", input_name);

    return EXIT_SUCCESS;
}