/* =====================================
 * Generated by Mod-c 0.26.5
 * PROGRAM modc
 * ===================================== */

#include "modc.h"

/**
 * TMod-c 0.26.5.176
 * By M. Scott Reynolds
 * Date 20 February 2026
 *
 * Port to Mod-c started 4 April 2026
 * 30 Apr 2026 - Add command line options.
 * 2 May 2026 - Use TYPE ... STRUCT for command line options.
 * 2 May 2026 - Refactor code into several functions.
 * 30 May 2026 - Add options for generating header file and other stuff.
 * 8 June 2026 - Add debug flag
 * 8 August 2026 - Add quiet flag
 * 3 September 2026 - Refactor for 0.26.5.176
 */
#include "stddef.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "parser_common.h"
#include "parser_main.h"
#include "node.h"
#include "codegen_common.h"
#include "version.h"
#include "codegen.h"
#include "dynbuf.h"
#include "arena.h"
#include "symtab.h"
#include "semantic.h"
#include "utils.h"
#include "codegen_mh.h"
typedef char* pchar;
typedef FILE* pFILE;
typedef Node* pNode;
typedef bool* pbool;
typedef char* array_of_pchar[];
typedef struct TCommandLine TCommandLine;
typedef struct TCommandLine {
    bool show_tokens;
    bool show_info;
    bool show_ast;
    bool show_version;
    bool be_quiet;
    bool debug_enabled;
    bool assert_off;
    bool dbc_off;
    bool generate_c;
    bool generate_h;
    bool generate_mh;
    pchar mc_input_file;
    pchar c_output_path;
    pchar h_output_path;
    pchar mh_output_path;
    pchar output_dir;
    pchar output_base;
} TCommandLine;
static const integer MAX_PATH = 4096;
static bool ends_with(string s, string suffix)
{
    if (s == nil || suffix == nil)
    {
        /* return value captured before defers */
        bool __ret_l85 = false;
        return __ret_l85;
    }
    const size_t slen_l87_c13 = strlen(s);
    const size_t xlen_l88_c13 = strlen(suffix);
    if (slen_l87_c13 < xlen_l88_c13)
    {
        /* return value captured before defers */
        bool __ret_l90 = false;
        return __ret_l90;
    }
    /* return value captured before defers */
    bool __ret_l92 = memcmp(s + (slen_l87_c13 - xlen_l88_c13), suffix, xlen_l88_c13) == 0;
    return __ret_l92;
}

static pchar path_replace_ext(string path, string new_ext)
{
    size_t slen = 0;
    size_t i = 0;
    size_t stem_len = 0;
    DynBuf buf = {0};
    if (path == nil || new_ext == nil || path[0] == '\0')
    {
        /* return value captured before defers */
        pchar __ret_l105 = nil;
        /* DEFER cleanup (unwinding to function) */
    /* DEFER end */
    dynbuf_free(&buf);
        return __ret_l105;
    }
    slen = strlen(path);
    stem_len = slen;
    i = slen;
    while (i > 0)
    {
        {
            i = i - 1;
            if (path[i] == '/' || path[i] == '\\')
            {
                                /* INVARIANT (on loop exit) */
                break;
            }
            if (path[i] == '.')
            {
                stem_len = i;
                                /* INVARIANT (on loop exit) */
                break;
            }
        }
    }
    dynbuf_init(&buf);
    /* DEFER begin */
    dynbuf_appendn(&buf, path, stem_len);
    dynbuf_append(&buf, new_ext);
    /* return value captured before defers */
    pchar __ret_l126 = utils_strndup((buf).data, (buf).used);
    /* DEFER cleanup (unwinding to function) */
    /* DEFER end */
    dynbuf_free(&buf);
    return __ret_l126;
}

static bool is_mc_source_path(string s)
{
    /* return value captured before defers */
    bool __ret_l132 = ends_with(s, ".mc") && s[0] != '-';
    return __ret_l132;
}

