// Árboles de expresión: número, variable y operación binaria.
//
// Expr no es opaco: el código mira kind y usa un switch, como en la semana 7.
//
// Ownership: cada nodo es owner de su nombre y de sus hijos. Quien recibe un árbol lo libera
// con expr_free. Los nodos no se comparten: cada hijo tiene un solo padre.
#ifndef EXPR_H
#define EXPR_H

#include <stdio.h>

#include "env.h"

typedef enum { EXPR_NUMBER, EXPR_VARIABLE, EXPR_BINARY } ExprKind;
typedef enum { OP_ADD, OP_SUB, OP_MUL, OP_DIV } BinaryOp;

typedef struct expr Expr;

struct expr {
    ExprKind kind;
    union {
        double number;                                             // EXPR_NUMBER
        char *name;                                                // EXPR_VARIABLE: el nodo es owner de su copia
        struct { BinaryOp op; Expr *left; Expr *right; } binary;   // EXPR_BINARY: el nodo es owner de sus hijos
    } as;
};

// Devuelve un nodo nuevo, o NULL si no hay memoria.
Expr *expr_number(double value);

// Guarda una copia de name. Devuelve NULL si no hay memoria, sin dejar nada reservado.
Expr *expr_variable(const char *name);

// Se queda con los hijos, también cuando falla. Si no hay memoria, o si un hijo es NULL,
// libera los hijos que tenga y devuelve NULL. Por eso se pueden anidar llamadas sin revisar
// cada una: basta con revisar el resultado final.
Expr *expr_binary(BinaryOp op, Expr *left, Expr *right);

// Libera el árbol completo. Acepta NULL.
void expr_free(Expr *e);

// Escribe la expresión en out, con los números en %g y un espacio a cada lado del operador,
// sin salto de línea al final. Solo pone los paréntesis necesarios para conservar la forma del
// árbol, sabiendo que * y / van antes que + y -, y que todos asocian por la izquierda:
//   - un hijo izquierdo lleva paréntesis si su operador tiene menor precedencia que el del padre;
//   - un hijo derecho lleva paréntesis si su operador tiene menor o igual precedencia.
// Ejemplos: 2 + 3 * x     (2 + 3) * x     1 - 2 - 3     1 - (2 - 3)     1 + (2 + 3)
void expr_print(const Expr *e, FILE *out);

// Calcula el valor de e. Si puede, lo guarda en *result y devuelve 1. Si hay una variable que
// no existe en env, o una división entre un divisor igual a 0, devuelve 0 y no toca *result.
int expr_eval(const Expr *e, const Env *env, double *result);

// Devuelve una copia independiente del árbol, que no comparte nada con e. NULL si no hay
// memoria, sin dejar nada reservado.
Expr *expr_copy(const Expr *e);

// Devuelve 1 si los dos árboles tienen la misma forma y los mismos valores, y 0 si no.
int expr_equal(const Expr *a, const Expr *b);

// Se queda con e y devuelve el árbol simplificado. Libera todo lo que ya no se usa. Simplifica
// primero a los hijos, y luego aplica a cada nodo estas reglas:
//   x + 0, 0 + x, x - 0, x * 1, 1 * x, x / 1  ->  x
//   x * 0, 0 * x                              ->  0
//   dos números                               ->  el resultado, salvo si es una división entre 0
// Con NULL devuelve NULL.
Expr *expr_simplify(Expr *e);

// Devuelve un árbol nuevo con la derivada de e respecto de la variable name, o NULL si no hay
// memoria, sin dejar nada reservado. No cambia e ni comparte nodos con él.
Expr *expr_derive(const Expr *e, const char *name);

#endif
