#include "logic.h"

#include <stdlib.h>

// Reserva un nodo con su etiqueta. El resto lo llena quien llama.
static Logic *logic_node(LogicKind kind) {
    Logic *e = malloc(sizeof *e);
    if (e == NULL) {
        return NULL;
    }
    e->kind = kind;
    return e;
}

Logic *logic_constant(int value) {
    Logic *e = logic_node(LOGIC_CONSTANT);
    if (e == NULL) {
        return NULL;
    }
    e->as.constant = value;
    return e;
}

Logic *logic_input(char letter) {
    Logic *e = logic_node(LOGIC_INPUT);
    if (e == NULL) {
        return NULL;
    }
    e->as.input = letter;
    return e;
}

Logic *logic_not(Logic *child) {
    if (child == NULL) {
        return NULL;
    }
    Logic *e = logic_node(LOGIC_NOT);
    if (e == NULL) {
        logic_free(child);
        return NULL;
    }
    e->as.child = child;
    return e;
}

// AND y OR solo se diferencian en la etiqueta.
static Logic *logic_pair(LogicKind kind, Logic *left, Logic *right) {
    if (left == NULL || right == NULL) {
        logic_free(left);
        logic_free(right);
        return NULL;
    }
    Logic *e = logic_node(kind);
    if (e == NULL) {
        logic_free(left);
        logic_free(right);
        return NULL;
    }
    e->as.pair.left = left;
    e->as.pair.right = right;
    return e;
}

Logic *logic_and(Logic *left, Logic *right) {
    return logic_pair(LOGIC_AND, left, right);
}

Logic *logic_or(Logic *left, Logic *right) {
    return logic_pair(LOGIC_OR, left, right);
}

void logic_free(Logic *e) {
    if (e == NULL) {
        return;
    }
    switch (e->kind) {
    case LOGIC_CONSTANT:
    case LOGIC_INPUT:
        break;
    case LOGIC_NOT:
        logic_free(e->as.child);
        break;
    case LOGIC_AND:
    case LOGIC_OR:
        logic_free(e->as.pair.left);
        logic_free(e->as.pair.right);
        break;
    }
    free(e);
}

int logic_count(const Logic *e) {
    switch (e->kind) {
    case LOGIC_CONSTANT:
    case LOGIC_INPUT:
        return 1;
    case LOGIC_NOT:
        return 1 + logic_count(e->as.child);
    case LOGIC_AND:
    case LOGIC_OR:
        return 1 + logic_count(e->as.pair.left) + logic_count(e->as.pair.right);
    }
    return 0;
}

int logic_depth(const Logic *e) {
    switch (e->kind) {
    case LOGIC_CONSTANT:
    case LOGIC_INPUT:
        return 1;
    case LOGIC_NOT:
        return 1 + logic_depth(e->as.child);
    case LOGIC_AND:
    case LOGIC_OR: {
        int left = logic_depth(e->as.pair.left);
        int right = logic_depth(e->as.pair.right);
        return 1 + (left > right ? left : right);
    }
    }
    return 0;
}

// Escribe la fórmula como llamadas: and(a, not(b)). Así no hacen falta reglas de paréntesis.
void logic_print(const Logic *e, FILE *out) {
    switch (e->kind) {
    case LOGIC_CONSTANT:
        fputs(e->as.constant ? "true" : "false", out);
        break;
    case LOGIC_INPUT:
        fputc(e->as.input, out);
        break;
    case LOGIC_NOT:
        fputs("not(", out);
        logic_print(e->as.child, out);
        fputc(')', out);
        break;
    case LOGIC_AND:
    case LOGIC_OR:
        fputs(e->kind == LOGIC_AND ? "and(" : "or(", out);
        logic_print(e->as.pair.left, out);
        fputs(", ", out);
        logic_print(e->as.pair.right, out);
        fputc(')', out);
        break;
    }
}

int logic_eval(const Logic *e, const int *inputs) {
    switch (e->kind) {
    case LOGIC_CONSTANT:
        return e->as.constant;
    case LOGIC_INPUT:
        return inputs[e->as.input - 'a'];
    case LOGIC_NOT:
        return !logic_eval(e->as.child, inputs);
    case LOGIC_AND:
        return logic_eval(e->as.pair.left, inputs) && logic_eval(e->as.pair.right, inputs);
    case LOGIC_OR:
        return logic_eval(e->as.pair.left, inputs) || logic_eval(e->as.pair.right, inputs);
    }
    return 0;
}

static int is_constant(const Logic *e, int value) {
    return e->kind == LOGIC_CONSTANT && e->as.constant == value;
}

// Conserva un hijo, y libera al otro hijo y al nodo. Devuelve el que se conserva.
static Logic *keep_child(Logic *node, Logic *kept, Logic *dropped) {
    logic_free(dropped);
    free(node);
    return kept;
}

static Logic *simplify_not(Logic *e) {
    e->as.child = logic_simplify(e->as.child);
    Logic *child = e->as.child;
    if (child->kind == LOGIC_CONSTANT) {
        child->as.constant = !child->as.constant;
        free(e);
        return child;
    }
    if (child->kind == LOGIC_NOT) {
        Logic *inner = child->as.child;
        free(child);
        free(e);
        return inner;
    }
    return e;
}

static Logic *simplify_pair(Logic *e) {
    e->as.pair.left = logic_simplify(e->as.pair.left);
    e->as.pair.right = logic_simplify(e->as.pair.right);
    Logic *left = e->as.pair.left;
    Logic *right = e->as.pair.right;
    // En AND, true no cambia nada y false decide. En OR, al revés.
    int neutral = e->kind == LOGIC_AND;
    if (is_constant(left, neutral)) {
        return keep_child(e, right, left);
    }
    if (is_constant(right, neutral)) {
        return keep_child(e, left, right);
    }
    if (is_constant(left, !neutral)) {
        return keep_child(e, left, right);
    }
    if (is_constant(right, !neutral)) {
        return keep_child(e, right, left);
    }
    return e;
}

Logic *logic_simplify(Logic *e) {
    if (e == NULL) {
        return NULL;
    }
    switch (e->kind) {
    case LOGIC_CONSTANT:
    case LOGIC_INPUT:
        return e;
    case LOGIC_NOT:
        return simplify_not(e);
    case LOGIC_AND:
    case LOGIC_OR:
        return simplify_pair(e);
    }
    return e;
}
