/* c2snowc - Chris X Edwards - May 2026 */
/* Converts C code to SnowC -           */
/* Semantic Non-Optional Whitespace C   */

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

#define HERE(LINE) printf("HERE line:%d\n",LINE);

void usage(const char *prog) {
    printf("Usage: %s [options] file\n"
           "  -h --help\n"
           "  -i --indent N - Indent level. [default 2]\n"
           "  -m --map - Print parsed meta map.\n"
           "  -l --level - Print level diagnostics.\n"
           "  -d --debug\n",
           prog);
} // End usage()

typedef struct { int map; int indent; int level; int debug; } ConfigPack;
ConfigPack conf= {0}; /* Globally available. Initialize all to 0. */
ConfigPack parse_options(int argc, char *argv[]) {
    conf.indent= 2; /* Any non-zero default values. */
    int o;
    static struct option long_options[]= {
        {"help",   no_argument,       NULL, 'h'},
        {"map",    no_argument,       NULL, 'm'},
        {"level",  no_argument,       NULL, 'l'},
        {"indent", required_argument, NULL, 'i'},
        {"debug",  no_argument,       NULL, 'd'},
        {0, 0, 0, 0}
    }; // End struct long_options
    while (1) {
        o= getopt_long(argc, argv, "hmli:d", long_options, NULL);
        if (o == -1) break;
        switch (o) {
            case 'h': usage(argv[0]); exit(0);
            case 'm': conf.map= 1; break;
            case 'l': conf.level= 1; break;
            case 'i':
                conf.indent= atoi(optarg);
                if (conf.indent>0) break;
                else {
                    fprintf(stderr, "Indent invalid value: %d\n",conf.indent);
                    exit(1);
                }
            case 'd': conf.debug= (optarg) ? atoi(optarg) : 99; break;
            default:
                fprintf(stderr, "Unknown option: -%c\n", o);
                usage(argv[0]);
                exit(1);
        } // End switch
    }
    return conf;
} // End parse_options()

#define INDENTSIZE 4
// Define state modes.
#define IN_STR    (1 << 0)
#define IN_CHR    (1 << 1)
#define IN_CC_COM (1 << 2)
#define IN_C_COM  (1 << 3)
#define IN_PRE    (1 << 4)
#define IN_COM    (IN_CC_COM | IN_C_COM)

// Define markers which will fill meta map. Enum so they work in cases too.
enum { mC= 'C',   mSEC= ';',
       mSTR= '$', mCHR= '\'',
       mSPC= '_', mEOL= '+',
       mCOM= '/', mPRE= '#',
       mLSP= '.', mTSP= ',',
       mOPR= '(', mCPR= ')',
       mOBR= '{', mCBR= '}',
       mLAB= 'L', mCOL= ':',
       m1CK= '1'               // Singleton Control Keyword
     };

void map_major_parts(const char *buffer, size_t size, char *meta) {
    unsigned char state= 0;
    size_t i= 0;
    while (i < size) {
        char c= buffer[i];
        if (state & IN_COM) {                                    // In some kind of comment.
            if (state & IN_CC_COM && (c == '\n')) {              // EOL closing out a // comment?
                state &= ~IN_CC_COM;                             // Turn off CC comment mode.
                meta[i]= mEOL;                                   // Mark content of comment in meta.
            } else if (state & IN_C_COM && c == '*') {           // In C comment, possible close found.
                if (i + 1 < size && buffer[i + 1] == '/') {      // It is C comment closer.
                    state &= ~IN_C_COM;                          // Cancel C comment mode.
                    i++;                                         // One time index forward for both symbols.
                    meta[i-1]= mCOM;                             // In theory i-1 should always be > 0.
                    meta[i]= mCOM;                               // Mark second position in meta.
                } else {                                         // Just a * in a C comment.
                    meta[i]= mCOM;                               // Mark comment.
                }
            } else {
                if (buffer[i] == '\n') {
                    meta[i]= mEOL;                               // Mark EOL in a C comment.
                }
                meta[i]= mCOM;                                   // Mark content of comment in meta.
            }
        } else if (state & IN_STR) {                             // In a string literal.
            if (c == '"') {                                      // Escaped quotes checked - this must be end.
                state &= ~IN_STR;                                // Turn off string mode.
                meta[i]= mSTR;                                   // Mark this end quote's position as string.
            } else if (c == '\\' && i + 1 < size) {              // A backslash could escape the end quote.
                meta[i]= meta[i + 1]= mSTR;                      // Mark backslash and protectee.
                i++;                                             // Next.
            } else {
                meta[i]= mSTR;                                   // A normal string character.
            }
        } else if (state & IN_CHR) {                             // In character literal.
            if (c == '\'') {                                     // Found closing '.
                state &= ~IN_CHR;                                // Turn off character mode.
                meta[i]= mCHR;                                   // Mark char in meta with '.
            } else if (c == '\\' && i + 1 < size) {              // Found escaping backslash.
                meta[i]= meta[i + 1]= mCHR;                      // Mark backslash and protectee.
                i++;                                             // Next.
            } else {
                meta[i]= mCHR;                                   // Normal character closer.
            }
        } else if (state & IN_PRE) {                             // In preprocessor syntax.
            if (c == '\n') {                                     // May end PP macro.
                if (i > 0 && buffer[i - 1] != '\\') {            // Not an escaped line break, so the end.
                    state &= ~IN_PRE;                            // Turn off preprocessor mode.
                    meta[i]= mEOL;                               // Mark EOL.
                }
            } else {                                             // In pre mode but normal
                meta[i]= mPRE;                                   // Mark as preprocessor.
            }
        } else {                                                 // Not in any special mode.
            if (c == '"') {                                      // Must be a string literal starter.
                state |= IN_STR;                                 // Turn on string mode.
                meta[i]= mSTR;                                   // Mark string starter.
            } else if (c == '\'') {                              // Must be character starter.
                state |= IN_CHR;                                 // Turn on character mode.
                meta[i]= '\'';                                   // Mark character starter.
            } else if (c == '/') {                               // Possible comment starter.
                if (i + 1 < size && buffer[i + 1] == '/') {      // Next is / so CC comment.
                    state |= IN_CC_COM;                          // Turn on CC comment mode.
                    meta[i]= meta[i + 1]= '/';                   // Mark CC starter.
                    i++;                                         // Index once for double syntax.
                } else if (i + 1 < size && buffer[i + 1] == '*') { // Must be C comment starter.
                    state |= IN_C_COM;                           // Turn on C comment mode.
                    meta[i]= meta[i + 1]= mCOM;                  // Mark C comment starter.
                    i++;                                         // Index once for double syntax.
                } else {
                    meta[i]= mC;                                 // Just an isolated division slash.
                }
            } else if (c == '#') {                               // Pre macro start possible.
                int a=i;                                         // A lookback counter.
                while (a>=0) {                                   // Don't go past start.
                    a--;                                         // Go back in buffer.
                    if (buffer[a] == ' ') {                      // If spaces before #, still candidate.
                        continue;                                // Try again yet one earlier.
                    } else if (buffer[a] == '\n' || (a<0)) {     // If hit prev newline or start of file.
                        state |= IN_PRE;                         // In PRE macro.
                        meta[i]= mPRE;                           // Mark start of PRE.
                        break;                                   // Done back searching.
                    } else {                                     // Found other char, so not a PRE macro.
                        break;                                   // Give up.
                    }
                }
            } else if (isspace(c)) {                             // Whitespace.
                if (c == '\n') {
                    meta[i]= mEOL;
                } else {
                    meta[i]= mSPC;                               // Hopefully kills tabs dead.
                }
            } else {
                meta[i]= mC;                                     // Some non-special regular C syntax.
            }
        }
        i++;                                                     // On to the next character.
    } // End while iterating through entire buffer to map.
} // End map_major_parts()

