#include <set>

#include "lut.h"
#include "paras.h"

void LUT::get_fault_info(Gate* gate, int *t_fd, int* t_fpl) {
    
}

void LUT::cal_fault_info(int *fd, int* fpl) {

    fd[0] = fd[1] = fpl[0] = fpl[1] = 0;
    if(isPO) {
        fd[0] = value;
        fd[1] = !value;
        return;
    }

    for(int i=0; i<fanouts.size(); i++) {
        LUT* out = fanouts[i];

        if(!out->vsat) continue;

        FaultInfo* info = nullptr;

        int input = 0;
        for(int j=0; j<out->fanins.size(); j++) {
            if(out->fanins[j] == this) {
                info = out->fault_table[j];
            }
            input |= (out->fanins[j]->value << j);
        }
        input <<= 1;
        input |= out->value;

        assert(info != nullptr);

        int t_fd[2], t_fpl[2];

        t_fd[0] = info[input].fd[0] && out->fd[!value];
        t_fd[1] = info[input].fd[1] && out->fd[!value];

        t_fpl[0] = info[input].fpl[0] + info[input].fd[0] * out->fpl[!value];
        t_fpl[1] = info[input].fpl[1] + info[input].fd[1] * out->fpl[!value];

        fd[0] = std::max(fd[0], t_fd[0]);
        fd[1] = std::max(fd[1], t_fd[1]);

        fpl[0] = std::max(fpl[0], t_fpl[0]);
        fpl[1] = std::max(fpl[1], t_fpl[1]);
    }
}

int LUT::cal_value() {
    if(isPI) return value;
    int input = 0;
    for(int i=0; i<fanins.size(); i++) {
        input |= (fanins[i]->value << i);
    }
    return value_table[input];
}

LUT::LUT(Gate *gate, LUTCircuit *circuit):gate(gate), value(gate->value), name(gate->name.c_str()) {

    C = circuit;

    isPI = gate->isPI;
    isPO = gate->isPO;

    inner_gates.push_back(gate);

    std::set<Gate*> fanins;

    for(Gate* g : gate->fanins) {
        fanins.insert(g);
        // printf("init fanins: %s\n", g->name.c_str());
    }

    while(true) {

        std::vector<Gate*> candidates;
        for(Gate* fanin : fanins) {
            if(fanin->fanouts.size() >= 2) continue;
            if(fanin->isPI) continue;

            int add = 0;
            for(Gate* in : fanin->fanins) {
                if(fanins.count(in) == 0) {
                    add++;
                }
            }

            if(fanins.size() - 1 + add <= OPT(lut)) {
                candidates.push_back(fanin);
            }
        }

        if(candidates.size() == 0) break;

        Gate* random_gate = candidates[rand()%candidates.size()];

        inner_gates.push_back(random_gate);
        fanins.erase(random_gate);

        for(Gate* in : random_gate->fanins) {
            fanins.insert(in);
        }
    }

    for(Gate* in : fanins) {
        __gate_fanins.push_back(in);
    }
}

void LUT::init_lookup_table() {
    value_table = new int[1 << fanins.size()];
    fault_table = new FaultInfo *[fanins.size() + inner_gates.size()];
    for (int i = 0; i < (fanins.size() + inner_gates.size()); i++) {
        fault_table[i] = new FaultInfo[1 << (fanins.size() + 1)];
    }

    std::unordered_map<Gate*, int> gate_to_index;

    for(int i=0; i<fanins.size(); i++) {
        gate_to_index[fanins[i]->gate] = i;
    }
    for(int i=0; i<inner_gates.size(); i++) {
        gate_to_index[inner_gates[i]] = i + fanins.size();
    }

    for (int i = 0; i < (1 << fanins.size()); i++) {
        for (int j = 0; j < fanins.size(); j++) {
            fanins[j]->gate->value = (i >> j) & 1;
        }

        for (Gate *g : inner_gates) {
            g->value = g->cal_value();
        }

        assert(inner_gates[inner_gates.size() - 1] == gate);

        value_table[i] = gate->value;

        gate->value = 0;
        gate->fault_detected[0] = 0;
        gate->fault_detected[1] = 1;
        gate->fault_propagated_len[0] = 0;
        gate->fault_propagated_len[1] = 0;

        // continue;

        std::queue<Gate *> q;
        q.push(gate);

        while (!q.empty()) {
            Gate *g = q.front();
            q.pop();

            fault_table[gate_to_index[g]][(i<<1)|gate->value].fd[0] = g->fault_detected[0];
            fault_table[gate_to_index[g]][(i<<1)|gate->value].fd[1] = g->fault_detected[1];

            fault_table[gate_to_index[g]][(i<<1)|gate->value].fpl[0] = g->fault_propagated_len[0];
            fault_table[gate_to_index[g]][(i<<1)|gate->value].fpl[1] = g->fault_propagated_len[1];

            for (Gate *fanin : g->fanins) {
                fanin->fault_detected[0] = fanin->cal_fault_detected(0);
                fanin->fault_detected[1] = fanin->cal_fault_detected(1);
                fanin->fault_propagated_len[0] = fanin->cal_propagate_len(0);
                fanin->fault_propagated_len[1] = fanin->cal_propagate_len(1);

                if(gate_to_index.count(fanin)) q.push(fanin);
            }
        }

        gate->value = 1;
        gate->fault_detected[0] = 1;
        gate->fault_detected[1] = 0;
        gate->fault_propagated_len[0] = 0;
        gate->fault_propagated_len[1] = 0;

        q.push(gate);

        while (!q.empty()) {
            Gate *g = q.front();
            q.pop();

            fault_table[gate_to_index[g]][(i<<1)|gate->value].fd[0] = g->fault_detected[0];
            fault_table[gate_to_index[g]][(i<<1)|gate->value].fd[1] = g->fault_detected[1];
            fault_table[gate_to_index[g]][(i<<1)|gate->value].fpl[0] = g->fault_propagated_len[0];
            fault_table[gate_to_index[g]][(i<<1)|gate->value].fpl[1] = g->fault_propagated_len[1];

            for (Gate *fanin : g->fanins) {
                fanin->fault_detected[0] = fanin->cal_fault_detected(0);
                fanin->fault_detected[1] = fanin->cal_fault_detected(1);
                fanin->fault_propagated_len[0] = fanin->cal_propagate_len(0);
                fanin->fault_propagated_len[1] = fanin->cal_propagate_len(1);

                if(gate_to_index.count(fanin)) q.push(fanin);
            }
        }
    }
}