static bool is_c_output_path(string s)
{
    /* return value captured before defers */
    bool __ret_l138 = ends_with(s, ".c") && s[0] != '-';
    return __ret_l138;
}

static bool is_h_output_path(string s)
{
    /* return value captured before defers */
    bool __ret_l144 = ends_with(s, ".h") && s[0] != '-';
    return __ret_l144;
}

static bool is_mh_output_path(string s)
{
    /* return value captured before defers */
    bool __ret_l150 = ends_with(s, ".mh") && s[0] != '-';
    return __ret_l150;
}

static bool is_optional_output_arg(string next, bool for_header, bool for_mh, pchar input_file)
{
    if (next == nil || next[0] == '\0' || next[0] == '.' || next[0] == '-')
    {
        /* return value captured before defers */
        bool __ret_l161 = false;
        return __ret_l161;
    }
    if (for_mh)
    {
        if (is_mh_output_path(next))
        {
            /* return value captured before defers */
            bool __ret_l165 = true;
            return __ret_l165;
        }
    }
    else if (for_header)
    {
        if (is_h_output_path(next))
        {
            /* return value captured before defers */
            bool __ret_l169 = true;
            return __ret_l169;
        }
    }
    else if (is_c_output_path(next))
    {
        /* return value captured before defers */
        bool __ret_l172 = true;
        return __ret_l172;
    }
    if (is_mc_source_path(next) && input_file == nil)
    {
        /* return value captured before defers */
        bool __ret_l175 = false;
        return __ret_l175;
    }
    /* return value captured before defers */
    bool __ret_l177 = true;
    return __ret_l177;
}

static pchar path_dirname(string path)
{
    integer last = -1;
    size_t path_len = 0;
    integer n = 0;
    pchar dir = nil;
    if (path == nil)
    {
        /* return value captured before defers */
        pchar __ret_l193 = nil;
        return __ret_l193;
    }
    while (n < MAX_PATH && path[n] != '\0')
    {
        {
            if (path[n] == '/' || path[n] == '\\')
            {
                last = n;
            }
            n++;
        }
    }
    if (last < 0)
    {
        /* return value captured before defers */
        pchar __ret_l205 = utils_strndup(".", 1);
        return __ret_l205;
    }
    if (last == 0)
    {
        /* return value captured before defers */
        pchar __ret_l210 = utils_strndup("/", 1);
        return __ret_l210;
    }
    path_len = (size_t)last;
    dir = malloc(path_len + 1);
    if (dir == nil)
    {
        /* return value captured before defers */
        pchar __ret_l216 = nil;
        return __ret_l216;
    }
    memcpy(dir, path, path_len);
    dir[path_len] = '\0';
    /* return value captured before defers */
    pchar __ret_l220 = dir;
    return __ret_l220;
}

static pchar build_output_file_path(string dir, string base, size_t base_len, string ext)
{
    DynBuf buf = {0};
    size_t dir_len = 0;
    bool use_dir = true;
    if (base == nil || base_len == 0 || ext == nil)
    {
        /* return value captured before defers */
        pchar __ret_l231 = nil;
        /* DEFER cleanup (unwinding to function) */
    /* DEFER end */
    dynbuf_free(&buf);
        return __ret_l231;
    }
    dynbuf_init(&buf);
    /* DEFER begin */
    if (dir == nil || dir[0] == '\0' || strcmp(dir, ".") == 0)
    {
        use_dir = false;
    }
    if (use_dir)
    {
        dir_len = strlen(dir);
        dynbuf_appendn(&buf, dir, dir_len);
        if (dir[dir_len - 1] != '/' && dir[dir_len - 1] != '\\')
        {
            dynbuf_append_char(&buf, '/');
        }
    }
    dynbuf_appendn(&buf, base, base_len);
    dynbuf_append(&buf, ext);
    /* return value captured before defers */
    pchar __ret_l251 = utils_strndup((buf).data, (buf).used);
    /* DEFER cleanup (unwinding to function) */
    /* DEFER end */
    dynbuf_free(&buf);
    return __ret_l251;
}