void map_leading_whitespace(const char *buffer, size_t size, char *meta) {
    unsigned char in_leading_space= 1; // Starts assuming first line might have leading space.
    size_t i= 0;
    while (i < size) {
        char c= buffer[i];
        if (in_leading_space && meta[i] != mCOM) {               // In leading spc - ignore comments.
            if (c == ' ' || c == '\t') {                         // Space or tab found.
                meta[i]= mLSP;                                   // Mark as leading space.
            } else if (c == '\n' ||                              // Line of all spaces. Stay in mode.
                c == '{' || c == '}' || c == ';') {              // Also ignorable for purpose of LSP.
            } else {                                             // Found end of leading space.
                in_leading_space= 0;                             // Turn off leading space mode.
            }
        } // End if in_leading_space.
        else {
            if (c == '\n') {                                     // NL, could start leading spaces.
                in_leading_space= 1;                             // Turn on leading space mode.
            }
        } // End else not in_leading_space.
        i++;
    } // End while not through entire text.
} // End map_leading_whitespace()

void map_trailing_whitespace(const char *buffer, size_t size, char *meta) {
    unsigned char in_trailing_space= 1; // Starts assuming first line might have trailing space.
    size_t i= size - 1;
    // Strange thing here that >= 0 will bork entire program!
    while (i > 0) {                          // Can't be first char anyway.
        char m= meta[i], b= buffer[i];
        if (in_trailing_space) {                                 // In trailing space.
            if (m == mSPC ) {                                    // Whitespace found.
                meta[i]= mTSP;                                   // Overwrite mark to trailing space.
            // All of these are also ignorable for the purpose of TSP.
            } else if (m == mEOL || m == mCOM || b == '{' || m == '}' || m == ';') {
            } else {                                             // Found end of trailing space.
                in_trailing_space= 0;                            // Turn off trailing space mode.
            }
        } // End if in_trailing_space.
        else {
            if (m == mEOL) {                                     // NL, could start trailing spaces.
                in_trailing_space= 1;                            // Turn on trailing space mode.
            }
        } // End else not in_trailing_space.
        i--;
    } // End while not through entire text.
} // End map_trailing_whitespace()

void map_C_parens_braces(const char *buffer, size_t size, char *meta) {
    unsigned char paren_lvl= 0, brace_lvl= 0; // Can't start in a level.
    size_t i= 0;
    while (i < size) {
        char c= buffer[i];
        if (meta[i] == 'C') {                                    // C only - not preproc, comments, strings, etc.
            if (c == '(') {
                paren_lvl++;                                     // Any open paren deepens the level.
                if (paren_lvl == 1) {
                    meta[i]= mOPR;                               // Mark if outermost parens.
                }
            } else if (c == ')') {                               // Close paren found.
                if (paren_lvl > 0) {
                    paren_lvl--;
                    if (paren_lvl < 0) {
                        perror("ERROR: BAD PAREN MATCHING!");
                    } else if (paren_lvl == 0) {                 // Already decremented just above!
                        meta[i]= mCPR;                           // Mark if outermost parens.
                    }
                }
            } else if (c == '{') {
                brace_lvl++;                                     // Any open brace deepens the level.
                meta[i]= mOBR;
            } else if (c == '}') {
                if (brace_lvl > 0) {
                    brace_lvl--;
                    if (brace_lvl < 0){
                        perror("ERROR: BAD BRACE MATCHING!");
                    }
                    meta[i]= mCBR;
                }
            } else if (c == ';') {                               // Mark statement ending semicolons.
                if (paren_lvl == 0) {                            // Not in paren sets as with `for`.
                    meta[i]= mSEC;
                }
            } // End if open or closed parens or braces.
        } // End if meta marked as C code.
        i++;
    } // End while iterating through buffer.
} // End map_C_parens_braces()

