/**
 *
 * @file functions.cpp
 *
 * @author Simone Paolo Mottadelli
 *
 */

#include "functions.hpp"
#include "rbgraph.hpp"
#include <boost/graph/connected_components.hpp>
#include <boost/graph/depth_first_search.hpp>

std::pair<std::list<SignedCharacter>, bool>
realize_character(const SignedCharacter &sc, RBGraph &g) {
    std::list<SignedCharacter> output;

    // current character vertex
    RBVertex cv = 0;

    // get the vertex in g whose name is sc.character
    try {
        cv = get_vertex(sc.character, g);
    } catch (const std::out_of_range &e) {
        // g has no vertex named sc.character
        return std::make_pair(output, false);
    }

    RBVertexIMap i_map, c_map;
    RBVertexIAssocMap i_assocmap(i_map), c_assocmap(c_map);

    // fill vertex index map
    RBVertexIter v, v_end;
    std::tie(v, v_end) = vertices(g);
    for (size_t index = 0; v != v_end; ++v, ++index) {
        boost::put(i_assocmap, *v, index);
    }

    // build the components map
    boost::connected_components(g, c_assocmap,
                                boost::vertex_index_map(i_assocmap));

    if (sc.state == State::gain && is_inactive(cv, g)) {
        // c+ and c is inactive
        if (logging::enabled) {
            // verbosity enabled
            //std::cout << "Realizing " << sc;
        }
        // realize the character c+:
        // - add a red edge between c and each species in D(c) \ N(c)
        // - delete all black edges incident on c
        std::tie(v, v_end) = vertices(g);
        for (; v != v_end; ++v) {
            if (!is_species(*v, g) || c_map.at(*v) != c_map.at(cv)) {
                continue;
            }
            // for each species in the same connected component of cv

            RBEdge e;
            bool exists;
            std::tie(e, exists) = edge(*v, cv, g);

            if (exists) {
                // there is an edge (black) between *v and cv
                remove_edge(e, g);
            } else {
                // there isn't an edge between *v and cv
                add_edge(*v, cv, Color::red, g);
            }
        }

        if (logging::enabled) {
            // verbosity enabled
            //std::cout << std::endl;
        }
    } else if (sc.state == State::lose && is_active(cv, g)) {
        // c- and c is active
        if (logging::enabled) {
            // verbosity enabled
            //std::cout << "Realizing " << sc << std::endl;
        }

        // realize the character c- if it is connected through red edges to all the species of the component in which c resides. In this case:
        // - delete all edges incident on c
        bool connected = true;
        std::tie(v, v_end) = vertices(g);
        for (; v != v_end; ++v) {
            if (!is_species(*v, g) || c_map.at(*v) != c_map.at(cv)) {
                continue;
            }

            // if cv is not connected to the species v, which belongs to its same component, then cv is not connected to all the species of its component.
            if (!exists(*v, cv, g)) {
                connected = false;
                break;
            }
        }

        if (connected) {
            clear_vertex(cv, g);
        } else {
            if (logging::enabled) {
                // verbosity enabled
                //std::cout << "Could not realize " << sc << std::endl;
            }
            return std::make_pair(output, false);
        }
    } else {
        if (logging::enabled) {
            // verbosity enabled
            //std::cout << "Could not realize " << sc << std::endl;
        }

        // this should never happen during the algorithm, but it is handled just in
        // case something breaks (or user input happens)
        return std::make_pair(output, false);
    }

    output.push_back(sc);

    // delete all isolated vertices
    remove_singletons(g);

    return std::make_pair(output, true);
}

std::pair<std::list<SignedCharacter>, bool> realize_species(const RBVertex v,
                                                            RBGraph &g) {
    std::list<SignedCharacter> lsc;

    if (!is_species(v, g)) {
        return std::make_pair(lsc, false);
    }

    // build the list of inactive characters adjacent to v (species)

    std::list<RBVertex> adjacent_chars = get_adj_vertices(v, g);

    for (RBVertex c : adjacent_chars) {
        if (is_inactive(c, g)) {
            lsc.push_back({g[c].name, State::gain});
        }
    }

    return realize(lsc, g);
}

std::pair<std::list<SignedCharacter>, bool>
realize(const std::list<SignedCharacter> &lsc, RBGraph &g) {
    std::list<SignedCharacter> output;

    // realize the list of signed characters lsc; the algorithm stops when a
    // non-feasible realization is encountered, setting the boolean flag to false
    // TODO: maybe change this behaviour
    for (const SignedCharacter i : lsc) {
        if (std::find(output.cbegin(), output.cend(), i) != output.cend()) {
            // the signed character i has already been realized in a previous sc
            continue;
        }

        std::list<SignedCharacter> sc;
        bool feasible;

        std::tie(sc, feasible) = realize_character(i, g);

        if (!feasible) {
            return std::make_pair(sc, false);
        }

        output.splice(output.cend(), sc);
    }

    return std::make_pair(output, true);
}

bool is_complete(std::list<SignedCharacter> sc, const RBGraph &gm) {
    RBVertexIter v, v_end;
    auto scb = sc.begin();
    auto sce = sc.end();
    std::tie(v, v_end) = vertices(gm);
    while (v != v_end) {
        if (is_inactive(*v, gm)) {
            while (scb != sce) {
                if ((get_vertex(scb->character, gm) == *v)) {
                    return false;
                }
                scb++;
            }
        }
        v++;
    }
    return true;
}

void sort_by_degree(std::list<RBVertex> &list_to_sort, const RBGraph &g) {
    // constructing a list of pairs <RBVertex, int>, where the first element is a vertex and the second element is the degree of the vertex
    std::list<std::pair<RBVertex, int>> list_of_pairs;

    for (RBVertex v : list_to_sort) {
        list_of_pairs.push_back(std::make_pair(v, out_degree(v, g)));
    }

    auto comparator = [](const std::pair<RBVertex, int> &a,
                         const std::pair<RBVertex, int> &b) {
        return a.second > b.second;
    };

    list_of_pairs.sort(comparator);

    list_to_sort.clear();

    for (std::pair<RBVertex, int> pair : list_of_pairs) {
        list_to_sort.push_back(pair.first);
    }
}

std::list<RBVertex> get_all_minimal_p_active_species(const RBGraph &g,
                                                     bool all) {
    std::list<RBVertex> out;
    std::list<RBVertex> active_species = get_active_species(g);
    sort_by_degree(active_species, g);

    bool found;
    int num_inctv_chars_v, num_inctv_chars_u;
    for (RBVertex v : active_species) {
        found = false;
        // for every active species v in the sorted list of active species
        for (int i = 1; i < num_characters(g); ++i) {
            // index "i" is used after to check if vertex "u" has "i" more inactive characters than "v"

            for (RBVertex u : get_neighbors(v, g)) {
                if (u == v || is_character(u, g)) {
                    continue;
                }
                // for every species u (neighbor of v)

                if (includes_species(u, v, g)) {
                    // if u includes all the inactive species of v, then check if u has "i" species more than v
                    num_inctv_chars_v =
                        get_adj_inactive_characters(v, g).size();
                    num_inctv_chars_u =
                        get_adj_inactive_characters(u, g).size();
                    if (num_inctv_chars_u == num_inctv_chars_v + i) {

                        // check if the realization of v and then of u can generate any red-sigmagraphs in g
                        RBGraph g_copy;
                        copy_graph(g, g_copy);

                        realize_species(get_vertex(g[v].name, g_copy), g_copy);
                        realize_species(get_vertex(g[u].name, g_copy), g_copy);
                        if (!has_red_sigmagraph(g_copy)) {
                            out.push_back(v);
                            found = true;
                            break;
                        }
                    }
                }
            }
            if (found) {
                break;
            }
        }
        if (found && !all) {
            break;
        }
    }
    return out;
}

RBVertex get_minimal_p_active_species(const RBGraph &g) {
    return *get_all_minimal_p_active_species(g, false).begin();
}

RBVertex get_quasi_active_species(const RBGraph &g) {
    for (RBVertex v : g.m_vertices) {
        if (!is_species(v, g)) {
            continue;
        }
        RBOutEdgeIter e, e_end;
        std::tie(e, e_end) = out_edges(v, g);
        int black_edges = 0;
        int red_edges = 0;
        for (; e != e_end; ++e) {
            if (is_black(*e, g)) {
                ++black_edges;
            } else {
                ++red_edges;
            }
        }
        // if we are here then s is a species with some red incoming edges. So, we have to check whether its realization generates a red-sigmagraph
        if (black_edges > 0 && red_edges > 0) {
            RBGraph g_copy;
            copy_graph(g, g_copy);
            realize_species(get_vertex(g[v].name, g_copy), g_copy);
            if (!has_red_sigmagraph(g_copy)) {
                return v;
            }
        }
    }
    return nullptr;
}

std::list<SignedCharacter> ppp_maximal_reducible_graphs(RBGraph &g) {

    std::list<SignedCharacter> realized_chars =
        realize_red_univ_and_univ_chars(g).first;
    remove_duplicate_species(g);
    std::list<SignedCharacter> tmp;

    while (!is_empty(g)) {
        std::cout << "PRINT G " << std::endl << g << std::endl;
        if (get_pending_species(g).size() == 1) {
            tmp = realize_species(*get_pending_species(g).begin(), g).first;
        } else if (get_minimal_p_active_species(g) != 0) {
            tmp = realize_species(get_minimal_p_active_species(g), g).first;
        } else if (is_degenerate(g)) {
            for (RBVertex c : get_inactive_chars(g)) {
                tmp.splice(
                    tmp.end(),
                    realize_character({g[c].name, State::gain}, g).first);
            }
        } else if (get_active_species(g).size() == 1) {
            tmp = realize_species(*get_active_species(g).begin(), g).first;
        } else if (get_quasi_active_species(g) != 0
                   && all_species_with_red_edges(g)) {
            tmp = realize_species(get_quasi_active_species(g), g).first;
        } else {
            if (has_red_sigmagraph(g)) {
                std::cout << "[INFO] Red sigma graph generated" << std::endl;
            }
            throw std::runtime_error(
                "[ERROR] In ppp_maximal_reducible_graphs(): could not build "
                "the PPP");
        }

        realized_chars.splice(realized_chars.end(), tmp);
        realized_chars.splice(realized_chars.end(),
                              realize_red_univ_and_univ_chars(g).first);
        remove_duplicate_species(g);

        if (!is_empty(g)) {
            RBGraphVector conn_compnts = connected_components(g);
            auto cc = conn_compnts.begin();
            auto cc_end = conn_compnts.end();
            for (; cc != cc_end; ++cc) {
                RBGraph tmp_graph;
                copy_graph(*cc->get(), tmp_graph);
                tmp = ppp_maximal_reducible_graphs(*cc->get());
                if (logging::enabled) {
                    std::cout << "[INFO] iterating in conn_compt " << std::endl;
                }
                for (RBVertex v : tmp_graph.m_vertices) {
                    remove_vertex(tmp_graph[v].name, g);
                }
                realized_chars.splice(realized_chars.end(), tmp);
            }
        }
    }
    return realized_chars;
}

