/*

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 struct _embed
{
    char *input;
    char *output;
    char *format;
    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: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 = "c";
                break;
            case 'h':

                args.format = "h";
                break;
            default:
                usage(argv);
                exit(EXIT_FAILURE);
                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("  -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;
}

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

    if (args->format == NULL)
    {
        fprintf(stderr, "Output format not specified\n");
        return EXIT_FAILURE;
    }

    if (args->verbose)
    {
        printf("Input file: %s\n", args->input);
        printf("Output file: %s\n", args->output);
        printf("Output format: %s\n", 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");

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

    int c = 0;
    int i = 0;

    if (args->autowrap)
    {
        while ((c = fgetc(input)) != EOF)
        {
            if (i == 0)
            {
                fprintf(output, "    ");
            }

            fprintf(output, "0x%02X", c);

            fprintf(output, ", ");
            if (i < 15)
            {
                fprintf(output, ", ");
            }
            else
            {
                fprintf(output, ",\n");
                i = -1;
            }

            i++;
        }

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

        fprintf(output, "    0x00,\n");
        fprintf(output, "};\n");
    }
    else
    {
        while ((c = fgetc(input)) != EOF)
        {
            fprintf(output, "0x%02X", c);
            fprintf(output, ", ");
        }
        fprintf(output, "0x00");
        fprintf(output, "};\n\n");
    }

    if (args->format[0] == '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;
}