
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <array>
#include <iterator>
#include <sys/time.h>
#include <omp.h>

#include "db_functions.h"

#include "avail_mem.h"
#include "keyvalue.h"
#include "db.h"

#include "../ag_gen/exploit.h"
#include "../ag_gen/edge.h"
#include "../ag_gen/quality.h"
#include "../ag_gen/topology.h"
#include "../ag_gen/asset.h"
#include "../ag_gen/factbase.h"
#include "../ag_gen/network_state.h"

#include <fstream>

static DB db;
//static DB db2;
//static DB db3;
//static DB db4;

void init_db(std::string connect_str) {
    db.connect(connect_str);
    //db.connect("dbname=ag_parallel user=mingli password=N7SBaxkXdoA5venS");
    //db2.connect("postgresql://catherine:1234@localhost:5432/ag_parallel");
    //db3.connect("postgresql://mingli:f5410116@localhost:5432/ag_parallel");
    //db4.connect("postgresql://rosemary:110726@localhost:5432/ag_parallel");
    /*db.exec("BEGIN;");
    db.execAsync("DELETE FROM factbase_item WHERE TRUE;");
    db.execAsync("DELETE FROM edge_asset_binding WHERE TRUE;"); 
    db.execAsync("DELETE FROM edge WHERE TRUE;"); 
    db.execAsync("DELETE FROM factbase WHERE TRUE;"); 
    db.execAsync("DELETE FROM keyvalue WHERE TRUE;"); 
    db.execAsync("DELETE FROM exploit_postcondition WHERE TRUE;"); 
    db.execAsync("DELETE FROM exploit_precondition WHERE TRUE;"); 
    db.execAsync("DELETE FROM exploit WHERE TRUE;"); 
    db.execAsync("DELETE FROM quality WHERE TRUE;"); 
    db.execAsync("DELETE FROM topology WHERE TRUE;"); 
    db.execAsync("DELETE FROM asset WHERE TRUE;"); 
    db.execAsync("COMMIT;");*/
}

void import_models(std::string nm, std::string xp) {
    db.exec(nm);
    db.exec(xp);
}

int get_max_factbase_id() {
    std::vector<Row> res = db.exec("SELECT MAX(id) FROM factbase;");
    return stoi(res[0][0]);
}

std::vector<std::string> fetch_keyvalues() {
    std::vector<Row> rows = db.exec("SELECT property FROM keyvalue;");
    std::vector<std::string> kvs{};

    for(auto &row : rows) {
        kvs.push_back(row[0]);
    }

    return kvs;
}

Keyvalue fetch_kv() {
    auto kv_raw = fetch_keyvalues();
    Keyvalue kv{};
    kv.populate(kv_raw);
    return kv;
}

void delete_edges(std::vector<int> edge_ids) {
    std::ostringstream ss;

    ss << "(";

    std::copy(edge_ids.begin(), edge_ids.end() - 1, std::ostream_iterator<int>(ss, ", "));
    ss << edge_ids.back() << ")";

    std::string ids = ss.str();

    std::string sql = "DELETE FROM edge_asset_binding WHERE edge_id IN " + ids
                    + "; DELETE FROM edge WHERE id IN" + ids + ";";

    db.exec(sql);
}

GraphInfo fetch_graph_info() {
    std::vector<Row> factbase_rows = db.exec("SELECT id FROM factbase ORDER BY id;");
    std::vector<Row> edge_rows = db.exec("SELECT * FROM edge ORDER BY id;");

    std::vector<int> factbase_ids;
    factbase_ids.resize(factbase_rows.size());

    std::transform(factbase_rows.begin(), factbase_rows.end(), factbase_ids.begin(),
                   [](Row row){ return std::stoi(row[0]); });

    std::vector<std::array<int, 4>> edges;

    for (auto& row : edge_rows) {
        int id = std::stoi(row[0]);
        int from_node = std::stoi(row[1]);
        int to_node = std::stoi(row[2]);
        int exploit_id = std::stoi(row[3]);

        std::array<int, 4> arr = {id, from_node, to_node, exploit_id};
        edges.push_back(arr);
    }

    return std::make_pair(factbase_ids, edges);
}

std::vector<std::vector<std::pair<size_t, std::string>>> fetch_all_factbase_items() {
    std::vector<std::vector<std::pair<size_t, std::string>>> fi;
    std::vector<Row> firows = db.exec("SELECT * FROM factbase_item;");
    if (firows.empty())
        throw CustomDBException();

    int current_index = -1;
    for (auto firow : firows) {
        int index = stoi(firow[0]);
        if (index != current_index) {
            current_index = index;
            fi.emplace_back();
        }
        size_t st;
        sscanf(firow[1].c_str(), "%zu", &st);
        fi[index].push_back(make_pair(st, firow[2]));
    }

    return fi;
}