static pchar resolve_output_path(string input_file, string unit_name, size_t unit_name_len, string output_dir_opt, string output_base_opt, string ext)
{
    pchar dir_owned = nil;
    string dir = output_dir_opt;
    string base = unit_name;
    size_t base_len = unit_name_len;
    if (output_base_opt != nil && output_base_opt[0] != '\0')
    {
        base = output_base_opt;
        base_len = strlen(output_base_opt);
    }
    if (dir == nil || dir[0] == '\0')
    {
        dir_owned = path_dirname(input_file);
        if (dir_owned == nil)
        {
            /* return value captured before defers */
            pchar __ret_l271 = nil;
            return __ret_l271;
        }
        dir = dir_owned;
    }
    const pchar result_l276_c15 = build_output_file_path(dir, base, base_len, ext);
    free(dir_owned);
    /* return value captured before defers */
    pchar __ret_l278 = result_l276_c15;
    return __ret_l278;
}

static TCommandLine parse_command_line(int argc, array_of_pchar argv)
{
    TCommandLine results = {0};
    int i = 1;
    (results).show_tokens = false;
    (results).show_info = false;
    (results).show_ast = false;
    (results).show_version = false;
    (results).be_quiet = false;
    (results).debug_enabled = false;
    (results).assert_off = false;
    (results).dbc_off = false;
    (results).generate_c = false;
    (results).generate_h = false;
    (results).generate_mh = false;
    (results).mc_input_file = nil;
    (results).c_output_path = nil;
    (results).h_output_path = nil;
    (results).mh_output_path = nil;
    (results).output_dir = nil;
    (results).output_base = nil;
    if (argc > 1)
    {
        while (i < argc)
        {
            {
                pchar arg = argv[i];
                if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
                {
                    fprintf(stderr, "TMod-c %s (%d)\n", VERSION_BASE, BUILD_NUMBER);
                    fprintf(stderr, "Usage: tmodc input.mc [options]\n");
                    fprintf(stderr, "Options:\n");
                    fprintf(stderr, "  -a, --ast        Display the Abstract Syntax Tree with semantic analysis.\n");
                    fprintf(stderr, "  -i, --info       Dislay options passed to Mod-c\n");
                    fprintf(stderr, "  -t, --tokens     Enable token tracing in AST\n");
                    fprintf(stderr, "  -C [file.c]      Generate C and companion .h; default name/dir from PROGRAM/MODULE\n");
                    fprintf(stderr, "  -H [file.h]      Generate header; default name/dir from PROGRAM/MODULE\n");
                    fprintf(stderr, "  -M [file.mh]     Generate modc-mh/1 export table\n");
                    fprintf(stderr, "  -d directory     Output directory for auto-named -C/-H files\n");
                    fprintf(stderr, "  -b basename      Output base name (stem) for auto-named -C/-H files\n");
                    fprintf(stderr, "  -q, --quiet      Quiet operation\n");
                    fprintf(stderr, "  --debug          Turn on debug statements (default off)\n");
                    fprintf(stderr, "  --assert-off     Turn off assert statements (default on)\n");
                    fprintf(stderr, "  --dbc-off        Turn off DbC statements (default on)\n");
                    fprintf(stderr, "  -v, --version    Display version and exit\n");
                    fprintf(stderr, "  -h, --help       Show this help\n");
                    fprintf(stderr, "\n");
                    fprintf(stderr, "No options given, display AST without semantic analysis.\n");
                    exit(1);
                }
                else if (strcmp(arg, "-a") == 0 || strcmp(arg, "--ast") == 0)
                {
                    (results).show_ast = true;
                }
                else if (strcmp(arg, "--debug") == 0)
                {
                    (results).debug_enabled = true;
                }
                else if (strcmp(arg, "--assert-off") == 0)
                {
                    (results).assert_off = true;
                }
                else if (strcmp(arg, "--dbc-off") == 0)
                {
                    (results).dbc_off = true;
                }
                else if (strcmp(arg, "-q") == 0 || strcmp(arg, "--quiet") == 0)
                {
                    (results).be_quiet = true;
                }
                else if (strcmp(arg, "-i") == 0 || strcmp(arg, "--info") == 0)
                {
                    (results).show_info = true;
                }
                else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--tokens") == 0)
                {
                    (results).show_tokens = true;
                }
                else if (strcmp(arg, "-v") == 0 || strcmp(arg, "--version") == 0)
                {
                    (results).show_version = true;
                }
                else if (strcmp(arg, "-C") == 0)
                {
                    (results).generate_c = true;
                    if (i + 1 < argc && is_optional_output_arg(argv[i + 1], false, false, (results).mc_input_file))
                    {
                        i++;
                        (results).c_output_path = argv[i];
                    }
                }
                else if (strcmp(arg, "-H") == 0)
                {
                    (results).generate_h = true;
                    if (i + 1 < argc && is_optional_output_arg(argv[i + 1], true, false, (results).mc_input_file))
                    {
                        i++;
                        (results).h_output_path = argv[i];
                    }
                }
                else if (strcmp(arg, "-M") == 0)
                {
                    (results).generate_mh = true;
                    if (i + 1 < argc && is_optional_output_arg(argv[i + 1], false, true, (results).mc_input_file))
                    {
                        i++;
                        (results).mh_output_path = argv[i];
                    }
                }
                else if (strcmp(arg, "-d") == 0 && i + 1 < argc)
                {
                    i++;
                    (results).output_dir = argv[i];
                }
                else if (strcmp(arg, "-b") == 0 && i + 1 < argc)
                {
                    i++;
                    (results).output_base = argv[i];
                }
                else if (arg[0] == '-')
                {
                    fprintf(stderr, "Mod-c: Error: Invalid option '%s'\n", arg);
                    exit(1);
                }
                else if ((results).mc_input_file == nil)
                {
                    (results).mc_input_file = (pchar)arg;
                }
                i++;
            }
        }
    }
    /* return value captured before defers */
    TCommandLine __ret_l406 = results;
    return __ret_l406;
}