std::pair<std::list<SignedCharacter>, bool>
realize_red_univ_and_univ_chars(RBGraph &g) {
    std::list<SignedCharacter> output;

    RBVertexIter v, v_end, next;
    std::tie(v, v_end) = vertices(g);

    std::list<SignedCharacter> lsc;
    for (next = v; v != v_end; v = next) {
        next++;
        if (is_red_universal(*v, g)) {
            // if v is red_universal
            // realize v-
            if (logging::enabled) {
                // verbosity enabled
                //std::cout << "G red-universal character " << g[*v].name << std::endl;
            }
            std::tie(lsc, std::ignore) =
                realize_character({g[*v].name, State::lose}, g);

            output.splice(output.cend(), lsc);

            // we have to loop again from the beginning, because if some isolate node has been deleted, the for loop is compromised
            std::tie(v, v_end) = vertices(g);
            next = v;
        } else if (is_universal(*v, g)) {
            // if v is universal
            // realize v+
            if (logging::enabled) {
                // verbosity enabled
                //std::cout << "G universal character " << g[*v].name << std::endl;
            }

            std::list<SignedCharacter> lsc;

            std::tie(lsc, std::ignore) =
                realize_character({g[*v].name, State::gain}, g);

            output.splice(output.cend(), lsc);

            // we have to loop again from the beginning, because if some isolate node has been deleted, the for loop is compromised
            std::tie(v, v_end) = vertices(g);
            next = v;
        }
    }

    if (!output.empty()) {
        return std::make_pair(output, true);
    } else {
        return std::make_pair(output, false);
    }
}

RBVertex get_extension(const RBVertex &s, const RBGraph &gmax,
                       const RBGraph &gmin) {
    if (logging::enabled) {
        std::cout << "[INFO] get_extension " << std::endl;
    }

    // ******** CASE 1 ********
    // check whether s already exists in gmin without any additional minimal characters. If it exists, then we have already found the extension of s.

    // find the characters of s
    std::set<std::string> s_chars;
    for (RBVertex v : get_adj_vertices(s, gmax)) {
        s_chars.insert(gmax[v].name);
    }

    for (RBVertex v : gmin.m_vertices) {
        if (is_species(v, gmin)) {
            std::set<std::string> v_chars;
            for (RBVertex u : get_adj_vertices(v, gmin)) {
                v_chars.insert(gmin[u].name);
            }

            if (v_chars == s_chars) {
                return v;
            }
        }
    }

    // ******** CASE 2 ********
    // find all the species such that (1) they include s and (2) they do not have additional maximal characters. These species will be possible candidates

    std::list<RBVertex> max_chars_of_gmin = maximal_characters(gmin);
    std::list<RBVertex> candidates;// it contains the possible candidates
    for (RBVertex v : gmin.m_vertices) {
        if (is_species(v, gmin)) {

            // find the characters of v
            std::set<std::string> v_chars;
            for (RBVertex u : get_adj_vertices(v, gmin)) {
                v_chars.insert(gmin[u].name);
            }

            // v must include s
            if (std::includes(v_chars.begin(), v_chars.end(), s_chars.begin(),
                              s_chars.end())) {
                std::cout << "includes " << gmin[v].name << std::endl;

                // v must not have more max chars than s
                bool more_max_chars = false;
                for (RBVertex max_char : max_chars_of_gmin) {
                    if (!exists(gmax[s].name, gmax[max_char].name, gmax)
                        && exists(gmin[v].name, gmin[max_char].name, gmin)) {
                        more_max_chars = true;
                        break;
                    }
                }

                if (!more_max_chars) {
                    std::cout << "has not more max chars than s "
                              << gmin[v].name << std::endl;
                    candidates.push_back(v);
                }
            }
        }
    }

    // if there is only a candidate, then return it
    if (candidates.size() == 1) {
        return *candidates.begin();
    }

    // if we have more candidates, we have to choose the candidate X that satisfies
    // the following property: (1) the realization of the characters of X and of
    // all the minimal characters that overlap with the minimal characters of s
    // must not induce a red-sigma graph and (2) X must be minimal.

    // we first sort the candidate list so that (2) is satisfied
    sort_by_degree(candidates, gmin);
    candidates.reverse();
    for (RBVertex candidate : candidates) {

        // this list will contain the minimal characters that overlap with the ones
        // of s. We are going to build it by first inserting all the vertices of gmin
        // and then reducing it progressively.
        std::set<RBVertex> overlapping_min_chars(gmin.m_vertices.begin(),
                                                 gmin.m_vertices.end());
        for (RBVertex u : gmin.m_vertices) {
            if (is_species(u, gmin)) {
                overlapping_min_chars.erase(u);
            }
        }

        std::list<RBVertex> candidate_chars = get_adj_vertices(candidate, gmin);
        for (RBVertex u : candidate_chars) {
            overlapping_min_chars.erase(u);
        }

        for (RBVertex u : max_chars_of_gmin) {
            overlapping_min_chars.erase(u);
        }

        for (RBVertex z : overlapping_min_chars) {
            for (RBVertex u : candidate_chars) {
                if (!overlaps_character(z, u, gmin)) {
                    overlapping_min_chars.erase(z);
                }
            }
        }

        // check for the red-sigma graph
        RBGraph g_copy;
        copy_graph(gmin, g_copy);
        realize_species(get_vertex(gmin[candidate].name, g_copy), g_copy);
        for (RBVertex u : overlapping_min_chars) {
            if (exists(gmin[u].name, g_copy)) {
                realize_species(get_vertex(gmin[u].name, g_copy), g_copy);
            }
        }

        if (!has_red_sigmagraph(g_copy)) {
            std::cout << "Candidate does not induce red sigmagraph"
                      << std::endl;
            return candidate;
        } else {
            std::cout << "RED sigmagraph" << std::endl;
        }
    }

    return 0;// return null
}

std::list<RBVertex> get_sources(const RBGraph &gm) {
    std::list<RBVertex> sources = get_all_minimal_p_active_species(gm, true);
    if (sources.empty()) {
        std::list<RBVertex> pending_species = get_pending_species(gm);
        if (pending_species.size() == 1) {
            sources.push_back(*pending_species.begin());
        }
    }
    return sources;
}

bool is_2_solvable(std::list<RBVertex> &sources, const RBGraph &gm) {
    if (sources.size() == 1) {
        if (logging::enabled) {
            std::cout << " [INFO] mono_solvable " << std::endl;
        }
        return true;
    } else if (sources.size() != 2) {
        return false;
    } else {
        RBVertex source1 = *sources.begin();
        RBVertex source2 = *++sources.begin();
        if (get_adj_vertices(source1, gm).size() < 2
            || get_adj_vertices(source2, gm).size() < 2) {
            return false;
        }
    }
    return true;
}

bool is_3_canonical(std::list<RBVertex> &sources, const RBGraph &gm) {
    if (sources.size() != 3) {
        return false;
    }
    RBVertex source1 = *sources.begin();
    RBVertex source2 = *++sources.begin();
    RBVertex source3 = *++++sources.begin();

    if (logging::enabled) {
        std::cout << " [TODO]: is_3_solvable semantics " << std::endl;
    }
    return true;
}

bool is_m_solvable(std::list<RBVertex> &sources, const RBGraph &gm) {
    if (sources.size() <= 3) {
        return false;
    }
    //

    return true;
}

std::list<RBVertex> closure(const RBVertex &v,
                            const RBGraph &g)//C(S) char massimali
{
    //CL(s) is defined as follows: a ∈ CL(s) if and only if a is minimal in G and moreover it is
    //included in all maximal characters of s.

    // get the minimal characters as follow:
    // cmin = vertices(g) - cmax - species(g)

    std::list<RBVertex> cmax = maximal_characters(g);
    std::list<RBVertex> cmin(g.m_vertices.begin(), g.m_vertices.end());
    RBVertexIter b = cmin.begin(), e = cmin.end(), next;
    for (next = b; b != e; b = next) {
        ++next;
        if (is_species(*b, g)) {
            cmin.remove(*b);
        }
        for (RBVertex u : cmax) {
            if (g[*b].name == g[u].name) {
                cmin.remove(*b);
                break;
            }
        }
    }

    // keep just the max chars of v
    b = cmax.begin(), e = cmax.end();
    for (next = b; b != e; b = next) {
        ++next;
        if (!exists(*b, v, g)) {
            cmax.remove(*b);
        }
    }

    // keep just the min chars that are included in all max chars of v
    b = cmin.begin(), e = cmin.end();
    for (next = b; b != e; b = next) {
        ++next;
        for (RBVertex u : cmax) {
            if (!includes_characters(u, *b, g)) {
                cmin.remove(*b);
                break;
            }
        }
    }

    return cmin;
}

////////////////////////////////////////////////
//bool type_one(RBVertex &s, RBVertex &salt) {
//    // s source and salt source alternative source
//}