// Braces are critical to semantic levels but some braces, while proper and
// essential synbtax, are more decorative. This reverse searches for mCBR and
// then does a sub reverse search for the previous important character. If it's
// not mSEM (or nested mCBR) then that brace set is considered decorative and
// marked as mC. By running this function multiple times `while` any m?BR are
// converted to mC, all levels of nested decorative braces can be nerfed until
// no changes are possible to make and this returns 0. This remap allows
// aggregate type declarations and definitions to use braces that are ignored.
int map_C_weak_braces(const char *buffer, size_t size, char *meta) {
    int tmp_c;                                                   // Temporary locations of a found open/closing set.
    int changed= 0;                                              // How many changes this round.
    for (int i=size;i>0;i--) {                                   // Reverse scan through file.
        if (meta[i] == mCBR) {                                   // Could be one.
            tmp_c= i;
            for (int j=i-1;j>0;j--) {                            // Reverse subscan through file.
                if (meta[j] == mC) {
                    meta[tmp_c]= mC;                             // Mark the closer.
                    for (int k=tmp_c-1;meta[k]!=mC;k--) {        // Hunt for TSP which are now SPC.
                        if (k<0) break;                          // Don't go too far.
                        if (meta[k] == mC) break;                // Stop at first sign of non-TSP.
                        if (meta[k] == mTSP) meta[k]= mSPC;      // Upgrade the space.
                    }
                    while (j > -1 && meta[j] != mOBR) j--;       // Zoom back to next opener.
                    if (j>-1) {
                        meta[j]= mC;                             // Mark the opener.
                        for (int k=j-1;meta[k]!=mC;k--) {        // Hunt for TSP which are now SPC.
                            if (k<0) break;                      // Don't go too far.
                            if (meta[k] == mC) break;            // Stop at first sign of non-TSP.
                            if (meta[k] == mTSP) meta[k]= mSPC;  // Upgrade the space.
                        }
                    }
                    i= j;
                    changed++;
                    break; // Back to i loop.
                } else if (meta[j] == mCOL || meta[j] == mLAB) {
                    continue; // See if there's some real C.
                } else if (meta[j] == mSEC || meta[j] == mCBR) {
                    i= j+1; // Cool, leave it.
                    break; // Back to i loop.
                } else if (meta[j] == mOBR) { // Empty {}, mark both.
                    meta[i]= mC; meta[j]= mC; // Mark both.
                    for (int k=j-1;meta[k]!=mC;k--) {        // Hunt for TSP which are now SPC.
                        if (k<0) break;                      // Don't go too far.
                        if (meta[k] == mC) break;            // Stop at first sign of non-TSP.
                        if (meta[k] == mEOL) break;          // Previous line could have legit TSP.
                        if (meta[k] == mTSP) meta[k]= mSPC;  // Upgrade the space.
                    }
                    i= j; // Continue from opener.
                    changed++;
                    break; // Back to i loop.
                }
                //else // Pass by COM, LSP, TSP, SPC, PRE, OPR, CPR, etc
            } // End sub j loop.
        } // End if mCBR found.
    } // End for loop reverse scan through file.
    return changed;
} // End map_C_weak_braces()

size_t identify_next_eol(const char *buffer, size_t size, const char *meta, size_t pos) {
    while (pos < size) {
        if (meta[pos] == mEOL) {
            return pos;
        }
        pos++;
    } // End while position is not past end.
    return size;
} // End identify_next_eol()

int mark_label_in_line(const char *buffer,const size_t size, char *meta,const size_t s,const size_t e) {
    if (s >= size || e >= size) return 0;
    for (int i= e; i > s; i--) {
        if (meta[i] == mPRE
        || meta[i] == mSEC
        || meta[i] == mCHR
        || meta[i] == mOPR
        || meta[i] == mCPR
        || meta[i] == mOBR
        || meta[i] == mCBR
        || meta[i] == mSTR) {
            return 0;
        }
     } // End if a forbidden label type.
    int colon_pos= -1; // Where is label colon? -1 is none, so far.
    int lab_s_pos= -1; // Where does the label start?
    int lab_e_pos= -1; // Where does the label end?
    int char_end= (int) e; // This mess is needed because final line may not end in EOL.
    if (meta[e] == mEOL) char_end--; // Line params will include EOL which is not helpful.
    for (int i= char_end; i >= (int) s; i--) {  // Scan backwards, must cast to avoid size_t problems.
        if (lab_e_pos > -1 && meta[i] == mSPC) { // Non-lead/trail space before end of label body?
            return 0; // Can't be a valid contiguous label.
        } else if (meta[i] == mSPC && lab_e_pos == -1) { // Space between label body and colon.
            continue; // Annoying but vaild C.
        } else if (meta[i] == mCOM || meta[i] == mTSP) { // Comment? Or trailing space?
            continue; // Just ignore.
        } else if (meta[i] == mLSP) { // Leading space?
        // TODO: Maybe a problem if `,,},,mylabel:`? Is that even legal?
            break; // Leading spaces assumed properly marked, no need to look earlier.
        } else if (meta[i] == mC) { // C char.
            if (!(isalnum(buffer[i]) || buffer[i] == '_' || buffer[i] == ':')) {
                return 0; // Must be alpha numeric or _ or :. First char digit check is later.
            } // End must be [a-z0-9_:].
            if (lab_e_pos == -1) { // End position not set, not yet in label_core.
                if (buffer[i] == mCOL) {
                    if (colon_pos > -1) { // Colon position already set. Which is bad.
                        return 0;
                    } else { // Colon unset, this is the first.
                        colon_pos= i;
                    } // End if colon was already set or not.
                } else { // End found a colon. Else must be other C code.
                    if (colon_pos == -1) { // Non-colon char but no colon found yet.
                        return 0; // The rightmost C char wasn't a colon so DQ.
                    } else { // Right most C char was a valid colon!
                        lab_e_pos= i; // Label body better end here. (Rev scanning.)
                    } // Else colon position properly set for label purposes.
                } // End non-colon C code char not yet in label core.
            } // End if end pos not set, i.e. not yet in label body.
            lab_s_pos= i; // Every valid label char keeps lowering this until correct.
        } else {// Not expecting anything else.
            //printf("ERROR: Unusual character? %c [%c]\n",buffer[i],meta[i]); /* ERROR */
        }
    } // End for backwards through line.
    if (colon_pos > -1                 // A colon was found.
        && lab_s_pos > -1              // A label start position was found.
        && lab_e_pos > -1              // A lable end position was found.
        && lab_e_pos >= lab_s_pos      // End must not be before than start.
        && colon_pos > lab_e_pos       // Colon must be after end of label body.
        && !isdigit(buffer[lab_s_pos]) // First label char must not be a digit!
        ) {
        char *d= "default"; // Check for switch's default which looks just like a label.
        if ( memcmp(buffer+(size_t)lab_s_pos, d, strlen(d)) // Non-0 is non match for "default".
             || ((lab_e_pos - lab_s_pos) > 6) // Ok if "default" plus. Checking order is important.
           )  {
            for (int i=lab_s_pos; i<=lab_e_pos; i++) {
                meta[i]= mLAB; // Mark it as a label!
            }
            meta[colon_pos]= mCOL; // Mark the colon too.
        } // End if label correct format is not the verboten keyword "default:"
        return 1; // Yes, it's a label line.
    } else {
        return 0; // Some problem with what was found. Not a label then.
    } // End
} // End mark_label_in_line()

