#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

// String utilities
int slen(const char *s) { return strlen(s); }
int scmp(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; }
void scpy(char *d, const char *s) { strcpy(d, s); }
int isspc(char c) { return c == ' ' || c == '\n' || c == '\t'; }
int isdg(char c) { return c >= '0' && c <= '9'; }
int atoi(const char *s) { return (int)strtol(s, NULL, 10); }

// Tokenizer
char *_CURTOK = NULL;
char *strtok(char *s, const char *delim) {
    if (s != NULL) _CURTOK = s;
    if (_CURTOK == NULL || *_CURTOK == '\0') return NULL;

    // Skip whitespace
    while (*_CURTOK != '\0' && isspc(*_CURTOK)) _CURTOK++;

    if (*_CURTOK == '\0') return NULL;

    // Find token end
    char *start = _CURTOK;
    while (*_CURTOK != '\0' && !isspc(*_CURTOK)) _CURTOK++;

    if (*_CURTOK != '\0') {
        *_CURTOK = '\0';
        _CURTOK++;
    }

    return start;
}

// Variation of strtok that can detect strings
char *sstrtok(char *s, const char *delim) {
    int inString = 0;

    if (s != NULL) _CURTOK = s;
    if (_CURTOK == NULL || *_CURTOK == '\0') return NULL;

    // Skip whitespace
    while (*_CURTOK != '\0' && isspc(*_CURTOK)) _CURTOK++;

    if (*_CURTOK == '\0') return NULL;

    // Find token end, respecting strings
    char *start = _CURTOK;
    while (*_CURTOK != '\0' && (inString || !isspc(*_CURTOK))) {
        if (*_CURTOK == '"') {
            if (inString) break;
            inString = 1;
        }
        _CURTOK++;
    }

    if (*_CURTOK != '\0') {
        *_CURTOK = '\0';
        _CURTOK++;
    }

    return start;
}

//////////////////////////////////////////////////////////////////////

// Variable stuff
#define MAX_VARS 100
char varnames[MAX_VARS][50];
int varcontent[MAX_VARS];
int varcount = 0;

void initvars() {
    varcount = 0;
}

int getvar(const char *s) {
    if (strcmp(s, "RANDOM") == 0) return rand() % 32767;
    for (int i = 0; i < varcount; i++) {
        if (strcmp(varnames[i], s) == 0) {
            return varcontent[i];
        }
    }
    return 0;
}

void setvar(const char *s, int v) {
    for (int i = 0; i < varcount; i++) {
        if (strcmp(varnames[i], s) == 0) {
            varcontent[i] = v;
            return;
        }
    }
    if (varcount < MAX_VARS) {
        strcpy(varnames[varcount], s);
        varcontent[varcount] = v;
        varcount++;
    }
}

// Init program memory
#define MAX_LINES 2000
#define MAX_LINE_LEN 256
char prgm[MAX_LINES][MAX_LINE_LEN];

void initprgm() {
    for (int i = 0; i < MAX_LINES; i++) {
        prgm[i][0] = '\0';
    }
}

// GOSUB stack
#define MAX_STACK 100
int _linestack[MAX_STACK];
int _linestackpos = 0;

void lnpush(int v) {
    if (_linestackpos < MAX_STACK) {
        _linestack[_linestackpos++] = v;
    }
}

int lnpop() {
    if (_linestackpos > 0) {
        return _linestack[--_linestackpos];
    }
    return -1;
}

// Error handling
void berror(int linenum, const char *e) {
    if (linenum == -1) {
        printf("ERROR: %s\n", e);
    } else {
        printf("ERROR AT %d: %s\n", linenum, e);
    }
    exit(1);
}

// Commands
typedef enum {
    PRINT,
    INPUT,
    VAR,
    IF,
    GOTO,
    GOSUB,
    RET,
    REM
} BasicCommands;

const char *bcmds[] = {"PRINT", "INPUT", "VAR", "IF", "GOTO", "GOSUB", "RET", "END"};

int getbcmd(const char *s) {
    for (int i = 0; i < sizeof(bcmds)/sizeof(bcmds[0]); i++) {
        if (strcmp(s, bcmds[i]) == 0) {
            return i;
        }
    }
    return -1;
}

int cprint(int ln, char *s) {
    char *token = sstrtok(s, " ");
    char output[256] = "";
    while (token != NULL) {
        if (token[0] == '"') {
            strcat(output, token + 1);
        } else {
            char num[50];
            sprintf(num, "%d", emath(token));
            strcat(output, num);
        }
        token = sstrtok(NULL, " ");
    }
    printf("%s\n", output);
    return ln;
}

int cinput(int ln, char *s) {
    char vn[50];
    if (sscanf(s, "%s", vn) != 1) berror(ln, "INVALID ARGS");

    int value;
    printf("%s? ", vn);
    scanf("%d", &value);
    setvar(vn, value);

    return ln;
}

int cvar(int ln, char *s) {
    char vn[50];
    char tok[50];
    if (sscanf(s, "%s %s", vn, tok) != 2) berror(ln, "INVALID ARGS");
    setvar(vn, emath(tok));
    return ln;
}

int runcmd(int ln, char *s) {
    char buf[256];
    strcpy(buf, s);
    char *token = sstrtok(buf, " ");
    if (token == NULL) return ln;
    int cmd = getbcmd(token);
    if (cmd == -1) berror(ln, "INVALID COMMAND");

    switch (cmd) {
        case PRINT: return cprint(ln, _CURTOK);
        case INPUT: return cinput(ln, _CURTOK);
        case VAR: return cvar(ln, _CURTOK);
        case IF: return cif(ln, _CURTOK);
        case GOTO: return cgoto(ln, _CURTOK);
        case GOSUB: return cgosub(ln, _CURTOK);
        case RET: return cret(ln, _CURTOK);
        case REM: return crem(ln, _CURTOK);
        default: berror(ln, "UNKNOWN COMMAND");
    }
    return ln;
}

