#include "circuit.h"
#include "set"
#include "paras.h"


LUT* build_lut(Gate* gate) {

    // printf("=== build %s\n", gate->name.c_str());

    LUT* lut = new LUT();
    lut->fanout = gate;
    lut->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()];

        lut->gates.push_back(random_gate);
        fanins.erase(random_gate);

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

    for(Gate* in : fanins) {
        lut->fanins.push_back(in);
    }

    return lut;
}

LUTCircuit* Circuit::build_lut_circuit() {


    std::queue<Gate*> q;
    std::set<Gate*> done;

    for(Gate* gate : gates) {
        // assert fanouts >= 2
        if(gate->fanouts.size() >= 2 || gate->isPI || gate->isPO) {
            q.push(gate);
            done.insert(gate);
        }
    }

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

        LUT* lut = build_lut(gate);
        
        printf("size: %d lut: %s inputs: ( ", lut->fanins.size(), lut->fanout->name.c_str());
        for(Gate* in : lut->fanins) {
            printf("%s ", in->name.c_str());
        }
        printf(")\n");

        for(Gate* in : lut->fanins) {
            if(in->fanouts.size() < 2 && !in->isPI && done.count(in) == 0) {
                done.insert(in);
                q.push(in);
            }
        }
    }

    return nullptr;
}