void map_C_labels(const char *buffer, size_t size, char *meta) {
    size_t s= 0, e;  // Set start and end.
    //int l= 0; // Line number.
    while (s < size) {
        e= identify_next_eol(buffer,size,meta,s);
        //l++; // On to the next line number.
        mark_label_in_line(buffer,size,meta,s,e);
        s= e + 1;  // Next start of line is one past this end.
    }
} // End map_C_labels()

// seek_1x_statement() is for map_C_ctl_singles() and looks ahead through the
// meta map from a given position for the arrangement of a keyword control
// syntax that could have had a block but only has a single statement. If it's
// all on the same line, leave it alone. If the single statement that is part
// of the keyword's execution clause is on the next line, return the position
// of its semi-colon. Otherwise return size+1 to signal not a singleton.
size_t seek_1x_statement(const char *meta, const size_t size, const size_t start) {
    size_t i= start;
    size_t singleton_pos= size+1; // mSEC position. Default (size+1) means none found.
    int P_lvl= 0; // Paren level tracking to properly skip over key word conditional clauses.
    while (i<size) {
        switch (meta[i]) {
            case mSPC: case mCOM: case mPRE: case mLAB: case mCOL:
                break;
            case mC:
                if (!P_lvl) return i;
                break;
            case mOPR:
                P_lvl++; // Because only outer parens are marked this should only ever get to 1.
                if (P_lvl > 1) printf("Why did paren count go past 1 at pos: %ld?",i);
                break;
            case mCPR:
                P_lvl--;
                if (P_lvl < 0) printf("Why did paren count go negative at pos: %ld?",i);
                break;
            case mCBR: // Umm, this is kind of an error.
            case mOBR: // Must be a full block. Ignore this keyword.
                return size+1;
            case mEOL:
                singleton_pos= i; // Not none! Basically marking that it's on another line.
                break;
            case mSEC:
                //if (singleton_pos == size+1) return singleton_pos; // Same line exception.
                //else return i;
                return i;
        } // End switch
        i++;
    } // End while iterating through lookahead.
    return singleton_pos;
} // End seek_1x_statement()

// map_C_ctl_singles solves the problem of indenting for single line "blocks"
// that could have had braces but stylistically didn't. Consider lines...
// if (q)
// a();
// c();
// That should convert to...
// if (q)::
//   a()
// c()
// But how will the single line "then" clause know it is in an indent block?
// The only clue comes from the six keywords: if, else, for, while, do, switch
// These are meta mapped as Singleton Control Keyword or m1CK. 
// Note that there is no way for snowc to selectively be converted back _to_
// this braceless kind of syntax based on indent alone.
void map_C_ctl_singles(const char *buffer, size_t size, char *meta) {
    size_t i= 0; // So check on previous is safe. Maybe bad if file starts with keyword?
    while (i<size) {
        if (meta[i] != mC) { // Keyword must be regular C code.
            i++; // Move along.
            continue; // This is not a control word.
        }
        if (i > 0 && meta[i-1] == mC) { // Zero is ok, or previous mark must be non mC.
            i++;
            continue;
        }
        switch (buffer[i]) {
            case 'i': {
                if (i+2>=size || meta[i+2]==mC) break;
                if (i+1<size && buffer[i+1] == 'f') {
                    int closer= seek_1x_statement(meta,size,i+2);
                    if (closer <= size) { // Above size means nothing found.
                        memset(meta+i,m1CK,2); // Mark as a singleton control keyword.
                    }
                    i+=2; // Skip rest of matched word and, below, the space.
                }
                break;
            }
            case 'f': {
                if (i+3>=size || meta[i+3]==mC) break;
                if (i+2<size && !memcmp(buffer+i+1, "or", 2) ) { // Zero is match.
                    int closer= seek_1x_statement(meta,size,i+3);
                    if (closer <= size) { // Above size means nothing found.
                        memset(meta+i,m1CK,3); // Mark as a singleton control keyword.
                    }
                    i+=3; // Skip rest of matched word and space.
                }
                break;
            }
            case 'e': {
                if (i+4>=size || meta[i+4]==mC) break;
                if (i+3<size &&  !memcmp(buffer+i+1, "lse", 3) ) { // Zero is match.
                    int closer= seek_1x_statement(meta,size,i+4);
                    if (closer <= size) { // Above size means nothing found.
                        memset(meta+i,m1CK,4); // Mark as a singleton control keyword.
                    }
                    i+=4; // Skip rest of matched word and space.
                }
                break;
            }
            case 'w': {
                if (i+5>=size || meta[i+5]==mC) break;
                if (i+4<size &&  !memcmp(buffer+i+1, "hile", 4) ) { // Zero is match.
                    int closer= seek_1x_statement(meta,size,i+5);
                    if (closer <= size) { // Above size means nothing found.
                        memset(meta+i,m1CK,5); // Mark as a singleton control keyword.
                    }
                    i+=5; // Skip rest of matched word and space.
                }
                break;
            }
            case 'd': {
                if (i+2>=size || meta[i+2]==mC) break;
                if (i+1<size && buffer[i+1] == 'o') {
                    int closer= seek_1x_statement(meta,size,i+2);
                    if (closer <= size) { // Above size means nothing found.
                        memset(meta+i,m1CK,2); // Mark as a singleton control keyword.
                    }
                    i+=2; // Skip rest of matched word and space.
                }
                break;
            }
            case 's': {
                if (i+6>=size || meta[i+6]==mC) break;
                if ( i+5<size && !memcmp(buffer+i+1, "witch", 5) ) { // Zero is match.
                    int closer= seek_1x_statement(meta,size,i+6);
                    if (closer <= size) { // Above size means nothing found.
                        memset(meta+i,m1CK,6); // Mark as a singleton control keyword.
                    }
                    i+=6; // Skip rest of matched word and space.
                }
                break;
            }
        } // End switch.
        i++; // Next char after any skipping.
    } // End while iterating through all characters with i.
} // End map_C_ctl_singles

