#include "interp.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Una entrada de la tabla de funciones. Toma prestado el def: su owner es el programa,
// que vive más que la tabla.
typedef struct function {
    const Stmt *definition;
    struct function *next;
} Function;

// El contexto del intérprete. Va por parámetro: no hay variables globales en C.
typedef struct {
    Env *global;            // el marco global de Mini
    Function *functions;    // la tabla de funciones de Mini
    FILE *out;
    RunError *error;
} Interp;

static int eval(Interp *in, const Expr *e, const Env *frame, double *result);

// Anota un error de ejecución y devuelve 0, para poder escribir: return run_fail(...);
static int run_fail(Interp *in, const char *what, const char *name) {
    snprintf(in->error->message, sizeof in->error->message, "%s: %.64s", what, name);
    return 0;
}

static int no_memory(Interp *in) {
    snprintf(in->error->message, sizeof in->error->message, "no hay memoria");
    return 0;
}

static int wrong_arg_count(Interp *in, const char *name, int expected, int given) {
    snprintf(in->error->message, sizeof in->error->message,
             "número de argumentos incorrecto: %.64s espera %d y recibió %d", name, expected, given);
    return 0;
}

// Devuelve el def de esa función, o NULL si no se ha ejecutado ninguno con ese nombre.
static const Stmt *find_function(const Interp *in, const char *name) {
    for (const Function *f = in->functions; f != NULL; f = f->next) {
        if (strcmp(f->definition->as.def.name, name) == 0) {
            return f->definition;
        }
    }
    return NULL;
}

// Agrega la función a la tabla. Si el nombre ya estaba, el def nuevo reemplaza al anterior.
static int define_function(Interp *in, const Stmt *definition) {
    for (Function *f = in->functions; f != NULL; f = f->next) {
        if (strcmp(f->definition->as.def.name, definition->as.def.name) == 0) {
            f->definition = definition;
            return 1;
        }
    }
    Function *f = malloc(sizeof *f);
    if (f == NULL) {
        return no_memory(in);
    }
    f->definition = definition;
    f->next = in->functions;
    in->functions = f;
    return 1;
}

static void free_functions(Function *functions) {
    while (functions != NULL) {
        Function *next = functions->next;
        free(functions);
        functions = next;
    }
}

static int eval_binary(Interp *in, const Expr *e, const Env *frame, double *result) {
    double a, b;
    if (!eval(in, e->as.binary.left, frame, &a) || !eval(in, e->as.binary.right, frame, &b)) {
        return 0;
    }
    if (!expr_apply(e->as.binary.op, a, b, result)) {
        snprintf(in->error->message, sizeof in->error->message, "división entre cero");
        return 0;
    }
    return 1;
}

// Evalúa la condición, y luego solo la rama elegida.
static int eval_if(Interp *in, const Expr *e, const Env *frame, double *result) {
    // --- Por escribir. ---
    (void) e;
    (void) frame;
    (void) result;
    return run_fail(in, "falta escribir", "eval_if");
}

// Evalúa una llamada: el modelo de entornos de la semana 3.
static int eval_call(Interp *in, const Expr *e, const Env *frame, double *result) {
    const char *name = e->as.call.name;
    const Stmt *function = find_function(in, name);
    if (function == NULL) {
        return run_fail(in, "función desconocida", name);
    }
    int count = e->as.call.arg_count;
    if (count != function->as.def.param_count) {
        return wrong_arg_count(in, name, function->as.def.param_count, count);
    }
    // --- Por escribir, en este orden:
    // 1. Evaluar cada argumento en frame, el marco de quien llama, y guardar los valores en un arreglo
    //    de double con malloc. Si count es 0, no reserven nada: malloc(0) puede devolver NULL.
    // 2. Crear el marco nuevo con env_new(in->global).
    // 3. Definir cada parámetro con env_set, y liberar el arreglo.
    // 4. Evaluar function->as.def.body en el marco nuevo, liberar el marco y devolver el resultado.
    // Cada camino de error libera lo que ya reservó. ---
    (void) frame;
    (void) result;
    return run_fail(in, "falta terminar eval_call", name);
}

static int eval(Interp *in, const Expr *e, const Env *frame, double *result) {
    switch (e->kind) {
    case EXPR_NUMBER:
        *result = e->as.number;
        return 1;
    case EXPR_VARIABLE:
        if (!env_get(frame, e->as.name, result)) {
            return run_fail(in, "variable desconocida", e->as.name);
        }
        return 1;
    case EXPR_BINARY:
        return eval_binary(in, e, frame, result);
    case EXPR_IF:
        return eval_if(in, e, frame, result);
    case EXPR_CALL:
        return eval_call(in, e, frame, result);
    }
    return 0;
}

// Ejecuta una instrucción. Las instrucciones siempre corren en el marco global.
static int exec_statement(Interp *in, const Stmt *s) {
    switch (s->kind) {
    case STMT_LET:
        // --- Por escribir: evaluar en in->global, y guardar con env_set. ---
        return run_fail(in, "falta escribir", "let");
    case STMT_PRINT:
        // --- Por escribir: evaluar en in->global, y escribir en in->out con "%g\n". ---
        return run_fail(in, "falta escribir", "print");
    case STMT_DEF:
        return define_function(in, s);
    }
    return 0;
}

int run_program(const Stmt *program, FILE *out, RunError *error) {
    error->message[0] = '\0';
    Interp in = {.global = env_new(NULL), .functions = NULL, .out = out, .error = error};
    if (in.global == NULL) {
        return no_memory(&in);
    }
    int ok = 1;
    for (const Stmt *s = program; s != NULL && ok; s = s->next) {
        ok = exec_statement(&in, s);
    }
    free_functions(in.functions);
    env_free(in.global);
    return ok;
}