// new
bool type_one(const RBGraph &g, const RBVertex &main_source,
              const RBVertex &other_source, const std::list<RBVertex> &closure,
              const std::list<RBVertex> &interjection) {
    // TODO: check type1 algorithm and resolution
    std::list<RBVertex> ms_chars = get_comp_vertex(main_source, g);
    std::list<RBVertex> os_chars = get_comp_vertex(other_source, g);
    // ms_chars - max(chars) - os_chars
    std::list<RBVertex> max_chars = maximal_characters(g);

    std::list<RBVertex> a_chars(g.m_vertices.begin(), g.m_vertices.end());
    RBVertexIter b = a_chars.begin(), e = a_chars.end(), next;

    for (next = b; b != e; b = next) {
        bool found = false;
        ++next;
        if (is_species(*b, g)) {
            a_chars.remove(*b);
            found = true;
            break;
        } else {
            for (RBVertex v : os_chars) {
                if (g[*b].name == g[v].name & !found) {
                    a_chars.remove(*b);
                    found = true;
                    break;
                }
            }
            for (RBVertex v : max_chars) {
                if (g[*b].name == g[v].name & !found) {
                    a_chars.remove(*b);
                    found = true;
                    break;
                }
            }
        }

        // for each a - begin

        // leaf-species-not-in-inverted-path
        std::list<RBVertex> leaf_species(g.m_vertices.begin(),
                                         g.m_vertices.end());
        RBVertexIter b_L = leaf_species.begin(), e_L = leaf_species.end(),
                     next_L;
        for (next_L = b_L; b_L != e_L; b_L = next_L) {
            bool found_L = false;
            ++next_L;
            if (is_character(*b_L, g)) {
                leaf_species.remove(*b_L);
                found_L = true;
                break;
            } else {

                std::list<RBVertex> char_set = get_comp_vertex(*b_L, g);
                bool pass = false;
                for (RBVertex v : interjection) {
                    if (contains(char_set, v)) {
                        // not pass;
                    } else {
                        // add sm to leaf
                        pass = true;
                        break;
                    }
                }
                if (!pass) {
                    leaf_species.remove(*b_L);
                    found_L = true;
                    break;
                }
            }
        }

        // Set-B
        std::list<RBVertex> b_chars(g.m_vertices.begin(), g.m_vertices.end());
        RBVertexIter b_B = b_chars.begin(), e_B = b_chars.end(), next_B;
        for (next_B = b_B; b_B != e_B; b_B = next_B) {
            bool found_B = false;
            ++next_B;
            if (is_species(*b_B, g)) {
                b_chars.remove(*b_B);
                found_B = true;
                break;
            } else {
                for (RBVertex v : max_chars) {
                    if (g[*b_B].name == g[v].name & !found_B) {
                        b_chars.remove(*b_B);
                        found_B = true;
                        break;
                    }
                }

                for (RBVertex v : closure) {
                    if (found_B)
                        break;
                    if (g[*b_B].name == g[v].name & !found_B) {
                        b_chars.remove(*b_B);
                        found_B = true;
                        break;
                    }
                }

                for (RBVertex v : os_chars) {
                    if (found_B)
                        break;
                    if (g[*b_B].name == g[v].name & !found_B) {
                        b_chars.remove(*b_B);
                        found_B = true;
                        break;
                    }
                }

                if (!overlaps_character(*b_B, *b, g)) {
                    b_chars.remove(*b_B);
                    found_B = true;
                    break;
                }
            }
            // for-each B left
            if (!found_B) {
                bool pass = true;
                for (RBVertex v : leaf_species) {
                    if (contains(get_comp_vertex(v, g), *b_B)) {
                        // pass FALSE
                        pass = false;
                        break;
                    }
                }
                if (pass) {
                    return true;
                }
            }
        }
    }
    return false;
}

bool is_linetree(const RBGraph &g) {
    std::list<RBVertex> vertices(g.m_vertices.begin(), g.m_vertices.end());
    RBVertexIter b = vertices.begin(), e = vertices.end(), next;
    ulong tmp;
    bool found = false;
    for (next = b; b != e; b = next) {
        ++next;
        // TODO: replace with degree of incident EDGES
        if (out_degree(*b, g) > 2) {
            std::cout << "GUARD_IS_A_SPLIT_NODE" << std::endl;
            std::cout << "NODE: " << g[*b].name << std::endl;
            found = true;
        }
    }

    return !found;
}

bool test_l_source(const RBVertex &s1, const RBVertex &s2,
                   const RBGraph &g_skeleton, const RBGraph &g) {

    // given s1 species, return a list of connected chars
    // remove from s1_max_chars, minimal chars of g
    std::list<RBVertex> tmp;
    // prendo le componenti di s1 in g
    std::list<RBVertex> s1_max_chars = get_comp_vertex(s1, g);

    // prendo i max char di g
    std::list<RBVertex> cmax = maximal_characters(g);

    // computo i min char di g, rimuovendo le specie e rimuovendo i char maximali dall'iterator di vertici di g
    std::list<RBVertex> cmin(g.m_vertices.begin(), g.m_vertices.end());
    RBVertexIter b = cmin.begin(), e = cmin.end(), next;

    for (next = b; b != e; b = next) {
        ++next;
        if (is_species(*b,
                       g)) {// se e' specie, rimuovila
            cmin.remove(*b);
        }
        for (RBVertex u : cmax) {
            if (g[*b].name == g[u].name) {
                cmin.remove(*b);
                break;
            }
        }
    }

    // rimuovo dalle componenti di s1, i char minimali
    b = s1_max_chars.begin(), e = s1_max_chars.end(), next;
    for (next = b; b != e; b = next) {
        ++next;
        for (RBVertex u : cmin) {
            if (g[*b].name == g[u].name) {
                s1_max_chars.remove(*b);
                break;
            }
        }
    }// got s1_max_chars

    // riga 3 algoritmo --> per ogni a in cmin
    bool included = true;
    for (RBVertex a : cmin) {
        b = cmin.begin(), e = cmin.end();
        for (next = b; b != e; b = next) {
            ++next;
            for (RBVertex u : s1_max_chars) {
                if (!includes_characters(u, *b, g)) {
                    included = false;
                    break;
                } else {// se ogni u *carattere di C_s ovvero di s1_max_chars* include il char a
                    // allora computo la closure
                    tmp = closure(s1, g);
                    //tmp.push_back(a);// serve??? CL(s) = {a} U CL(s)
                }
            }
        }
    }

    // computo s-graph : sottografo di G indotto da C_s U CL(s)

    // s1_max_chars = c_s
    // tmp = closure di s
    //    std::list<RBVertex> closure_ext = tmp; TODO check this value

    std::cout << "GUARD_TREE GRAPH\n" << g << std::endl;

    RBGraph s_graph;

    //adding tmp
    for (RBVertex v : tmp) {
        if (!exists(g[v].name, s_graph)) {
            add_character(g[v].name, s_graph);

            RBOutEdgeIter e, e_end;
            std::tie(e, e_end) = out_edges(v, g);
            for (; e != e_end; ++e) {
                if (!exists(g[e->m_target].name, s_graph)) {
                    add_species(g[e->m_target].name, s_graph);
                }
                add_edge(g[v].name, g[e->m_target].name, Color::black, s_graph);
            }
        }
    }

    //adding s1_max_chars
    for (RBVertex v : s1_max_chars) {
        if (!exists(g[v].name, s_graph)) {
            add_character(g[v].name, s_graph);

            RBOutEdgeIter e, e_end;
            std::tie(e, e_end) = out_edges(v, g);
            for (; e != e_end; ++e) {
                if (!exists(g[e->m_target].name, s_graph)) {
                    add_species(g[e->m_target].name, s_graph);
                }
                add_edge(g[v].name, g[e->m_target].name, Color::black, s_graph);
            }
        }
    }
    std::cout << "GUARD_TREE S_GRAPH\n" << s_graph << std::endl;

    //interjection
    std::list<RBVertex> s1_chars = get_adj_inactive_characters(s1, s_graph);
    std::list<RBVertex> s2_chars = get_adj_inactive_characters(s2, s_graph);
    std::list<RBVertex> interjection;

    for (RBVertex i : s1_chars) {
        for (RBVertex j : s2_chars) {
            if (s_graph[j].name == s_graph[i].name) {
                interjection.push_back(i);
                break;
            }
        }
    }

    std::cout << "GUARD_INTERJECTION\ninterjection" << std::endl;

    for (RBVertex p : interjection) {
        std::cout << "(" << s_graph[p].name << ") ";
    }
    std::cout << std::endl;

    std::cout << "\nMAIN Source" << std::endl;
    for (RBVertex s : s1_chars) {
        std::cout << "(" << s_graph[s].name << ") ";
    }
    std::cout << "\nOTHER Source" << std::endl;
    for (RBVertex s : s2_chars) {
        std::cout << "(" << s_graph[s].name << ") ";
    }
    std::cout << std::endl;

    // computing G_s
    RBGraph sub_s_graph;

    //se tutta l'intersezione fa parte di una specie, salvo il set di specie
    // e prendo il sottografo con quelle

    // if comp

    for (RBVertex s : s_graph.m_vertices) {
        if (is_species(s, s_graph)) {
            std::list<RBVertex> specie_chars = get_adj_inactive_characters(s, s_graph);

            std::cout << "GUARD_ADJ_vertices" << std::endl;
            std::cout << "\nadjjj" << std::endl;
            for (RBVertex s : specie_chars) {
                std::cout << "(" << s_graph[s].name << ") ";
            }
            std::cout << std::endl;

            bool inSpecie = true;
            for (RBVertex v : interjection) {
                if (!containsV2(specie_chars, v, s_graph)) {
                    std::cout << "GUARD_SUB_CANT_ADD\n" << s_graph[v].name << std::endl;
                    inSpecie = false;
                    break;
                }
            }

            if (inSpecie) { // keep specie in the sub_s_graph
                if (!exists(s_graph[s].name, sub_s_graph)) {
                    add_species(s_graph[s].name, sub_s_graph);
                    std::cout << "GUARD_SUB_S_GRAPH_OK" << std::endl;
                    RBOutEdgeIter e, e_end;
                    std::tie(e, e_end) = out_edges(s, s_graph);
                    for (; e != e_end; ++e) {
                        if (!exists(s_graph[e->m_target].name, sub_s_graph)) {
                            add_character(s_graph[e->m_target].name, sub_s_graph);
                        }
                        add_edge(s_graph[s].name, s_graph[e->m_target].name,
                                 Color::black, sub_s_graph);
                    }

                }

            }
        }
//        for (RBVertex v : interjection) {
//            bool notinspecie = false;
//
//            if (!exists(s_graph[v].name, sub_s_graph)) {
//                add_character(s_graph[v].name, sub_s_graph);
//
//                RBOutEdgeIter e, e_end;
//                std::tie(e, e_end) = out_edges(v, s_graph);
//                for (; e != e_end; ++e) {
//                    if (!exists(s_graph[e->m_target].name, sub_s_graph)) {
//                        add_species(s_graph[e->m_target].name, sub_s_graph);
//                    }
//                    add_edge(s_graph[v].name, s_graph[e->m_target].name,
//                             Color::black, sub_s_graph);
//                }
//            }
//        }
    }
    std::cout << "GUARD_TREE SUB_S_GRAPH\n" << sub_s_graph << std::endl;

    if (logging::enabled)
        std::cout << "[INFO] Reached Linetree " << std::endl;

    if (is_linetree(sub_s_graph)) {
        std::cout << "GUARD_LINETREE_OK" << std::endl;
        if (type_one(g, s1, s2, tmp, interjection)) {
            std::cout << "GUARD_TYPE1_NOT" << std::endl;
            return false;
        } else {
            std::cout << "GUARD_TYPE1" << std::endl;
            return true;
        }
    } else {
        std::cout << "GUARD_LINETREE_NOT" << std::endl;

        return false;
    }
}

