/* ============================================================
 * calc.c — motor da calculadora
 * ------------------------------------------------------------
 * Todo o comportamento vive aqui: a máquina de estados, as
 * operações, o tratamento de erro e até a formatação do número
 * no padrão brasileiro (milhar com ponto, decimal com vírgula).
 * O JavaScript da página é só cola: entrega a tecla apertada e
 * lê as duas linhas do visor.
 *
 * Compilado para WebAssembly em modo freestanding (sem libc),
 * então tudo que normalmente viria de <math.h> ou <stdio.h> é
 * implementado à mão — raiz quadrada por Newton-Raphson e a
 * conversão double→texto logo abaixo.
 *
 * Semântica: execução imediata, como a calculadora padrão do
 * Windows (5 + 3 × 2 = 16, não 11). O "%" também segue o
 * Windows: em + e −, vira porcentagem do acumulado; em × e ÷,
 * vira o operando dividido por 100.
 * ============================================================ */

#define WASM_EXPORT __attribute__((visibility("default")))

/* ---------- teclas (espelhadas em app.js) ---------- */
enum {
    K_0 = 0, K_1, K_2, K_3, K_4, K_5, K_6, K_7, K_8, K_9,
    K_VIRGULA = 10,
    K_IGUAL   = 11,
    K_SOMA    = 12,
    K_SUB     = 13,
    K_MUL     = 14,
    K_DIV     = 15,
    K_PCT     = 16,   /* %          */
    K_CE      = 17,   /* limpa entrada          */
    K_C       = 18,   /* limpa tudo             */
    K_BS      = 19,   /* backspace              */
    K_INV     = 20,   /* 1/x        */
    K_QUAD    = 21,   /* x²         */
    K_RAIZ    = 22,   /* ²√x        */
    K_NEG     = 23,   /* +/−        */
};

/* ---------- estado ---------- */
#define ENTRADA_MAX 16      /* 15 dígitos + vírgula */

static char   entrada[ENTRADA_MAX + 1]; /* o que está sendo digitado   */
static int    entrada_len;
static int    digitando;                /* 1 = visor mostra a entrada  */
static double acumulado;
static double operando;                 /* último operando (p/ "=" repetido) */
static int    op_pendente;              /* K_SOMA.. ou -1              */
static int    op_repetir;               /* operação do "=" repetido    */
static int    erro;                     /* 1 = visor travado em erro   */
static double pronto_valor;             /* operando produzido por % ou
                                           unária, à espera de = / op   */
static int    pronto;

static char visor[64];                  /* linha principal             */
static char historico[128];             /* linha de cima (expressão)   */
static char hist_operando[64];          /* como o operando aparece no
                                           histórico (vira "√(9)" etc.) */

/* ============================================================
 * utilidades de texto (sem libc)
 * ============================================================ */
static int str_len(const char *s) { int n = 0; while (s[n]) n++; return n; }

static void str_copia(char *dst, const char *src) {
    while ((*dst++ = *src++)) {}
}

static void str_cola(char *dst, const char *src) {
    str_copia(dst + str_len(dst), src);
}

/* ============================================================
 * matemática (sem libm)
 * ============================================================ */
static double abs_d(double v) { return v < 0 ? -v : v; }

/* Raiz quadrada por Newton-Raphson: parte de um chute razoável e
 * refina até estabilizar. Converge em poucas iterações para
 * qualquer double positivo. */
static double raiz(double v) {
    if (v == 0) return 0;
    double x = v > 1 ? v : 1;           /* chute inicial   */
    for (int i = 0; i < 64; i++) {
        double prox = 0.5 * (x + v / x);
        if (prox == x) break;           /* estabilizou     */
        x = prox;
    }
    return x;
}

/* 10^n por exponenciação binária: uma multiplicação por bit em vez
 * de n multiplicações em loop — o erro acumulado cai de ~n ULPs
 * para ~log2(n), o que importa ao renderizar 16 dígitos. */
static double pot10(int n) {
    double base = 10, r = 1;
    int neg = n < 0;
    if (neg) n = -n;
    while (n) {
        if (n & 1) r *= base;
        base *= base;
        n >>= 1;
    }
    return neg ? 1.0 / r : r;
}

static int eh_infinito(double v) { return v > 1.7976931348623157e308 || v < -1.7976931348623157e308; }
static int eh_nan(double v)      { return v != v; }

/* ============================================================
 * double → texto no padrão brasileiro
 * ------------------------------------------------------------
 * Estratégia: normaliza o valor para [1e14, 1e15), arredonda
 * para um inteiro de 15 dígitos e decide onde a vírgula cai.
 * Números fora de 10^15 / 10^-6 saem em notação científica.
 * Arredondar em 15 dígitos significativos também "limpa" os
 * resíduos binários clássicos (0,1 + 0,2 vira 0,3 no visor).
 * ============================================================ */
