/* SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-2.0-only
**  Copyright (c) 1996,1997,1998 Ralf S. Engelschall <rse@engelschall.com>
*/

#include "eperl.h"

enum ppmode { ppmode_file, ppmode_buffer };
struct span { char *str; size_t len; };

/*
**  expand #include directives in buffer
*/
static struct span ePerl_PP_Process(char *cpInput, const char *const *cppINC, size_t cppINCLen, enum ppmode mode, const char *ePerl_begin_delimiter, const char *ePerl_end_delimiter, bool ePerl_case_sensitive_delimiters)
{
    char *cp;
    char *cpT = NULL;
    char *cpInBuf = NULL;
    char *cpBuf;
    size_t nBuf;

    if (*cpInput == '\0')
        return (struct span){strdup(""), 0};

    if (mode == ppmode_buffer) {
        /* treat input as buffer */
        cpBuf = cpInput;
        nBuf = strlen(cpBuf);
    }
    else {
        /* treat input as filename */
        FILE *fp;
        if (strncmp(cpInput, "http://", 7) == 0) {
            fp = HTTP_openURLasFP(cpInput);
        }
        else if (*cpInput == '/') {
            fp = fopen(cpInput, "r");
        }
        else {
            fp = fopen(cpInput, "r");
            if (fp == NULL) {
                /* we have to try in all include directories! */
                char *full = NULL;
                for (size_t i = 0; i != cppINCLen; ++i) {
                    free(full);
                    if (asprintf(&full, "%s/%s", cppINC[i], cpInput) == -1)
                        break;
                    if ((fp = fopen(full, "r")) != NULL)
                        break;
                }
                free(full);
            }
        }
        if (fp == NULL) {
            ePerl_SetError("Cannot open source file %s for reading", cpInput);
            return (struct span){};
        }
        FILE *Buf = open_memstream(&cpBuf, &nBuf);
        if (!ePerl_CopyFILE(fp, Buf)) {
            ePerl_SetError("Cannot read from file %s", cpInput);
            return (struct span){};
        }
        fclose(fp);
        fclose(Buf);
        cpInBuf = cpBuf;
    }

    char *cpEND = cpBuf+nBuf;
    char *cps = cpBuf;
    struct span ret;
    FILE *OutBuf = open_memstream(&ret.str, &ret.len);