std::vector<std::pair<size_t, std::string>> fetch_one_factbase_items(int index) {
    std::vector<std::pair<size_t, std::string>> fi;
    std::vector<Row> firows = db.exec("SELECT f,type FROM factbase_item WHERE factbase_id=" + std::to_string(index) + ";");
    if (firows.empty())
        throw CustomDBException();

    for (auto firow : firows) {
        size_t st;
        sscanf(firow[0].c_str(), "%zu", &st);
        fi.push_back(std::make_pair(st, firow[1]));
    }

    return fi;
}

std::vector<std::string> fetch_unique_values() { //from the database to fetch all the values and properties and put them in a string vector
    std::string keyvaluesql = "SELECT property FROM quality\n"
                              "UNION DISTINCT\n"
                              "SELECT value FROM quality\n"
                              "UNION DISTINCT\n"
                              //"SELECT op FROM quality\n"
                              //"UNION DISTINCT\n"
                              "SELECT property FROM exploit_postcondition\n"
                              "UNION DISTINCT\n"
                              "SELECT value FROM exploit_postcondition\n"
                              "UNION DISTINCT\n"
                              "SELECT property FROM topology\n"
                              "UNION DISTINCT\n"
                              "SELECT value FROM topology;";//all the values and properties will be distinct from each other

    std::vector<Row> rows = db.exec(keyvaluesql);
    std::vector<std::string> attrs; //this will be a set of values and properties

    for(auto &row : rows) {
        attrs.push_back(row[0]);
    }

    return attrs;
}

/**
 * @brief       Fetches all possible quality attributes.
 *
 * @return     Returns a vector of strings with all possible quality attributes.
 */
std::vector<std::string> fetch_quality_attributes() {
    std::vector<std::string> attrs;
    std::vector<Row> qrows = db.exec("SELECT DISTINCT property FROM quality;");
    std::vector<Row> erows =
        db.exec("SELECT DISTINCT property FROM exploit_postcondition;");

    for (auto &row : qrows) {
        std::string prop = row[0];
        attrs.push_back(prop);
    }

    for (auto &row : erows) {
        std::string prop = row[0];
        attrs.push_back(prop);
    }

    return attrs;
}

/**
 * @brief      Fetches all possible quality values.
 *
 * @return     Returns
 */
std::vector<std::string> fetch_quality_values() {
    std::vector<std::string> vals;
    std::vector<Row> qrows = db.exec("SELECT DISTINCT value FROM quality;");
    std::vector<Row> erows =
        db.exec("SELECT DISTINCT value FROM exploit_postcondition;");

    for (auto &row : qrows) {
        std::string val = row[0];
        vals.push_back(val);
    }

    for (auto &row : erows) {
        std::string val = row[0];
        vals.push_back(val);
    }

    return vals;
}

std::vector<std::string> fetch_topology_attributes() {
    std::vector<std::string> attrs;
    std::vector<Row> rows = db.exec("SELECT DISTINCT property FROM topology;");

    for (auto &row : rows) {
        std::string prop = row[0];
        attrs.push_back(prop);
    }

    return attrs;
}

/**
 * @brief      Fetches all possible topology values.
 *
 * @return     Returns a vector of strings with all possible topology values
 */
std::vector<std::string> fetch_topology_values() {
    std::vector<std::string> vals;
    std::vector<Row> rows = db.exec("SELECT DISTINCT value FROM topology;");

    for (auto &row : rows) {
        std::string val = row[0];
        vals.push_back(val);
    }

    return vals;
}

/**
 * @brief Fetches the preconditions of an Exploit from the database.
 */
std::unordered_map<
    int, std::tuple<std::vector<ParameterizedQuality>, std::vector<ParameterizedTopology>>>
fetch_exploit_preconds() {
    std::vector<Row> rows = db.exec("SELECT * FROM exploit_precondition;");

    std::unordered_map<
        int, std::tuple<std::vector<ParameterizedQuality>, std::vector<ParameterizedTopology>>>
        precond_map;

    int curr_id = -1;
    std::vector<ParameterizedQuality> preconds_q;
    std::vector<ParameterizedTopology> preconds_t;
    for (auto &row : rows) {
        int type = stoi(row[2]);
        int exploit_id = stoi(row[1]);

        if (exploit_id != curr_id) {
            if (curr_id != -1) {
                std::tuple<std::vector<ParameterizedQuality>,
                      std::vector<ParameterizedTopology>>
                    tup{preconds_q, preconds_t};
                precond_map[curr_id] = tup;

                preconds_q.clear();
                preconds_t.clear();
            }

            curr_id = exploit_id;
        }

        if (type == 0) {//type 0 is quality-typed precondition
            int param1 = stoi(row[3]);
            std::string property = row[5];
            std::string value = row[6];
			std::string op = row[7];

            ParameterizedQuality qual{param1, property, value, op};
            preconds_q.push_back(qual);
        } else { //type 1 is topology-typed precondition
            int param1 = stoi(row[3]);
            int param2 = stoi(row[4]);
            std::string property = row[5];
            std::string value = row[6];
            std::string op = row[7];
            std::string dir_str = row[8];

            DIRECTION_T dir;
            if(dir_str == "->") {
                dir = FORWARD_T;
            } else if (dir_str == "<-") {
                dir = BACKWARD_T;
            } else if (dir_str == "<->") {
                dir = BIDIRECTION_T;
            } else {
                std::cerr << "Unknown direction '" << dir_str << "'" << std::endl;
                exit(1);
            }

            ParameterizedTopology topo{param1,   param2, dir,
                                       property, op,     value}; //parameterized into a struct
            preconds_t.push_back(topo);
        }
    }

    std::tuple<std::vector<ParameterizedQuality>, std::vector<ParameterizedTopology>> tup{
        preconds_q, preconds_t};
    precond_map[curr_id] = tup;//the last exploit's preconditions (quality and topology) also needs to put in the unordered map

    return precond_map; //the unordered map use the exploit id as the key, the value of each item is the parameterized quality and topology
}

