/**
 * SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
 * SPDX-License-Identifier: MPL-2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "xml.h"
#include "aifc.h"
#include "samplebank.h"
#include "util.h"

NORETURN static void
usage(const char *progname)
{
    fprintf(stderr, "Usage: %s [--matching] [--makedepend <out.d>] <in.xml> <out.s>\n", progname);
    exit(EXIT_FAILURE);
}

int
main(int argc, char **argv)
{
    static uint8_t match_buf[BUG_BUF_SIZE];
    const char *filename = NULL;
    xmlDocPtr document;
    const char *outfilename = NULL;
    const char *mdfilename = NULL;
    FILE *mdfile;
    FILE *outf;
    samplebank sb;
    uint8_t *match_buf_ptr;
    size_t match_buf_pos;
    bool matching = false;

    // parse args

#define arg_error(fmt, ...)                       \
    do {                                          \
        fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
        usage(argv[0]);                           \
    } while (0)

    int argn = 0;
    for (int i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            // Optional args

            if (strequ(argv[i], "--matching")) {
                if (matching)
                    arg_error("Received --matching option twice");

                matching = true;
                continue;
            }
            if (strequ(argv[i], "--makedepend")) {
                if (mdfilename != NULL)
                    arg_error("Received --makedepend option twice");
                if (i + 1 == argc)
                    arg_error("--makedepend missing required argument");

                mdfilename = argv[++i];
                continue;
            }
            arg_error("Unknown option \"%s\"", argv[i]);
        } else {
            // Required args

            switch (argn) {
                case 0:
                    filename = argv[i];
                    break;
                case 1:
                    outfilename = argv[i];
                    break;
                default:
                    arg_error("Unknown positional argument \"%s\"", argv[i]);
                    break;
            }
            argn++;
        }
    }
    if (argn != 2)
        arg_error("Not enough positional arguments");

#undef arg_error

    // open xml
    document = xmlReadFile(filename, NULL, XML_PARSE_NONET);
    if (document == NULL)
        return EXIT_FAILURE;

    // parse xml
    read_samplebank_xml(&sb, document);

    // open output asm file
    outf = fopen(outfilename, "w");
    if (outf == NULL)
        error("Unable to open output file [%s] for writing", outfilename);

    // open output dep file if applicable
    if (mdfilename != NULL) {
        mdfile = fopen(mdfilename, "w");
        if (mdfile == NULL)
            error("Unable to open dependency file [%s] for writing", mdfilename);

        fprintf(mdfile, "%s: \\\n    %s", outfilename, filename);
    }

    // write output

    fprintf(outf,
            // clang-format off
           ".rdata"                 "\n"
           ".balign 16"             "\n"
                                    "\n"
           ".global %s_Start"       "\n"
           "%s_Start:"              "\n"
           "$start:"                "\n",
            // clang-format on
            sb.name, sb.name);

    // original tool appears to have a buffer clearing bug involving a buffer sized BUG_BUF_SIZE
    match_buf_ptr = (matching && sb.buffer_bug) ? match_buf : NULL;
    match_buf_pos = 0;

    for (size_t i = 0; i < sb.num_samples; i++) {
        const char *name = sb.sample_names[i];
        const char *path = sb.sample_paths[i];
        bool is_sample = sb.is_sample[i];

        if (mdfilename != NULL)
            fprintf(mdfile, " \\\n    %s", path);

        if (!is_sample) {
            // blob
            fprintf(outf,
                    // clang-format off
                                        "\n"
                   "# BLOB %s"          "\n"
                                        "\n"
                   ".incbin \"%s\""     "\n"
                                        "\n"
                   ".balign 16"         "\n"
                                        "\n",
                    // clang-format on
                    name, path);
            continue;
        }

        // aifc sample
        fprintf(outf,
                // clang-format off
                                                "\n"
               "# SAMPLE %lu"                   "\n"
                                                "\n"
               ".global %s_%s_Abs"              "\n"
               "%s_%s_Abs:"                     "\n"
               ".global %s_%s_Off"              "\n"
               ".set %s_%s_Off, . - $start"     "\n"
                                                "\n",
                // clang-format on
                i, sb.name, name, sb.name, name, sb.name, name, sb.name, name);

        aifc_data aifc;
        aifc_read(&aifc, path, match_buf_ptr, &match_buf_pos);

        fprintf(outf, ".incbin \"%s\", 0x%lX, 0x%lX\n", path, aifc.ssnd_offset, aifc.ssnd_size);

        if (match_buf_ptr != NULL && i == sb.num_samples - 1) {
            // emplace garbage
            fprintf(outf, "\n# Garbage data from buffer bug\n");

            size_t end = ALIGN16(match_buf_pos);
            for (; match_buf_pos < end; match_buf_pos++)
                fprintf(outf, ".byte 0x%02X\n", match_buf_ptr[match_buf_pos]);
        } else {
            fputs("\n.balign 16\n", outf);
        }

        aifc_dispose(&aifc);
    }

    if (mdfilename != NULL) {
        fputs("\n", mdfile);
        fclose(mdfile);
    }

    fprintf(outf,
            // clang-format off
           ".global %s_Size"            "\n"
           ".set %s_Size, . - $start"   "\n",
            // clang-format on
            sb.name, sb.name);

    fclose(outf);
    xmlFreeDoc(document);
    return EXIT_SUCCESS;
}
