#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <math.h>
#include <ctype.h>
#include <string.h>

const char* parse_ptr;
double current_x;
PyObject* user_variables = NULL; // Stores {"a": 2.0, "b": 10.5, etc.}

typedef enum { OP_NUM, OP_X, OP_VAR, OP_ADD, OP_MUL, OP_POW } OpType;

typedef struct {
    OpType type;
    double val;          // Used if OP_NUM
    const char* var_name; // Used if OP_VAR
} Token;

void skip_whitespace() {
    while (*parse_ptr == ' ' || *parse_ptr == '\t' || *parse_ptr == '\n' || *parse_ptr == '\r') {
        parse_ptr++;
    }
}

double parse_expression();

// High-efficiency factorial implementation
double c_factorial(double n) {
    if (n < 0) return 0.0;
    double val = round(n);
    double result = 1.0;
    for (int i = 2; i <= (int)val; i++) {
        result *= i;
    }
    return result;
}

// Greatest Common Divisor helper
long long c_gcd(long long a, long long b) {
    while (b != 0) {
        long long temp = b;
        b = a % b;
        a = temp;
    }
    return a < 0 ? -a : a;
}

// Least Common Multiple helper
long long c_lcm(long long a, long long b) {
    if (a == 0 || b == 0) return 0;
    return (a * b) / c_gcd(a, b);
}

double parse_expression(); // Forward declaration for internal nesting