/**
 * @brief Fetches the postconditions of an Exploit from the database.
 */
std::unordered_map<
    int, std::tuple<std::vector<PostconditionQ>, std::vector<PostconditionT>>>
fetch_exploit_postconds() {
    std::vector<Row> rows = db.exec("SELECT * FROM exploit_postcondition;");

    std::unordered_map<
        int, std::tuple<std::vector<PostconditionQ>, std::vector<PostconditionT>>>
        postcond_map;

    int curr_id = -1;
    std::vector<PostconditionQ> postconds_q; //PostconditionQ and PostconditionT are defined in exploit.h
    std::vector<PostconditionT> postconds_t;
    for (auto &row : rows) {
        int type = stoi(row[2]);//type 0 change on quality, 1 change on topology(seldom)
        int exploit_id = stoi(row[1]);

        std::string action_str = row[9];
        ACTION_T action;

        // std::cout << action_str << "\n";

        if(action_str == "add" || action_str == "insert") {
            action = ADD_T;
        } else if (action_str == "update") {
            action = UPDATE_T;
        } else if (action_str == "delete") {
            action = DELETE_T;
        } else {
            std::cout << "Bad Action '" << action_str << "'" << std::endl;
            exit(1);
        }

        if (exploit_id != curr_id) {
            if (curr_id != -1) {
                std::tuple<std::vector<PostconditionQ>,
                      std::vector<PostconditionT>>
                    tup{postconds_q, postconds_t};
                postcond_map[curr_id] = tup;

                postconds_q.clear();
                postconds_t.clear();
            }

            curr_id = exploit_id;
        }

        if (type == 0) {
            int param1 = stoi(row[3]);
            std::string property = row[5];
            std::string value = row[6];
			std::string op = row[7];

            ParameterizedQuality qual{param1, property, value, op};
            postconds_q.push_back(std::make_tuple(action, qual));
        } else {
            int param1 = stoi(row[3]);
            int param2 = stoi(row[4]);
            std::string property = row[5];
            std::string value = row[6];
            std::string op = row[7];
            std::string dir_str = row[8];

            DIRECTION_T dir;
            if(dir_str == "->") {
                dir = FORWARD_T;
            } else if (dir_str == "<-") {
                dir = BACKWARD_T;
            } else if (dir_str == "<->") {
                dir = BIDIRECTION_T;
            } else {
                std::cerr << "Unknown direction '" << dir_str << "'" << std::endl;
                exit(1);
            }

            ParameterizedTopology topo{param1,   param2, dir,
                                       property, op,     value};
            postconds_t.push_back(std::make_tuple(action, topo));
        }
    }

    std::tuple<std::vector<PostconditionQ>, std::vector<PostconditionT>> tup{
        postconds_q, postconds_t};
    postcond_map[curr_id] = tup;

    return postcond_map;
}

std::vector<Exploit> fetch_all_exploits() {
    std::vector<Exploit> exploits;
    std::vector<Row> rows = db.exec("SELECT * FROM exploit;");

    auto pre = fetch_exploit_preconds();
    auto post = fetch_exploit_postconds();

    for (auto &row : rows) {
        int id = std::stoi(row[0]);
        std::string name = row[1];
        int num_params = std::stoi(row[2]);
        std::string group = row[3];

        auto preconds = pre[id];
        auto postconds = post[id];
        //Use Exploit class's obj exploit to store each exploit's complete info, especially pre-and-post conditions
        Exploit exploit(id, name, num_params, group, preconds, postconds);
        exploits.push_back(exploit);//push into exploits vector
    }

    return exploits; //the vector will be returned to the calling function.
}

/**
 * @brief Gets all of the qualities for the Asset
 * @details Grabs all of the qualities in the database associated with
 *          the Asset's ID and gives them to the Asset
 */