static size_t get_file_size(pFILE fp)
{
    const long file_pos_l417_c17 = ftell(fp);
    if (file_pos_l417_c17 < 0)
    {
        /* return value captured before defers */
        size_t __ret_l419 = 0;
        return __ret_l419;
    }
    if (fseek(fp, 0, SEEK_END) != 0)
    {
        /* return value captured before defers */
        size_t __ret_l423 = 0;
        return __ret_l423;
    }
    const long file_end_l425_c17 = ftell(fp);
    fseek(fp, file_pos_l417_c17, SEEK_SET);
    if (file_end_l425_c17 < 0)
    {
        /* return value captured before defers */
        size_t __ret_l430 = 0;
        return __ret_l430;
    }
    /* return value captured before defers */
    size_t __ret_l432 = (size_t)file_end_l425_c17;
    return __ret_l432;
}

/**
 * Open input file, allocate space, read source, close output file. 
 * @param: input_file = Name of input file to read.
 * @return: Allocated pointer to source if successful. Nil if not successful.
 * Caller owns source.
 */
static pchar allocate_source_file(pchar input_file)
{
    pchar source = nil;
    const pFILE fp_l446_c11 = fopen(input_file, "r");
    if (fp_l446_c11 == nil)
    {
        perror("ERROR: modc.allocate_source_file: Cannot open input file");
    }
    else
    {
        /* DEFER begin */
        const size_t fsize_l451_c18 = get_file_size(fp_l446_c11);
        if (fsize_l451_c18 == 0 && ftell(fp_l446_c11) != 0)
        {
            perror("ERROR: modc.allocate_source_file: Cannot determine file size");
        }
        else
        {
            source = malloc(fsize_l451_c18 + 1);
            if (source == nil)
            {
                perror("ERROR: modc.allocate_source_file: Memory allocation failed");
            }
            else
            {
                fread(source, 1, fsize_l451_c18, fp_l446_c11);
                source[fsize_l451_c18] = '\0';
            }
        }
        /* DEFER end */
        fclose(fp_l446_c11);
    }
    /* return value captured before defers */
    pchar __ret_l467 = source;
    return __ret_l467;
}