double parse_primary() {
    skip_whitespace();
    if (*parse_ptr == '(') {
        parse_ptr++; 
        double val = parse_expression();
        skip_whitespace();
        if (*parse_ptr == ')') parse_ptr++; 
        return val;
    }
    if (*parse_ptr == '-') {
        parse_ptr++;
        return -parse_primary();
    }
    if (*parse_ptr == '+') {
        parse_ptr++;
        return parse_primary();
    }
    if (isdigit(*parse_ptr) || *parse_ptr == '.') {
        char* next_ptr;
        double val = strtod(parse_ptr, &next_ptr);
        parse_ptr = next_ptr;
        return val;
    }
    if (isalpha(*parse_ptr)) {
        char name[32];
        int len = 0;
        while (isalnum(*parse_ptr) && len < 31) { 
            name[len++] = *parse_ptr++;
        }
        name[len] = '\0';

        if (strcmp(name, "x") == 0 || strcmp(name, "X") == 0) return current_x;
        if (strcmp(name, "pi") == 0) return 3.14159265358979323846;
        if (strcmp(name, "e") == 0)  return 2.71828182845904523536;

        if (user_variables) {
            PyObject* val_obj = PyDict_GetItemString(user_variables, name);
            if (val_obj && PyFloat_Check(val_obj)) {
                return PyFloat_AsDouble(val_obj);
            }
        }

        skip_whitespace();
        if (*parse_ptr == '(') {
            parse_ptr++; // Move past '('
            
            // Parse First Argument
            // Parse First Argument
            double arg1 = parse_expression();
            double arg2 = 0.0;
            
            skip_whitespace();
            if (*parse_ptr == ',') {
                parse_ptr++; // Move past comma for multi-argument functions
                arg2 = parse_expression();
                skip_whitespace();
            }
            
            if (*parse_ptr == ')') parse_ptr++;

            // Trigonometric
            if (strcmp(name, "sin") == 0)   return sin(arg1);
            if (strcmp(name, "cos") == 0)   return cos(arg1);
            if (strcmp(name, "tan") == 0)   return tan(arg1);
            if (strcmp(name, "asin") == 0)  return asin(arg1);
            if (strcmp(name, "acos") == 0)  return acos(arg1);
            if (strcmp(name, "atan") == 0)  return atan(arg1);
            if (strcmp(name, "atan2") == 0) return atan2(arg1, arg2);

            // Hyperbolic
            if (strcmp(name, "sinh") == 0)  return sinh(arg1);
            if (strcmp(name, "cosh") == 0)  return cosh(arg1);
            if (strcmp(name, "tanh") == 0)  return tanh(arg1);
            if (strcmp(name, "asinh") == 0) return asinh(arg1);
            if (strcmp(name, "acosh") == 0) return acosh(arg1);
            if (strcmp(name, "atanh") == 0) return atanh(arg1);

            // Exponential & Logarithmic
            if (strcmp(name, "exp") == 0)   return exp(arg1);
            if (strcmp(name, "log") == 0)   return log(arg1); // defaults to natural log
            if (strcmp(name, "ln") == 0)    return log(arg1);
            if (strcmp(name, "log10") == 0) return log10(arg1);
            if (strcmp(name, "log2") == 0)  return log2(arg1);

            // Roots & Powers
            if (strcmp(name, "sqrt") == 0)  return sqrt(arg1);
            if (strcmp(name, "cbrt") == 0)  return cbrt(arg1);
            if (strcmp(name, "pow") == 0)   return pow(arg1, arg2);

            // Rounding
            if (strcmp(name, "floor") == 0) return floor(arg1);
            if (strcmp(name, "ceil") == 0)  return ceil(arg1);
            if (strcmp(name, "round") == 0) return round(arg1);
            if (strcmp(name, "trunc") == 0) return trunc(arg1);

            // Absolute & Sign
            if (strcmp(name, "abs") == 0)   return fabs(arg1);
            if (strcmp(name, "fabs") == 0)  return fabs(arg1);
            if (strcmp(name, "sign") == 0)  return (arg1 > 0) - (arg1 < 0);

            // Min/Max
            if (strcmp(name, "min") == 0)   return (arg1 < arg2) ? arg1 : arg2;
            if (strcmp(name, "max") == 0)   return (arg1 > arg2) ? arg1 : arg2;

            // Special Functions
            if (strcmp(name, "factorial") == 0) return c_factorial(arg1);
            if (strcmp(name, "gcd") == 0)   return (double)c_gcd((long long)arg1, (long long)arg2);
            if (strcmp(name, "lcm") == 0)   return (double)c_lcm((long long)arg1, (long long)arg2);
            if (strcmp(name, "gamma") == 0) return tgamma(arg1);
            if (strcmp(name, "lgamma") == 0) return lgamma(arg1);
            if (strcmp(name, "erf") == 0)   return erf(arg1);
            if (strcmp(name, "erfc") == 0)  return erfc(arg1);

            // Other
            if (strcmp(name, "degrees") == 0) return arg1 * (180.0 / 3.14159265358979323846);
            if (strcmp(name, "radians") == 0) return arg1 * (3.14159265358979323846 / 180.0);
            if (strcmp(name, "hypot") == 0)   return hypot(arg1, arg2);

            // Bitwise (Functional word alternative variants)
            if (strcmp(name, "and") == 0) return (double)((long long)arg1 & (long long)arg2);
            if (strcmp(name, "or") == 0)  return (double)((long long)arg1 | (long long)arg2);
            if (strcmp(name, "xor") == 0) return (double)((long long)arg1 ^ (long long)arg2);
            if (strcmp(name, "not") == 0) return (double)(~(long long)arg1);
        }
    }
    return 0.0;
}

double parse_pow() {
    double val = parse_primary();
    skip_whitespace();
    while (*parse_ptr == '^' || (*parse_ptr == '*' && *(parse_ptr + 1) == '*')) {
        if (*parse_ptr == '^') {
            parse_ptr++;
        } else {
            parse_ptr += 2;
        }
        double exponent = parse_primary();
        val = pow(val, exponent);
        skip_whitespace();
    }
    return val;
}

double parse_term() {
    double val = parse_pow();
    skip_whitespace();
    while (*parse_ptr == '*' || *parse_ptr == '/') {
        char op = *parse_ptr++;
        double next = parse_pow();
        if (op == '*') val *= next;
        else if (op == '/') val /= next;
        skip_whitespace();
    }
    return val;
}

// Standard math tier: Handles +, -
double parse_math_ops() {
    double val = parse_term();
    skip_whitespace();
    while (*parse_ptr == '+' || *parse_ptr == '-') {
        char op = *parse_ptr++;
        double next = parse_term();
        if (op == '+') val += next;
        else if (op == '-') val -= next;
        skip_whitespace();
    }
    return val;
}