std::unordered_map<int, std::vector<Quality>> fetch_asset_qualities(Keyvalue &facts) {
    std::vector<Row> rows = db.exec("SELECT * FROM quality;");

    std::unordered_map<int, std::vector<Quality>> qmap;

    int curr_id = -1;
    std::vector<Quality> qualities;
    for (auto &row : rows) {
        int asset_id = std::stoi(row[0]);
        std::string property = row[1];
        std::string op = row[2];
        std::string value = row[3];
        // cout << "op: " << op << endl;

        if (asset_id != curr_id) {
            if (curr_id != -1) { //a new asset's quality has been reached
                qmap[curr_id] = qualities; //all the old qualities are put in the old asset's element in the unordered map 
                qualities.clear(); //clear the qualities for the new asset
            }
            curr_id = asset_id; //unconditional update curr_id to asset_id whenever a new asset is reached
        }
        // Quality qual(asset_id, property, "=", value);
        // qualities.push_back(qual);

        qualities.emplace_back(asset_id, property, op, value, facts);//each asset's qualities will be put in a dedicated Quality vector
    }

    qmap[curr_id] = qualities;//the last asset's qualities must be put in the unordered map too

    return qmap;
}

/**
 * @brief Gets all of the Assets under the network
 * @details Grabs all of the Assets in the database under the network given in
 *          the argument and returns a vector of those Assets
 *
 * @param network Name of the network to grab from
 */
std::vector<Asset> fetch_all_assets(Keyvalue &facts) {
    std::vector<Row> rows = db.exec("SELECT * FROM asset;");  
    std::vector<Asset> new_assets; //use class Asset (in asset.h) to define a vector, each element will be an object

    auto qmap = fetch_asset_qualities(facts); //combine each asset with all its qualities

    for (auto &row : rows) {
        int id = std::stoi(row[0]);
        std::string name = row[1];
        auto q = qmap[id]; //this returns a Quality vector, which includes all the records in the quality table related to the current id.
        new_assets.emplace_back(name, q);
    }

    return new_assets;
}

std::vector<Quality> fetch_all_qualities(Keyvalue &facts) {
    std::vector<Quality> qualities;
    std::vector<Row> rows = db.exec("SELECT * FROM quality;");

    for (auto &row : rows) {
        int asset_id = std::stoi(row[0]);  //stoi means string to integer, since rows is a vector of string vectors. The smallest elements in rows are string type.
        std::string property = row[1];
        std::string op = row[2];
        std::string value = row[3];

        Quality qual(asset_id, property, op, value, facts); //associate each record from quality table with the facts(the hash_table and fact string vector). 
        qualities.push_back(qual);
    }

    return qualities;
}

std::vector<Topology> fetch_all_topologies(Keyvalue &facts) {
    std::vector<Topology> topologies;

    std::vector<Row> rows = db.exec("SELECT * FROM topology;");
    for (auto &row : rows) {
        int from_asset = std::stoi(row[0]);
        int to_asset = std::stoi(row[1]);
        std::string dir_str = row[2];

        DIRECTION_T dir; //DIRECTION_T is a user defined enum type (in topology.h), it has three different values: FORWARD_T 0, BACKWARD_T 1, and BIDIRECTION_T 2 
        if(dir_str == "->") {
            dir = FORWARD_T;
        } else if (dir_str == "<-") {
            dir = BACKWARD_T;
        } else if (dir_str == "<->") {
            dir = BIDIRECTION_T;
        } else {
            std::cerr << "Unknown direction '" << dir_str << "'" << std::endl;
            exit(1);
        }

        std::string property = row[3];
        std::string op = row[4];
        std::string value = row[5];

        Topology t(from_asset, to_asset, dir, property, op, value, facts); //associate each record from topology table with the facts
        topologies.push_back(t);
    }

    return topologies;
}

Keyvalue fetch_facts() { //Keyvalue is a class name(defined in Keyvalue.h), this function will return an Keyvalue obj
    Keyvalue initfacts;

   // initfacts.populate(fetch_quality_attributes());
   // initfacts.populate(fetch_quality_values());
   // initfacts.populate(fetch_topology_attributes());
   // initfacts.populate(fetch_topology_values());
    initfacts.populate(fetch_unique_values());

    return initfacts;
}

