#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <unistd.h>

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

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/graph/visitors.hpp>
#include <boost/graph/depth_first_search.hpp>


#include <boost/archive/tmpdir.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

#include <boost/serialization/base_object.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/assume_abstract.hpp>
#include <boost/serialization/string.hpp>

#include <boost/mpi.hpp>
#include <boost/mpi/environment.hpp>
#include <boost/mpi/communicator.hpp>

#include <boost/serialization/is_bitwise_serializable.hpp>

namespace mpi = boost::mpi;


void save_quality(const Quality &q, const char * filename){
    std::ofstream ofs(filename);
    boost::archive::text_oarchive oa(ofs);
    oa << q;
}

void restore_quality(Quality &q, const char * filename){
    std::ifstream ifs(filename);
    boost::archive::text_iarchive ia(ifs);
    ia >> q;
}

int quality_check(Quality &q1, Quality &q2){
    if (q1.get_asset_id() == q2.get_asset_id() &&
        q1.get_name() == q2.get_name() && 
        q1.get_op() == q2.get_op() &&
        q1.get_value() == q2.get_value())
    {
        return 1;
    }

    else return 0;
}

int param_quality_check(ParameterizedQuality &pq1, ParameterizedQuality &pq2){
    if (pq1.param == pq2.param &&
        pq1.name == pq2.name &&
        pq1.value == pq1.value &&
        pq1.op == pq2.op)
   {
    return 1;
   }

    else return 0;
}

int param_topology_check(ParameterizedTopology &pt1, ParameterizedTopology &pt2){
    if (pt1.get_from_param() == pt2.get_from_param() &&
        pt1.get_to_param() == pt2.get_to_param() &&
        pt1.get_dir() == pt2.get_dir() && 
        pt1.get_property() == pt2.get_property() &&
        pt1.get_operation() == pt2.get_operation() &&
        pt1.get_value() == pt2.get_value())
    {
        return 1;
    }

    else return 0;
}

int postcond_quality_check(PostconditionQ &pq1, PostconditionQ &pq2){

    auto preq1 = std::get<1>(pq1);
    auto preq2 = std::get<1>(pq2);

    if(param_quality_check(preq1, preq2) == 1 &&
        std::get<0>(pq1) == std::get<0>(pq2))
    {
        return 1;
    }

    else return 0;
}

int postcond_topology_check(PostconditionT &pt1, PostconditionT &pt2){

    auto pret1 = std::get<1>(pt1);
    auto pret2 = std::get<1>(pt2);

    if(param_topology_check(pret1, pret2) == 1 &&
        std::get<0>(pt1) == std::get<0>(pt2))
    {
        return 1;
    }

    else return 0;
}