int cif(int ln, char *s) {
    char tok[50];
    if (sscanf(s, "%s", tok) != 1 || _CURTOK == NULL) berror(ln, "INVALID IF STATEMENT");
    return emath(tok) ? runcmd(ln, _CURTOK) : ln;
}

int cgoto(int ln, char *s) {
    char tok[50];
    if (sscanf(s, "%s", tok) != 1) berror(ln, "INVALID GOTO");
    return emath(tok) - 1;
}

int cgosub(int ln, char *s) {
    char tok[50];
    if (sscanf(s, "%s", tok) != 1) berror(ln, "INVALID GOSUB");
    int c = emath(tok);
    lnpush(ln);
    return c - 1;
}

int cret(int ln, char *s) {
    return lnpop();
}

int crem(int ln, char *s) {
    exit(0);
}

// Math operators
const char *mathops = "&|><~=%*/+-";
int iand(int a, int b) { return a & b; }
int ior(int a, int b) { return a | b; }
int igt(int a, int b) { return a > b; }
int ilt(int a, int b) { return a < b; }
int ineq(int a, int b) { return a != b; }
int ieq(int a, int b) { return a == b; }
int imod(int a, int b) { return a % b; }
int imul(int a, int b) { return a * b; }
int idiv(int a, int b) { return a / b; }
int iadd(int a, int b) { return a + b; }
int isub(int a, int b) { return a - b; }

int (*mathfuncs[])(int, int) = {iand, ior, igt, ilt, ineq, ieq, imod, imul, idiv, iadd, isub};

int emath(char *s) {
    if (s == NULL || *s == '\0') return 0;

    for (int i = 0; i < strlen(mathops); i++) {
        for (int j = 0; j < strlen(s); j++) {
            if (s[j] == mathops[i]) {
                char left[50], right[50];
                strncpy(left, s, j);
                left[j] = '\0';
                strcpy(right, s + j + 1);
                return mathfuncs[i](emath(left), emath(right));
            }
        }
    }

    return isdg(s[0]) ? atoi(s) : getvar(s);
}

//////////////////////////////////////////////////////////////////////

void run_basic() {
    for (int i = 0; i < MAX_LINES; ++i) {
        if (prgm[i][0] != '\0') {
            i = runcmd(i, prgm[i]);
        }
    }
}

void read_program(FILE *stream) {
    char line[256];
    int ln = 0;

    while (fgets(line, sizeof(line), stream)) {
        ln++;
        char *ptr = line;
        while (isspc(*ptr)) ptr++;

        if (*ptr == '\0' || *ptr == '#') continue;

        if (!isdg(*ptr)) {
            berror(ln, "PARSER: MISSING NUMBER");
        }

        char *spacePos = strchr(ptr, ' ');
        if (spacePos == NULL) continue;

        *spacePos = '\0';
        int pln = atoi(ptr);
        strcpy(prgm[pln], spacePos + 1);
    }
}

// Used for testing
void emath_test() {
    printf("Math evaluation mode.\n");

    char line[256];
    while (fgets(line, sizeof(line), stdin)) {
        printf("= %d\n", emath(line));
    }
}

// Interactive shell mode
void interactive_shell() {
    initprgm();
    initvars();

    printf("Mbasic Interactive Shell\n");
    printf("Type commands with line numbers to store in program memory\n");
    printf("Type commands without line numbers for immediate execution\n");
    printf("Special commands: RUN, LIST, CLEAR, QUIT\n");

    char line[256];
    while (1) {
        printf("> ");
        if (fgets(line, sizeof(line), stdin) == NULL) break;

        char *ptr = line;
        while (isspc(*ptr)) ptr++;

        if (strcmp(ptr, "RUN\n") == 0) {
            run_basic();
        }
        else if (strcmp(ptr, "LIST\n") == 0) {
            for (int i = 0; i < MAX_LINES; ++i) {
                if (prgm[i][0] != '\0') {
                    printf("%d %s\n", i, prgm[i]);
                }
            }
        }
        else if (strcmp(ptr, "CLEAR\n") == 0) {
            initprgm();
            printf("Program memory cleared\n");
        }
        else if (strcmp(ptr, "QUIT\n") == 0) {
            printf("Goodbye!\n");
            break;
        }
        else {
            // Parse line number if present
            if (isdg(*ptr)) {
                // Store in program memory
                char *spacePos = ptr;
                while (*spacePos != '\0' && !isspc(*spacePos)) spacePos++;

                if (*spacePos != '\0') {
                    *spacePos = '\0';
                    int line_num = atoi(ptr);
                    char *content = spacePos + 1;
                    while (isspc(*content)) content++;

                    if (*content != '\0') {
                        strcpy(prgm[line_num], content);
                        printf("Line %d stored\n", line_num);
                    }
                }
            } else if (*ptr != '\0') {
                // Immediate mode execution
                *strchr(ptr, '\n') = '\0';
                int result = runcmd(-1, ptr);
                if (result != -1) {
                    printf("Result: %d\n", result);
                }
            }
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc == 2 && strcmp(argv[1], "-emath") == 0) {
        emath_test();
        return 0;
    }

    // If no arguments, run interactive shell
    if (argc == 1) {
        interactive_shell();
        return 0;
    }

    // Otherwise, run in file mode (original behavior)
    initprgm();
    initvars();

    FILE *file = fopen(argv[1], "r");
    if (file == NULL) {
        berror(-1, "FILE UNREADABLE");
    }

    read_program(file);
    fclose(file);
    run_basic();

    return 0;
}
