// Junta todo el módulo: escribe, evalúa, copia, simplifica y deriva unas expresiones.
// La derivada se comprueba contra una aproximación numérica.
#include <math.h>
#include <stdio.h>

#include "expr.h"

#define STEP 0.00001

// Evalúa e con name = x. Devuelve 1 si pudo.
static int eval_at(const Expr *e, const char *name, double x, double *result) {
    Env *env = env_new(NULL);
    if (env == NULL) {
        return 0;
    }
    int ok = env_set(env, name, x) && expr_eval(e, env, result);
    env_free(env);
    return ok;
}

// La pendiente entre dos puntos muy cercanos a x. Es casi igual a la derivada en x.
static int numeric_derivative(const Expr *e, const char *name, double x, double *result) {
    double before;
    double after;
    if (!eval_at(e, name, x - STEP, &before) || !eval_at(e, name, x + STEP, &after)) {
        return 0;
    }
    *result = (after - before) / (2 * STEP);
    return 1;
}

static void compare_derivative(const Expr *e, const Expr *derivative, double x) {
    double exact;
    double approximate;
    if (eval_at(derivative, "x", x, &exact) && numeric_derivative(e, "x", x, &approximate)) {
        printf("  x = %-3g derivada = %-9.4f numérica = %-9.4f %s\n", x, exact, approximate,
               fabs(exact - approximate) < 0.0001 ? "bien" : "MAL");
    } else {
        printf("  x = %-3g no se puede evaluar\n", x);
    }
}

// Se queda con e. Devuelve 1 si todo se pudo construir.
static int show(Expr *e) {
    if (e == NULL) {
        printf("no se pudo construir la expresión\n");
        return 0;
    }
    Expr *copy = expr_copy(e);
    Expr *derivative = expr_derive(e, "x");
    if (copy == NULL || derivative == NULL) {
        printf("no se pudo copiar o derivar la expresión\n");
        expr_free(copy);
        expr_free(derivative);
        expr_free(e);
        return 0;
    }

    printf("expresión:    ");
    expr_print(e, stdout);
    printf("\ncopia igual:  %s\n", expr_equal(e, copy) == 1 ? "sí" : "no");
    printf("derivada:     ");
    expr_print(derivative, stdout);
    derivative = expr_simplify(derivative);
    printf("\nsimplificada: ");
    if (derivative != NULL) {
        expr_print(derivative, stdout);
    }
    printf("\n");
    if (derivative != NULL) {
        compare_derivative(e, derivative, 0.0);
        compare_derivative(e, derivative, 1.0);
        compare_derivative(e, derivative, 2.5);
    }

    expr_free(derivative);
    expr_free(copy);
    expr_free(e);
    return 1;
}

int main(void) {
    // (x + 1) * (x - 2)
    int ok = show(expr_binary(OP_MUL, expr_binary(OP_ADD, expr_variable("x"), expr_number(1.0)),
                              expr_binary(OP_SUB, expr_variable("x"), expr_number(2.0))));
    printf("\n");
    // 3 * x * x + 2 * x + 7
    ok = show(expr_binary(OP_ADD,
                          expr_binary(OP_ADD,
                                      expr_binary(OP_MUL, expr_binary(OP_MUL, expr_number(3.0), expr_variable("x")),
                                                  expr_variable("x")),
                                      expr_binary(OP_MUL, expr_number(2.0), expr_variable("x"))),
                          expr_number(7.0))) && ok;
    printf("\n");
    // x / (x + 1)
    ok = show(expr_binary(OP_DIV, expr_variable("x"),
                          expr_binary(OP_ADD, expr_variable("x"), expr_number(1.0)))) && ok;
    printf("\n");
    // 1 / x: en x = 0 no se puede evaluar
    ok = show(expr_binary(OP_DIV, expr_number(1.0), expr_variable("x"))) && ok;
    return ok ? 0 : 1;
}
