// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// strip.c -- Release build stripping
//
// Removes debug information from a compiled module:
//   - Clears debug variable info (names, scopes, types)
//   - Clears debug UDT definitions
//   - Mangles procedure names that aren't needed for runtime dispatch.
//     The form runtime dispatches events by name (Control_Event pattern)
//     and SetEvent looks up handlers by name at runtime, so those proc
//     names must be preserved. Everything else becomes F1, F2, F3...
//
// OP_LINE removal is a separate pass: see compact.c.

#include "strip.h"
#include "basEvents.h"
#include "../runtime/values.h"

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


// Events fired by name via basFormRtFireEvent* in formrt.c. Any proc
// ending in "_<EventName>" must keep its name so the dispatcher can
// find it.  Declared in basEvents.h; defined here as the single source
// of truth.
const char *basEventSuffixes[] = {
    BAS_EVENT_LIST(BAS_EVENT_SUFFIX)
    NULL
};


// Function prototypes (alphabetical)
bool basEventSuffixMatch(const char *suffix);
void basStripModule(BasModuleT *mod);
static bool nameEndsWithEventSuffix(const char *name);
static bool nameInConstantPool(const BasModuleT *mod, const char *name);
static bool procNameTaken(const BasModuleT *mod, int32_t selfIdx, const char *name);


bool basEventSuffixMatch(const char *suffix) {
    for (int32_t i = 0; basEventSuffixes[i]; i++) {
        if (strcasecmp(suffix, basEventSuffixes[i]) == 0) {
            return true;
        }
    }

    return false;
}


void basStripModule(BasModuleT *mod) {
    if (!mod) {
        return;
    }

    // Clear debug variable info
    free(mod->debugVars);
    mod->debugVars     = NULL;
    mod->debugVarCount = 0;

    // Clear debug UDT definitions
    if (mod->debugUdtDefs) {
        for (int32_t i = 0; i < mod->debugUdtDefCount; i++) {
            free(mod->debugUdtDefs[i].fields);
        }

        free(mod->debugUdtDefs);
        mod->debugUdtDefs     = NULL;
        mod->debugUdtDefCount = 0;
    }

    // Mangle proc names. Keep names that are needed for runtime name
    // lookup: event handlers (Control_Event pattern) and any name
    // referenced as a string constant (e.g. SetEvent's target name).
    int32_t nextMangled = 1;

    for (int32_t i = 0; i < mod->procCount; i++) {
        BasProcEntryT *proc = &mod->procs[i];

        if (proc->name[0] == '\0') {
            continue;
        }

        if (nameEndsWithEventSuffix(proc->name)) {
            continue;
        }

        if (nameInConstantPool(mod, proc->name)) {
            continue;
        }

        // Skip any generated name that collides with a kept proc (event
        // handler or constant-pool-referenced) or a constant-pool entry, so
        // basModuleFindProc can't resolve the wrong procedure at runtime.
        char cand[BAS_MAX_IDENT];

        snprintf(cand, sizeof(cand), "F%ld", (long)nextMangled++);

        while (procNameTaken(mod, i, cand) || nameInConstantPool(mod, cand)) {
            snprintf(cand, sizeof(cand), "F%ld", (long)nextMangled++);
        }

        snprintf(proc->name, sizeof(proc->name), "%s", cand);
    }
}


static bool nameEndsWithEventSuffix(const char *name) {
    const char *underscore = strrchr(name, '_');

    if (!underscore) {
        return false;
    }

    return basEventSuffixMatch(underscore + 1);
}


static bool nameInConstantPool(const BasModuleT *mod, const char *name) {
    for (int32_t i = 0; i < mod->constCount; i++) {
        const BasStringT *s = mod->constants[i];

        if (s && strcasecmp(s->data, name) == 0) {
            return true;
        }
    }

    return false;
}


static bool procNameTaken(const BasModuleT *mod, int32_t selfIdx, const char *name) {
    for (int32_t i = 0; i < mod->procCount; i++) {
        if (i == selfIdx) {
            continue;
        }

        if (strcasecmp(mod->procs[i].name, name) == 0) {
            return true;
        }
    }

    return false;
}