int exploit_check(Exploit &exp1, Exploit &exp2){
    int base_corr = 0;

    //Precondition counters
    int pre_qual_corr = 0;
    int pre_qual_count = 0;
    int pre_topo_corr = 0;
    int pre_topo_count = 0;

    //Postcondition counters
    int post_qual_corr = 0;
    int post_qual_count = 0;
    int post_topo_corr = 0;
    int post_topo_count = 0;

    //Base member check correctness
    if (exp1.get_id() == exp2.get_id() &&
        exp1.get_name() == exp2.get_name() &&
        exp1.get_group() == exp2.get_group() &&
        exp1.get_num_params() == exp2.get_num_params())
    {
        base_corr = 1;
    }

    //Init lists for pre and post conditions for quals and topos
    auto exp1_pre_quals = exp1.precond_list_q();
    auto exp2_pre_quals = exp2.precond_list_q();

    auto exp1_pre_topos = exp1.precond_list_t();
    auto exp2_pre_topos = exp2.precond_list_t();

    auto exp1_post_quals = exp1.postcond_list_q();
    auto exp2_post_quals = exp2.postcond_list_q();

    auto exp1_post_topos = exp1.postcond_list_t();
    auto exp2_post_topos = exp2.postcond_list_t();

    //Init iterators for preconds
    auto itq1 = exp1_pre_quals.begin();
    auto itq2 = exp2_pre_quals.begin();

    auto itt1 = exp1_pre_topos.begin();
    auto itt2 = exp2_pre_topos.begin();

    //Check Precondition Qualities
    while(itq1 != exp1_pre_quals.end() || itq2 != exp2_pre_quals.end())
    {
        pre_qual_corr += param_quality_check(*itq1, *itq2);
        pre_qual_count++;

        if(itq1 != exp1_pre_quals.end())
            ++itq1;

        if(itq2 != exp2_pre_quals.end())
            ++itq2;
    }

    //Check Precondition Topologies
    while(itt1 != exp1_pre_topos.end() || itt2 != exp2_pre_topos.end())
    {
        pre_topo_corr += param_topology_check(*itt1, *itt2);
        pre_topo_count++;

        if(itt1 != exp1_pre_topos.end())
            ++itt1;

        if(itt2 != exp2_pre_topos.end())
            ++itt2;
    }

    //Init iterators for postconds
    auto itpq1 = exp1_post_quals.begin();
    auto itpq2 = exp2_post_quals.begin();

    auto itpt1 = exp1_post_topos.begin();
    auto itpt2 = exp2_post_topos.begin();

    //Check Postcondition Qualities
    while(itpq1 != exp1_post_quals.end() || itpq2 != exp2_post_quals.end())
    {
        post_qual_corr += postcond_quality_check(*itpq1, *itpq2);
        post_qual_count++;

        if(itpq1 != exp1_post_quals.end())
            ++itpq1;

        if(itpq2 != exp2_post_quals.end())
            ++itpq2;
    }

    //Check Postcondition Topologies
    while(itpt1 != exp1_post_topos.end() || itpt2 != exp2_post_topos.end())
    {
        post_topo_corr += postcond_topology_check(*itpt1, *itpt2);
        post_topo_count++;

        if(itpt1 != exp1_post_topos.end())
            ++itpt1;

        if(itpt2 != exp2_post_topos.end())
            ++itpt2;
    }
   
    if( base_corr == 1 && 
        pre_qual_count == pre_qual_corr &&
        pre_topo_count == pre_topo_corr &&
        post_qual_count == post_qual_corr &&
        post_topo_count == post_topo_corr)
    {
        return 1;
    }

    else return 0;
}

void save_topology(const Topology &t, const char * filename){
    std::ofstream ofs(filename);
    boost::archive::text_oarchive oa(ofs);
    oa << t;
}

void restore_topology(Topology &t, const char * filename){
    std::ifstream ifs(filename);
    boost::archive::text_iarchive ia(ifs);
    ia >> t;
}

int topology_check(Topology &t1, Topology &t2){
    if (t1.get_from_asset_id() == t2.get_from_asset_id() &&
        t1.get_to_asset_id() == t2.get_to_asset_id() &&
        t1.get_dir() == t2.get_dir() && 
        t1.get_property() == t2.get_property() &&
        t1.get_op() == t2.get_op() &&
        t1.get_value() == t2.get_value())
    {
        return 1;
    }

    else return 0;
}