#define DIGITOS 15

static void formata(double v, char *out) {
    char *p = out;

    if (eh_nan(v)) { str_copia(out, "Erro"); return; }

    if (v < 0) { *p++ = '-'; v = -v; }
    if (v == 0) { *p++ = '0'; *p = 0; return; }
    if (eh_infinito(v)) { str_copia(out, "Limite excedido"); return; }

    /* expoente decimal: quantas casas até o primeiro dígito */
    int exp10 = 0;
    double t = v;
    while (t >= 10)  { t /= 10; exp10++; }
    while (t < 1)    { t *= 10; exp10--; }

    /* Escala para DIGITOS dígitos numa única operação e arredonda.
       15 dígitos é escolha deliberada: é o máximo que um double de
       64 bits sustenta sem expor resíduo binário (0,1 + 0,2 vira
       0,3 no visor, e não 0,30000000000000004 como em JavaScript).
       O 16º dígito existiria, mas mentiria com frequência. */
    int desloc = exp10 - (DIGITOS - 1);
    double escala = desloc >= 0 ? v / pot10(desloc) : v * pot10(-desloc);
    unsigned long long r = (unsigned long long)(escala + 0.5);
    if (r >= 1000000000000000ULL) { r /= 10; exp10++; }   /* 9,99..9 → 10 */

    char digs[DIGITOS + 1];
    for (int i = DIGITOS - 1; i >= 0; i--) { digs[i] = '0' + (char)(r % 10); r /= 10; }
    digs[DIGITOS] = 0;

    int ultimo = DIGITOS - 1;                 /* corta zeros à direita */
    while (ultimo > 0 && digs[ultimo] == '0') ultimo--;

    if (exp10 >= DIGITOS || exp10 < -6) {
        /* científica: d,ddd e±xx */
        *p++ = digs[0];
        if (ultimo > 0) {
            *p++ = ',';
            for (int i = 1; i <= ultimo; i++) *p++ = digs[i];
        }
        *p++ = 'e';
        int e = exp10;
        if (e < 0) { *p++ = '-'; e = -e; } else *p++ = '+';
        char tmp[8]; int n = 0;
        do { tmp[n++] = '0' + (char)(e % 10); e /= 10; } while (e);
        while (n) *p++ = tmp[--n];
        *p = 0;
        return;
    }

    if (exp10 >= 0) {
        /* parte inteira com separador de milhar */
        int inteiros = exp10 + 1;
        for (int i = 0; i < inteiros; i++) {
            *p++ = i < DIGITOS ? digs[i] : '0';
            int restantes = inteiros - 1 - i;
            if (restantes > 0 && restantes % 3 == 0) *p++ = '.';
        }
        if (ultimo >= inteiros) {
            *p++ = ',';
            for (int i = inteiros; i <= ultimo; i++) *p++ = digs[i];
        }
    } else {
        /* 0,000ddd */
        *p++ = '0'; *p++ = ',';
        for (int i = 0; i < -exp10 - 1; i++) *p++ = '0';
        for (int i = 0; i <= ultimo; i++) *p++ = digs[i];
    }
    *p = 0;
}

/* entrada digitada ("1.234,5" não existe aqui: a entrada guarda
 * só dígitos e uma vírgula) → double */
static double parse_entrada(void) {
    double v = 0, frac = 0.1;
    int depois_da_virgula = 0, negativo = 0, i = 0;

    if (entrada[0] == '-') { negativo = 1; i = 1; }
    for (; entrada[i]; i++) {
        if (entrada[i] == ',') { depois_da_virgula = 1; continue; }
        int d = entrada[i] - '0';
        if (!depois_da_virgula) v = v * 10 + d;
        else { v += d * frac; frac /= 10; }
    }
    return negativo ? -v : v;
}

/* mostra a entrada no visor com separador de milhar em tempo real */
static void visor_da_entrada(void) {
    char *p = visor;
    int i = 0, fim_int = 0;

    if (entrada[0] == '-') { *p++ = '-'; i = 1; }
    fim_int = i;
    while (entrada[fim_int] && entrada[fim_int] != ',') fim_int++;

    int inteiros = fim_int - i;
    for (int k = 0; k < inteiros; k++) {
        *p++ = entrada[i + k];
        int restantes = inteiros - 1 - k;
        if (restantes > 0 && restantes % 3 == 0) *p++ = '.';
    }
    str_copia(p, entrada + fim_int);   /* vírgula e decimais, se houver */
}

/* ============================================================
 * histórico (linha de cima)
 * ============================================================ */