// Bitwise shift tier: Handles <<, >>
double parse_shifts() {
    double val = parse_math_ops();
    skip_whitespace();
    while ((*parse_ptr == '<' && *(parse_ptr + 1) == '<') || (*parse_ptr == '>' && *(parse_ptr + 1) == '>')) {
        char dir = *parse_ptr;
        parse_ptr += 2; // skip both angle brackets
        double next = parse_math_ops();
        if (dir == '<') {
            val = (double)((long long)val << (long long)next);
        } else {
            val = (double)((long long)val >> (long long)next);
        }
        skip_whitespace();
    }
    return val;
}

// Master Unified Expression Entry: Resolves top layer operations
double parse_expression() {
    return parse_shifts();
}

double run_evaluator(const char* expr, double x) {
    parse_ptr = expr;
    current_x = x;
    return parse_expression();
}

// Master Unified Entry Point
static PyObject* acentic_evaluate(PyObject* self, PyObject* args, PyObject* keywds) {
    PyObject* expr_obj = NULL; 
    PyObject* x_array_obj = NULL; 
    int iterations = 100; 
    
    static char* kwlist[] = {"expr", "x_array", "iterations", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|Oi", kwlist, &expr_obj, &x_array_obj, &iterations)) {
        return NULL;
    }

    PyObject* expr_list = NULL;
    int is_single_string = 0;

    if (PyUnicode_Check(expr_obj)) {
        expr_list = PyList_New(1);
        Py_INCREF(expr_obj);
        PyList_SET_ITEM(expr_list, 0, expr_obj);
        is_single_string = 1;
    } else if (PyList_Check(expr_obj)) {
        expr_list = expr_obj;
        Py_INCREF(expr_list);
    } else {
        PyErr_SetString(PyExc_TypeError, "The 'expr' parameter must be a string or a list of strings.");
        return NULL;
    }

    int expr_count = PyList_Size(expr_list);
    PyObject* master_results_list = PyList_New(expr_count);

    for (int e = 0; e < expr_count; e++) {
        PyObject* str_item = PyList_GetItem(expr_list, e);
        if (!PyUnicode_Check(str_item)) {
            Py_DECREF(expr_list);
            Py_DECREF(master_results_list);
            PyErr_SetString(PyExc_TypeError, "All elements in the expression list must be strings.");
            return NULL;
        }
        
        const char* eq_str = PyUnicode_AsUTF8(str_item);
        
        // --- FIX: Smart check for standalone 'x' or 'X' variable ---
        int has_x = 0;
        const char* p = eq_str;
        while (*p) {
            if ((*p == 'x' || *p == 'X')) {
                // Ensure it's not part of a larger word like max, exp, or xor
                int left_bound = (p == eq_str || !isalnum(*(p - 1)));
                int right_bound = (*(p + 1) == '\0' || !isalnum(*(p + 1)));
                if (left_bound && right_bound) {
                    has_x = 1;
                    break;
                }
            }
            p++;
        }
        // ------------------------------------------------------------

        const char* eq_sign = strchr(eq_str, '=');
        PyObject* current_result = NULL;

        // -------------------------------------------------------------
        // MODE 1: PURE MATHS MODE (No variable 'x' present)
        // -------------------------------------------------------------
        if (!has_x) {
            if (!eq_sign) {
                current_result = PyFloat_FromDouble(run_evaluator(eq_str, 0.0));
            } else {
                char left[256] = {0};
                char right[256] = {0};
                size_t left_len = eq_sign - eq_str;
                if (left_len >= 256) left_len = 255;
                strncpy(left, eq_str, left_len);
                strncpy(right, eq_sign + 1, 255);

                double l_val = run_evaluator(left, 0.0);
                double r_val = run_evaluator(right, 0.0);

                if (fabs(l_val - r_val) < 1e-6) {
                    current_result = Py_True;
                    Py_INCREF(Py_True);
                } else {
                    current_result = Py_False;
                    Py_INCREF(Py_False);
                }
            }
        }
        // -------------------------------------------------------------
        // MODE 2: ALGEBRA MODE (Variable 'x' detected)
        // -------------------------------------------------------------
        else {
            char left[256] = {0};
            char right[256] = {0};
            if (eq_sign) {
                size_t left_len = eq_sign - eq_str;
                if (left_len >= 256) left_len = 255;
                strncpy(left, eq_str, left_len);
                strncpy(right, eq_sign + 1, 255);
            } else {
                strncpy(left, eq_str, 255);
                strcpy(right, "0");
            }

            double roots[10];
            int root_count = 0;
            double h = 1e-6;

            double default_guesses[] = {-100.0, -10.0, -2.0, 0.0, 2.0, 10.0, 100.0};
            double* test_guesses = default_guesses;
            int num_guesses = sizeof(default_guesses) / sizeof(default_guesses[0]);

            int dynamic_alloc = 0;
            if (x_array_obj && PyList_Check(x_array_obj)) {
                int list_sz = PyList_Size(x_array_obj);
                if (list_sz > 0) {
                    test_guesses = (double*)malloc(list_sz * sizeof(double));
                    num_guesses = 0;
                    dynamic_alloc = 1;
                    for (int i = 0; i < list_sz; i++) {
                        PyObject* item = PyList_GetItem(x_array_obj, i);
                        if (PyFloat_Check(item)) {
                            test_guesses[num_guesses++] = PyFloat_AsDouble(item);
                        } else if (PyLong_Check(item)) {
                            test_guesses[num_guesses++] = (double)PyLong_AsLong(item);
                        }
                    }
                }
            }

            for (int g = 0; g < num_guesses; g++) {
                double x = test_guesses[g];
                int converged = 0;

                for (int iter = 0; iter < iterations; iter++) {
                    double fx = run_evaluator(left, x) - run_evaluator(right, x);
                    if (fabs(fx) < 1e-6) {
                        converged = 1;
                        break;
                    }
                    
                    double fx_h = run_evaluator(left, x + h) - run_evaluator(right, x + h);
                    double df = (fx_h - fx) / h;
                    if (fabs(df) < 1e-12) break;
                    x = x - fx / df;
                }

                if (converged) {
                    x = round(x * 1000000.0) / 1000000.0;
                    if (x == -0.0) x = 0.0;

                    int is_unique = 1;
                    for (int r = 0; r < root_count; r++) {
                        if (fabs(roots[r] - x) < 1e-3) {
                            is_unique = 0;
                            break;
                        }
                    }
                    if (is_unique && root_count < 10) {
                        roots[root_count++] = x;
                    }
                }
            }

            if (dynamic_alloc) {
                free(test_guesses);
            }

            if (root_count == 0) {
                current_result = Py_None;
                Py_INCREF(Py_None);
            } else if (root_count == 1) {
                current_result = PyFloat_FromDouble(roots[0]);
            } else {
                current_result = PyList_New(root_count);
                for (int i = 0; i < root_count; i++) {
                    PyList_SET_ITEM(current_result, i, PyFloat_FromDouble(roots[i]));
                }
            }
        }

        // Add the current result into our master collection list
        PyList_SET_ITEM(master_results_list, e, current_result); 
    } 

    Py_DECREF(expr_list);

    if (is_single_string) {
        PyObject* final_res = PyList_GetItem(master_results_list, 0);
        Py_INCREF(final_res);
        Py_DECREF(master_results_list);
        return final_res;
    }

    return master_results_list;
}