RBVertex source_2_solvable(const std::list<RBVertex> &sources,
                           const RBGraph &g_skeleton, const RBGraph &g_min,
                           const RBGraph &g) {
    RBVertex tmp = nullptr;

    if (sources.size() == 1) {
        RBVertex source = *sources.begin();
        RBVertex extension = get_extension(source, g_skeleton, g_min);
        std::cout << "GUARD_B" << std::endl;
        //extension
        //tmp is the s-extension to source_2_solvable
        if (extension != nullptr && is_species(extension, g_skeleton)) {
            std::cout << "GUARD_B1" << std::endl;
            return extension;
        }
        std::cout << "GUARD_B2" << std::endl;

    } else if (sources.size() == 2) {
        if (logging::enabled)
            std::cout << "[INFO] 2-sources " << std::endl;

        RBVertex source1 = *sources.begin();
        RBVertex source2 = *++sources.begin();
        RBVertex extension1 = get_extension(source1, g_skeleton, g_min);
        RBVertex extension2 = get_extension(source2, g_skeleton, g_min);

        if (extension1 != nullptr && extension2 != nullptr
            && is_species(extension1, g_skeleton)
            && is_species(extension2, g_skeleton)) {

            std::cout << "GUARD_C" << std::endl;

            if (test_l_source(extension1, extension2, g_skeleton, g)) {
                std::cout << "GUARD_EXT1" << std::endl;
                tmp = extension1;
                //                tmp.splice(tmp.end(),
                //                           realize_species(extension1, g_skeleton).first);
            }
            std::cout << "GUARD_C1" << std::endl;

            if (test_l_source(extension2, extension1, g_skeleton, g)) {

                std::cout << "GUARD_EXT2" << std::endl;
                tmp = extension2;
                //                tmp.splice(tmp.end(),
                //                           realize_species(extension2, g_skeleton).first);
            }
            if (tmp != nullptr) {
                std::cout << "GUARD_EXT_1_2_NULL" << std::endl;
                return tmp;
            }

            std::cout << "GUARD_C2" << std::endl;
        }
    }
    throw std::runtime_error("[ERROR] In ppr_general: could not "
                             "compute persistent phylogeny");
}

#if 0
std::list<SignedCharacter> source_3_canonical(std::list<RBVertex> &sources,
                                              const RBGraph &g_skeleton) {
    if (logging::enabled) {
        std::cout << "[TODO] implement source_3_canonical " << std::endl;
    }
}

//#if 0
std::list<SignedCharacter> source_m_solvable(std::list<RBVertex> &sources,
                                             const RBGraph &g_skeleton) {
    if (logging::enabled) {
        std::cout << "[TODO] implement source_m_solvable  " << std::endl;
    }
}
#endif

std::list<SignedCharacter>
ppr_general(RBGraph &g) /*, std::list<SignedCharacter> a) */
{
    // g-skeleton computation g->g_min->max_reducible_graph

    //    if (logging::enabled) std::cout << "[PRINT] Graph print " << g << std::endl;

    // ** found that gmin->g-max-red it's the same as computing a g-skelton (making a graph from all Inactive max char plus all active chars

    if (logging::enabled) {
        std::cout << "[INFO] Realizing chars" << std::endl;
    }
    std::list<SignedCharacter> realized_chars =
        realize_red_univ_and_univ_chars(g).first;
    remove_duplicate_species(g);
    std::list<SignedCharacter> tmp;

    if (logging::enabled) {
        std::cout << "[INFO] Running PPPH iteration " << std::endl;
    }

    while (!is_empty(g)) {

        RBGraph gm;
        RBGraph g_max;
        RBGraph g_min;
        std::cout << "GUARD_PRINT_TREE\n" << g <<  std::endl;

        if (logging::enabled) {
            std::cout << "[INFO] Computing minimal form" << std::endl;
        }
        minimal_form_graph(g, g_min);

        if (logging::enabled) {
            std::cout << "[INFO] Computing g-skeleton" << std::endl;
        }
        g_skeleton(g, gm);
        std::cout << "GUARD_PRINT_SKELETON\n" << gm <<  std::endl;

        if (logging::enabled) {
            std::cout << "[INFO] Computing max_red_graph" << std::endl;
        }
        maximal_reducible_graph(g_min, g_max, true);

        // need this in advance for realizing characters, do this before the main loop
        if (logging::enabled) {
            std::cout << "[INFO] Getting Sources" << std::endl;
        }
        std::list<RBVertex> gm_sources = get_sources(gm);

        if (is_2_solvable(gm_sources, gm)) {

            tmp =
                realize_species(source_2_solvable(gm_sources, gm, g_min, g), g)
                    .first;

            if (logging::enabled) {
                std::cout << "[INFO] 2-solvable " << std::endl;
            }
            std::cout << "GUARD_A 2_SOLVABLE" << std::endl;

            //} else if (!is_2_solvable(sources_skeleton, gm)) {
            // return result; // skip controls,
        } else if (is_degenerate(gm)) {
            if (logging::enabled) {
                std::cout << "[INFO] is-degenerate " << std::endl;
            }
            //            throw std::runtime_error("[ERROR] In ppr_general: could not "
            //                                     "compute persistent phylogeny");
            break;
        } else {
            if (logging::enabled) {
                std::cout << "[INFO] no-2-solv " << std::endl;
            }
            break;
            //            throw std::runtime_error("[ERROR] In ppr_general: could not "
            //                                     "compute persistent phylogeny");
            // red sigmagraph??
        }

        if (!tmp.empty()) {
            if (logging::enabled)
                std::cout << "[INFO] realization not empty " << std::endl;

            // realize result in G
            //update G and A
            // A set of characters of r (relized chars)

            realized_chars.splice(realized_chars.end(), tmp);
            realized_chars.splice(realized_chars.end(),
                                  realize_red_univ_and_univ_chars(g).first);
            remove_duplicate_species(g);
            // iterates over connected components
            if (!is_empty(g)) {
                if (logging::enabled)
                    std::cout << "[INFO] Iterating conn_cmpt " << std::endl;
                std::cout << "GUARD_ITERATING_COMPT" << std::endl;
                RBGraphVector conn_compnts = connected_components(g);
                auto cc = conn_compnts.begin();
                auto cc_end = conn_compnts.end();
                for (; cc != cc_end; ++cc) {
                    RBGraph tmp_graph;
                    copy_graph(*cc->get(), tmp_graph);
                    tmp = ppr_general(*cc->get());
                    for (RBVertex v : tmp_graph.m_vertices) {
                        remove_vertex(tmp_graph[v].name, g);
                    }
                    realized_chars.splice(realized_chars.end(), tmp);

                    if (logging::enabled)
                        std::cout << "[INFO] Realized chars ";
                    for (SignedCharacter sc : realized_chars)
                        std::cout << "(" << sc << ") ";
                    std::cout << std::endl;
                }
            }
        }
    }
    return realized_chars;
}


// This file currently requires 137 MB to compile (optimizing).

// Copyright (c) 2003, 2004 by Jonathan Brandmeyer and others.
// See the file license.txt for complete license terms.
// See the file authors.txt for a complete list of contributors.

#include "util/vector.hpp"

#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/implicit.hpp>
#include <boost/python/operators.hpp>
#include <boost/python/init.hpp>
#include <boost/python/overloads.hpp>
#include <boost/python/return_value_policy.hpp>
#include <boost/python/copy_const_reference.hpp>
#include <boost/python/to_python_converter.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/extract.hpp>
#include "python/num_util.hpp"