int ag_check(AssetGroup &ag1, AssetGroup &ag2){
    int qual_count = 0;
    int qual_corr = 0;
    int topo_count = 0;
    int topo_corr = 0;

    auto ag1_quals = ag1.get_hypo_quals();
    auto ag2_quals = ag2.get_hypo_quals();

    auto ag1_topos = ag1.get_hypo_topos();
    auto ag2_topos = ag2.get_hypo_topos();

    auto itq1 = ag1_quals.begin();
    auto itq2 = ag2_quals.begin();

    auto itt1 = ag1_topos.begin();
    auto itt2 = ag2_topos.begin();

    while(itq1 != ag1_quals.end() || itq2 != ag2_quals.end())
    {
        qual_corr += quality_check(*itq1, *itq2);
        qual_count++;

        if(itq1 != ag1_quals.end())
            ++itq1;

        if(itq2 != ag2_quals.end())
            ++itq2;
    }

    while(itt1 != ag1_topos.end() || itt2 != ag2_topos.end())
    {
        topo_corr += topology_check(*itt1, *itt2);
        topo_count++;

        if(itt1 != ag1_topos.end())
            ++itt1;

        if(itt2 != ag2_topos.end())
            ++itt2;
    }
   
    if(ag1.get_perm() == ag2.get_perm() &&
        qual_count == qual_corr &&
        topo_count == topo_corr)
    {
        return 1;
    }

    else return 0;

}

void save_factbase(const Factbase &fb, const char * filename){
    std::ofstream ofs(filename);
    boost::archive::text_oarchive oa(ofs);
    oa << fb;
}

void restore_factbase(Factbase &fb, const char * filename){
    std::ifstream ifs(filename);
    boost::archive::text_iarchive ia(ifs);
    ia >> fb;
}

int factbase_check(Factbase &fb1, Factbase &fb2){
    int qual_count = 0;
    int qual_corr = 0;
    int topo_count = 0;
    int topo_corr = 0;

    auto fb1_tuple = fb1.get_facts_tuple();
    auto fb1_quals = std::get<0>(fb1_tuple);
    auto fb1_topos = std::get<1>(fb1_tuple);
    
    auto fb2_tuple = fb2.get_facts_tuple();
    auto fb2_quals = std::get<0>(fb2_tuple);
    auto fb2_topos = std::get<1>(fb2_tuple);

    auto itq1 = fb1_quals.begin();
    auto itq2 = fb2_quals.begin();

    auto itt1 = fb1_topos.begin();
    auto itt2 = fb2_topos.begin();

    while(itq1 != fb1_quals.end() || itq2 != fb2_quals.end())
    {
        qual_corr += quality_check(*itq1, *itq2);
        qual_count++;

        if(itq1 != fb1_quals.end())
            ++itq1;

        if(itq2 != fb2_quals.end())
            ++itq2;
    }

    while(itt1 != fb1_topos.end() || itt2 != fb2_topos.end())
    {
        topo_corr += topology_check(*itt1, *itt2);
        topo_count++;

        if(itt1 != fb1_topos.end())
            ++itt1;

        if(itt2 != fb2_topos.end())
            ++itt2;
    }
   

    if(fb1.get_id() == fb2.get_id() &&
        qual_count == qual_corr &&
        topo_count == topo_corr)
    {
        return 1;
    }

    else return 0;
}

void save_network_state(const NetworkState &ns, const char * filename){
    std::ofstream ofs(filename);
    boost::archive::text_oarchive oa(ofs);
    oa << ns;
}

void restore_network_state(NetworkState &ns, const char * filename){
    std::ifstream ifs(filename);
    boost::archive::text_iarchive ia(ifs);
    ia >> ns;
}

int network_state_check(NetworkState &ns1, NetworkState &ns2){
    auto fb1 = ns1.get_factbase();
    auto fb2 = ns2.get_factbase();

    return factbase_check(fb1, fb2);
}


