#include <map>

#include "vtr_log.h"

#include "check_rr_graph_obj.h"

/*********************************************************************** 
 * This function aims at checking any duplicated edges (with same EdgeId) 
 * of a given node. 
 * We will walkthrough the input edges of a node and see if there is any duplication
 **********************************************************************/
static bool check_rr_graph_node_duplicated_edges(const RRGraph& rr_graph,
                                                 const RRNodeId& node) {
    bool no_duplication = true;

    /* Create a map for each input edge */
    std::map<RREdgeId, size_t> edge_counter;

    /* Check each input edges */
    for (const auto edge : rr_graph.node_in_edges(node)) {
        auto result = edge_counter.insert(std::pair<RREdgeId, size_t>(edge, 1));
        VTR_ASSERT(edge_counter.end() != result.first);
        if (false == result.second) {
            result.first->second++;
        }
    }

    for (auto& elem : edge_counter) {
        if (elem.second > 1) {
            /* Reach here it means we find some duplicated edges and report errors */
            /* Print a warning! */
            VTR_LOG_WARN("Node %d has duplicated input edges (id = %d)!\n",
                         size_t(node), size_t(elem.first));
            rr_graph.print_node(node);
            no_duplication = false;
        }
    }

    return no_duplication;
}

/*********************************************************************** 
 * Check the whole Routing Resource Graph  
 * identify and report any duplicated edges between two nodes 
 **********************************************************************/
static bool check_rr_graph_duplicated_edges(const RRGraph& rr_graph) {
    bool no_duplication = true;
    /* For each node:
     * Search input edges, see there are two edges with same id or address 
     */
    for (const auto& node : rr_graph.nodes()) {
        if (false == check_rr_graph_node_duplicated_edges(rr_graph, node)) {
            no_duplication = false;
        }
    }

    return no_duplication;
}

/*********************************************************************** 
 * Identify and report any dangling node (nodes without any fan-in or fan-out)
 * in the RRGraph
 **********************************************************************/
static bool check_rr_graph_dangling_nodes(const RRGraph& rr_graph) {
    bool no_dangling = true;
    /* For each node: 
     * check if the number of input edges and output edges are both 0
     * If so, this is a dangling nodes and report 
     */
    for (auto node : rr_graph.nodes()) {
        if ((0 == rr_graph.node_fan_in(node))
            && (0 == rr_graph.node_fan_out(node))) {
            /* Print a warning! */
            VTR_LOG_WARN("Node %s is dangling (zero fan-in and zero fan-out)!\n",
                         node);
            VTR_LOG_WARN("Node details for debugging:\n");
            rr_graph.print_node(node);
            no_dangling = false;
        }
    }

    return no_dangling;
}

/*********************************************************************** 
 * check if all the source nodes are in the right condition:
 * 1. zero fan-in and non-zero fanout
 **********************************************************************/
static bool check_rr_graph_source_nodes(const RRGraph& rr_graph) {
    bool invalid_sources = false;
    /* For each node: 
     * check if the number of input edges and output edges are both 0
     * If so, this is a dangling nodes and report 
     */
    for (auto node : rr_graph.nodes()) {
        /* Pass nodes whose types are not SOURCE */
        if (e_rr_type::SOURCE != rr_graph.node_type(node)) {
            continue;
        }
        if ((0 != rr_graph.node_fan_in(node))
            || (0 == rr_graph.node_fan_out(node))) {
            /* Print a warning! */
            VTR_LOG_WARN("Source node %d is invalid (should have zero fan-in and non-zero fan-out)!\n",
                         size_t(node));
            VTR_LOG_WARN("Node details for debugging:\n");
            rr_graph.print_node(node);
            invalid_sources = true;
        }
    }

    return invalid_sources;
}

/*********************************************************************** 
 * check if all the sink nodes are in the right condition:
 * 1. non-zero fan-in and zero fanout
 **********************************************************************/
static bool check_rr_graph_sink_nodes(const RRGraph& rr_graph) {
    bool invalid_sinks = false;
    /* For each node: 
     * check if the number of input edges and output edges are both 0
     * If so, this is a dangling nodes and report 
     */
    for (auto node : rr_graph.nodes()) {
        /* Pass nodes whose types are not SINK */
        if (e_rr_type::SINK != rr_graph.node_type(node)) {
            continue;
        }
        if ((0 == rr_graph.node_fan_in(node))
            || (0 != rr_graph.node_fan_out(node))) {
            /* Print a warning! */
            VTR_LOG_WARN("Sink node %s is invalid (should have non-zero fan-in and zero fan-out)!\n",
                         node);
            VTR_LOG_WARN("Node details for debugging:\n");
            rr_graph.print_node(node);
            invalid_sinks = true;
        }
    }

    return invalid_sinks;
}

/*********************************************************************** 
 * This is an advanced checker for RRGraph object
 * Note that the checker try to report as many problems as it can. 
 * The problems may cause routing efficiency or even failures, depending
 * on routing algorithms.
 * It is strongly suggested to run this sanitizer before conducting
 * routing algorithms
 * 
 * For those who will develop customized rr_graphs and routers:
 * On the other hand, it is suggested that developers to create their 
 * own checking function for the rr_graph, to guarantee their routers
 * will work properly.
 **********************************************************************/
bool check_rr_graph(const RRGraph& rr_graph) {
    bool check_flag = true;
    size_t num_err = 0;

    if (false == check_rr_graph_duplicated_edges(rr_graph)) {
        VTR_LOG_WARN("Fail in checking duplicated edges !\n");
        check_flag = false;
        num_err++;
    }

    if (false == check_rr_graph_dangling_nodes(rr_graph)) {
        VTR_LOG_WARN("Fail in checking dangling nodes !\n");
        check_flag = false;
        num_err++;
    }

    if (false == check_rr_graph_source_nodes(rr_graph)) {
        VTR_LOG_WARN("Fail in checking source nodes!\n");
        check_flag = false;
        num_err++;
    }

    if (false == check_rr_graph_sink_nodes(rr_graph)) {
        VTR_LOG_WARN("Fail in checking sink nodes!\n");
        check_flag = false;
        num_err++;
    }

    if (false == check_rr_graph_source_nodes(rr_graph)) {
        VTR_LOG_WARN("Fail in checking source nodes!\n");
        check_flag = false;
        num_err++;
    }

    if (false == check_rr_graph_sink_nodes(rr_graph)) {
        VTR_LOG_WARN("Fail in checking sink nodes!\n");
        check_flag = false;
        num_err++;
    }

    /* Error out if there is any fatal errors found */
    VTR_LOG_WARN("Checked Routing Resource graph with %d errors !\n",
                 num_err);

    return check_flag;
}
