// 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.

// basRes.h  -- single source of truth for BASIC project / binary layout
//
// Keys used in .dbp project files and names of resources written into the
// compiled .app DXE.  These were previously scattered as raw string literals
// across the IDE (ideMain.c, ideProject.c) and the standalone compiler
// (bascomp.c), making it easy for one side to drift from the other.
// All readers and writers now include this header.

#ifndef BAS_RES_H
#define BAS_RES_H

#include <stdbool.h>
#include <stddef.h> // NULL
#include <stdint.h>
#include <strings.h> // strncasecmp

// dvxSkipWs is declared in platform/dvxPlat.h, which every translation
// unit that includes this header also includes.  We forward-declare it
// here (a redundant, compatible declaration is legal C) so the inline
// helper below can use it without pulling the whole platform header into
// basRes.h's include surface.
const char *dvxSkipWs(const char *s);

// ------------------------------------------------------------
// [Project] section of a .dbp file
// ------------------------------------------------------------

#define BAS_INI_SECTION_PROJECT  "Project"
#define BAS_INI_KEY_NAME         "Name"
#define BAS_INI_KEY_AUTHOR       "Author"
#define BAS_INI_KEY_PUBLISHER    "Publisher"
#define BAS_INI_KEY_VERSION      "Version"
#define BAS_INI_KEY_COPYRIGHT    "Copyright"
#define BAS_INI_KEY_DESCRIPTION  "Description"
#define BAS_INI_KEY_ICON         "Icon"
#define BAS_INI_KEY_HELPFILE     "HelpFile"

// [Settings] / [Modules] / [Forms] sections of a .dbp file
#define BAS_INI_SECTION_SETTINGS    "Settings"
#define BAS_INI_KEY_STARTUPFORM     "StartupForm"
#define BAS_INI_KEY_OPTIONEXPLICIT  "OptionExplicit"
#define BAS_INI_SECTION_MODULES     "Modules"
#define BAS_INI_SECTION_FORMS       "Forms"

// ------------------------------------------------------------
// Resource names inside a compiled .app DXE
// ------------------------------------------------------------

// Text metadata -- mirrors the [Project] keys above but lowercase
// (resource names are case-sensitive; apps query these at runtime).
#define BAS_RES_NAME         "name"
#define BAS_RES_AUTHOR       "author"
#define BAS_RES_PUBLISHER    "publisher"
#define BAS_RES_VERSION      "version"
#define BAS_RES_COPYRIGHT    "copyright"
#define BAS_RES_DESCRIPTION  "description"
#define BAS_RES_HELPFILE     "helpfile"

// Icon -- 32x32 application icon
#define BAS_RES_ICON32       "icon32"

// Binary payload -- bytecode + debug info
#define BAS_RES_MODULE       "MODULE"
#define BAS_RES_DEBUG        "DEBUG"

// Stub DXE embedded in the IDE app for release builds
#define BAS_RES_STUB         "STUB"
#define BAS_RES_NOICON       "noicon"     // fallback ICON32 source in the IDE / bascomp

// Per-form binary resource name, e.g. FORM0, FORM1, ...  Shared by the
// basBuild writer and the basstub reader so the naming cannot drift; %ld is
// the dense form index.
#define BAS_RES_FORM_FMT     "FORM%ld"
#define BAS_RES_FORM_NAME_LEN 16       // buffer for a formatted FORMn resource name

// ------------------------------------------------------------
// Form resource scan limit
// ------------------------------------------------------------
// Upper bound on FORM0..FORMn resources written by the compiler
// (bascomp/basBuild) and scanned by the stub at load time.  Shared
// so the writer and reader cannot drift.
#define BAS_MAX_FORM_RESOURCES   256

// ------------------------------------------------------------
// Begin-Form name extraction (shared by bascomp and basstub)
// ------------------------------------------------------------