static const char *simbolo(int op) {
    switch (op) {
        case K_SOMA: return "+";
        case K_SUB:  return "−";
        case K_MUL:  return "×";
        case K_DIV:  return "÷";
    }
    return "?";
}

static void historico_pendente(void) {
    char num[64];
    formata(acumulado, num);
    str_copia(historico, num);
    str_cola(historico, " ");
    str_cola(historico, simbolo(op_pendente));
}

static void poe_erro(const char *msg) {
    erro = 1;
    str_copia(visor, msg);
}

/* ============================================================
 * máquina de estados
 * ============================================================ */
static void limpa_entrada(void) {
    entrada[0] = '0'; entrada[1] = 0;
    entrada_len = 1;
    digitando = 1;
    pronto = 0;
    visor_da_entrada();
}

WASM_EXPORT void calc_init(void) {
    acumulado = operando = 0;
    op_pendente = op_repetir = -1;
    erro = 0;
    historico[0] = 0;
    hist_operando[0] = 0;
    limpa_entrada();
}

/* valor atual do visor (entrada digitada ou resultado) */
static double valor_atual(void) {
    if (digitando) return parse_entrada();
    if (pronto)    return pronto_valor;
    return acumulado;
}

static void mostra_resultado(double v) {
    acumulado = v;
    digitando = 0;
    if (eh_nan(v))       { poe_erro("Entrada inválida"); return; }
    if (eh_infinito(v))  { poe_erro("Limite excedido");  return; }
    formata(v, visor);
}

static double aplica(int op, double a, double b, int *falha) {
    *falha = 0;
    switch (op) {
        case K_SOMA: return a + b;
        case K_SUB:  return a - b;
        case K_MUL:  return a * b;
        case K_DIV:
            if (b == 0) { *falha = 1; return 0; }
            return a / b;
    }
    return b;
}

static void tecla_digito(int d) {
    if (!digitando) {                       /* começa número novo */
        limpa_entrada();
        if (op_pendente < 0) historico[0] = 0;
    }
    if (entrada_len >= ENTRADA_MAX) return; /* visor cheio: ignora */
    if (entrada[0] == '0' && entrada_len == 1) {  /* troca o 0 inicial */
        if (d == 0) return;
        entrada[0] = '0' + (char)d;
    } else {
        entrada[entrada_len++] = '0' + (char)d;
        entrada[entrada_len] = 0;
    }
    visor_da_entrada();
}

static void tecla_virgula(void) {
    if (!digitando) { limpa_entrada(); if (op_pendente < 0) historico[0] = 0; }
    for (int i = 0; entrada[i]; i++)
        if (entrada[i] == ',') return;      /* já tem vírgula */
    if (entrada_len >= ENTRADA_MAX) return;
    entrada[entrada_len++] = ',';
    entrada[entrada_len] = 0;
    visor_da_entrada();
}

static void tecla_backspace(void) {
    if (!digitando) return;                 /* resultado não se apaga */
    entrada_len--;
    if (entrada_len == 0 || (entrada_len == 1 && entrada[0] == '-')) {
        limpa_entrada();
        return;
    }
    entrada[entrada_len] = 0;
    visor_da_entrada();
}

static void tecla_neg(void) {
    if (digitando) {
        if (entrada[0] == '0' && entrada_len == 1) return;
        if (entrada[0] == '-') {
            for (int i = 0; i < entrada_len; i++) entrada[i] = entrada[i + 1];
            entrada_len--;
        } else {
            for (int i = entrada_len; i >= 0; i--) entrada[i + 1] = entrada[i];
            entrada[0] = '-';
            entrada_len++;
        }
        visor_da_entrada();
    } else if (pronto) {
        pronto_valor = -pronto_valor;
        formata(pronto_valor, visor);
    } else {
        mostra_resultado(-acumulado);
    }
}

static void tecla_operador(int op) {
    double v = valor_atual();

    if (op_pendente >= 0 && (digitando || pronto)) {   /* resolve a pendência */
        int falha;
        double r = aplica(op_pendente, acumulado, v, &falha);
        if (falha) { poe_erro("Não dá para dividir por zero"); return; }
        acumulado = r;
    } else {
        acumulado = v;
    }
    op_pendente = op;
    op_repetir = -1;
    digitando = 0;
    pronto = 0;
    formata(acumulado, visor);
    historico_pendente();
    hist_operando[0] = 0;
}

