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

#define PI 3.14159265358979323846
#define E  2.71828182845904523536

typedef enum { 
    OP_PUSH_CONST, OP_PUSH_VAR, OP_ADD, OP_SUB, OP_MUL, OP_DIV, 
    OP_POW, OP_SQRT, OP_SIN, OP_COS, OP_TAN, OP_LOG, OP_NEG 
} OpCode;

typedef struct { OpCode code; double value; } Instruction;
typedef struct { Instruction* code; int count; int capacity; } BytecodeExpr;

void comp_expression(const char** str, BytecodeExpr* expr);
void skip_ws(const char** str) { while (**str && isspace(**str)) (*str)++; }

void emit(BytecodeExpr* expr, OpCode code, double val) {
    if (expr->count >= expr->capacity) {
        expr->capacity = expr->capacity ? expr->capacity * 2 : 16;
        expr->code = realloc(expr->code, sizeof(Instruction) * expr->capacity);
    }
    expr->code[expr->count].code = code;
    expr->code[expr->count].value = val;
    expr->count++;
}

int match(const char** str, const char* keyword) {
    size_t len = strlen(keyword);
    if (strncmp(*str, keyword, len) == 0 && !isalnum((*str)[len])) {
        *str += len; return 1;
    }
    return 0;
}

void comp_factor(const char** str, BytecodeExpr* expr) {
    skip_ws(str);
    if (match(str, "pi")) { emit(expr, OP_PUSH_CONST, PI); }
    else if (match(str, "e")) { emit(expr, OP_PUSH_CONST, E); }
    else if (match(str, "sqrt")) { comp_factor(str, expr); emit(expr, OP_SQRT, 0); }
    else if (match(str, "sin"))  { comp_factor(str, expr); emit(expr, OP_SIN, 0); }
    else if (match(str, "cos"))  { comp_factor(str, expr); emit(expr, OP_COS, 0); }
    else if (match(str, "tan"))  { comp_factor(str, expr); emit(expr, OP_TAN, 0); }
    else if (match(str, "log") || match(str, "ln")) { comp_factor(str, expr); emit(expr, OP_LOG, 0); }
    else if (**str == '(') { (*str)++; comp_expression(str, expr); if (**str == ')') (*str)++; }
    else if (**str == 'x' || **str == 'X') { (*str)++; emit(expr, OP_PUSH_VAR, 0); }
    else if (isdigit(**str) || **str == '.') { 
        char* end; emit(expr, OP_PUSH_CONST, strtod(*str, &end)); *str = end; 
    }
}

void comp_power(const char** str, BytecodeExpr* expr) {
    comp_factor(str, expr);
    skip_ws(str);
    
    // Support both '^' and '**'
    if (**str == '^' || (**str == '*' && *(*str + 1) == '*')) {
        if (**str == '^') {
            (*str)++;
        } else {
            *str += 2; // Skip both '*'
        }
        comp_power(str, expr); 
        emit(expr, OP_POW, 0);
    }
}

void comp_term(const char** str, BytecodeExpr* expr) {
    comp_power(str, expr);
    skip_ws(str);
    while (**str == '*' || **str == '/' || **str == '(' || isalpha(**str) || isdigit(**str)) {
        // Prevent matching '**' as multiplication
        if (**str == '*' && *(*str + 1) == '*') break;
        
        char op = '*';
        if (**str == '*' || **str == '/') op = *(*str)++;
        comp_power(str, expr);
        emit(expr, (op == '*') ? OP_MUL : OP_DIV, 0);
        skip_ws(str);
    }
}

void comp_expression(const char** str, BytecodeExpr* expr) {
    skip_ws(str);
    int neg = 0; if (**str == '-') { neg = 1; (*str)++; }
    comp_term(str, expr);
    if (neg) emit(expr, OP_NEG, 0);
    skip_ws(str);
    while (**str == '+' || **str == '-') {
        char op = *(*str)++;
        comp_term(str, expr);
        emit(expr, (op == '+') ? OP_ADD : OP_SUB, 0);
        skip_ws(str);
    }
}

void* compile(const char* equation) {
    BytecodeExpr* expr = calloc(1, sizeof(BytecodeExpr));
    const char* p = equation;
    comp_expression(&p, expr);
    return (void*)expr;
}

double execute_bytecode(void* ptr, double x_val) {
    BytecodeExpr* expr = (BytecodeExpr*)ptr;
    double stack[1024]; int top = -1;
    for (int i = 0; i < expr->count; i++) {
        Instruction inst = expr->code[i];
        switch (inst.code) {
            case OP_PUSH_CONST: stack[++top] = inst.value; break;
            case OP_PUSH_VAR:   stack[++top] = x_val; break;
            case OP_ADD:  if(top > 0) { stack[top-1] += stack[top]; top--; } break;
            case OP_SUB:  if(top > 0) { stack[top-1] -= stack[top]; top--; } break;
            case OP_MUL:  if(top > 0) { stack[top-1] *= stack[top]; top--; } break;
            case OP_DIV:  if(top > 0) { stack[top-1] /= stack[top]; top--; } break;
            case OP_POW:  if(top > 0) { stack[top-1] = pow(stack[top-1], stack[top]); top--; } break;
            case OP_SQRT: if(top >= 0) stack[top] = sqrt(stack[top]); break;
            case OP_SIN:  if(top >= 0) stack[top] = sin(stack[top]); break;
            case OP_COS:  if(top >= 0) stack[top] = cos(stack[top]); break;
            case OP_TAN:  if(top >= 0) stack[top] = tan(stack[top]); break;
            case OP_LOG:  if(top >= 0) stack[top] = log(stack[top]); break;
            case OP_NEG:  if(top >= 0) stack[top] = -stack[top]; break;
        }
    }
    return (top >= 0) ? stack[0] : 0.0;
}

double execute_bulk(void* ptr, int iterations) {
    BytecodeExpr* expr = (BytecodeExpr*)ptr;
    double last_res = 0;
    for (int i = 0; i < iterations; i++) {
        double stack[256]; 
        int top = -1;
        double x_val = (double)i;

        for (int j = 0; j < expr->count; j++) {
            Instruction inst = expr->code[j];
            switch (inst.code) {
                case OP_PUSH_CONST: stack[++top] = inst.value; break;
                case OP_PUSH_VAR:   stack[++top] = x_val; break;
                case OP_ADD:  stack[top-1] += stack[top]; top--; break;
                case OP_SUB:  stack[top-1] -= stack[top]; top--; break;
                case OP_MUL:  stack[top-1] *= stack[top]; top--; break;
                case OP_DIV:  stack[top-1] /= stack[top]; top--; break;
                case OP_POW:  stack[top-1] = pow(stack[top-1], stack[top]); top--; break;
                case OP_SQRT: stack[top] = sqrt(stack[top]); break;
                case OP_SIN:  stack[top] = sin(stack[top]); break;
                case OP_COS:  stack[top] = cos(stack[top]); break;
                case OP_TAN:  stack[top] = tan(stack[top]); break;
                case OP_LOG:  stack[top] = log(stack[top]); break;
                case OP_NEG:  stack[top] = -stack[top]; break;
            }
        }
        last_res = (top >= 0) ? stack[0] : 0.0;
    }
    return last_res;
}