// ag_gen.cpp contains the methods for building an attack graph and generating
// an attack graph's exploits and printing them

#include <algorithm>
#include <chrono>
#include <iostream>
#include <vector>
#include <tuple>
#include <unordered_map>
#include <omp.h>
#include <sys/time.h>
#include <string.h>

#include "ag_gen.h"

#include "../util/odometer.h"
#include "../util/db_functions.h"
#include "../util/avail_mem.h"
#include "../mpi/tasks.h"

#ifdef REDIS

/**
 * @brief Constructor for generator
 * @details Builds a generator for creating attack graphs.
 *
 * @param _instance The initial information for generating the graph
 */
AGGen::AGGen(AGGenInstance &_instance, RedisManager &_rman) : instance(_instance), rman(&_rman) {
    rman->clear();
    auto init_quals = instance.initial_qualities;
    auto init_topos = instance.initial_topologies;
    NetworkState init_state(init_quals, init_topos);
    init_state.set_id();
    int init_id = init_state.get_id();
    FactbaseItems init_items =
                make_tuple(make_tuple(init_quals, init_topos), init_id);
    instance.factbases.push_back(init_state.get_factbase());
    instance.factbase_items.push_back(init_items);
    std::string hash = std::to_string(init_state.get_hash(instance.facts));
    // std::cout << "before init insertion" << std::endl;
    rman->insert_factbase(hash, init_id);
    // rman->insert_facts(hash, init_quals, init_topos);
    rman->commit();
    // std::cout << "after init insertion" << std::endl;
    // hash_map.insert(std::make_pair(init_state.get_hash(instance.facts), init_id));
    frontier.push_back(init_state);
    use_redis = true;
}

#endif

AGGen::AGGen(AGGenInstance &_instance) : instance(_instance) {
    auto init_quals = instance.initial_qualities;
    auto init_topos = instance.initial_topologies;
    NetworkState init_state(init_quals, init_topos);//instantiate an obj init_state with initial input
    init_state.set_id();
    int init_id = init_state.get_id();
    FactbaseItems init_items =
                make_tuple(make_tuple(init_quals, init_topos), init_id);
    instance.factbases.push_back(init_state.get_factbase());
    instance.factbase_items.push_back(init_items);
    std::string hash = std::to_string(init_state.get_hash(instance.facts));
    hash_map.insert(std::make_pair(init_state.get_hash(instance.facts), init_id));
    frontier.push_back(init_state);
    use_redis = false;
}

/**
 * @brief Generates exploit postconditions
 * @details When an exploit is known to apply to a set of assets,
 * the postconditions must be generated. This is done by iterating
 * through each parameterized fact and inserting the applicable
 * assets.
 *
 * @param group A tuple containing the exploit and applicable assets
 * @return A tuple containing the "real" qualities and "real" topologies
 */
static std::tuple<std::vector<std::tuple<ACTION_T, Quality>>, std::vector<std::tuple<ACTION_T, Topology>>>
createPostConditions(std::tuple<Exploit, AssetGroup> &group, Keyvalue &facts) {
    auto ex = std::get<0>(group);
    auto ag = std::get<1>(group);

    auto perm = ag.get_perm();

    auto param_postconds_q = ex.postcond_list_q();
    auto param_postconds_t = ex.postcond_list_t();

    std::vector<std::tuple<ACTION_T, Quality>> postconds_q;
    std::vector<std::tuple<ACTION_T, Topology>> postconds_t;

    for (auto &postcond : param_postconds_q) {
        auto action = std::get<0>(postcond);
        auto fact = std::get<1>(postcond);
		
        Quality q(perm[fact.get_param_num()], fact.name, fact.op,
                  fact.value, facts);
        postconds_q.emplace_back(action, q);
    }

    for (auto &postcond : param_postconds_t) {
        auto action = std::get<0>(postcond);
        auto fact = std::get<1>(postcond);

        auto dir = fact.get_dir();
        auto prop = fact.get_property();
        auto op = fact.get_operation();
        auto val = fact.get_value();

        Topology t(perm[fact.get_from_param()],
                   perm[fact.get_to_param()], dir, prop, op, val, facts);
        postconds_t.emplace_back(action, t);
    }
    return make_tuple(postconds_q, postconds_t);

}