namespace cvisual {
namespace py = boost::python;
using namespace cvisual::python;

using py::numeric::array;
using py::object;
using py::extract;

//AS add
using py::allow_null;

// Operations on Numeric arrays
namespace {

void
validate_array( const array& arr)
{
	std::vector<npy_intp> dims = shape(arr);
	if (type(arr) != NPY_DOUBLE) {
		throw std::invalid_argument( "Array must be of type Float64.");
	}
	if (!iscontiguous(arr)) {
		throw std::invalid_argument( "Array must be contiguous."
			"(Did you pass a slice?)");
	}
	if (dims.size() != 2) {
		if (dims.size() == 1 && dims[0] == 3)
			return;
		else
			throw std::invalid_argument( "Array must be Nx3 in shape.");
	}
	if (dims[1] != 3) {
		throw std::invalid_argument( "Array must be Nx3 in shape.");
	}
}

// Numeric doens't support the Sequence protocol, so I have to use this hack
// instead.
// 2008/2/16 BAS asks, "What is the situation with numpy?" Should look into this.
inline int
length(boost::python::object seq)
{
	int ret = PySequence_Size( seq.ptr());
	if (ret == -1) {
		boost::python::throw_error_already_set();
	}
	return ret;
}

} // !namespace anonymous

vector
tovector( py::object arr)
{
	switch (length(arr)) {
		case 2:
			return vector(
				extract<double>(arr[0]),
				extract<double>(arr[1]));
		case 3:
			return vector(
				extract<double>(arr[0]),
				extract<double>(arr[1]),
				extract<double>(arr[2]));
		default:
			throw std::invalid_argument("Vectors must have length 2 or 3");
	}
}

object
mag_a( const array& arr)
{
	validate_array( arr);
	std::vector<npy_intp> dims = shape(arr);
	// Magnitude of a flat 3-length array
	if (dims.size() == 1 && dims[0] == 3) {
		return object( vector(
			extract<double>(arr[0]),
			extract<double>(arr[1]),
			extract<double>(arr[2])).mag());
	}
	std::vector<npy_intp> rdims(1);
	rdims[0] = dims[0];
	array ret = makeNum( rdims);
	for (int i = 0; i< rdims[0]; ++i) {
		ret[i] = tovector(arr[i]).mag();
	}
	return ret;
}

object
mag2_a( const array& arr)
{
	validate_array( arr);
	std::vector<npy_intp> dims = shape(arr);
	if (dims.size() == 1 && dims[0] == 3) {
		// Returns an object of type float.
		return object( vector(
			extract<double>(arr[0]),
			extract<double>(arr[1]),
			extract<double>(arr[2])).mag2());
	}
	std::vector<npy_intp> rdims(1);
	rdims[0] = dims[0];
	array ret = makeNum( rdims);
	for (int i = 0; i < rdims[0]; ++i) {
		ret[i] = tovector(arr[i]).mag2();
	}
	// Returns an object of type Numeric.array.
	return ret;
}

object
norm_a( const array& arr)
{
	validate_array( arr);
	std::vector<npy_intp> dims = shape(arr);
	if (dims.size() == 1 && dims[0] == 3) {
		// Returns a float
		return object( vector(
			extract<double>(arr[0]),
			extract<double>(arr[1]),
			extract<double>(arr[2])).norm());
	}
	array ret = makeNum(dims);
	for (int i = 0; i < dims[0]; ++i) {
		ret[i] = tovector(arr[i]).norm();
	}
	// Returns a Numeric.array
	return ret;
}

array
dot_a( const array& arg1, const array& arg2)
{
	validate_array( arg1);
	validate_array( arg2);
	std::vector<npy_intp> dims1 = shape(arg1);
	std::vector<npy_intp> dims2 = shape(arg2);
	if (dims1 != dims2) {
		throw std::invalid_argument( "Array shape mismatch.");
	}

	std::vector<npy_intp> dims_ret(1);
	dims_ret[0] = dims1[0];
	array ret = makeNum( dims_ret);
	const double* arg1_i = (double*)data(arg1);
	const double* arg2_i = (double*)data(arg2);
	for ( int i = 0; i < dims1[0]; ++i, arg1_i +=3, arg2_i += 3) {
		ret[i] = vector(arg1_i).dot( vector(arg2_i));
	}
	return ret;
}

array
cross_a_a( const array& arg1, const array& arg2)
{
	validate_array( arg1);
	validate_array( arg2);
	std::vector<npy_intp> dims1 = shape(arg1);
	std::vector<npy_intp> dims2 = shape(arg2);
	if (dims1 != dims2) {
		throw std::invalid_argument( "Array shape mismatch.");
	}

	array ret = makeNum( dims1);
	const double* arg1_i = (double*)data(arg1);
	const double* arg2_i = (double*)data(arg2);
	double* ret_i = (double*)data(ret);
	double* const ret_stop = ret_i + 3*dims1[0];
	for ( ; ret_i < ret_stop; ret_i += 3, arg1_i += 3, arg2_i += 3) {
		vector ret = vector(arg1_i).cross( vector( arg2_i));
		ret_i[0] = ret.get_x();
		ret_i[1] = ret.get_y();
		ret_i[2] = ret.get_z();
	}
	return ret;
}

array
cross_a_v( const array& arg1, const vector& arg2)
{
	validate_array( arg1);
	std::vector<npy_intp> dims = shape( arg1);
	array ret = makeNum( dims);
	const double* arg1_i = (double*)data( arg1);
	double* ret_i = (double*)data( ret);
	double* const ret_stop = ret_i + 3*dims[0];
	for ( ; ret_i < ret_stop; ret_i += 3, arg1_i += 3) {
		vector ret = vector( arg1_i).cross( arg2);
		ret_i[0] = ret.get_x();
		ret_i[1] = ret.get_y();
		ret_i[2] = ret.get_z();

	}
	return ret;
}

array
cross_v_a( const vector& arg1, const array& arg2)
{
	validate_array( arg2);
	std::vector<npy_intp> dims = shape( arg2);
	array ret = makeNum( dims);
	const double* arg2_i = (double*)data( arg2);
	double* ret_i = (double*)data( ret);
	double* const ret_stop = ret_i + 3*dims[0];
	for ( ; ret_i < ret_stop; ret_i += 3, arg2_i += 3) {
		vector ret = arg1.cross( vector( arg2_i));
		ret_i[0] = ret.get_x();
		ret_i[1] = ret.get_y();
		ret_i[2] = ret.get_z();

	}
	return ret;

}

namespace {
using namespace boost::python;
BOOST_PYTHON_FUNCTION_OVERLOADS( free_rotate, rotate, 2, 3 )
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS( vector_rotate, vector::rotate, 1, 2)
} // !namespace anonymous

struct vector_from_seq
{
	vector_from_seq()
	{
		py::converter::registry::push_back(
			&convertible,
			&construct,
			py::type_id<vector>());
	}

	static void* convertible( PyObject* obj)
	{
		using py::handle;
		using py::allow_null;

		object o( handle<>( borrowed(obj) ) );

		int obj_size = PyObject_Length(obj);
		if (obj_size < 0) {
			PyErr_Clear();
			return 0;
		}
		if (obj_size != 2 && obj_size != 3)
			return 0;
		for(int i=0; i<obj_size; i++)
			if (!py::extract<double>(o[i]).check())
				return 0;
		return obj;
	}

	static void construct(
		PyObject* _obj,
		py::converter::rvalue_from_python_stage1_data* data)
	{
		using namespace boost::python;

		object obj = object(handle<>(borrowed(_obj)));
		void* storage = (
			(boost::python::converter::rvalue_from_python_storage<vector>*)
			data)->storage.bytes;
		int obj_size = PyObject_Length(_obj);
		switch (obj_size) {
			case 1:
				new (storage) vector( py::extract<double>(obj[0]));
				break;
			case 2:
				new (storage) vector(
					py::extract<double>( obj[0]),
					py::extract<double>( obj[1]));
					break;
			case 3: default:
				// Will probably trigger an exception if it is the default
				// case.
				new (storage) vector(
					py::extract<double>( obj[0]),
					py::extract<double>( obj[1]),
					py::extract<double>( obj[2]));
		}
		data->convertible = storage;
	}
};

py::tuple
vector_as_tuple( const vector& v)
{
	return py::make_tuple( v.x, v.y, v.z);
}

vector
vector_pos( const vector& v)
{
	return v;
}

void
wrap_vector()
{
	// Numeric versions for some of the above
	// TODO: round out the set.
	def( "mag", mag_a);
	def( "dot", dot_a);
	def( "cross", cross_a_a);
	def( "cross", cross_a_v);
	def( "cross", cross_v_a);
	def( "mag2", mag2_a);
	def( "norm", norm_a);

	// Free functions for vectors
	// The following two functions have never been implemented:
	//py::def( "det3",a_dot_b_cross_c, "The determinant of the matrix of 3 vectors.");
	//py::def( "cross3",a_cross_b_cross_c, "The vector triple product.");
	py::def( "dot", dot, "The dot product between two vectors.");
	py::def( "cross", cross, "The cross product between two vectors.");
	py::def( "mag", mag, "The magnitude of a vector.");
	py::def( "mag2", mag2, "A vector's magnitude squared.");
	py::def( "norm", norm, "Returns the unit vector of its argument.");
	py::def( "comp", comp, "The scalar projection of arg1 to arg2.");
	py::def( "proj", proj, "The vector projection of arg1 to arg2.");
	py::def( "diff_angle", diff_angle, "The angle between two vectors, in radians.");
	py::def( "rotate", rotate, free_rotate( args("vector", "angle", "axis"),
		"Rotate a vector about an axis vector through an angle.") );

	//AS added throw()

	vector (vector::* truediv)( double) const throw()= &vector::operator/;
	const vector& (vector::* itruediv)( double) throw() = &vector::operator/=;

	// The vector class, constructable from 0, one, two or three doubles.
	py::class_<vector>("vector", py::init< py::optional<double, double, double> >())
		// Explicit copy.
		.def( init<vector>())
		// member variables.
		.add_property( "x", &vector::get_x, &vector::set_x)
		.add_property( "y", &vector::get_y, &vector::set_y)
		.add_property( "z", &vector::get_z, &vector::set_z)
		// Member functions masquerading as properties.
		.add_property( "mag", &vector::mag, &vector::set_mag)
		.add_property( "mag2", &vector::mag2, &vector::set_mag2)
		// Member functions
		.def( "dot", &vector::dot, "The dot product of this vector and another.")
		.def( "cross", &vector::cross, "The cross product of this vector and another.")
		.def( "norm", &vector::norm, "The unit vector of this vector.")
		.def( "comp", &vector::comp, "The scalar projection of this vector onto another.")
		.def( "proj", &vector::proj, "The vector projection of this vector onto another.")
		.def( "diff_angle", &vector::diff_angle, "The angle between this vector "
			"and another, in radians.")
		.def( "clear", &vector::clear, "Zero the state of this vector.  Potentially "
			"useful for reusing a temporary variable.")
		.def( "rotate", &vector::rotate, vector_rotate( "Rotate this vector about "
			"the specified axis through the specified angle, in radians",
			args( "angle", "axis")))
		.def( "__abs__", &vector::mag, "Return the magnitude of this vector.")
		.def( "__pos__", vector_pos, "Return an unmodified copy of this vector.")
		// Some support for the sequence protocol.
		.def( "__len__", &vector::py_len)
		.def( "__getitem__", &vector::py_getitem)
		.def( "__setitem__", &vector::py_setitem)
		// Use this to quickly convert vector's to tuples.
		.def( "astuple", vector_as_tuple, "Convert this vector to a tuple.  "
			"Same as tuple(vector), but much faster.")
		// Member operators
		.def( -self)
		.def( self + self)
		.def( self += self)
		.def( self - self)
		.def( self -= self)
		.def( self * double())
		.def( self *= double())
		.def( self / double())
		.def( self /= double())
		.def( double() * self)
		.def( self == self )
		.def( self != self )

		// This doesn't work either (NPY_FLOAT not recognized as a type):
		//.def( other<NPY_FLOAT>() * self)

		// Suggestion from Jonathan Brandmeyer, which doesn't compile:
		//.def( "__mul__", &vector::operator*(double), "Multiply vector times scalar")
		//.def( "__rmul__", &operator*(const double&, const vector&), "Multiply scalar times vector")

		// Same as self / double, when "from __future__ import division" is in effect.
		.def( "__itruediv__", itruediv, return_value_policy<copy_const_reference>())
		// Same as self /= double, when "from __future__ import division" is in effect.
		.def( "__truediv__",  truediv)
    	.def( self_ns::str(self))        // Support ">>> print foo"
		.def( "__repr__", &vector::repr) // Support ">>> foo"
		;

	// Pass a sequence to some functions that expect type visual::vector.
	vector_from_seq();
}

} // !namespace cvisual


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2014 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_LINALG_FUNCTIONS_GENERAL_TRSOLVE_HPP_INCLUDED
#define NT2_LINALG_FUNCTIONS_GENERAL_TRSOLVE_HPP_INCLUDED

