// El árbol de expresión de Mini: lo de la semana 13, con las variantes if y llamada.
//
// Expr no es opaco: el código hace switch sobre kind, como en la semana 7.
// Ownership: cada nodo es owner de sus hijos y de sus cadenas.
#ifndef EXPR_H
#define EXPR_H

#include <stdio.h>

#include "env.h"

typedef enum { EXPR_NUMBER, EXPR_VARIABLE, EXPR_BINARY, EXPR_IF, EXPR_CALL } ExprKind;

typedef enum {
    OP_ADD, OP_SUB, OP_MUL, OP_DIV,
    OP_LESS, OP_LESS_EQUAL, OP_GREATER, OP_GREATER_EQUAL, OP_EQUAL, OP_NOT_EQUAL
} BinaryOp;

typedef struct expr Expr;

struct expr {
    ExprKind kind;
    union {
        double number;
        char *name;                                                 // EXPR_VARIABLE: copia propia
        struct { BinaryOp op; Expr *left; Expr *right; } binary;
        struct { Expr *condition; Expr *then_branch; Expr *else_branch; } conditional;
        struct { char *name; Expr **args; int arg_count; } call;    // args es NULL si arg_count es 0
    } 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);

// Estos tres se vuelven owners de lo que reciben, aunque fallen: en ese caso lo liberan y
// devuelven NULL. Un hijo NULL cuenta como falla.
Expr *expr_binary(BinaryOp op, Expr *left, Expr *right);
Expr *expr_if(Expr *condition, Expr *then_branch, Expr *else_branch);
Expr *expr_call(const char *name, Expr **args, int arg_count);

// 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:
//   - un hijo izquierdo los lleva si su operador tiene menor precedencia que el del padre;
//   - un hijo derecho los lleva si su operador tiene menor o igual precedencia;
//   - una comparación hija de otra los lleva siempre, y un if dentro de una operación también.
// Ejemplos: 2 + 3 * x     1 - (2 - 3)     1 + (2 + 3)     (a < b) < c     (if a then 1 else 2) + 3
void expr_print(const Expr *e, FILE *out);

// Calcula a op b. Si puede, guarda el valor en *result y devuelve 1. En una división entre un
// divisor igual a 0, devuelve 0 y no toca *result. Una comparación vale 1 o 0.
int expr_apply(BinaryOp op, double a, double b, double *result);

// Calcula el valor de e, sin funciones. Si puede, lo guarda en *result y devuelve 1. Si hay una
// variable que no existe en env, una división entre cero o una llamada, devuelve 0 y no toca
// *result. El intérprete no la usa: interp.c tiene su propio eval, que conoce las funciones.
int expr_eval(const Expr *e, const Env *env, double *result);

// Devuelve una copia independiente del árbol. NULL si no hay memoria, sin dejar nada reservado.
// Con NULL devuelve NULL.
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
// En un if y en una llamada solo simplifica a los hijos. Con NULL devuelve NULL.
Expr *expr_simplify(Expr *e);

// Devuelve un árbol nuevo con la derivada de e respecto de la variable name; e no cambia. Solo
// cubre + - * /: con una comparación, un if o una llamada, o si no hay memoria, devuelve NULL.
Expr *expr_derive(const Expr *e, const char *name);

#endif