// void save_ag_to_db(std::vector<FactbaseItems> &factbase_items,
//                    std::vector<Factbase> &factbases, std::vector<Edge> &edges,
//                    Keyvalue &factlist) {
void save_ag_to_db(AGGenInstance &instance, bool save_keyvalue){
    std::vector<FactbaseItems>& factbase_items = instance.factbase_items;
    std::vector<Factbase>& factbases = instance.factbases;
    std::vector<Edge>& edges = instance.edges;
    auto tot_sys_mem = getTotalSystemMemory();
	
    Keyvalue& factlist = instance.facts;

    struct timeval t1,t2;
	
	/** backup cout buffer and redirect to states.txt **/
	std::ofstream out("states.txt");
	auto *coutbuf = std::cout.rdbuf();
	std::cout.rdbuf(out.rdbuf());

    //this part takes 0.3s
    gettimeofday(&t1,NULL);
    db.exec("BEGIN;");
    if (!factbases.empty()){
        //printf("The size of the factbases is %ld\n",factbases.size());
        
		std::string factbase_sql_query = "INSERT INTO factbase VALUES ";
        int factbase_counter = 0;
        int flag = 0;
        double fbsize = 0;

        for (int i = 0; i < factbases.size(); ++i) {


			/** redirect state info printing to states.txt file **/
			factbases[i].print();

            if (i == 0 || flag == 1) {
                factbase_sql_query += "(" + std::to_string(factbases[i].get_id()) +
                                      ",'" +                                   
										std::to_string(factbases[i].hash(factlist)) + "')";	
                flag = 0;							
			} else {
                factbase_sql_query += ",(" + std::to_string(factbases[i].get_id()) +
                                      ",'" +
                                      std::to_string(factbases[i].hash(factlist)) + "')";
										
			}
            //Break up query due to memory constraints. Suboptimal approach.
            factbase_counter += 1;
            fbsize = sizeof(factbase_sql_query);
            if (fbsize/tot_sys_mem >= 0.1){
                factbase_sql_query += ";";
                db.execAsync(factbase_sql_query);
                flag = 1;
                factbase_sql_query = "INSERT INTO factbase VALUES ";
                db.exec("COMMIT;");
                db.exec("BEGIN;");

            }
        }
        factbase_sql_query += ";";
        db.execAsync(factbase_sql_query);	
    }
    db.execAsync("COMMIT;");

	/** reset cout buffer **/
	std::cout.rdbuf(coutbuf);

    //gettimeofday(&t2,NULL);
    //printf("The preparation of factbases took %lf ms\n",(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_usec-t1.tv_usec)/1000.0);
    
    

    //this part takes 1.5s
    //gettimeofday(&t1,NULL);
    //potentially bug in forming the string for db storage, check if the first record has a comma before it
    /*FACTBASE ITEMS
    if (!factbase_items.empty()) {
	int fis=factbase_items.size();
	//fis=4;
	//#pragma omp parallel for num_threads(4)\
	//default(none) shared(factbase_items,fis,db,db2,db3,db4,std::cout) schedule(static,1)
	#pragma omp parallel for num_threads(1)\
	default(none) shared(factbase_items,fis,db,std::cout)
	//for(int k=0;k<4;k++){	
    for(int k=0; k<1; k++) {
        std::string item_sql_query = "INSERT INTO factbase_item VALUES ";
        std::string quality_sql_query = "";
        std::string topology_sql_query = "";
	int sql_index=0;
    *///FACTBASE ITEMS

    /*
	for (int j = 0; j<fis/4+((k==3)?(fis%4):0); j++){
        //for (int j = 0, sql_index = 0; j < factbase_items.size(); ++j) {
            auto fbi = factbase_items[j+k*(fis/4)];
            int id = std::get<1>(fbi);
            auto items = std::get<0>(fbi);
            auto quals = std::get<0>(items);
            auto topo = std::get<1>(items);
            for (auto qi : quals) {
    */
    //UNCOMMENT FOR FACTBASE ITEM STORAGE
    /* 
    int fbi_counter = 0;
    int fbi_q_flag = 0;
    int fbi_t_flag = 0;
    for (int j = 0, sql_index = 0; j < factbase_items.size(); ++j) {
            auto fbi = factbase_items[j];
            int id = std::get<1>(fbi);
            auto items = std::get<0>(fbi);
            auto quals = std::get<0>(items);
            auto topo = std::get<1>(items);
            for (auto qi : quals) {
                if (sql_index == 0 || fbi_q_flag == 1){
                    quality_sql_query += "(" + std::to_string(id) + "," +
                                         std::to_string(qi.get_encoding()) +
                                         ",'quality')";
                    fbi_q_flag = 0;
                }

                else
                    quality_sql_query += ",(" + std::to_string(id) + "," +
                                         std::to_string(qi.get_encoding()) +
                                         ",'quality')";
                sql_index++;
            }

            for (auto ti : topo) {
                if (sql_index == 0 || fbi_t_flag == 1) {
                    topology_sql_query += "(" + std::to_string(id) + "," +
                                          std::to_string(ti.get_encoding()) +
                                          ",'topology')";
                    fbi_t_flag = 0;
                }

                else
                    topology_sql_query += ",(" + std::to_string(id) + "," +
                                          std::to_string(ti.get_encoding()) +
                                          ",'topology')";
                sql_index++;
            }
         //Break up query due to memory constraints. Suboptimal approach.
            fbi_counter += 1;
            if (fbi_counter >= 500){
                item_sql_query += quality_sql_query + topology_sql_query + "ON CONFLICT DO NOTHING;";
                db.exec("BEGIN;");
                db.execAsync(item_sql_query);
                db.execAsync("COMMIT;");
                fbi_q_flag = 1;
                fbi_t_flag = 1;
                item_sql_query = "INSERT INTO factbase_item VALUES ";
                quality_sql_query = "";
                topology_sql_query = "";
            } 

        }
	
    item_sql_query += quality_sql_query + topology_sql_query + "ON CONFLICT DO NOTHING;";
	int thrd_num=omp_get_thread_num();
	if(thrd_num==0){
	db.exec("BEGIN;");
        db.execAsync(item_sql_query);
	db.execAsync("COMMIT;");
	}
	else if(thrd_num==1){
	//db2.exec("BEGIN;");
        //db2.execAsync(item_sql_query);
	//db2.execAsync("COMMIT;");
	}
	else if(thrd_num==2){
	//db3.exec("BEGIN;");
        //db3.execAsync(item_sql_query);
	//db3.execAsync("COMMIT;");
	}
	else {
	//db4.exec("BEGIN;");
        //db4.execAsync(item_sql_query);
	//db4.execAsync("COMMIT;");
	}
	}
    }
    gettimeofday(&t2,NULL);
    //printf("The saving of factbase and items took %lf ms\n",(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_usec-t1.tv_usec)/1000.0);
    *///FACTBASE ITEMS

     //this part takes 42.0s
    //probably where to apply parallelization, this part takes most of the db saving time (>=80%)
    gettimeofday(&t1,NULL);
    int edge_counter = 0;
    int edge_flag = 0;
    if (!edges.empty()) {
        std::vector<std::string> edge_queries;
        edge_queries.resize(edges.size());
        //convert each edge into a string which will be used by db insert
        std::transform(edges.begin(), edges.end(), edge_queries.begin(), to_query);//to_query is a unary operation on all the elements in edges. It is defined in db_function.
     
        struct timeval t3,t4;
        //---first way to build the map
        std::unordered_map<std::string, int> eq;
	gettimeofday(&t3,NULL);
        auto ei = edge_queries.begin(); //returns an iterator, not the 1st element!
        //std::cout<<"The first element should be "<<edge_queries.at(0)<<std::endl;
	int j;
        for (j = 0; ei != edge_queries.end(); j++, ei++) //this for loop takes only 76.5ms, it gives each edge string an integer value and stores in unordered map (like a dictionary)
	{        
	eq.insert({*ei, j});//all keys must be different, which means not all key-value pairs are inserted!!!
	}

        //----first method to store into db
        //---------------------------------
        //---------------------------------
	//eq is dictionary
	//edges an Edge vector as the member of input postinstance
        std::string edge_sql_query = "INSERT INTO edge VALUES ";
        std::string edge_assets_sql_query = "INSERT INTO edge_asset_binding VALUES ";
        int ii = 0;
        double esql = 0;
        double easql = 0;
        for (auto ei : eq) {
            int i = ei.second;

            int eid = edges[i].get_id();
            //int eid = edges[i].set_id();

            if (ii == 0 | edge_flag == 1) {
                edge_sql_query += "(" + std::to_string(eid) + "," + ei.first;
                edge_assets_sql_query += edges[i].get_asset_query();
                edge_flag = 0;
            } else {
                edge_sql_query += ",(" + std::to_string(eid) + "," + ei.first;
                edge_assets_sql_query += "," + edges[i].get_asset_query();
            }

            ++ii;
            //Break up query due to memory constraints. Suboptimal approach.
            edge_counter += 1;
            esql = sizeof(edge_sql_query);
            easql = sizeof(edge_assets_sql_query);

            if (esql/tot_sys_mem >= 0.1 || easql/tot_sys_mem >= 0.1){
                edge_sql_query += "ON CONFLICT DO NOTHING;";
                edge_assets_sql_query += "ON CONFLICT DO NOTHING;";
                db.execAsync(edge_sql_query);
                db.execAsync(edge_assets_sql_query);
                edge_flag = 1;
                edge_sql_query = "INSERT INTO edge VALUES ";
                edge_assets_sql_query = "INSERT INTO edge_asset_binding VALUES ";
            } 
        }	
        edge_sql_query += " ON CONFLICT DO NOTHING;";
        edge_assets_sql_query += " ON CONFLICT DO NOTHING;";
	struct timeval t5,t6,t7;
	gettimeofday(&t5,NULL);
        db.execAsync(edge_sql_query);//33.7s
	gettimeofday(&t6,NULL);
        db.execAsync(edge_assets_sql_query); //7.6s
	gettimeofday(&t7,NULL);
    //	printf("The saving of edges took %lf ms\n",(t6.tv_sec-t5.tv_sec)*1000.0+(t6.tv_usec-t5.tv_usec)/1000.0);
	//printf("The saving of edges_assets_bingding took %lf ms\n",(t7.tv_sec-t6.tv_sec)*1000.0+(t7.tv_usec-t6.tv_usec)/1000.0);

      //----second method to store into db
	  //----------------------------------
	//----------------------------------
	/*
	std::vector<std::string> unique_eq;
	int unique_idx[eq.size()];
	int jj=0;
	for(auto ei: eq) {
	    unique_eq.push_back(ei.first);
	    unique_idx[jj]=ei.second;
	    jj++;
	}
	gettimeofday(&t4,NULL);
    	//printf("The preparation of eq took %lf ms\n",(t4.tv_sec-t3.tv_sec)*1000.0+(t4.tv_usec-t3.tv_usec)/1000.0);

	struct timeval t5,t6;
	gettimeofday(&t5,NULL);
	//jj=6;
	//#pragma omp parallel for num_threads(2)\
	//default(none) shared(jj,unique_idx,edges,unique_eq,std::cout,db,db2,db3,db4)
	#pragma omp parallel for num_threads(1)\
	default(none) shared(jj,unique_idx,edges,unique_eq,std::cout,db)
        for(int k=0;k<2;k++)
	{
        std::string edge_sql_query = "INSERT INTO edge VALUES ";
        std::string edge_assets_sql_query = "INSERT INTO edge_asset_binding VALUES ";
		std::cout << "TEST CONDITION: " << ((jj/2)+(k==1)?(jj%2):0);
		for(int i=0;i<jj/2+(k==1)?(jj%2):0;i++){
	    int idx=unique_idx[i+k*(jj/2)];
	    int eid = edges[idx].get_id();
            if(i==0){
	    	edge_sql_query += "(" + std::to_string(eid) + "," + unique_eq[i+k*(jj/2)];
                edge_assets_sql_query += edges[idx].get_asset_query();
	    }
	    else{
                edge_sql_query += ",(" + std::to_string(eid) + "," + unique_eq[i+k*(jj/2)];
                edge_assets_sql_query += "," + edges[idx].get_asset_query();		
	    }
	}	
        edge_sql_query += ";";
        edge_assets_sql_query += ";";
	int thrd_num=omp_get_thread_num();
	if(thrd_num==0){
	db.exec("BEGIN;");
        db.execAsync(edge_sql_query);//33.7s
        db.execAsync(edge_assets_sql_query); //7.6s
	db.execAsync("COMMIT;");//this part only takes 0.5ms
	}
	else if(thrd_num==1){
	//db2.exec("BEGIN;");
        //db2.execAsync(edge_sql_query);//33.7s
        //db2.execAsync(edge_assets_sql_query); //7.6s
	//db2.execAsync("COMMIT;");//this part only takes 0.5ms
	}
	else if(thrd_num==2){
	//db3.exec("BEGIN;");
        //db3.execAsync(edge_sql_query);//33.7s
        //db3.execAsync(edge_assets_sql_query); //7.6s
	//db3.execAsync("COMMIT;");//this part only takes 0.5ms
	}
	else{
	//db4.exec("BEGIN;");
        //db4.execAsync(edge_sql_query);//33.7s
        //db4.execAsync(edge_assets_sql_query); //7.6s
	//db4.execAsync("COMMIT;");//this part only takes 0.5ms
	}
	}
	gettimeofday(&t6,NULL);
	//printf("The parallel part took %lf ms\n",(t6.tv_sec-t5.tv_sec)*1000.0+(t6.tv_usec-t5.tv_usec)/1000.0);
 */
	}

    gettimeofday(&t2,NULL);
    //printf("The saving of edge and edge_asset_binding took %lf ms\n",(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_usec-t1.tv_usec)/1000.0);//42.0s

    //this part takes around 8.6s
    gettimeofday(&t1,NULL);
    db.exec("BEGIN;");
    int cnt1=0;
    if (save_keyvalue) {
        std::ostringstream out;
        out << "INSERT INTO keyvalue VALUES ";
        std::vector<std::string> keyvalue_vector = factlist.get_str_vector();
        size_t count = 0;
        for(auto &value : keyvalue_vector) {
            if(count == 0)
                out << "(" << std::to_string(count++) << ",'" << value << "')";
            else
                out << ",(" << std::to_string(count++) << ",'" << value << "')";
            cnt1=cnt1+1;
        }
        out << ";";
        db.execAsync(out.str());
    }
    db.execAsync("COMMIT;");
    gettimeofday(&t2,NULL);
    //printf("The saving of keyvalue took %lf ms\n",(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_usec-t1.tv_usec)/1000.0);
}