#include <nt2/linalg/functions/trsolve.hpp>
#include <nt2/core/functions/transpose.hpp>
#include <nt2/core/functions/ctranspose.hpp>
#include <nt2/include/functions/trsm.hpp>
#include <nt2/include/functions/mtimes.hpp>
#include <nt2/include/constants/one.hpp>
#include <nt2/include/constants/zero.hpp>
#include <nt2/core/container/dsl/size.hpp>
#include <nt2/core/container/dsl/alias.hpp>
#include <nt2/core/container/dsl/as_terminal.hpp>
#include <nt2/core/utility/assign_swap.hpp>
#include <nt2/core/settings/locality.hpp>
#include <nt2/sdk/memory/forward/container.hpp>
#include <boost/dispatch/meta/terminal_of.hpp>
#include <nt2/sdk/memory/category.hpp>
#include <nt2/sdk/meta/as_real.hpp>
#include <nt2/sdk/meta/size_as.hpp>
#include <nt2/sdk/meta/value_as.hpp>
#include <boost/proto/traits.hpp>
#include <boost/assert.hpp>

namespace nt2 { namespace tag
{
  struct side
  {
    BOOST_FORCEINLINE static char call()
    {
      return 'L';
    }
  };

  struct diag
  {
    BOOST_FORCEINLINE static char call()
    {
      return 'N';
    }
  };

} }

namespace nt2 { namespace ext
{
  template<class Domain, int N, class Expr>
  struct  size_of<tag::trsolve_,Domain,N,Expr>
        : meta::size_as<Expr,1>
  {};