    while (cps < cpEND) {
        /*
         *   search for any more directives
         */
        cp = NULL;
        if (cps == cpBuf || ((cps > cpBuf) && (*(cps-1) == '\n'))) {
            if ((strncmp(cps, "#include",  sizeof("#include")  - 1) == 0) && (cp == NULL)) cp = cps;
            if ((strncmp(cps, "#sinclude", sizeof("#sinclude") - 1) == 0) && (cp == NULL)) cp = cps;
            if ((strncmp(cps, "#if",       sizeof("#if")       - 1) == 0) && (cp == NULL)) cp = cps;
            if ((strncmp(cps, "#elsif",    sizeof("#elsif")    - 1) == 0) && (cp == NULL)) cp = cps;
            if ((strncmp(cps, "#else",     sizeof("#else")     - 1) == 0) && (cp == NULL)) cp = cps;
            if ((strncmp(cps, "#endif",    sizeof("#endif")    - 1) == 0) && (cp == NULL)) cp = cps;
            if ((strncmp(cps, "#c",        sizeof("#c")        - 1) == 0) && (cp == NULL)) cp = cps;
        }
        if (((cpT = memmem(cps, cpEND-cps, "\n#include",  sizeof("\n#include")  - 1)) != NULL) && ((cpT < cp) || (cp == NULL))) cp = cpT+1;
        if (((cpT = memmem(cps, cpEND-cps, "\n#sinclude", sizeof("\n#sinclude") - 1)) != NULL) && ((cpT < cp) || (cp == NULL))) cp = cpT+1;
        if (((cpT = memmem(cps, cpEND-cps, "\n#if",       sizeof("\n#if")       - 1)) != NULL) && ((cpT < cp) || (cp == NULL))) cp = cpT+1;
        if (((cpT = memmem(cps, cpEND-cps, "\n#elsif",    sizeof("\n#elsif")    - 1)) != NULL) && ((cpT < cp) || (cp == NULL))) cp = cpT+1;
        if (((cpT = memmem(cps, cpEND-cps, "\n#else",     sizeof("\n#else")     - 1)) != NULL) && ((cpT < cp) || (cp == NULL))) cp = cpT+1;
        if (((cpT = memmem(cps, cpEND-cps, "\n#endif",    sizeof("\n#endif")    - 1)) != NULL) && ((cpT < cp) || (cp == NULL))) cp = cpT+1;
        if (((cpT = memmem(cps, cpEND-cps, "\n#c",        sizeof("\n#c")        - 1)) != NULL) && ((cpT < cp) || (cp == NULL))) cp = cpT+1;

        if (cp != NULL && (cp == cpBuf || (cp > cpBuf && *(cp-1) == '\n'))) {
            /* copy data up to directive */
            fwrite(cps, 1, cp-cps, OutBuf);

            /*
             *  now process the specific directives...
             */
            if (strncmp(cp, "#include", 8) == 0 || strncmp(cp, "#sinclude", 8) == 0) {
                bool sinclude = cp[1] == 's';
                cps = cp + 8 + sinclude;

                /* skip whitespace */
                for ( ; cps < cpEND && (*cps == ' ' || *cps == '\t'); cps++)
                    ;
                /* skip possible quotation mark or opening angle bracket */
                if (*cps == '"' || *cps == '<')
                    cps++;

                /* check for EOL */
                if (*cps == '\n') {
                    ePerl_SetError("Missing filename or URL for #%.*sinclude directive", sinclude, "s");
                    goto CUS;
                }

                /* grab the filename, skip to end of line later */
                char *caName = cps;
                while ( cps < cpEND &&
                            (*cps != ' ' && *cps != '\t' &&
                             *cps != '>' && *cps != '"'  &&
                             *cps != '\n'                  ))
                    ++cps;
                char orig = *cps;
                *cps = '\0';
                /* recursive usage */
                struct span loaded = ePerl_PP_Process(caName, cppINC, cppINCLen, ppmode_file, ePerl_begin_delimiter, ePerl_end_delimiter, ePerl_case_sensitive_delimiters);
                *cps = orig;
                if (!loaded.str)
                    goto CUS;

                if (sinclude) {
                    /* make it secure by removing all begin/end delimiters!! */
#define KILLDEL(delimiter) \
                    size_t delimiter##_len = strlen(delimiter); \
                    for (char *del; (del = (ePerl_case_sensitive_delimiters ? memmem : memcasemem)(loaded.str, loaded.len, delimiter, delimiter##_len));) { \
                        memmove(del, del + delimiter##_len, loaded.len - (del + delimiter##_len - loaded.str)); \
                        loaded.len -= delimiter##_len; \
                    }
                    KILLDEL(ePerl_begin_delimiter)
                    KILLDEL(ePerl_end_delimiter)
                }
                fwrite(loaded.str, 1, loaded.len, OutBuf);
                free(loaded.str);
            }
            else if (strncmp(cp, "#if", 3) == 0 || strncmp(cp, "#elsif", 6) == 0) {
                bool elsif = cp[1] == 'e';
                cps = cp + (elsif ? 6 : 3);

                /* skip whitespaces */
                for ( ; cps < cpEND && (*cps == ' ' || *cps == '\t'); cps++)
                    ;
                if (*cps == '\n') {
                    ePerl_SetError("Missing expression for #%sif directive", elsif ? "els" : "");
                    goto CUS;
                }

                /* copy the argument and create replacement string */
                char *argstart = cps;
                while (*cps && *cps != '\n')
                    ++cps;
                char orig = *cps;
                *cps = '\0';
                fprintf(OutBuf, "%s %sif (%s) { _%s//\n",
                        ePerl_begin_delimiter, elsif ? "} els" : "", argstart, ePerl_end_delimiter);
                *cps = orig;
            }
            else if (strncmp(cp, "#else", 5) == 0) {
                cps = cp+5;

                fprintf(OutBuf, "%s } else { _%s//\n",
                        ePerl_begin_delimiter, ePerl_end_delimiter);
            }
            else if (strncmp(cp, "#endif", 6) == 0) {
                cps = cp+6;

                fprintf(OutBuf, "%s } _%s//\n",
                        ePerl_begin_delimiter, ePerl_end_delimiter);
            }
            else if (strncmp(cp, "#c", 2) == 0) {
                cps = cp+2;

                /* completely discard line */
            }

            /* skip to end of line */
            for ( ; cps < cpEND && *cps != '\n'; cps++)
                ;
            if (*cps == '\n')
                cps++;
        }
        else {
            /* no more found */

            /* add data */
            fputs(cps, OutBuf);

            break;
        }
    }

    if (fclose(OutBuf))
        goto CUS2;

ret:
    if (cpInBuf)
        free(cpInBuf);
    return ret;
CUS:
    fclose(OutBuf);
CUS2:
    free(ret.str);
    ret = (struct span){};
goto ret;
}

char *ePerl_PP(char *cpBuf, const char *const *cppINC, size_t cppINCLen, const char *ePerl_begin_delimiter, const char *ePerl_end_delimiter, bool ePerl_case_sensitive_delimiters)
{
    return ePerl_PP_Process(cpBuf, cppINC, cppINCLen, ppmode_buffer, ePerl_begin_delimiter, ePerl_end_delimiter, ePerl_case_sensitive_delimiters).str;
}