void save_unexplored_to_db(NetworkState netstate) {
    int fid = netstate.get_id();
    const Factbase fb = netstate.get_factbase();

    auto facts_tuple = fb.get_facts_tuple();
    auto qualities = std::get<0>(facts_tuple);
    auto topologies = std::get<1>(facts_tuple);

    std::string qual_sql_query = "INSERT INTO unex_state_q VALUES ";
    std::string top_sql_query = "INSERT INTO unex_state_t VALUES ";

    int i = 0;

    for(auto qual : qualities) {
        auto asset_id = qual.get_asset_id();
        auto name = qual.get_name(); 
        auto op = qual.get_op();
        auto val = qual.get_value();

        if (i==0) {
                qual_sql_query += "(" + std::to_string(fid) + "," \
                + std::to_string(asset_id) + "," + "'" + name + "'" + ","\
                + "'" + op +"'" + "," + "'" + val + "'" + ")";

        } 
        else {
                qual_sql_query += ",(" + std::to_string(fid) + "," \
                + std::to_string(asset_id) + "," + "'" + name + "'" + ","\
                + "'" + op +"'" + "," + "'" + val + "'" + ")";
        }

            ++i;
    }   

    i = 0;

    for (auto ti : topologies) {
        auto from_id = ti.get_from_asset_id();
        auto to_id = ti.get_to_asset_id();
        auto dir = ti.get_dir();
        auto prop = ti.get_property();
        auto op = ti.get_op();
        auto val = ti.get_value();
        
        if (i == 0) {
            top_sql_query += "(" + std::to_string(fid) + "," \
                                  + std::to_string(from_id) + ","\
                                  + std::to_string(to_id) + ","\
                                  + std::to_string(dir) + ","\
                                  + "'" + prop + "'" + ","\
                                  + "'" + op + "'" + ","\
                                  + "'" + val + "'" + ")";
        }
        else {
            top_sql_query += ",(" + std::to_string(fid) + "," \
                                  + std::to_string(from_id) + ","\
                                  + std::to_string(to_id) + ","\
                                  + std::to_string(dir) + ","\
                                  + "'" + prop + "'" + ","\
                                  + "'" + op + "'" + ","\
                                  + "'" + val + "'" + ")";
        }

        ++i;
    }
    
    qual_sql_query += " ON CONFLICT DO NOTHING;";
    top_sql_query += " ON CONFLICT DO NOTHING;";

    struct timeval t1,t2;
    gettimeofday(&t1,NULL);

    db.execAsync(qual_sql_query);
    db.execAsync(top_sql_query);
    
    gettimeofday(&t2,NULL);
    //printf("The saving of unexplored states took %lf ms\n",(t2.tv_sec-t1.tv_sec)*1000.0+(t2.tv_usec-t1.tv_usec)/1000.0);    
}