void print_annotated_buffer(const char *buffer, size_t size, const char *meta) {
    size_t i,m= 0;
    for (i= 0; i < size; i++) {
        if (buffer[i] == '\n') {
            putchar('\n');
            for (; m <= i; m++) {
                putchar(meta[m]);
            }
        }
        putchar(buffer[i]);
    } // End for all the characters.
} // End print_annotated_buffer()

int more_code_before_eol(const char *meta, const size_t size, const size_t start, const int brace_lvl) {
    int found_wrong_lvl_code= 0, end_brace_lvl= brace_lvl;
    for (size_t i= start; i<size; i++) {
        if (meta[i] == mOBR) end_brace_lvl++;
        if (meta[i] == mCBR || meta[i] == m1CK) end_brace_lvl--;
        if (meta[i] == mC) {
            if (brace_lvl == end_brace_lvl) found_wrong_lvl_code= 0;
            else found_wrong_lvl_code= 1;
        }
        if (meta[i] == mEOL) break;
    } // End for iterating until next EOL.
    return found_wrong_lvl_code;
} // End more_code_before_eol()

// Checks if a supplied word is present and not muddled with leading or traling chars.
int keyword_search(const char* buffer, size_t size, const char* word, size_t pos) {
    size_t word_len= strlen(word);
    if (pos + word_len > size) return 0; // Insufficient size for this word.
    if (memcmp(buffer + pos, word, word_len) != 0) return 0; // Word does not match.
    if (pos > 0) {                       // Check for word as subset on previous char.
        char c= buffer[pos - 1];
        if ((c >= '0' && c <= '9') ||
            (c >= 'A' && c <= 'Z') ||
            (c >= 'a' && c <= 'z') ||
            c == '_')
            return 0;
    }
    if (pos + word_len < size) {         // Check for word as subset on subsequent char.
        char c= buffer[pos + word_len];
        if ((c >= '0' && c <= '9') ||
            (c >= 'A' && c <= 'Z') ||
            (c >= 'a' && c <= 'z') ||
            c == '_')
            return 0;
    }
    return 1; // This is a clean well-bounded match.
} // End keyword_search()

// SIC is Singleton Indent Counters, a heap growable array. It is required to
// track the temporary increase in indent levels that may accrue in any
// particular brace depth level due to singleton control keywords (m1CK). This
// indentation is only relevant for the particular brace level and why each
// brace level must independently track this. Since it can't be known how deep
// blocks may be nested in obnoxious C code, an expandable array is used and
// grown (doubled) when the brace depth needs exceed the array's capacity.
// The starter array requires heap malloc so malloc expansions are compatible.
#define InitDepthLevelMax 10
void ensure_grow_arr_ok(int need, int **grow_arr, int *grow_arr_size) {
    if (need >= *grow_arr_size) {
        int new_size= need * 2; // Grows to double its previous size when full.
        *grow_arr= realloc(*grow_arr, new_size * sizeof(int));
        if (*grow_arr == NULL) {
            perror("ERROR: Expandable array failed to expand.\n");
            exit(1);
        }
        memset(*grow_arr + *grow_arr_size, 0, (new_size - *grow_arr_size) * sizeof(int));
        *grow_arr_size= new_size;
    } // End if grow operation performed.
} // End ensure_grow_arr_ok()

// The IfStack is a way to preserve necessary state about the indent levels of
// `if` singletons because `else` indent levels need to be reset to the same as
// the last `if` and that may be a jump. Note that `else` doesn't follow normal
// singleton control keyword placement. It looks for the levels of the inner
// most `if` and only after that `else` is closed out can the `if` truly close.
// It should be possible to manage this with a stack containing a pair of
// values: the current brace depth level and the current singleton depth level.
// Here are the types and the functions to manage such a stack.
typedef struct ifstate { int B; int S; } ifstate;
typedef struct IfStackNode {
    ifstate data;
    struct IfStackNode* next;
} IfStackNode;
IfStackNode* IfAnchor_top= NULL;
void IfAnchor_push(ifstate v) {
    IfStackNode* n= malloc(sizeof(IfStackNode));
    n->data= v;
    n->next= IfAnchor_top;
    IfAnchor_top= n;
}
int IfAnchor_readlast(void) {
    if (!IfAnchor_top) return 0;
    return IfAnchor_top->data.B;
}
ifstate IfAnchor_pop(void) {
    if (!IfAnchor_top) return (ifstate){0, 0};
    IfStackNode* n= IfAnchor_top;
    ifstate v= n->data;
    IfAnchor_top= n->next;
    free(n);
    return v;
}

