#include <set>

#include "circuit.h"
#include "paras.h"
#include "simulator.h"

LUT* LUTCircuit::ls_pick() {

    LUT* pick = nullptr;

    // for(LUT* lut : luts) {
    //     if(!lut->vsat) return lut;
    // }

    // assert(false);

    if(rand() % 10000  <=  OPT(brk_sp) * 10000) {
        printf("BREAK!!!!!\n");
        pick = luts[rand() % luts.size()];
        return pick;   
    }

    if(good_luts.size() > 0) {
        pick = good_luts[rand() % good_luts.size()];
        if(pick->score <= 0 || pick->tabu > step) {
            pick = nullptr;
            for(int i=0; i<good_luts.size(); i++) {
                if(good_luts[i]->score <= 0 || good_luts[i]->tabu > step) {
                    good_luts[i]->is_good_var = 0;
                    good_luts[i] = good_luts.back();
                    good_luts.pop_back();
                    i--;
                }
            }

            if(good_luts.size() > 0) {
                pick = good_luts[rand() % good_luts.size()];
                assert(pick->score > 0 && pick->tabu <= step);
            }
        }
    }

    if(pick == nullptr) {
        printf("UPDATE!!!!!\n");
        ls_update();

        std::vector<LUT*> t;

        for(LUT* lut : luts) {
            if(!lut->vsat) {
                t.push_back(lut);
            }
        }

        pick = t[rand() % t.size()];

        // pick = luts[rand() % luts.size()];

        assert(pick != nullptr);

    } else {
        printf("GOOD_VAR!!!!!\n");
    }

    return pick;
}

void LUTCircuit::ls_flip(LUT *lut) {

    lut->uptag = 0;
    lut->value = !lut->value;
    if(!lut->isPI) lut->vsat = !lut->vsat;

    lut->tabu = lut->vunat_cost + step;

    for(LUT* out : lut->fanouts) {
        out->vsat = (out->cal_value() == out->value);
    }

    lut->cal_fault_info(lut->fd, lut->fpl);

    for(LUT* r : lut->reigon) {
        int t_fd[2], t_fpl[2];
        r->cal_fault_info(t_fd, t_fpl);

        if(t_fd[0] != r->fd[0] || t_fd[1] != r->fd[1] || t_fpl[0] != r->fpl[0] || t_fpl[1] != r->fpl[1]) {
            r->uptag = 1;
        }
    }

    lut->cal_score();
    for(LUT* r : lut->reigon) {
        r->cal_score();
    }
    for(LUT* r : lut->fanouts) {
        r->cal_score();
    }
    for(LUT* r : lut->update_reigon) {
        r->cal_update();
    }
}

void LUTCircuit::ls_update() {

    std::unordered_set<LUT*> need_up;

    for(LUT* lut : luts) {
        if(!lut->vsat) {
            lut->vunat_cost += OPT(vsat_inc);
            need_up.insert(lut);
            need_up.insert(lut->fanins.begin(), lut->fanins.end());
        }
    }

    for(LUT* lut : need_up) {
        lut->cal_score();
    }

}

void LUTCircuit::ls_main() {
    printf("====== local search start ======\n");

    printf("initing lookup table ...\n");
    for(LUT* lut : luts) {
        lut->init_lookup_table();
    }

    for(LUT* lut : luts) {
        for(LUT* out :lut->fanouts) {
            for(int i=0; i<out->fanins.size(); i++) {
                if(out->fanins[i] == lut) {
                    lut->fault_info.push_back(out->fault_table[i]);
                }
            }
        }
    }

    for(LUT* lut : luts) {
        std::set<LUT*> t_reigon;
        for(LUT* out : lut->fanouts) {
            for(LUT* in : out->fanins) {
                t_reigon.insert(in);
            }
        }
        for(LUT* in : lut->fanins) {
            t_reigon.insert(in);
        }
        t_reigon.erase(lut);
        for(LUT* r : t_reigon) {
            lut->reigon.push_back(r);
        }
    }

    for(LUT* lut : luts) {
        std::set<LUT*> t_reigon;
        for(LUT* r: lut->reigon) {
            t_reigon.insert(r);
            for(LUT* out2 : r->fanouts) {
                t_reigon.insert(out2);
                for(LUT* in2 : out2->fanins) {
                    t_reigon.insert(in2);
                }
            }
        }
        for(LUT* r : lut->fanouts) {
            t_reigon.insert(r);
        }
        for(LUT* r : t_reigon) {
            lut->update_reigon.push_back(r);
        }
        // printf("rg: %d %d\n", lut->reigon.size(), lut->update_reigon.size());
    }

    printf("generating initial solution ...\n");

    for(LUT* lut : luts) {
        lut->up_cost = 1;

        lut->vunat_cost = 1;
        if(lut->isPI) {
            lut->vunat_cost = OPT(vsat_inc) * 100;
        }

        lut->is_good_var = 0;
        lut->tabu = 0;
        for(Gate* g : lut->inner_gates) {
            g->fault_detected_weight[0] = g->fault_detected_weight[1] = 1;
            g->fault_propagated_weight[0] = g->fault_propagated_weight[1] = 1;
        }
    }
    
    for(LUT* lut : luts) {
        lut->uptag = 0;
        lut->value = rand() % 2;
    }

    for(LUT* lut : luts) {
        lut->vsat = (lut->value == lut->cal_value());
    }

    printf("topo: %d %d\n", luts.size(), rtopo_luts.size());

    for(LUT* lut : rtopo_luts) {
        lut->cal_fault_info(lut->fd, lut->fpl);
    }

    for(LUT* lut : luts) {
        lut->cal_score();
    }

    step = 0;

    print();

    int seed = time(0);
    srand(1689658115);

    int T = 100000;
    while(T--) {

    if(T % 1000 == 0) {
        printf("T: %d\n", T);
    }

    // int t1 = check();

    // print();

    LUT* pick = ls_pick();
    // pick->cal_score();

    printf(">>>> flip: %s\n", pick->name);

    // int s = pick->score;

    printf("##### score: %d seed: %d\n", pick->score, seed);
    printf("vusat_cost: %d fdw: %d fplw: %d up: %d\n", pick->score_value_unsatisfied_cost, pick->score_fault_detected_weight, pick->score_fault_propagated_weight, pick->score_fault_update_cost);

    int score;
    simulator->simulate(PIs, score);
    printf("score: %d\n", score);

    ls_flip(pick);

    // int t2 = check();
    check();

    int cnt = 0;
    for(LUT* lut : luts) {
        if(!lut->vsat) cnt++;
    }
    printf("unsat: %d\n", cnt);

    // print();

    // assert(s == (t2 - t1));
    step++;

    for(LUT *lut : luts) {
        if(!lut->is_good_var && lut->score > 0 && lut->tabu <= step) {
            lut->is_good_var = 1;
            good_luts.push_back(lut);
        }
    }

    }
    // print();
    // exit(0);

}