  template<class Domain, int N, class Expr>
  struct  value_type<tag::trsolve_,Domain,N,Expr>
        : meta::value_as<Expr,1>
  {};

} }

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)
                            , ((ast_<A0, nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                            )
  {
    typedef typename meta::option<typename A0::settings_type,nt2::tag::shape_>::type shape;
    typedef typename A0::value_type T;
    BOOST_DISPATCH_RETURNS(2, (A0 const& a0, A1 const& a1),
      trsolve(a0, a1, One<T>(), tag::blas_normal_(), shape::id_value , tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)
                            , ((node_< A0, nt2::tag::transpose_, boost::mpl::long_<1> , nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                            )
  {
    typedef typename boost::proto::result_of::child_c<A0&,0>::type    child0;
    typedef typename boost::dispatch::meta::terminal_of<typename boost::dispatch::meta::
                     semantic_of<child0>::type>::type                in0_t;
    typedef typename meta::option<typename in0_t::settings_type,nt2::tag::shape_>::type shape;
    typedef typename A0::value_type T;
    BOOST_DISPATCH_RETURNS(2, (A0 const& a0, A1 const& a1),
      trsolve(boost::proto::child_c<0>(a0), a1, One<T>(), tag::blas_transpose_(), shape::id_value, tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)
                            , ((node_< A0, nt2::tag::ctranspose_, boost::mpl::long_<1> , nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                            )
  {
    typedef typename boost::proto::result_of::child_c<A0&,0>::type    child0;
    typedef typename boost::dispatch::meta::terminal_of<typename boost::dispatch::meta::
                     semantic_of<child0>::type>::type                in0_t;
    typedef typename meta::option<typename in0_t::settings_type,nt2::tag::shape_>::type shape;
    typedef typename A0::value_type T;
    BOOST_DISPATCH_RETURNS(2, (A0 const& a0, A1 const& a1),
      trsolve(boost::proto::child_c<0>(a0), a1, One<T>(), tag::blas_ctranspose_(), shape::id_value, tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((ast_<A0, nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                              (scalar_< unspecified_<A2> >)
                            )
  {
    typedef typename meta::option<typename A0::settings_type,nt2::tag::shape_>::type shape;
    typedef typename A0::value_type T;
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      trsolve(a0, a1, a2, tag::blas_normal_(), shape::id_value, tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((node_< A0, nt2::tag::transpose_, boost::mpl::long_<1> , nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                              (scalar_< unspecified_<A2> >)
                            )
  {
    typedef typename boost::proto::result_of::child_c<A0&,0>::type    child0;
    typedef typename boost::dispatch::meta::terminal_of<typename boost::dispatch::meta::
                     semantic_of<child0>::type>::type                in0_t;
    typedef typename meta::option<typename in0_t::settings_type,nt2::tag::shape_>::type shape;
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      trsolve(boost::proto::child_c<0>(a0), a1, a2, tag::blas_transpose_(), shape::id_value, tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((node_< A0, nt2::tag::ctranspose_, boost::mpl::long_<1> , nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                              (scalar_< unspecified_<A2> >)
                            )
  {
    typedef typename boost::proto::result_of::child_c<A0&,0>::type    child0;
    typedef typename boost::dispatch::meta::terminal_of<typename boost::dispatch::meta::
                     semantic_of<child0>::type>::type                in0_t;
    typedef typename meta::option<typename in0_t::settings_type,nt2::tag::shape_>::type shape;
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      trsolve(boost::proto::child_c<0>(a0), a1, a2, tag::blas_ctranspose_(), shape::id_value, tag::side(), tag::diag() )
    )
  };

  // no shape for input matrix so add it as a parameter

    BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((ast_<A0, nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                              (scalar_< ints8_<A2> >)
                            )
  {
    typedef typename meta::option<typename A0::settings_type,nt2::tag::shape_>::type shape;
    typedef typename A0::value_type T;
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      trsolve(a0, a1, One<T>(), tag::blas_normal_(), a2 , tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((node_< A0, nt2::tag::transpose_, boost::mpl::long_<1> , nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                              (scalar_< ints8_<A2> >)
                            )
  {
    typedef typename A0::value_type T;
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      trsolve(boost::proto::child_c<0>(a0), a1, One<T>(), tag::blas_transpose_(), a2 , tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((node_< A0, nt2::tag::ctranspose_, boost::mpl::long_<1> , nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                              (scalar_< ints8_<A2> >)
                            )
  {
    typedef typename A0::value_type T;
    BOOST_DISPATCH_RETURNS(3, (A0 const& a0, A1 const& a1, A2 const& a2),
      trsolve(boost::proto::child_c<0>(a0), a1, One<T>(), tag::blas_ctranspose_(), a2 , tag::side(), tag::diag() )
    )
  };

  BOOST_DISPATCH_IMPLEMENT  ( trsolve_, tag::cpu_
                            , (A0)(A1)(A2)(A3)(A4)(A5)(A6)
                            , ((ast_<A0, nt2::container::domain>))
                              ((ast_<A1, nt2::container::domain>))
                              (scalar_< unspecified_<A2> >)
                              (unspecified_<A3>)
                              (unspecified_<A4>)
                              (unspecified_<A5>)
                              (unspecified_<A6>)
                            )
  {
    BOOST_DISPATCH_RETURNS(7, (A0 const& a0, A1 const& a1, A2 const& a2, A3 const& a3, A4 const& a4,A5 const& a5,A6 const& a6),
                                 (boost::proto:: make_expr<nt2::tag::trsolve_, container::domain>
                                    (boost::cref(a0), boost::cref(a1), boost::cref(a2), boost::cref(a3), boost::cref(a4), boost::cref(a5), boost::cref(a6))
                                 )
    )
  };

  // run_assign
  BOOST_DISPATCH_IMPLEMENT  ( run_assign_, tag::cpu_
                            , (A0)(A1)
                            , ((ast_<A0, nt2::container::domain>))
                              ((node_< A1, nt2::tag::trsolve_, boost::mpl::long_<7> , nt2::container::domain>))
                            )
  {
    typedef A0& result_type;
    typedef typename A1::proto_child1::proto_child0::value_type T;
    typedef typename meta::option<typename A1::proto_child1::proto_child0::settings_type
                                 ,nt2::tag::shape_
                                 >::type shape;

    typedef typename meta::option<typename A1::proto_child1::proto_child0::settings_type
                                 ,tag::locality_
                                 ,typename  A1::proto_child1::proto_child0::kind_type
                                  >::type  locality_in;

    typedef typename meta::option<typename A0::settings_type
                                 ,tag::locality_
                                 ,typename A0::kind_type
                                  >::type  locality_out;

    typedef nt2::memory::container<tag::table_, T, nt2::settings(nt2::_2D,locality_in )> desired_semantic;
    typedef nt2::memory::container<tag::table_, T, nt2::settings(nt2::_2D,shape,locality_out)> desired_semantic1;

    result_type operator()(A0& a0, A1& a1) const
    {
      char side  =  boost::proto::value(boost::proto::child_c<5>(a1)).call();
      char diag  =  boost::proto::value(boost::proto::child_c<6>(a1)).call();
      char trans =  boost::proto::value(boost::proto::child_c<3>(a1)).call();
      char uplo  = boost::proto::value(boost::proto::child_c<4>(a1));
      T alpha    = boost::proto::value(boost::proto::child_c<2>(a1));

      NT2_AS_TERMINAL_INOUT(desired_semantic1,result,boost::proto::child_c<1>(a1),a0);
      NT2_AS_TERMINAL_IN(desired_semantic,in,boost::proto::child_c<0>(a1));

      nt2::trsm(side, uplo, trans, diag, boost::proto::value(in)
                                       , boost::proto::value(result) , alpha );

      assign_swap(a0, result);

      return a0;
    }
  };
}
}

#endif


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Filename    : config.cpp                                                                            *
 * Project     : Planewalker - Schnorr-Euchner sphere decoder simulation for space-time lattice codes  *
 * Authors     : Pasi Pyrrö, Oliver Gnilke                                                             *
 * Version     : 1.0                                                                                *
 * Copyright   : Aalto University ~ School of Science ~ Department of Mathematics and Systems Analysis *
 * Date        : 17.8.2017                                                                             *
 * Language    : C++ (2011 or newer standard)                                                          *
 * Description : Implements functions for handling user input and simulation configuration             *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#define ARMA_NO_DEBUG /* disable Armadillo bound checks for addiotional speed */

#include <iostream>
#include <armadillo> /* linear algebra library */
#include <complex>
#include <algorithm>
#include <vector>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <regex>

#include "config.hpp"
#include "misc.hpp"

#define NUM_OPTIONS 22

using namespace std;
using namespace arma;

/* Creates a default configuration file in case one does not exist */
void create_config(){
    ofstream defconf(filenames["settings_default"]);
    // the file can contain comments similar to this line here
    defconf << "// configuration settings and simulation parameters for the sphere decoder program" << endl << endl;
    defconf << "basis_file=bases.txt            // Text file containing the basis matrices" << endl;
    defconf << "coset_file=                     // Optionally specify the coset encoding sublattice basis matrix file" << endl;
    defconf << "output_file=                    // Optionally specify the output filename" << endl;
    defconf << "error_file=                     // Optionally specify the file containing error requirements for the SNR simulations" << endl;
    defconf << "channel_model=mimo              // Define the channel model for the simulation (either 'mimo' or 'siso')" << endl;
    defconf << "x-PAM=4                         // The size of the PAM signaling set (even positive integer)" << endl;
    defconf << "energy_estimation_samples=-1    // Number of samples to make the code energy estimation (-1 = sample all)" << endl;
    defconf << "no_of_matrices=2                // Number of basis matrices (dimension of the data vectors)" << endl;
    defconf << "matrix_coefficient=1.0          // Multiply all basis matrices by this constant" << endl;
    defconf << "time_slots=2                    // Number of time slots used in the code" << endl;
    defconf << "no_of_transmit_antennas=2       // Number of transmit antennas" << endl;
    defconf << "no_of_receiver_antennas=2       // Number of receiver antennas"  << endl;
    defconf << "snr_min=6                       // Minimum value for signal-to-noise ratio" << endl;
    defconf << "snr_max=12                      // Maximum value for signal-to-noise ratio" << endl;
    defconf << "snr_step=2                      // Increase SNR by this value per each iteration" << endl;
    defconf << "simulation_rounds=100000        // Number of simulation rounds to run" << endl;
    defconf << "required_errors=500             // Demand at minimum this many errors before the simulation ends" << endl;
    defconf << "plot_results=-1                 // Draw plots? (1 = yes, -1 = no)" << endl;
    defconf << "stat_display_interval=-1        // Defines after each how many rounds to display the current simulation stats (-1 = disabled)" << endl;
    defconf << "spherical_shaping_max_power=-1  // Defines the maximum distance from origin for codebook elements (-1 = unbounded)" << endl;
    defconf << "codebook_size_exponent=-1       // The codebook will have 2^s codewords where s is this parameter (overrides above parameter)" << endl;
    defconf << "radius_search_density=-1        // Defines how accurate the codebook squared radius estimation will be" << endl;
    defconf.close();
}

/* Read simulation parameters from a settings file */
void configure() {
    string filepath = filenames["settings"];
    string default_filepath = filenames["settings_default"];

    ifstream config_file(filepath);

    if (!config_file.good() && filepath.compare(default_filepath) != 0) {
        log_msg("No settings file '" + filepath + "' found, using the default one...", "Alert");
        config_file = ifstream(default_filepath);
    }

    if (!config_file.good()) {
        log_msg("No default settings file found, creating a new one with default settings...");
        create_config();
        log_msg("Make your changes to the settings file and rerun this program.");
        log_msg("Exiting...");
        exit(0);
    }

    /* List special (not integer) parameter names that should be handled differently */
    vector<string> sparam_names = {"channel_model"};
    vector<string> dparam_names = {"spherical_shaping_max_power", "matrix_coefficient"};
    vector<string> accept_empty_names = {"output_file", "error_file", "coset_file"};

    string line;
    char *endptr; /* used for parsing error checking */
    int lines = 0;
    while (getline(config_file, line))
    {
        istringstream iss(line);
        string key;
        if(getline(iss, key, '=') )
        {
            if (key.find("//", 0) != string::npos)
                continue;
            lines++;
            // cout << lines << endl;
            string value;
            getline(iss, value);
            clean_input(value);
            if (!value.empty()) { /* Valid key-value pair found */
                /* kinda messy checking but works */
                if (key.compare("basis_file") == 0) {
                    filenames["bases"] = "bases/" + value;
                } else if (key.compare("coset_file") == 0) {
                    filenames["cosets"] = "bases/" + value;
                } else if (key.compare("output_file") == 0) {
                    filenames["output"] = "output/" + value;
                } else if (key.compare("error_file") == 0) {
                    filenames["error"] = "settings/" + value;
                } else if (find(dparam_names.begin(), dparam_names.end(), key) != dparam_names.end()) {
                    dparams[key] = strtod(value.c_str(), &endptr);
                    if (*endptr != 0) {
                        log_msg("Invalid value for option '" + key + "'", "Error");
                        exit(1);
                    }
                } else if (find(sparam_names.begin(), sparam_names.end(), key) != sparam_names.end()) {
                    sparams[key] = value;
                } else {
                    params[key] = strtol(value.c_str(), &endptr, 10);
                    if (*endptr != 0) {
                        log_msg("Invalid value for option '" + key + "'", "Error");
                        exit(1);
                    }
                }
            } else if (find(accept_empty_names.begin(), accept_empty_names.end(), key) == accept_empty_names.end()){
                log_msg("[Error] Value for option '" + key + "' not spesified!");
                exit(1);
            }
        }
    }
    if (lines < NUM_OPTIONS){
        log_msg("Too few options spesified in the '" + filepath + "'!", "Error");
        log_msg("Consider deleting the default settings file which will reset the program settings.");
        exit(1);
    }
    if ((params["x-PAM"] <= 0) || (params["x-PAM"] % 2 == 1)){
        log_msg("Invalid x-PAM signal set spesified!", "Error");
        log_msg("x-PAM option accepts positive even integers.");
        exit(1);
    }

    // helper variable (size of the codebook) calculated from the input parameters
    dparams["codebook_size"] = pow(params["x-PAM"], params["no_of_matrices"]);
}

/* reads k (m x t) matrices from the spesified basis_file */
vector<cx_mat> read_matrices(const string &filepath){

    /* - uses regular expression pattern to search
     *   matrix elements in almost any known format (eg. Mathematica, Matlab (with commas))
     * - ignores most white spaces and supports asterisk in front of 'I'
     * - pattern is also case insensitive
     * - Caveats: elements can't be separated by spaces alone like in Matlab,
     *            only one sign (x/-) is to be inserted between real and imag part of a complex number
     */

    /* Some supported formats for the matrix elements:
     * 1) "re ,"
     * 2) "re + im *I,"
     * 3) "re - im *I,"
     * 4) "im *I,"
     */

    /* The regex pattern */
    regex elem_regex(
        "("                                                                                // three basic types for matrix element
            "([+-]?\\s*[^\\d\\.]+[Ii]{1})"                                                       // 1. plain letter 'i' with sign
            "|"
            "(([+-]?\\s*\\d*\\.?\\d+|[+-]?\\s*\\d+\\.?\\d*)\\s*\\*?[Ii]?)"                       // 2. pure real or complex number
            "|"
             "([+-]?\\s*\\d*\\.?\\d+|[+-]?\\s*\\d+\\.?\\d*)"                                     // 3. both real part
            "\\s*"
            "(([+-]?\\s*\\d*\\.?\\d+|[+-]?\\s*\\d+\\.?\\d*|[+-]?\\s*[^\\d\\.]+)\\s*\\*?[Ii]{1})" // and imaginary part
        ")"
        "\\s*[,)};\\]\n]{1}"       // element ends in comma, semicolon, newline or closing bracket (WHITE SPACE SEPARATION NOT SUPPORTED)
    );

    int m = params["no_of_transmit_antennas"];
    int t = params["time_slots"];
    int k = params["no_of_matrices"];
    int idx = 0;

    ifstream matrix_file(filepath);
    vector<cx_mat> output;
    smatch match; /* regex match object, contains capture groups */
    vector< complex<double> > numbers; /* store the parsed elements in here */

    string content((istreambuf_iterator<char>(matrix_file)),
        istreambuf_iterator<char>());

    /* capture group substrings */
    string  m2,  // plain i
            m3,  // real or pure complex number
            m5,  // real part
            m6,  // imaginary part with i
            m7;  // imaginary part without i

    /* Search the configured basis_file for basis matrix elements */
    while(regex_search(content, match, elem_regex)){

        /***** print all capture groups for debugging! *****/
        // for (auto i = 0u; i < match.size(); ++i)
        //     cout << i << ": " << match[i].str() << endl;
        // cout << endl;

        /* assign and clean the helper substrings */
        m2 = match[2].str();
        m3 = match[3].str();
        m5 = match[5].str();
        m6 = match[6].str();
        m7 = match[7].str();

        clean_input(m2);
        clean_input(m3);
        clean_input(m5);
        clean_input(m6);
        clean_input(m7);

        /* both real and imaginary part are present */
        if (!m5.empty() && !m6.empty()) {
            if (isdigit(m7.back()) || (m7.back() == '.'))
                numbers.push_back(complex<double>(stod(m5), stod(m7)));
            else { /* imag element is of type +-i */
                numbers.push_back(complex<double>(stod(m5), stod(m7 + string("1.0"))));
            }
        }
        /* plain +-i element found */
        else if (!m2.empty()) {
            remove_from_string(m2, 'i');
            remove_from_string(m2, 'I');
            numbers.push_back(complex<double>(0.0, stod(m2 + string("1.0"))));
        }
        /* pure real or imag element found */
        else {
            if (isdigit(m3.back()) || (m3.back() == '.')) {
                numbers.push_back(complex<double>(stod(m3), 0.0));
            } else {
                numbers.push_back(complex<double>(0.0, stod(m3)));
            }
        }
        /* iterate the content, i.e. remove the part of the string we examined already */
        content = match.suffix().str();
        idx++;
    }

    /* Sanity check */
    if (m*t*k != (int)numbers.size()){
        log_msg("Failed to read the " + to_string(m*t*k) + " matrix elements (found " + to_string(numbers.size()) + \
            ") configured in '" + filepath + "'!", "Error");
        exit(1);
    }

    /* store the read matrix elements in a complex matrix datatype from Armadillo library */
    idx = 0;
    cx_mat X(m, t);
    for (int i = 0; i < k; i++){
        for (int j = 0; j < m; j++){
            for (int s = 0; s < t; s++){
                X(j,s) = numbers[idx];
                idx++;
            }
        }
        output.push_back(dparams["matrix_coefficient"]*X);
        X.zeros();
    }

    return output;
}

/* Reads the user spesified error requirements for each SNR simulation from a csv file
 * Example: 'settings/errors.csv' file contains two lines:
 * --------------------
 * 10,12,14,16,18
 * 1000,800,600,400,200
 * --------------------
 * first line has SNR values separated by commas and
 * second line has the corresponding error requirement for each SNR value
 * this means that SNR 16 simulation will be run until we hit 400 errors etc.
 */
unordered_map<int, int> read_error_requirements(const string &filepath){
    unordered_map<int, int> snr_to_error;
    vector<int> snrs, errors;
    int line_num = 1;
    ifstream error_file(filepath);
    string line;
    string value;

    if (!error_file.good()) {
        log_msg("No error requirements file '" + filepath + "' found!", "Error");
        exit(1);
    }

    while(getline(error_file, line)){
        clean_input(line);
        if (line.empty()) continue;
        istringstream iss(line);
        while(getline(iss, value, ',')){
            // clean_input(value);
            if (line_num == 1){
                snrs.push_back(strtol(value.c_str(), NULL, 10));
            } else if (line_num == 2){
                errors.push_back(strtol(value.c_str(), NULL, 10));
            } else break;
        }
        line_num++;
    }

    /* assert error requirements validity... */
    /* especially make sure that the data here matches with the data in the settings file */

    if (snrs.empty()){
        log_msg("Error requirements file '" + filepath + "' is empty!", "Error");
        exit(1);
    }

    int min = params["snr_min"];
    int max = params["snr_max"];
    int step = params["snr_step"];
    unsigned required_size = (max-min)/step + 1;

    if (snrs.size() != required_size){
        log_msg("Error requirements file '" + filepath + "' has an incorrect amount of SNR values!", "Error");
        exit(1);
    }

    if (snrs.size() != errors.size()){
        log_msg("Error requirements file '" + filepath + "' has an incorrect amount of error values!", "Error");
        exit(1);
    }

    for (auto i = 0u; i < snrs.size(); i++){
        if (min + (int)i*step != snrs[i]){
            log_msg("Error requirements file '" + filepath + "' has unspesified SNR value at column " + to_string(i+1) + "!", "Error");
            exit(1);
        }
        snr_to_error[snrs[i]] = errors[i];
    }

    /* All good! */

    return snr_to_error;
}


//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//*****************************************************************************

#pragma once

#include <Eigen/Dense>
#include <cmath>
#include <omp.h>
#include <utility>

#include "ngraph/runtime/reference/broadcast.hpp"
#include "ngraph/shape_util.hpp"
#include "ngraph/util.hpp"

namespace ngraph
{
    namespace runtime
    {
        namespace gcpu
        {
            namespace kernel
            {
#ifdef PARALLEL
                static std::tuple<size_t, size_t> get_start_finish(size_t size)
                {
                    const size_t nthreads = omp_get_num_threads();
                    const size_t ithread = omp_get_thread_num();
                    const size_t start = ithread * size / nthreads;
                    const size_t finish = (ithread + 1) * size / nthreads;
                    return std::make_tuple(start, finish);
                }
#endif
                template <typename T>
                void broadcast_2d(const T* in,
                                  T* out,
                                  const Shape& in_shape,
                                  const Shape& out_shape,
                                  const AxisSet& broadcast_axes)
                {
                    size_t index[2];
                    size_t* out_index =
                        (broadcast_axes.find(0) == broadcast_axes.end() ? &index[0] : &index[1]);
                    for (index[0] = 0; index[0] < out_shape[0]; ++index[0])
                    {
                        for (index[1] = 0; index[1] < out_shape[1]; ++index[1])
                        {
                            out[index[0] * out_shape[1] + index[1]] = in[*out_index];
                        }
                    }
                }

                // #define PARALLEL
                template <typename T>
                void broadcast_3d(const T* in,
                                  T* out,
                                  const Shape& in_shape,
                                  const Shape& out_shape,
                                  const AxisSet& broadcast_axes)
                {
#ifdef PARALLEL
#pragma omp parallel
#endif
                    {
                        size_t start;
                        size_t finish;
#ifdef PARALLEL
                        std::tie(start, finish) = get_start_finish(out_shape[0]);
#else
                        start = 0;
                        finish = out_shape[0];
#endif
                        size_t index[3];
                        size_t* out_index = 0;
                        for (size_t i = 0; i < 3; i++)
                        {
                            if (broadcast_axes.count(i) == 0)
                            {
                                out_index = &index[i];
                                break;
                            }
                        }
                        for (index[0] = start; index[0] < finish; ++index[0])
                        {
                            for (index[1] = 0; index[1] < out_shape[1]; ++index[1])
                            {
                                for (index[2] = 0; index[2] < out_shape[2]; ++index[2])
                                {
                                    out[index[0] * out_shape[1] * out_shape[2] +
                                        index[1] * out_shape[2] + index[2]] = in[*out_index];
                                }
                            }
                        }
                    }
                }

                template <typename T>
                void broadcast_4d(const T* in,
                                  T* out,
                                  const Shape& in_shape,
                                  const Shape& out_shape,
                                  const AxisSet& broadcast_axes)
                {
                    size_t index[4];
                    size_t* out_index = 0;
                    for (size_t i = 0; i < 4; i++)
                    {
                        if (broadcast_axes.count(i) == 0)
                        {
                            out_index = &index[i];
                            break;
                        }
                    }
                    for (index[0] = 0; index[0] < out_shape[0]; ++index[0])
                    {
                        for (index[1] = 0; index[1] < out_shape[1]; ++index[1])
                        {
                            for (index[2] = 0; index[2] < out_shape[2]; ++index[2])
                            {
                                for (index[3] = 0; index[3] < out_shape[3]; ++index[3])
                                {
                                    out[index[0] * out_shape[1] * out_shape[2] * out_shape[3] +
                                        index[1] * out_shape[2] * out_shape[3] +
                                        index[2] * out_shape[3] + index[3]] = in[*out_index];
                                }
                            }
                        }
                    }
                }

                template <typename T>
                void broadcast_5d(const T* in,
                                  T* out,
                                  const Shape& in_shape,
                                  const Shape& out_shape,
                                  const AxisSet& broadcast_axes)
                {
                    size_t index[5];
                    size_t* out_index = 0;
                    for (size_t i = 0; i < 5; i++)
                    {
                        if (broadcast_axes.count(i) == 0)
                        {
                            out_index = &index[i];
                            break;
                        }
                    }
                    for (index[0] = 0; index[0] < out_shape[0]; ++index[0])
                    {
                        for (index[1] = 0; index[1] < out_shape[1]; ++index[1])
                        {
                            for (index[2] = 0; index[2] < out_shape[2]; ++index[2])
                            {
                                for (index[3] = 0; index[3] < out_shape[3]; ++index[3])
                                {
                                    for (index[4] = 0; index[4] < out_shape[4]; ++index[4])
                                    {
                                        out[index[0] * out_shape[1] * out_shape[2] * out_shape[3] *
                                                out_shape[4] +
                                            index[1] * out_shape[2] * out_shape[3] * out_shape[4] +
                                            index[2] * out_shape[3] * out_shape[4] +
                                            index[3] * out_shape[4] + index[4]] = in[*out_index];
                                    }
                                }
                            }
                        }
                    }
                }

                template <typename T>
                void broadcast_6d(const T* in,
                                  T* out,
                                  const Shape& in_shape,
                                  const Shape& out_shape,
                                  const AxisSet& broadcast_axes)
                {
                    size_t index[6];
                    size_t* out_index = 0;
                    for (size_t i = 0; i < 6; i++)
                    {
                        if (broadcast_axes.count(i) == 0)
                        {
                            out_index = &index[i];
                            break;
                        }
                    }
                    for (index[0] = 0; index[0] < out_shape[0]; ++index[0])
                    {
                        for (index[1] = 0; index[1] < out_shape[1]; ++index[1])
                        {
                            for (index[2] = 0; index[2] < out_shape[2]; ++index[2])
                            {
                                for (index[3] = 0; index[3] < out_shape[3]; ++index[3])
                                {
                                    for (index[4] = 0; index[4] < out_shape[4]; ++index[4])
                                    {
                                        for (index[5] = 0; index[5] < out_shape[5]; ++index[5])
                                        {
                                            out[index[0] * out_shape[1] * out_shape[2] *
                                                    out_shape[3] * out_shape[4] * out_shape[5] +
                                                index[1] * out_shape[2] * out_shape[3] *
                                                    out_shape[4] * out_shape[5] +
                                                index[2] * out_shape[3] * out_shape[4] *
                                                    out_shape[5] +
                                                index[3] * out_shape[4] * out_shape[5] +
                                                index[4] * out_shape[5] + index[5]] =
                                                in[*out_index];
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                template <typename T>
                void broadcast(const T* in,
                               T* out,
                               const Shape& in_shape,
                               const Shape& out_shape,
                               const AxisSet& broadcast_axes)
                {
                    if (in_shape.size() == 0)
                    {
                        for (size_t i = 0; i < shape_size(out_shape); ++i)
                        {
                            out[i] = in[0];
                        }
                    }
                    else if (in_shape.size() == 1)
                    {
                        switch (out_shape.size())
                        {
                        case 2:
                            broadcast_2d<T>(in, out, in_shape, out_shape, broadcast_axes);
                            break;
                        case 3:
                            broadcast_3d<T>(in, out, in_shape, out_shape, broadcast_axes);
                            break;
                        case 4:
                            broadcast_4d<T>(in, out, in_shape, out_shape, broadcast_axes);
                            break;
                        case 5:
                            broadcast_5d<T>(in, out, in_shape, out_shape, broadcast_axes);
                            break;
                        case 6:
                            broadcast_6d<T>(in, out, in_shape, out_shape, broadcast_axes);
                            break;
                        default:
                            runtime::reference::broadcast<T>(
                                in, out, in_shape, out_shape, broadcast_axes);
                            break;
                        }
                    }
                    else
                    {
                        runtime::reference::broadcast<T>(
                            in, out, in_shape, out_shape, broadcast_axes);
                    }
                }
            }
        }
    }
}