void serialization_unit_testing(AGGenInstance &instance, boost::mpi::communicator &world){

    char hammer_host[256];
    gethostname(hammer_host, 256);
    std::string str_host(hammer_host);

    if(world.rank() == 0){
        printf("\n");
        std::cout << "---------STARTING SERIALIZATION UNIT TESTING---------" << std::endl;
    }
    world.barrier();

    std::string rollcall = "Hello from process " + std::to_string(world.rank())\
     + " of " + std::to_string(world.size()) + " running on "\
      + str_host + ".";

    if(world.rank() != 0)
        world.send(0, 0, rollcall);
    else{
        std::cout << rollcall << std::endl;
        for(int i = 0; i < world.size()-1; i++){
            world.recv(mpi::any_source, 0, rollcall);
            std::cout << rollcall << std::endl;
        }
        std::cout << "" << std::endl;
    }
    world.barrier();

    std::string filename(boost::archive::tmpdir());
    filename += "/qualfile.txt";

    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

    int e_flag = 0;
    int qual_count = 0;
    int qual_corr = 0;
    int top_count = 0;
    int top_corr = 0;
    int fb_count = 0;
    int fb_corr = 0;
    int ns_count = 0;
    int ns_corr = 0;
    int ag_count = 0;
    int ag_corr = 0;
    int exp_corr = 0;
    int exp_count = 0;

    std::vector<AssetGroup> asset_groups;
    std::vector<Quality> asset_group_quals;
    std::vector<Topology> asset_group_topos;


    if(world.rank() == 0){
        std::cout << "Performing Unit Testing on Exploit Serialization." << std::endl;
    }

    for (auto exp : instance.exploits)
    {
        Exploit new_exp;
        if(world.rank() == 0)
            new_exp = exp;

        mpi::request req;

        broadcast(world, new_exp, 0);
        exp_count++;
        exp_corr += exploit_check(exp, new_exp);
    }
    int total_exp_corr;

    reduce(world, exp_corr, total_exp_corr, std::plus<int>(), 0);

    if (world.rank() == 0){
        std::cout << "Exploit Unit Testing: " << std::to_string(total_exp_corr) << "/" << std::to_string(world.size() * exp_count) << std::endl;
        printf("\n");

        std::cout << "Performing Unit Testing on Quality Serialization." << std::endl;
    }

    for (auto qual : instance.initial_qualities)
    {
        Quality new_qual;
        if(world.rank() == 0)
            new_qual = qual;

        mpi::request req;

        broadcast(world, new_qual, 0);
        qual_count++;
        qual_corr += quality_check(qual, new_qual);

        asset_group_quals.emplace_back(qual);
    }

    int total_qual_corr;

    reduce(world, qual_corr, total_qual_corr, std::plus<int>(), 0);

    if (world.rank() == 0){
        std::cout << "Quality Unit Testing: " << std::to_string(total_qual_corr) << "/" << std::to_string(world.size() * qual_count) << std::endl;
        printf("\n");
  
        std::cout << "Performing Unit Testing on Topology Serialization." << std::endl;
    }

    for (auto topo : instance.initial_topologies)
    {
        Topology new_top;
        if(world.rank() == 0)
            new_top = topo;

        mpi::request req;

        broadcast(world, new_top, 0);
        top_count++;
        top_corr += topology_check(topo, new_top);

        asset_group_topos.emplace_back(topo);
    }

    std::vector<size_t> perm;
    perm.emplace_back(2);

    int total_top_corr;

    reduce(world, top_corr, total_top_corr, std::plus<int>(), 0);

    if (world.rank() == 0){
        std::cout << "Topology Unit Testing: " << std::to_string(total_top_corr) << "/" << std::to_string(world.size() * top_count) << std::endl;
        printf("\n");
        
        std::cout << "Performing Unit Testing on AssetGroup Serialization." << std::endl;
    }

    int total_ag_corr;
    std::vector<size_t> vec;
    vec.resize(2);
    std::fill(vec.begin(),vec.end(),0); 

    AssetGroup default_ag = AssetGroup(asset_group_quals, asset_group_topos, vec);

    for (int i = 0; i < 3; i++)
    {
        AssetGroup new_ag;
        if(world.rank() == 0){
            new_ag = AssetGroup(asset_group_quals, asset_group_topos, vec);
        }

        mpi::request req;

        broadcast(world, new_ag, 0);
        ag_count++;
        ag_corr += ag_check(new_ag, default_ag);
    }
    
    reduce(world, ag_corr, total_ag_corr, std::plus<int>(), 0);
    
    if (world.rank() == 0){
        std::cout << "AssetGroup Unit Testing: " << std::to_string(total_ag_corr) << "/" << std::to_string(world.size() * ag_count) << std::endl;
        printf("\n");

        std::cout << "Performing Unit Testing on Factbase Serialization." << std::endl;
    }

    for (auto fb : instance.factbases)
    {
        Factbase new_fb;
        int id;
        if(world.rank() == 0){
            new_fb = fb;
            id = fb.get_id();
        }

        mpi::request req;

        broadcast(world, new_fb, 0);
        broadcast(world, id, 0);
        new_fb.force_set_id(id);
        fb_count++;
        fb_corr += factbase_check(fb, new_fb);
    }
        
    int total_fb_corr;

    reduce(world, fb_corr, total_fb_corr, std::plus<int>(), 0);  

    if (world.rank() == 0){
        std::cout << "Factbase Unit Testing: " << std::to_string(total_fb_corr) << "/" << std::to_string(world.size() * fb_count) << std::endl;
        printf("\n");
  
        std::cout << "Performing Unit Testing on Network State Serialization." << std::endl;
    }

    NetworkState new_ns;
    int id;
    if(world.rank() == 0){
        new_ns = init_state;
        id = init_state.get_id();
    }

    mpi::request req;

    broadcast(world, new_ns, 0);
    broadcast(world, id, 0);
    new_ns.force_set_id(id);
    ns_count++;
    ns_corr += network_state_check(init_state, new_ns);

    int total_ns_corr;

    reduce(world, ns_corr, total_ns_corr, std::plus<int>(), 0);
    
    if (world.rank() == 0){
        std::cout << "Network State Unit Testing: " << std::to_string(total_ns_corr) << "/" << std::to_string(world.size() * ns_count) << std::endl;
        printf("\n");
    }

    if(world.rank() == 0){

        if(total_exp_corr == world.size() * exp_count)
        {
            std::cout << "100% Success Rate for Exploit Serialization." << std::endl;
        }
        else{
            std::cout << "Errors occurred in the Exploit Serialization." << std::endl;
            e_flag = 1;
        }
        
        if(total_qual_corr == world.size() * qual_count)
        {
            std::cout << "100% Success Rate for Quality Serialization." << std::endl;
        }
        else{
            std::cout << "Errors occurred in the Quality Serialization." << std::endl;
            e_flag = 1;
        }

        if(total_top_corr == world.size() * top_count)
        {
            std::cout << "100% Success Rate for Topology Serialization." << std::endl;
        }
        else{
            std::cout << "Errors occurred in the Topology Serialization." << std::endl;
            e_flag = 1;
        }

        if(total_ag_corr == world.size() * ag_count)
        {
            std::cout << "100% Success Rate for AssetGroup Serialization." << std::endl;
        }
        else{
            std::cout << "Errors occurred in the AssetGroup Serialization." << std::endl;
            e_flag = 1;
        }

        if(total_fb_corr == world.size() * fb_count)
        {
            std::cout << "100% Success Rate for Factbase Serialization." << std::endl;
        }
        else{
            std::cout << "Errors occurred in the Factbase Serialization." << std::endl;
            e_flag = 1;
        }

        if(total_ns_corr == world.size() * ns_count)
        {
            std::cout << "100% Success Rate for Network State Serialization." << std::endl;
        }
        else{
            std::cout << "Errors occurred in the Network State Serialization." << std::endl;
            e_flag = 1;
        }

       std::cout << "" << std::endl;
        if(e_flag == 1)
            std::cout << "-------------ERRORS OCCURRED DURING SERIALIZATION UNIT TESTING---------------" << std::endl;
        std::cout << "---------FINISHED SERIALIZATION UNIT TESTING---------" << std::endl;
        printf("\n");
    }
    //MPI Clean-up
    //MPI_Finalize();
   
    return;
}