/**
 * Generate code from AST.
 * @param context = context with ast, debug_enabled, and target previous set.
 * @param file_name = name of output file.
 * @param quiet = quiet operation
 * @ return: Return 0 on success. > 0 on error.
 */
static int generate_from_ast(InitialContext context, pchar file_name, bool quiet)
{
    int return_code = 0;
    DynBuf out_buffer = {0};
    dynbuf_init(&out_buffer);
    /* DEFER begin */
    (context).out = &out_buffer;
    codegen_generate(context);
    (context).out = nil;
    if (file_name == nil)
    {
        perror("ERROR: modc.generate_from_ast: output file_name not specified");
        return_code += 1;
    }
    else
    {
        const pFILE out_file_l499_c21 = fopen(file_name, "w");
        if (out_file_l499_c21 == nil)
        {
            perror("ERROR: modc.generate_from_ast: Cannot open out_file");
            return_code += 1;
        }
        else
        {
            fputs((out_buffer).data, out_file_l499_c21);
            fclose(out_file_l499_c21);
            if (!quiet)
            {
                printf("Code generation complete: %s\n", file_name);
            }
        }
    }
    /* return value captured before defers */
    int __ret_l512 = return_code;
    /* DEFER cleanup (unwinding to function) */
    /* DEFER end */
    dynbuf_free(&out_buffer);
    return __ret_l512;
}

/**
 * Main entry point
 */