static PyObject* acentic_define_vars(PyObject* self, PyObject* args) {
    const char* var_str;
    if (!PyArg_ParseTuple(args, "s", &var_str)) return NULL;

    char working_str[512];
    strncpy(working_str, var_str, sizeof(working_str) - 1);
    working_str[sizeof(working_str) - 1] = '\0';

    char* token = strtok(working_str, ",");
    while (token != NULL) {
        const char* eq_sign = strchr(token, '=');
        if (eq_sign) {  
            const char* p = token;
            while (*p == ' ' || *p == '\t') p++;
            
            if (isalpha(*p)) {
                char var_name[32] = {0};
                int len = 0;
                while (isalnum(*p) && len < 31) {
                    var_name[len++] = *p++;
                }
                
                while (*p == ' ' || *p == '\t') p++;
                
                if (p == eq_sign) {
                    double assigned_val = run_evaluator(eq_sign + 1, 0.0);
                    PyObject* py_val = PyFloat_FromDouble(assigned_val);
                    if (user_variables) {
                        PyDict_SetItemString(user_variables, var_name, py_val);
                    }
                    Py_DECREF(py_val);
                }
            }
        }
        token = strtok(NULL, ",");
    }

    Py_RETURN_NONE; 
}

static PyObject* acentic_evaluate_vector(PyObject* self, PyObject* args, PyObject* keywds) {
    const char* expr_str = NULL;
    PyObject* dict_obj = NULL;
    static char* kwlist[] = {"expr", "vars", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "sO!", kwlist, &expr_str, &PyDict_Type, &dict_obj)) {
        return NULL;
    }

    // Extract the NumPy arrays by their variable names
    PyObject *a_arr = PyDict_GetItemString(dict_obj, "a");
    PyObject *b_arr = PyDict_GetItemString(dict_obj, "b");
    PyObject *c_arr = PyDict_GetItemString(dict_obj, "c");
    PyObject *d_arr = PyDict_GetItemString(dict_obj, "d");

    if (!a_arr || !b_arr || !c_arr || !d_arr) {
        PyErr_SetString(PyExc_KeyError, "Dictionary must contain arrays for 'a', 'b', 'c', and 'd'.");
        return NULL;
    }

    Py_buffer view_a, view_b, view_c, view_d;
    if (PyObject_GetBuffer(a_arr, &view_a, PyBUF_SIMPLE) < 0) return NULL;
    if (PyObject_GetBuffer(b_arr, &view_b, PyBUF_SIMPLE) < 0) { PyBuffer_Release(&view_a); return NULL; }
    if (PyObject_GetBuffer(c_arr, &view_c, PyBUF_SIMPLE) < 0) { PyBuffer_Release(&view_a); PyBuffer_Release(&view_b); return NULL; }
    if (PyObject_GetBuffer(d_arr, &view_d, PyBUF_SIMPLE) < 0) { 
        PyBuffer_Release(&view_a); PyBuffer_Release(&view_b); PyBuffer_Release(&view_c); 
        return NULL; 
    }

    int count = view_a.len / sizeof(double);
    double* data_a = (double*)view_a.buf;
    double* data_b = (double*)view_b.buf;
    double* data_c = (double*)view_c.buf;
    double* data_d = (double*)view_d.buf;

    PyObject* result_list = PyList_New(count);
    
    // Create a local dictionary container for the element-by-element evaluator
    PyObject* local_vars = PyDict_New();

    // SUPERSONIC VECTOR LOOP: Binds data values to symbols and runs the parser
    for (int i = 0; i < count; i++) {
        // Set individual index values into the math engine state
        PyDict_SetItemString(local_vars, "a", PyFloat_FromDouble(data_a[i]));
        PyDict_SetItemString(local_vars, "b", PyFloat_FromDouble(data_b[i]));
        PyDict_SetItemString(local_vars, "c", PyFloat_FromDouble(data_c[i]));
        PyDict_SetItemString(local_vars, "d", PyFloat_FromDouble(data_d[i]));
        
        // Swap global context pointers temporarily for parsing execution
        PyObject* old_vars = user_variables;
        user_variables = local_vars;

        // Run string engine directly on current index frame
        double res = run_evaluator(expr_str, 0.0);
        
        // Restore context state
        user_variables = old_vars;

        PyList_SET_ITEM(result_list, i, PyFloat_FromDouble(res));
    }

    Py_DECREF(local_vars);
    PyBuffer_Release(&view_a);
    PyBuffer_Release(&view_b);
    PyBuffer_Release(&view_c);
    PyBuffer_Release(&view_d);

    return result_list;
}

// Update your method registration table
static PyMethodDef AcenticMethods[] = {
    {"evaluate", (PyCFunction)(void(*)(void))acentic_evaluate, METH_VARARGS | METH_KEYWORDS, "Check github for docs, no docstrings in C!"},
    {"evaluate_vector", (PyCFunction)(void(*)(void))acentic_evaluate_vector, METH_VARARGS | METH_KEYWORDS, "Check github(though i dont even have a github!) for docs, no docstrings in C!"},
    {"define_vars", acentic_define_vars, METH_VARARGS, "Same!"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef acenticmodule = {
    PyModuleDef_HEAD_INIT, "acentic", "Supersonic Unified Math Engine", -1, AcenticMethods
};

PyMODINIT_FUNC PyInit_acentic(void) {
    user_variables = PyDict_New(); 
    return PyModule_Create(&acenticmodule);
}