static void tecla_igual(void) {
    int falha = 0;
    char num[64];

    if (op_pendente >= 0) {
        double b;
        if (digitando)   b = parse_entrada();
        else if (pronto) b = pronto_valor;
        else             b = acumulado;      /* "5 + =" → 5 + 5 */

        /* monta "a op b =" antes de calcular */
        formata(acumulado, historico);
        str_cola(historico, " ");
        str_cola(historico, simbolo(op_pendente));
        str_cola(historico, " ");
        if (hist_operando[0]) str_cola(historico, hist_operando);
        else { formata(b, num); str_cola(historico, num); }
        str_cola(historico, " =");

        double r = aplica(op_pendente, acumulado, b, &falha);
        if (falha) { poe_erro("Não dá para dividir por zero"); return; }

        operando = b;
        op_repetir = op_pendente;
        op_pendente = -1;
        pronto = 0;
        hist_operando[0] = 0;
        mostra_resultado(r);
    } else if (op_repetir >= 0) {           /* "=" repetido refaz a conta */
        formata(acumulado, historico);
        str_cola(historico, " ");
        str_cola(historico, simbolo(op_repetir));
        str_cola(historico, " ");
        formata(operando, num);
        str_cola(historico, num);
        str_cola(historico, " =");

        double r = aplica(op_repetir, acumulado, operando, &falha);
        if (falha) { poe_erro("Não dá para dividir por zero"); return; }
        mostra_resultado(r);
    } else {
        double v = valor_atual();
        formata(v, historico);
        str_cola(historico, " =");
        mostra_resultado(v);
    }
}

/* unários: 1/x, x², √ — anotam no histórico como "√(9)" etc. */
static void tecla_unaria(int op) {
    double v = valor_atual();
    char dentro[64];

    if (hist_operando[0]) str_copia(dentro, hist_operando);
    else formata(v, dentro);

    double r;
    switch (op) {
        case K_INV:
            if (v == 0) { poe_erro("Não dá para dividir por zero"); return; }
            str_copia(hist_operando, "1/(");
            str_cola(hist_operando, dentro);
            str_cola(hist_operando, ")");
            r = 1.0 / v;
            break;
        case K_QUAD:
            str_copia(hist_operando, "sqr(");
            str_cola(hist_operando, dentro);
            str_cola(hist_operando, ")");
            r = v * v;
            break;
        case K_RAIZ:
            if (v < 0) { poe_erro("Entrada inválida"); return; }
            str_copia(hist_operando, "√(");
            str_cola(hist_operando, dentro);
            str_cola(hist_operando, ")");
            r = raiz(v);
            break;
        default: return;
    }

    if (op_pendente >= 0) {
        /* vira o segundo operando; o acumulado segue intacto */
        pronto = 1;
        pronto_valor = r;
        digitando = 0;
        formata(r, visor);
    } else {
        mostra_resultado(r);
    }

    /* sem operação pendente, o histórico mostra só a expressão unária */
    if (op_pendente < 0) str_copia(historico, hist_operando);
    else {
        historico_pendente();
        str_cola(historico, " ");
        str_cola(historico, hist_operando);
    }
}

/* "%" no estilo Windows: com + e −, vira porcentagem do
 * acumulado; com × e ÷, o operando dividido por 100. O resultado
 * fica como operando pronto para o "=" ou o próximo operador. */
static void tecla_pct(void) {
    if (op_pendente < 0) {                  /* % solto zera o visor */
        historico[0] = 0;
        hist_operando[0] = 0;
        mostra_resultado(0);
        return;
    }

    double b = valor_atual();
    double novo = (op_pendente == K_SOMA || op_pendente == K_SUB)
                  ? acumulado * b / 100.0
                  : b / 100.0;

    pronto = 1;
    pronto_valor = novo;
    digitando = 0;
    formata(novo, visor);
    formata(novo, hist_operando);           /* aparece resolvido no histórico */

    historico_pendente();
    str_cola(historico, " ");
    str_cola(historico, hist_operando);
}

WASM_EXPORT void calc_tecla(int k) {
    /* em erro, só C/CE destravam */
    if (erro && k != K_C && k != K_CE) return;
    if (erro) { calc_init(); if (k == K_CE) return; }

    switch (k) {
        case K_C:        calc_init(); break;
        case K_CE:       limpa_entrada(); break;
        case K_BS:       tecla_backspace(); break;
        case K_VIRGULA:  tecla_virgula(); break;
        case K_NEG:      tecla_neg(); break;
        case K_SOMA: case K_SUB: case K_MUL: case K_DIV:
                         tecla_operador(k); break;
        case K_IGUAL:    tecla_igual(); break;
        case K_INV: case K_QUAD: case K_RAIZ:
                         tecla_unaria(k); break;
        case K_PCT:      tecla_pct(); break;
        default:
            if (k >= 0 && k <= 9) tecla_digito(k);
    }
}

WASM_EXPORT const char *calc_visor(void)     { return visor; }
WASM_EXPORT const char *calc_historico(void) { return historico; }