int modc(int argc, char** argv)
{
    Arena arena = {0};
    TParser p = {0};
    pchar source = nil;
    pNode ast = nil;
    TCommandLine options = {0};
    int return_code = 0;
    options = parse_command_line(argc, argv);
    if ((options).show_info)
    {
        printf("show_tokens = %d", (options).show_tokens);
        printf(", show_ast = %d", (options).show_ast);
        if ((options).mc_input_file != nil)
        {
            printf(", mc_input_file = %s", (options).mc_input_file);
        }
        printf(", generate_c = %d", (options).generate_c);
        if ((options).c_output_path != nil)
        {
            printf(", c_output_path = %s", (options).c_output_path);
        }
        printf(", generate_h = %d", (options).generate_h);
        if ((options).h_output_path != nil)
        {
            printf(", h_output_path = %s", (options).h_output_path);
        }
        printf("\n");
    }
    if ((options).show_version)
    {
        fprintf(stderr, "TMod-c %s (%d)\n", VERSION_BASE, BUILD_NUMBER);
        fprintf(stderr, "VERSION_BASE: %s\n", VERSION_BASE);
        fprintf(stderr, "BUILD_NUMBER: %d\n", BUILD_NUMBER);
        fprintf(stderr, "INSTALLED: %s\n", argv[0]);
        /* return value captured before defers */
        int __ret_l554 = 1;
        return __ret_l554;
    }
    if ((options).mc_input_file == nil)
    {
        fprintf(stderr, "Usage: tmodc [options] input.mc\n");
        fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
        /* return value captured before defers */
        int __ret_l560 = 1;
        return __ret_l560;
    }
    source = allocate_source_file((options).mc_input_file);
    if (source != nil)
    {
        /* DEFER begin */
        arena_init(&arena, 1024 * 1024);
        /* DEFER begin */
        TParser__init(&p, &arena, source, nil);
        (p).show_tokens = (options).show_tokens;
        (p).debug_enabled = (options).debug_enabled;
        ast = TParser__parse(&p);
        if (ast == nil)
        {
            perror("ERROR: main: TParser::parse returned NIL\n");
            return_code += 1;
        }
        else
        {
            if ((options).generate_c || (options).generate_h || (options).generate_mh)
            {
                InitialContext context = {0};
                pchar c_file = (options).c_output_path;
                pchar h_file = (options).h_output_path;
                pchar mh_file = (options).mh_output_path;
                pchar c_auto = nil;
                pchar h_auto = nil;
                pchar mh_auto = nil;
                string unit_name = nil;
                size_t unit_len = 0;
                semantic_analyze(&arena, ast, (options).mc_input_file);
                unit_name = ((*(ast)).program_decl).name;
                unit_len = ((*(ast)).program_decl).name_len;
                (context).ast = ast;
                (context).debug_enabled = (options).debug_enabled;
                (context).assert_off = (options).assert_off;
                (context).dbc_off = (options).dbc_off;
                if ((options).generate_c && c_file == nil)
                {
                    c_auto = resolve_output_path((options).mc_input_file, unit_name, unit_len, (options).output_dir, (options).output_base, ".c");
                    c_file = c_auto;
                }
                if ((options).generate_c && h_file == nil)
                {
                    if (c_file != nil)
                    {
                        h_auto = path_replace_ext(c_file, ".h");
                        h_file = h_auto;
                    }
                }
                if ((options).generate_h && h_file == nil)
                {
                    h_auto = resolve_output_path((options).mc_input_file, unit_name, unit_len, (options).output_dir, (options).output_base, ".h");
                    h_file = h_auto;
                }
                if (((options).generate_mh || ((options).generate_c && codegen_mh_has_exports(ast))) && mh_file == nil)
                {
                    mh_auto = resolve_output_path((options).mc_input_file, unit_name, unit_len, (options).output_dir, (options).output_base, ".mh");
                    mh_file = mh_auto;
                }
                if ((options).generate_c)
                {
                    if (c_file == nil)
                    {
                        fprintf(stderr, "ERROR: could not determine C output path\n");
                        return_code += 1;
                    }
                    else
                    {
                        (context).target = TARGET_C;
                        return_code += generate_from_ast(context, c_file, (options).be_quiet);
                    }
                }
                if ((options).generate_h || (options).generate_c)
                {
                    if (h_file == nil)
                    {
                        fprintf(stderr, "ERROR: could not determine header output path\n");
                        return_code += 1;
                    }
                    else
                    {
                        (context).target = TARGET_HEADER;
                        return_code += generate_from_ast(context, h_file, (options).be_quiet);
                    }
                }
                if ((options).generate_mh || ((options).generate_c && codegen_mh_has_exports(ast)))
                {
                    if (mh_file == nil)
                    {
                        fprintf(stderr, "ERROR: could not determine .mh output path\n");
                        return_code += 1;
                    }
                    else
                    {
                        (context).target = TARGET_MH;
                        return_code += generate_from_ast(context, mh_file, (options).be_quiet);
                    }
                }
                if (c_auto != nil)
                {
                    free(c_auto);
                }
                if (h_auto != nil)
                {
                    free(h_auto);
                }
                if (mh_auto != nil)
                {
                    free(mh_auto);
                }
            }
            else if ((options).show_ast)
            {
                semantic_analyze(&arena, ast, (options).mc_input_file);
                node_print(ast, 0);
            }
            else
            {
                node_print(ast, 0);
            }
        }
        /* DEFER end */
        arena_free(&arena);
        /* DEFER end */
        free(source);
    }
    /* return value captured before defers */
    int __ret_l694 = return_code;
    return __ret_l694;
}

int main(int argc, char** argv)
{
    return modc(argc, argv);
}