/**
 * @brief Generate attack graph
 * @details Begin the generation of the attack graph. The algorithm is as
 * follows:
 *
 *      1. Fetch next factbase to expand from the frontier
 *      2. Fetch all exploits
 *      3. Loop over each exploit to determine if it is applicable.
 *          a. Fetch preconditions of the exploit
 *          b. Generate all permutations of assets using the Odometer utility
 *          c. Apply each permutation of the assets to the preconditions.
 *          d. Check if ALL generated preconditions are present in the current
 * factbase. 4a. If all preconditions are found, apply the matching asset group
 * to the postconditions of the exploit. 4b. If not all preconditions are found,
 * break and continue checking with the next exploit.
 *      5. Push the new network state onto the frontier to be expanded later.
 */
AGGenInstance &AGGen::generate(bool batch_process, int batch_size, int numThrd, int initQSize, double mem_threshold) {
    std::vector<Exploit> exploit_list = instance.exploits;

    //Create a vector that contains all the groups of exploits to be fired synchonously
    std::vector<std::string> ex_groups;
    for (const auto &ex : exploit_list) {
        //If the group isn't already in the vector  
        if(!(std::find(ex_groups.begin(), ex_groups.end(), ex.get_group()) !=ex_groups.end())) {
            //Don't include the "no" group
            if(ex.get_group()!="null")
                ex_groups.emplace_back(ex.get_group());                        
        }   
           
    }

    //Print out the groups if desired
    std::cout <<"\nThere are "<<ex_groups.size()<<" groups: ";
    for(int i=0; i<ex_groups.size(); i++){
        std::cout<<ex_groups[i] << ". ";
    }
    std::cout<<"\n";

    auto counter = 0;
    auto start = std::chrono::system_clock::now();

    unsigned long esize = exploit_list.size();
	printf("esize: %lu\n", esize);
    bool save_queued = false;

    std::cout << "Generating Attack Graph" << std::endl;

    std::unordered_map<size_t, PermSet<size_t>> od_map;
    size_t assets_size = instance.assets.size();
    for (const auto &ex : exploit_list) {
        size_t num_params = ex.get_num_params();
        if (od_map.find(num_params) == od_map.end()) {
            Odometer<size_t> od(num_params, assets_size);
            od_map[num_params] = od.get_all();
        }
    }
    /*
//might be where to apply parallelization.
    while (frontier.size()<initQSize){//while starts, test multiple thread case THIS WAS THE ONE MING USED
    //while (frontier.size()!=0){//while starts, test single thread case
   //  while(!frontier.empty()) {
		auto current_state = frontier.back();
        auto current_hash = current_state.get_hash(instance.facts);
        frontier.pop_back();
        std::vector<std::tuple<Exploit, AssetGroup>> appl_exploits;
        for (size_t i = 0; i < esize; i++) {//for loop for applicable exploits starts
            auto e = exploit_list.at(i);
            size_t num_params = e.get_num_params();
            auto preconds_q = e.precond_list_q();
            auto preconds_t = e.precond_list_t();
            auto perms = od_map[num_params];
            std::vector<AssetGroup> asset_groups;
            for (auto perm : perms) {
                std::vector<Quality> asset_group_quals;
                std::vector<Topology> asset_group_topos;
                asset_group_quals.reserve(preconds_q.size());
                asset_group_topos.reserve(preconds_t.size());
                for (auto &precond : preconds_q) {
                    asset_group_quals.emplace_back(
                        perm[precond.get_param_num()], precond.name, precond.op,
                        precond.value, instance.facts);
					}
                for (auto &precond : preconds_t) {
                    auto dir = precond.get_dir();
                    auto prop = precond.get_property();
                    auto op = precond.get_operation();
                    auto val = precond.get_value();
		
                    asset_group_topos.emplace_back(
                        perm[precond.get_from_param()],
                        perm[precond.get_to_param()], dir, prop, op, val, instance.facts);
                }

                asset_groups.emplace_back(asset_group_quals, asset_group_topos,
                                          perm);
            }
            auto assetgroup_size = asset_groups.size();
            for (size_t j = 0; j < assetgroup_size; j++) {
                auto asset_group = asset_groups.at(j);
                for (auto &quality : asset_group.get_hypo_quals()) {
                    if (!current_state.get_factbase().find_quality(quality)) {
                        goto LOOPCONTINUE;
                    }
                }
                for (auto &topology : asset_group.get_hypo_topos()) {
                    if (!current_state.get_factbase().find_topology(topology)) {
                        goto LOOPCONTINUE;
                    }
                }
				//MING DID NOT HAVE THIS CRITICAL, BUT KYLE DID
			//	#pragma omp critical
                {
                    auto new_appl_exploit = std::make_tuple(e, asset_group);
                    appl_exploits.push_back(new_appl_exploit);
                }
            LOOPCONTINUE:;
            }
        } //for loop for applicable exploits ends

        auto appl_expl_size = appl_exploits.size();
        for (size_t j = 0; j < appl_expl_size; j++) { //for loop for new states starts
            auto e = appl_exploits.at(j);
            auto exploit = std::get<0>(e);
            auto assetGroup = std::get<1>(e);
            auto postconditions = createPostConditions(e, instance.facts);
            auto qualities = std::get<0>(postconditions);
            auto topologies = std::get<1>(postconditions);
            NetworkState new_state{current_state};
            for(auto &qual : qualities) {
                auto action = std::get<0>(qual);
                auto fact = std::get<1>(qual);
                switch(action) {
                case ADD_T:
                    new_state.add_quality(fact);
                    break;
                case UPDATE_T:
                    new_state.update_quality(fact);
                    break;
                case DELETE_T:
                    new_state.delete_quality(fact);
                    break;
                }
            }
            for(auto &topo : topologies) {
                auto action = std::get<0>(topo);
                auto fact = std::get<1>(topo);
                switch(action) {
                case ADD_T:
                    new_state.add_topology(fact);
                    break;
                case UPDATE_T:
                    new_state.update_topology(fact);
                    break;
                case DELETE_T:
                    new_state.delete_topology(fact);
                    break;
                }
            }

            auto hash_num = new_state.get_hash(instance.facts);
            if (hash_num == current_hash)
                continue;
            if (hash_map.find(hash_num) == hash_map.end()) {
                    new_state.set_id();
                    auto facts_tuple = new_state.get_factbase().get_facts_tuple();
                    FactbaseItems new_items =
                        std::make_tuple(facts_tuple, new_state.get_id());
                    instance.factbase_items.push_back(new_items);
                    instance.factbases.push_back(new_state.get_factbase());
                    hash_map.insert(std::make_pair(new_state.get_hash(instance.facts), new_state.get_id()));
                    frontier.emplace_front(new_state);
                    Edge ed(current_state.get_id(), new_state.get_id(), exploit, assetGroup);
                    ed.set_id();
                    instance.edges.push_back(ed);
                    counter++;
            }   
            else {
                    int id = hash_map[hash_num];
                    Edge ed(current_state.get_id(), id, exploit, assetGroup);
                    ed.set_id();
                    instance.edges.push_back(ed);
            }
        } //for loop for new states ends
    } //while ends
    
    //int numThrd=32;
    printf("The number of threads used is %d\n",numThrd);
    printf("The initial QSize is %d\n",initQSize); 
    */
    int frt_size=frontier.size();
    printf("The actual QSize to start using multiple threads is %d\n",frt_size); 

	
	double total_t=0.0;
    struct timeval t1,t2;
    gettimeofday(&t1,NULL);
    //#pragma omp parallel for num_threads(numThrd) default(none) shared(esize,counter,exploit_list,od_map,frt_size,total_t,t1,t2) schedule(dynamic,1)
    #pragma omp parallel for num_threads(numThrd) default(none) shared(esize,counter,exploit_list,od_map,frt_size,total_t,t1,t2,std::cout, mem_threshold) schedule(dynamic,1)
    //auto ag_start = std::chrono::system_clock::now();
    for(int k=0;k<frt_size;k++){
    

        //printf("State %d in Frontier\n",k);
    //double alpha = get_alpha();
	double f_alpha = 0.0;
    auto tot_sys_mem = getTotalSystemMemory();
    std::deque<NetworkState> localFrontier;
	localFrontier.emplace_front(frontier[k]);
	while (!localFrontier.empty() || !unex_empty()){//while starts
        //We need to refill the localFrontier with states from the database if it's empty
        if(localFrontier.empty()) {
            std::cout << "Frontier empty, retrieving from database" << std::endl;
            double total_tt = 0.0;
            struct timeval tt1,tt2;
            gettimeofday(&tt1,NULL);
            int retrv_counter = 0;
            
            //TODO: One (or a few) larger queries to pull in new states, rather than single queries that pull states one-by-one
            do {
                NetworkState db_new_state = fetch_unexplored(instance.facts);
                localFrontier.emplace_front(db_new_state);
                //alpha = get_alpha();
                f_alpha = (static_cast<double>(localFrontier.size()) * (localFrontier.back().get_size()))/tot_sys_mem;
                retrv_counter += 1;
            }
            //Leave a 30% buffer in alpha
            while((f_alpha <= (mem_threshold * 0.7)) && !unex_empty());

            std::cout << "Retrieved " << retrv_counter << " factbases from the database." << std::endl;
            gettimeofday(&tt2,NULL);
            total_tt+=(tt2.tv_sec-tt1.tv_sec)*1000.0+(tt2.tv_usec-tt1.tv_usec)/1000.0;
            //printf("Retrieving from db took %lf s.\n", total_tt);
        }
        //std::cout<<"FRONTIER SIZE: "<<localFrontier.size()<<std::endl;
        auto current_state = localFrontier.back();
        auto current_hash = current_state.get_hash(instance.facts);
        localFrontier.pop_back();
        std::vector<std::tuple<Exploit, AssetGroup>> appl_exploits;
		for (size_t i = 0; i < esize; i++) {//for loop for applicable exploits starts
            auto e = exploit_list.at(i);
            size_t num_params = e.get_num_params();
            auto preconds_q = e.precond_list_q();
            auto preconds_t = e.precond_list_t();
            auto perms = od_map[num_params];
            std::vector<AssetGroup> asset_groups;
			for (auto perm : perms) {
                std::vector<Quality> asset_group_quals;
                std::vector<Topology> asset_group_topos;
                asset_group_quals.reserve(preconds_q.size());
                asset_group_topos.reserve(preconds_t.size());

				
				//std::vector<int>::size_type sz;
				//sz=asset_group_quals.capacity();
				for (auto &precond : preconds_q) {
                                    
                    //Old quality encode caused this to crash
					asset_group_quals.emplace_back(
                       perm[precond.get_param_num()], precond.name, precond.op,
                       precond.value, instance.facts);
                }
                for (auto &precond : preconds_t) {
                    auto dir = precond.get_dir();
                    auto prop = precond.get_property();
                    auto op = precond.get_operation();
                    auto val = precond.get_value();
                    asset_group_topos.emplace_back(
                        perm[precond.get_from_param()],
                        perm[precond.get_to_param()], dir, prop, op, val, instance.facts);
                }
                asset_groups.emplace_back(asset_group_quals, asset_group_topos,
                                          perm);
            }
            auto assetgroup_size = asset_groups.size();
            for (size_t j = 0; j < assetgroup_size; j++) {
                auto asset_group = asset_groups.at(j);
				for (auto &quality : asset_group.get_hypo_quals()) {
                    if (!current_state.get_factbase().find_quality(quality)) {
                        goto LOOPCONTINUE1;
                    }
                }
                for (auto &topology : asset_group.get_hypo_topos()) {
                    if (!current_state.get_factbase().find_topology(topology)) {
                        goto LOOPCONTINUE1;
                    }
                }
                {
                    auto new_appl_exploit = std::make_tuple(e, asset_group);
                    appl_exploits.push_back(new_appl_exploit);
                }
            LOOPCONTINUE1:;
            }
        } //for loop for applicable exploits ends


        auto appl_expl_size = appl_exploits.size();
   
        //skip flag is used to ensure that the egroup loop is not repeatedly run more than necessary 
        int skip_flag=0;

        //for (size_t j = 0; j < appl_expl_size; j++) { //(OLD) for loop for new states starts
        
        //vector for holding the appl_exploits indices at which groups exist
        std::vector<int> idr_idx;

        //vector for holding indices that have already fired
        std::vector<int> fired_idx;

        //iterator for the applicable exploits vector
        auto itr=appl_exploits.begin();

        int break_flag=0;
        int testing_flag=0;
        //loop through the vector
        for(auto itr=appl_exploits.begin(); itr!=appl_exploits.end(); itr++){  
            //keep track of index for later use
            auto index=std::distance(appl_exploits.begin(), itr);
            //reset break flag
            break_flag=0;

            //To avoid double-fire, check if an index has already been run.
            //If it has, then there is no need to run through this loop again.
            for(auto itr_f=fired_idx.begin(); itr_f!=fired_idx.end(); itr_f++){
                auto index_f=std::distance(fired_idx.begin(),itr_f);
                if(index==index_f)
                    break_flag=1;
            }

            if (break_flag==1)
                break;

            //empty the appl_exploits index vector at the start of each loop so that
            //it doesn't contain stale data from a previous loop
            idr_idx.clear();
        
            NetworkState new_state{current_state};
            //auto e = appl_exploits.at(j);

            /* Synchronous fire function
                
                First: double/sanity checks to see if there are other exploits that need to be fired
                This also prevents the firing from occurring when it shouldn't via a regular passthrough
                After popping, it checks if the vector is empty. If it is, then we no longer need to
                re-fill the vector since we've gone through all possibilities
            */
            SYNCH_FIRE:;
            if(!idr_idx.empty()){
                //std::cout<<"IDR Size " << idr_idx.size()<<std::endl;
                index=idr_idx.back();
                idr_idx.pop_back();
                if(idr_idx.empty())
                    skip_flag=1;
                fired_idx.push_back(index);
            }
            

            auto e = appl_exploits.at(index);
           	auto exploit = std::get<0>(e);
            //std::cout<<exploit.get_name()<<std::endl;

            //For synchronous firing: get indices of all exploits in the same group and
            //push them onto the index vector for later use
            auto egroup=exploit.get_group();
            
            if (egroup!="null" && idr_idx.empty() && skip_flag==0){
                for(int i=0; i!=appl_exploits.size(); i++){
                    if((std::get<0>(appl_exploits.at(i))).get_group()==egroup && i!=index){
                        idr_idx.emplace_back(i);                      
                    }
                }

                //TODO: If there are other assets in group,
                    //but you check idr_idx after filling and it's still empty
                    //you know that the other asset isn't ready to be fired yet, so wait.
                //THIS BREAKS CODE IF ONLY 1 ASSET IN GROUP EXPLOIT. NEED TO FIGURE OUT HOW TO SEE HOW MANY ASSETS ARE IN GROUP
                //std::cout<<std::get<1>(e).size()<<std::endl;
                //if(std::get<1>(e).size()>1){
                      if(idr_idx.empty()){
                          testing_flag=1;
                      }
               // }
            }
            if(testing_flag==1)
               break;
            skip_flag=0;
            auto assetGroup = std::get<1>(e);
            //assetGroup.print_group();
            //std::cout<<std::endl;
            auto postconditions = createPostConditions(e, instance.facts);
            auto qualities = std::get<0>(postconditions);
            auto topologies = std::get<1>(postconditions);
            
            for(auto &qual : qualities) {
                auto action = std::get<0>(qual);
                auto fact = std::get<1>(qual);
                switch(action) {
                case ADD_T:
                    new_state.add_quality(fact);
                    break;
                case UPDATE_T:
                    new_state.update_quality(fact);
                    
                    //TODO: if fact!= "="" call new_state function, passing fact and instance.facts. Update the quality, and insert it into the hash_table instead of this convoluted mess
                    if(fact.get_op()=="+="){
                        
                        //std::cout<<" AFTER UPDATE "<<new_state.compound_assign(fact)<<std::endl;
                        std::unordered_map<std::string,int>::const_iterator got = instance.facts.hash_table.find(new_state.compound_assign(fact));

                        //If the value is not already in the hash_table, insert it. 
                        //Since the compound operators include a value that is not in the original Keyvalue object, the unordered map does not include it
                        //As a result, you have to manually add it.
                        if(got==instance.facts.hash_table.end()){
                            instance.facts.hash_table[new_state.compound_assign(fact)]=instance.facts.size();
                            instance.facts.length++;
                            instance.facts.str_vector.push_back(new_state.compound_assign(fact));
                        }
                    }
                    break;
                case DELETE_T:
                    new_state.delete_quality(fact);
                    break;
                }
            }
            for(auto &topo : topologies) {
                auto action = std::get<0>(topo);
                auto fact = std::get<1>(topo);
                switch(action) {
                case ADD_T:
                    new_state.add_topology(fact);
                    break;
                case UPDATE_T:
                    new_state.update_topology(fact);
                    break;
                case DELETE_T:
                    new_state.delete_topology(fact);
                    break;
                }
            }
            //appl_exploits.erase(appl_exploits.begin()+index);
            
            if(!idr_idx.empty())
                goto SYNCH_FIRE;
            

            //THIS ALSO CRASHES
            auto hash_num = new_state.get_hash(instance.facts);
            
            if (hash_num == current_hash)
                continue;
    	    //gettimeofday(&t1,NULL);
	    #pragma omp critical
            if (hash_map.find(hash_num) == hash_map.end()) {//although local frontier is updated, the global hash is also updated to avoid testing on explored states.
                    new_state.set_id();
                    auto facts_tuple = new_state.get_factbase().get_facts_tuple();
                    FactbaseItems new_items =
                        std::make_tuple(facts_tuple, new_state.get_id());
                    instance.factbase_items.push_back(new_items);
                    instance.factbases.push_back(new_state.get_factbase());
                    hash_map.insert(std::make_pair(new_state.get_hash(instance.facts), new_state.get_id()));
                    
                    //localFrontier.emplace_front(new_state);
                    //See memory usage. If it exceeds the threshold, store new states in the DB
                    double i_alpha = 0.0;
                    //double i_usage = (sizeof(instance.factbases) + (sizeof(instance.factbases[0]) * instance.factbases.size()) +\
                                    sizeof(instance.factbase_items) + (sizeof(instance.factbase_items[0]) * instance.factbase_items.size()) +\
                                    sizeof(instance.edges) + (sizeof(instance.edges[0]) * instance.edges.size()));
                    //Get the most recent Factbase's size * total number of factbases, rough approximation of *2 to account for factbase_items
                    double i_usage = instance.factbases.back().get_size() * instance.factbases.size() * 2 + sizeof(instance.edges[0]) * instance.edges.size();

                    i_alpha = i_usage/tot_sys_mem;
                    if (!localFrontier.empty())
                        f_alpha = (static_cast<double>(localFrontier.size()) * (localFrontier.back().get_size()))/tot_sys_mem;
                    else
                        f_alpha = 0.0;
                    //std::cout << "Frontier Alpha: " << f_alpha << std::endl;
                    //std::cout << "Instance Alpha: " << i_alpha << std::endl;
                    //std::cout << "Mem Threshold: " << mem_threshold << std::endl;
                    if (f_alpha >= (mem_threshold/2)) {
                        std::cout << "Frontier Alpha prior to database storing: " << f_alpha << std::endl;
                        //std::cout << "Factbase Usage Before: " << sizeof(instance.factbases) + (sizeof(instance.factbases[0]) * instance.factbases.size()) << std::endl;
                        //std::cout << "Factbase Item Usage Before: " << sizeof(instance.factbase_items) + (sizeof(instance.factbase_items[0]) * instance.factbase_items.size()) << std::endl;
                        //std::cout << "Edge Usage Before: " << sizeof(instance.edges) + (sizeof(instance.edges[0]) * instance.edges.size()) << std::endl;

                        save_unexplored_to_db(new_state);
                        if (!localFrontier.empty())
                            f_alpha = (static_cast<double>(localFrontier.size()) * (localFrontier.back().get_size()))/tot_sys_mem;
                        else
                            f_alpha = 0;
                        std::cout << "Frontier Alpha after database storing: " << f_alpha << std::endl;

                        //std::cout << "Storing in database." << std::endl;
                    }

                    //Store new state in database to ensure proper ordering of the FIFO queue
                    else if (!unex_empty()){
                        save_unexplored_to_db(new_state);
                    }
                    //Otherwise, we can just store in memory
                    else {
                        localFrontier.emplace_front(new_state);
                    }

                    if (i_alpha >= mem_threshold/2){
                        std::cout << "Instance Alpha prior to database storing: " << i_alpha << std::endl;
                        save_ag_to_db(instance, true);

                        //Clear vectors and free memory
                        std::vector<Factbase>().swap(instance.factbases);
                        std::vector<FactbaseItems>().swap(instance.factbase_items);
                        std::vector<Edge>().swap(instance.edges);
                        
                        i_usage = (sizeof(instance.factbases) + (sizeof(instance.factbases[0]) * instance.factbases.size()) +\
                                    sizeof(instance.factbase_items) + (sizeof(instance.factbase_items[0]) * instance.factbase_items.size()) +\
                                    sizeof(instance.edges) + (sizeof(instance.edges[0]) * instance.edges.size()));
                        i_alpha = i_usage/tot_sys_mem;
                        std::cout << "Instance Alpha after database storing: " << i_alpha << std::endl;

                    }
                    
                    //NetworkState new_state = fetch_unexplored(instance.facts);
                    
                    Edge ed(current_state.get_id(), new_state.get_id(), exploit, assetGroup);
                    ed.set_id();
                    instance.edges.push_back(ed);
                    counter++;
            }   
            else {
                    int id = hash_map[hash_num];
                    Edge ed(current_state.get_id(), id, exploit, assetGroup);
                    ed.set_id();
                    instance.edges.push_back(ed);
            }
            //gettimeofday(&t2,NULL);
	    //total_t+=(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_usec-t1.tv_usec)/1000.0;
        } //for loop for new states ends
    	} //while ends
        auto ag_end= std::chrono::system_clock::now();
       // std::chrono::duration<double> ag_elapsed = ag_end - ag_start;
    //std::cout << "AG TOOK" << ag_elapsed.count() << std::endl;

    }//OpenMP block ends
    //printf("The critical took %lf s.\n",total_t);
    gettimeofday(&t2,NULL);
    total_t+=(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_usec-t1.tv_usec)/1000.0;
    printf("AG TOOK %lf s.\n", total_t);

    auto end = std::chrono::system_clock::now();
    
    std::chrono::duration<double> elapsed_seconds = end - start;
    instance.elapsed_seconds = elapsed_seconds;

    return instance;
}