// Lengths of the .frm keywords scanned for below.
#define BAS_BEGIN_PREFIX_LEN       6   // "Begin "
#define BAS_FORM_PREFIX_LEN        5   // "Form "
#define BAS_BEGIN_FORM_PREFIX_LEN  11  // "Begin Form "
#define BAS_END_KEYWORD_LEN        3   // "End"

// Extract the form name from a "Begin Form <name>" line in .frm text.
// Writes a NUL-terminated name into nameBuf (max nameBufSize bytes,
// including the terminator) and returns true if a name was found.
// Shared verbatim by bascomp (concat injection) and basstub (lazy
// frm registration); they previously duplicated this loop with only
// the destination buffer size differing.
static inline bool basExtractFormName(const char *frmText, char *nameBuf, int32_t nameBufSize) {
    if (nameBuf == NULL || nameBufSize <= 0) {
        return false;
    }

    nameBuf[0] = '\0';

    if (frmText == NULL) {
        return false;
    }

    const char *p = frmText;

    while (*p) {
        p = dvxSkipWs(p);

        if (strncasecmp(p, "Begin Form ", BAS_BEGIN_FORM_PREFIX_LEN) == 0) {
            p = dvxSkipWs(p + BAS_BEGIN_FORM_PREFIX_LEN);

            int32_t ni = 0;

            while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && ni < nameBufSize - 1) {
                nameBuf[ni] = *p;
                ni++;
                p++;
            }

            nameBuf[ni] = '\0';
            return nameBuf[0] != '\0';
        }

        while (*p && *p != '\n') {
            p++;
        }

        if (*p == '\n') {
            p++;
        }
    }

    return false;
}


// ------------------------------------------------------------
// Outer "Begin Form ... End" extent (shared by bascomp, basBuild and the
// obfuscator)
// ------------------------------------------------------------

// Returns the offset just past the line holding the End that closes the
// outermost Begin Form block in text[0..len), i.e. the length of the pure
// form-definition part.  Everything after it is the .frm's BASIC code
// section.  Returns len when no Begin Form block is found or closed.
// Lines are delimited by LF or CR LF; an End keyword counts only when it
// stands alone or is followed by whitespace.
static inline int32_t basFindFormEndPos(const char *text, int32_t len) {
    int32_t nesting = 0;
    bool    inForm  = false;
    int32_t pos     = 0;

    while (pos < len) {
        int32_t lineStart = pos;

        while (pos < len && text[pos] != '\n' && text[pos] != '\r') {
            pos++;
        }

        int32_t lineEnd = pos;

        if (pos < len && text[pos] == '\r') {
            pos++;
        }

        if (pos < len && text[pos] == '\n') {
            pos++;
        }

        int32_t l = lineStart;

        while (l < lineEnd && (text[l] == ' ' || text[l] == '\t')) {
            l++;
        }

        int32_t rest = lineEnd - l;

        if (rest >= BAS_BEGIN_PREFIX_LEN && strncasecmp(text + l, "Begin ", BAS_BEGIN_PREFIX_LEN) == 0) {
            if (!inForm) {
                int32_t r = l + BAS_BEGIN_PREFIX_LEN;

                while (r < lineEnd && (text[r] == ' ' || text[r] == '\t')) {
                    r++;
                }

                if (lineEnd - r >= BAS_FORM_PREFIX_LEN && strncasecmp(text + r, "Form ", BAS_FORM_PREFIX_LEN) == 0) {
                    inForm = true;
                }
            }

            nesting++;
        } else if (rest >= BAS_END_KEYWORD_LEN && strncasecmp(text + l, "End", BAS_END_KEYWORD_LEN) == 0 && (rest == BAS_END_KEYWORD_LEN || text[l + BAS_END_KEYWORD_LEN] == ' ' || text[l + BAS_END_KEYWORD_LEN] == '\t')) {
            nesting--;

            if (inForm && nesting == 0) {
                return pos;
            }
        }
    }

    return len;
}

#endif // BAS_RES_H