//Fetch Qualities, Topologies, reform into a factbase, then return the factbase
NetworkState fetch_unexplored(Keyvalue &facts){
    std::vector<Quality> qualities;
    std::vector<Topology> topologies;
    int id;

    //Pulls the fid from the first row in the unex_state Table, deletes all rows with this fid, then returns all deleted rows
    std::string qdel_str = "DELETE FROM unex_state_q WHERE fid=(SELECT fid FROM unex_state_q LIMIT 1) RETURNING *;";
    std::string tdel_str = "DELETE FROM unex_state_t WHERE fid=(SELECT fid FROM unex_state_t LIMIT 1) RETURNING *;";

    std::vector<Row> qrows = db.exec(qdel_str);

    for (auto &row : qrows) {
        int fid = std::stoi(row[0]);
        int asset_id = std::stoi(row[1]);
        std::string property = row[2];
        std::string op = row[3];
        std::string value = row[4];

        Quality qual(asset_id, property, op, value, facts); //associate each record from unex_state table with the facts(the hash_table and fact string vector). 
        qualities.push_back(qual);
        id = fid;
    }

    std::vector<Row> trows = db.exec(tdel_str);
    for (auto &row : trows) {
        //int fid = std::stoi(row[0]);
        int from_asset = std::stoi(row[1]);
        int to_asset = std::stoi(row[2]);
        std::string dir_str = row[3];

        DIRECTION_T dir; //DIRECTION_T is a user defined enum type (in topology.h), it has three different values: FORWARD_T 0, BACKWARD_T 1, and BIDIRECTION_T 2 
        if(dir_str == "->" || dir_str == "0") {
            dir = FORWARD_T;
        } else if (dir_str == "<-" || dir_str == "1") {
            dir = BACKWARD_T;
        } else if (dir_str == "<->" || dir_str == "2") {
            dir = BIDIRECTION_T;
        } else {
            std::cerr << "Unknown direction '" << dir_str << "'" << std::endl;
            exit(1);
        }

        std::string property = row[4];
        std::string op = row[5];
        std::string value = row[6];

        Topology t(from_asset, to_asset, dir, property, op, value, facts); //associate each record from topology table with the facts
        topologies.push_back(t);
    }

    NetworkState fetched_state(qualities, topologies);
    fetched_state.force_set_id(id);

    return fetched_state;
}

bool unex_empty()
{
    std::vector<Row> row = db.exec("SELECT count(*) FROM unex_state_q LIMIT 1;");
    for (auto &r : row){  
        auto count = std::stoi(r[0]);
        if (count == 0)
            return true;
        else
            return false;
    }
}

int get_num_states()
{
    std::vector<Row> row = db.exec("SELECT count(*) AS exact_count FROM factbase;");
    for (auto &r : row) {
        return std::stoi(r[0]);
    }
}

int get_num_edges()
{
    std::vector<Row> row = db.exec("SELECT count(*) AS exact_count FROM edge;");
    for (auto &r : row) {
        return std::stoi(r[0]);
    }
}