void render_line(const char *buffer, const char *meta,size_t size,int *S,int E,int *SIC, int B_lvl, char Q) {
    if (conf.level) { // If level diagnostics are turned on.
        if (B_lvl>0)
            printf("%03d-%03d %c B:%02d S[%d]:%02d S[%d]:%02d|",*S,E,Q,B_lvl,B_lvl-1,SIC[B_lvl-1],B_lvl,SIC[B_lvl]);
        else
            printf("%03d-%03d %c B:%02d xxxxxxx S[%d]:%02d|",*S,E,Q,B_lvl,B_lvl,SIC[B_lvl]);
    }
    int skip= 1; // Assume nothing worth printing until proven otherwise.
    int escape= 0; // Assume sane breaks unless found otherwise.
    int fakeLSP= 1; // Don't assume mSPC are real (broken;__line?) until mC or m1CK found.
    int lastprintchar; // Pos of last character to be printed. So no trailing *SP*, ?BR, SEM, etc.
    for (lastprintchar=E;lastprintchar>*S;lastprintchar--) {
        if (buffer[lastprintchar] == ' '
            && (meta[lastprintchar] == mCOM || meta[lastprintchar] == mPRE) )
            continue;
        switch (meta[lastprintchar]) {
            case mEOL: case mSEC: case mOBR: case mCBR:
            case mTSP: case mLSP: case mSPC: continue; // Cont decr loop.
        }
        break; // Falls here if actual printing char found. Break loop, keep this value.
    }
    for (int c=*S;c<=E;c++) {
        switch (meta[c]) {
            case mC: case m1CK: case mCOM: case mPRE: case mSPC:
            case mOPR: case mCPR: case mSTR: case mCHR:
            case mLAB: case mCOL: skip= 0; // Act immediately upon finding any printed char.
        } // End switch check for subtantive line.
    }
    if (skip) { // Maybe some others that shouldn't be skipped..
        if (E && meta[E] == mEOL) {  // If (E) because can't be 0 to 0.
            if (meta[E-1] == mEOL) skip= 0;
        }
        if (Q == '@' || Q == '0') { // Aggregate type semicolon hint.
            skip= 0; // mCBR+mSEM must be kept. Also if 0th char is mEOL.
        }
    } // End skip exceptions.
    int indent_total= B_lvl;     // Brace level to convey with indents.
    if (*S == E && meta[E] == mEOL) indent_total= 0; // Don't indent blank lines.
    if (!skip) {
        for (int s=0;s<=B_lvl;s++) { // Add up m1CK sub levels for each brace level.
            indent_total += SIC[s]; // Additional singleton indent levels.
        }
        indent_total *= conf.indent; // Convert to spaces needed.
        for (;indent_total > 0;indent_total--) putchar(' '); // Actual SnowC indentation!
        for (int c=*S;c<=E;c++) { // Characters to actually print a line.
            switch (meta[c]) {    // When marked, the following are not in SnowC.
                case mOBR: case mCBR: case mLSP: {
                    escape= 0; // A natural sane break. No escape needed.
                    continue; // Next in for c loop; this char is ignored.
                }
                case mSEC: {
                    escape= 0;
                    fakeLSP= 0;
                    if (Q == '@') putchar(';');
                    if (c==E) {
                        continue; // At end mSEM is dropped. Continue (past) for c.
                    }
                    for (int c2=c+1;c2<=E;c2++) {
                        if (meta[c2] == mC) { // More code on this line render.
                            goto breakx2; // Break switch. This mSEM stays.
                        }
                    }
                    continue; // Continue for c to finish, e.g. comments.
                    breakx2:
                    break; // Break out of case - from inside c2 loop.
                }
                case mEOL:
                    // Note that this backslash Q tip is all the hinting possible for escape
                    // from beyond the submitted line. Needs to be determined before render.
                    if (Q == '\\' && escape) {
                        putchar('\\');
                        escape= 0; // Reset escape.
                    }
                case mSPC: { // Let fall into honorary mTSP if past last printed character.
                    if (fakeLSP)
                        continue; // Skip! Cont for loop through line chars.
                    if (c <= lastprintchar) break; // Break case to go print this
                    // else failover as same behavior as mTSP.
                } // End case mSPC.
                case mCOM: {
                    if (Q == '\\' && escape) { // Escape primed and followed directly by mCOM.
                        for (int c2=c;c2<E;c2++) { // Make sure it's only comments to EOL.
                            if (meta[c2] != mCOM || meta[c2] != mTSP) break;
                        }
                        putchar('\\');     // Put escape here just before mCOM starts. 
                        Q= ' '; escape= 0; // Ensures it only escapes once.
                    }
                    break;
                } // End case mCOM.
                case mTSP: { // If subsequent comments, then keep TSPs.
                    if (Q == '\\' && escape) {
                        putchar('\\');
                        Q= ' '; escape= 0;
                    }
                    int junkTSP= 1; // Assume pointless TSP only.
                    for (int c2=c;c2<E;c2++) { // From here to EOL.
                        if (meta[c2] == mCOM) {
                            junkTSP= 0; // There is a comment so this TSP is wanted.
                            break;      // Stop immediately. From c2 for only.
                        }
                    }
                    if (junkTSP) { // Note: This still leaves mCOM TSPs!
                        c= E; // Done! Set line char to end.
                        continue; // Skip! Cont for loop through line chars.
                    }
                } // End case mTSP.
                case mSTR: {
                    if (c == E-1) { // Escape lines of string literals.
                        escape= 1; Q= '\\';
                    }
                    break;
                } // End case mSTR.
                case m1CK: case mCPR:
                case mLAB: case mCOL:
                case mC: {
                    fakeLSP= 0; // Turn off since something real is here.
                    if (buffer[c] == ':') { // Prevents case escaping.
                        escape= 0;
                    } else escape= 1;
                    //TODO: Problem with `q?1:\n0` ?
                }
            } // End switch.
            if (c > lastprintchar) break; // Break line char loop to finish now.
            if (meta[c] != mEOL) putchar(buffer[c]); // Render the output!
        } // End for characters in the line.
        if (!conf.level) { // Only not skipped lines, level debug is done later.
            putchar('\n');
        }
    } else {} //PASS
    if (conf.level) {
        putchar('\n'); // Out of loop so it always fires when level debug on.
    }
    *S= E+1;
} // End render_line()

void generate_snowc(const char *buffer, size_t size, const char *meta) {
    unsigned int i= 0, lineno= 0;
    int B_lvl= 0, P_lvl= 0; // Brace level tracking, parentheses level tracking.
    int *SIC= calloc(InitDepthLevelMax,sizeof(int)); // Singleton Indent Counters.
    int SIC_size= InitDepthLevelMax; // Max brace_lvls before growing SIC is required.
    int S= 0; // Start of semantic line.
    int in_singleton= 0; // Once a singleton is found, mSEC closes it.
    char esc_hint= '\\'; // Go with the default being to escape.
    int agg_possible= 0; // Aggregate type (struct,union,enum) could leave mSEC after mCBR.
    while (i < size) {
        if (meta[i] == mEOL) {
            if (i>0 && S==i && meta[i-1] == mEOL) // Start or blank line profile.
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,'^'); // All blank line.
            else if (!i) { // There's a blank line at the start.
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,'0'); // Starts blank.
            } else if (meta[i-1] == mSTR)  {
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,'$'); // Random EOL.
            } else if (esc_hint == '+' || agg_possible) {
                esc_hint= '+'; // Some kind of escape override active, e.g. case colon.
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,esc_hint); // Random EOL.
            } else { // Unexpected EOL - scan ahead to see if it is just before natural NL.
                for (int j=i+1;j<size;j++) {
                    switch (meta[j]) {
                        case mEOL: case mLSP: case mSPC: case mTSP: case mCOM: continue;
                        case mPRE: {j= size; break;} // Use whatever is set by now.
                        case mOBR: case mCBR: case mSEC: {
                            esc_hint= '+'; // Don't escape, sane syntax just ahead.
                            j= size;  break; // Break for j loop; break case.
                        }
                        default: {
                            esc_hint= '\\'; // Escape it!
                            j= size;  break; // Break for j loop; break case.
                        }
                    } // End switch.
                } // End for j scan ahead for natural breaking points.
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,esc_hint); // Random EOL.
            }
            lineno++;
        } else if (meta[i] == mC) {
            esc_hint= '\\'; // Assume unfinished C code must be escaped.
            if (buffer[i] == ':') esc_hint= '+'; // Case and defaults are ok.
            else if (keyword_search(buffer,size,"if",i)) {
                IfAnchor_push((ifstate){B_lvl,SIC[B_lvl]});
                i++; // Skip to last char in this now known if.
            }
            else if (keyword_search(buffer,size,"else",i)) {
                ifstate if_lvl= IfAnchor_pop(); // Retrieve its matching `if` levels off stack.
                for (int j= B_lvl;j>if_lvl.B;j--) SIC[j]=0; // Clear out any singleton levels.
                B_lvl= if_lvl.B;                // Set current brace level to this else's if's level.
                SIC[B_lvl]= if_lvl.S;           // Set singleton indent level to else's if's too.
                i+= 3; // Skip to last char in this now known else.
            }
            agg_possible= 0;
        } else if (meta[i] == mSEC) {
            if (in_singleton) {
                in_singleton= 0; // Not any more.
                for (i++;i<size;i++) { // Scan ahead for connected cruft.
                    if (meta[i] == mEOL) break;
                    if (meta[i] != mTSP && meta[i] != mCOM) {
                        i--; // Leave this for the next round.
                        break;
                    }
                }
                render_line(buffer,meta,size,&S,i,SIC,B_lvl,'!');
                SIC[B_lvl]= 0;
            } else if  (agg_possible) {
                for (i++;i<size;i++) { // Scan ahead for connected cruft.
                    if (meta[i] == mEOL) break;
                    if (meta[i] != mTSP && meta[i] != mCOM) {
                        i--; // Leave this for the next round.
                        break;
                    }
                }
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,'@');
                agg_possible= 0; // This semi-colon will stay but reset.
            } else if (more_code_before_eol(meta,size,i+1,B_lvl)) {
                agg_possible= 0; // This semi-colon will stay but reset.
            } else { // A non-singleton mSEC with no more mC, grab rest of line too.
                for (;i<size;i++) { // Scan ahead for other important characters.
                    if (meta[i] == mEOL) {
                        break;
                    } else if (meta[i] == mOBR || meta[i] == mCBR) {
                        i--; // Save for its own processing.
                        break;
                    }
                }
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,';');
            }
        } else if (meta[i] == mPRE || meta[i] == mCOM) {
            /* Pass. */
        } else if (meta[i] == m1CK) { // Singleton Control Keyword (if,do,for,else,while,switch)
            in_singleton= 1; // Unbraced close pending, next mSEC closes.
            agg_possible= 0;
            if (buffer[i] == 'i' && buffer[i+1] == 'f') {
                IfAnchor_push((ifstate){B_lvl,SIC[B_lvl]});
                i++; // Skip to last char in this now known if.
            }
            if (memcmp(&buffer[i], "el", 2) == 0) { // Since marked m1CK, `el` is sufficient.
                ifstate if_lvl= IfAnchor_pop(); // Retrieve its matching `if` levels off stack.
                for (int j= B_lvl;j>if_lvl.B;j--) SIC[j]=0; // Clear out any singleton levels.
                B_lvl= if_lvl.B;                // Set current brace level to this else's if's level.
                SIC[B_lvl]= if_lvl.S;           // Set singleton indent level to else's if's too.
                i+= 3; // Skip to last char in this now known else.
            }
            // TODO: Keyword minimum is 2 chars. In theory this could be: for (i+=2;i<size;i++)
            for (i++;i<size;i++) if (meta[i] != m1CK) break; // Fast forward thru singleton.
            int tmp_P_lvl= 0;
            int singleton_setup= 0;
            char esc_hint= '1';
            for (int j=i;j<size;j++) { // Already just past m1CKs so start right at i.
                if ((meta[j] == mC && !tmp_P_lvl) || (meta[j] == m1CK)) {
                    break; // Invalid setup if mC not in parens or another singleton is found.
                } else if (meta[j] == mOPR) { // Must track parens on this look ahead.
                    tmp_P_lvl++;
                    P_lvl++; // Seems obvious, but.... Why is this needed? See CPR!
                    esc_hint= (tmp_P_lvl)?'\\':'1'; // If parens unbalanced, must escape.
                } else if (meta[j] == mCPR) {
                    tmp_P_lvl--;
                    //P_lvl--; // Why not needed? Maybe a look ahead elsewhere? Set from tmp?
                    esc_hint= (tmp_P_lvl)?'\\':'1'; // If parens unbalanced, must escape.
                } else if (meta[j] == mSEC) { // Must be already complete on this line.
                    int k; // Must scoop up any connected mSPCs, mCOM, mTSP, too.
                    singleton_setup= 0;       // Clear since it just completed.
                    for (k=j+1;k<size;k++) {  // Scan ahead for mTSP, mCOM, and ?
                        if (meta[k] == mEOL) { // 1x and line completed.
                            i= k;  // Clear this mEOL with the line render too.
                            break; // From for k.
                        } else if ( meta[k] == mSPC ||
                                    meta[k] == mCOM ||
                                    meta[k] == mTSP ) {
                            continue; // Post singleton mSEC scan ahead k.
                        } else { // Back up. Avoid next major section.
                            i= k-1;
                            break; // From k.
                        } // End else meta[k] found something odd. Leave it.
                    } // End for scan ahead for next line.
                    render_line(buffer,meta,size,&S,i,SIC,B_lvl ,'o');
                    SIC[B_lvl]= 0;            // Close out singleton counter.
                    break;                    // From forward scan after singleton.
                } else if (meta[j] == mEOL) { // If mEOL is reached...
                    singleton_setup= 1;       // ...then this must be a correct singleton start.
                    i= j;                     // Consider this position more than tentative.
                    break;                    // Render to this mEOL now!
                }
            } // End for scan ahead for clean singleton set up.
            if (singleton_setup) {
                render_line(buffer,meta,size,&S,i,SIC,B_lvl ,esc_hint);
                SIC[B_lvl]++;             // A true multi-line singleton must be tracked.
            }
        } else if (meta[i] == mOBR) {
            in_singleton= 0; // E.g. if while (q) {a++;b++}
            int e,E;
            for (e=i+1;e<size;e++) { // Scan ahead from here to as far as needed or done.
                switch (meta[e]) {
                    //case mTSP: { continue; }
                    case mEOL: {
                        E=e;
                        e=size; // Set e to size as needed for loop double break.
                        break;
                    }
                    case mC: case m1CK: case mCBR: case mOBR: case mSEC: {
                        E=e-1; // Minus 1 to leave it for next line.
                        e=size; // Set e to size as needed for loop double break.
                        break;
                    }
                }
            }
            render_line(buffer,meta,size,&S,E,SIC,B_lvl ,'{');
            i= E; // Update progress through buffer.
            B_lvl++;
            ensure_grow_arr_ok(B_lvl,&SIC,&SIC_size); // Can sublevel tracking keep up?
        } else if (meta[i] == mCBR) {
            for (i++;i<size;i++) { // Skip ahead any dead spaces, mLSP and mTSP.
                if (meta[i] == mEOL) { // A mCBR followed by real or effective nothing to EOL.
                    break; // Counter is at that EOL, send it along.
                } else if (meta[i] == mSEC) {
                    i--;
                    break;
                } else if (meta[i] != mTSP && meta[i] != mLSP) {
                    i--; // A real char (mC?) so back up, leave it for next grouping/line.
                    break;
                }
            } // End scan ahead for skipping dead whitespace.
            SIC[B_lvl]= 0; // Clear any singleton indents at this brace level.
            render_line(buffer,meta,size,&S,i,SIC,B_lvl ,'}');
            agg_possible= 1;
            B_lvl--; // Leave any pending singleton counters alone.
            while (IfAnchor_readlast() > B_lvl) // Any if tracking on old now cleared brace levels?
                IfAnchor_pop(); // Clear it so that new if/else pairs only refer to this level.
            SIC[B_lvl]=0; // This brace closes any 1x stack back a level too.
            if (B_lvl < 0) {
                printf("ERROR: Line %d:%d - Brace mismatch problem.\n",lineno,i);
                return;
            }
        } else if (meta[i] == mOPR) {
            P_lvl++;
        } else if (meta[i] == mCPR) {
            P_lvl--;
            if (P_lvl < 0) {
                printf("ERROR: Line %d:%d - Paren mismatch problem.\n",lineno,i);
                return;
            }
        } else if (meta[i] == mCOL) {
            for (;i<size;i++) if (meta[i] == mEOL) break; // Fast forward to EOL
            render_line(buffer,meta,size,&S,i,SIC,B_lvl ,':');
        } else if (meta[i] == mLSP) { // Ignore.
            for (int e=i+1;e<size;e++) { // Scan ahead to get through all mLSP.
                if (meta[e] != mLSP) {
                    i= e-1;
                    break;
                }
            }
        } else if (meta[i] == mSPC) { // This happens on stuff like `if(q){a;}  b();`
            if (i == S) { // Starting off with space is defacto ignorable mLSP.
                S++; // May cause ickiness on -l option.
            }
        } // else { }
        i++;
    } // End while going through entire buffer.
    free(SIC); // Singleton Indent Counters done.
} // End generate_snowc()

int main(int argc, char *argv[]) {
    const ConfigPack conf= parse_options(argc, argv);
    if (argc-optind < 1) {
        usage(argv[0]);
        exit(1);
    }
    FILE *file= fopen(argv[optind], "r");
    if (!file) {
        perror("Error opening file");
        return 1;
    }
    fseek(file, 0, SEEK_END);   // Put File Position Indicator at end.
    long file_size= ftell(file); // With FPI at end, effectively reports file size.
    fseek(file, 0, SEEK_SET);   // Set FPI back to the start.
    char *buffer= malloc(file_size + 1); // +1 to make room for a null terminator.
    char *meta= malloc(file_size + 1);
    if (!buffer || !meta) {
        perror("Memory allocation failed");
        fclose(file);
        free(buffer); free(meta);
        return 1;
    }
    size_t size= fread(buffer, 1, file_size, file);
    buffer[size]= '\0'; // Not strictly necessary but allows buffer to act as string.
    fclose(file);
    memset(meta, 0, size + 1); // Zero out meta map. +1 is for null byte added.
    map_major_parts(buffer, size, meta);
    map_leading_whitespace(buffer, size, meta);
    map_trailing_whitespace(buffer, size, meta);
    map_C_parens_braces(buffer, size, meta);
    map_C_labels(buffer, size, meta);
    map_C_ctl_singles(buffer, size, meta);
    while (map_C_weak_braces(buffer,size,meta));
    if (conf.map) {
        print_annotated_buffer(buffer, size, meta);
    } else {
        generate_snowc(buffer, size, meta);
    }
    free(buffer); free(meta);
    return 0;
} // End main()
