/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
/*
 * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
 *                         University Research and Technology
 *                         Corporation.  All rights reserved.
 * Copyright (c) 2004-2012 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2004-2007 High Performance Computing Center Stuttgart,
 *                         University of Stuttgart.  All rights reserved.
 * Copyright (c) 2004-2005 The Regents of the University of California.
 *                         All rights reserved.
 * Copyright (c) 2007      Voltaire All rights reserved.
 * Copyright (c) 2016      Los Alamos National Security, LLC. All rights
 *                         reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "opal_config.h"

#include "opal/class/opal_list.h"
#include "opal/constants.h"
#include "opal/class/opal_graph.h"
#include "opal/util/output.h"

static int compare_vertex_distance(const void *item1, const void *item2);

/*
 *  Graph classes
 */


static void opal_graph_vertex_construct(opal_graph_vertex_t *vertex);
static void opal_graph_vertex_destruct(opal_graph_vertex_t *vertex);

OBJ_CLASS_INSTANCE(
    opal_graph_vertex_t,
    opal_list_item_t,
    opal_graph_vertex_construct,
    opal_graph_vertex_destruct
);


static void opal_graph_edge_construct(opal_graph_edge_t *edge);
static void opal_graph_edge_destruct(opal_graph_edge_t *edge);

OBJ_CLASS_INSTANCE(
    opal_graph_edge_t,
    opal_list_item_t,
    opal_graph_edge_construct,
    opal_graph_edge_destruct
);

static void opal_graph_construct(opal_graph_t *graph);
static void opal_graph_destruct(opal_graph_t *graph);

OBJ_CLASS_INSTANCE(
    opal_graph_t,
    opal_object_t,
    opal_graph_construct,
    opal_graph_destruct
);

static void opal_adjacency_list_construct(opal_adjacency_list_t *aj_list);
static void opal_adjacency_list_destruct(opal_adjacency_list_t *aj_list);

OBJ_CLASS_INSTANCE(
    opal_adjacency_list_t,
    opal_list_item_t,
    opal_adjacency_list_construct,
    opal_adjacency_list_destruct
);



/*
 *
 *      opal_graph_vertex_t interface
 *
 */

static void opal_graph_vertex_construct(opal_graph_vertex_t *vertex)
{
    vertex->in_adj_list = NULL;
    vertex->in_graph = NULL;
    vertex->vertex_data = NULL;
    vertex->sibling = NULL;
    vertex->copy_vertex_data = NULL;
    vertex->free_vertex_data = NULL;
    vertex->alloc_vertex_data = NULL;
    vertex->compare_vertex = NULL;
    vertex->print_vertex = NULL;
}

static void opal_graph_vertex_destruct(opal_graph_vertex_t *vertex)
{
    vertex->in_adj_list = NULL;
    vertex->in_graph = NULL;
    vertex->sibling = NULL;
    vertex->copy_vertex_data = NULL;
    vertex->alloc_vertex_data = NULL;
    vertex->compare_vertex = NULL;
    if (NULL != vertex->free_vertex_data) {
        vertex->free_vertex_data(vertex->vertex_data);
    }
    vertex->vertex_data = NULL;
    vertex->print_vertex = NULL;
}


/*
 *
 *      opal_graph_edge_t interface
 *
 */

static void opal_graph_edge_construct(opal_graph_edge_t *edge)
{
    edge->end = NULL;
    edge->start = NULL;
    edge->weight = 0;
    edge->in_adj_list = NULL;
}


static void opal_graph_edge_destruct(opal_graph_edge_t *edge)
{
    edge->end = NULL;
    edge->start = NULL;
    edge->weight = 0;
    edge->in_adj_list = NULL;
}


/*
 *
 *     opal_graph_t  interface
 *
 */


static void opal_graph_construct(opal_graph_t *graph)
{
    graph->adjacency_list = OBJ_NEW(opal_list_t);
    graph->number_of_vertices = 0;
    graph->number_of_edges = 0;
}

static void opal_graph_destruct(opal_graph_t *graph)
{
    OPAL_LIST_RELEASE(graph->adjacency_list);
    graph->number_of_vertices = 0;
    graph->number_of_edges = 0;
}

/*
 *
 *     opal_adjacency_list  interface
 *
 */

static void opal_adjacency_list_construct(opal_adjacency_list_t *aj_list)
{
    aj_list->vertex = NULL;
    aj_list->edges = OBJ_NEW(opal_list_t);
}

static void opal_adjacency_list_destruct(opal_adjacency_list_t *aj_list)
{
   aj_list->vertex = NULL;
   OPAL_LIST_RELEASE(aj_list->edges);
}

/**
 * This function deletes all the edges that are connected *to* a
 * vertex.
 *
 * @param graph
 * @param vertex
 */
static void delete_all_edges_conceded_to_vertex(opal_graph_t *graph, opal_graph_vertex_t *vertex)
{
    opal_adjacency_list_t *aj_list;
    opal_list_item_t *aj_list_item;
    opal_graph_edge_t *edge;
    opal_list_item_t *edge_item;

    /**
     * for all the adjacency list in the graph
     */
    for (aj_list_item = opal_list_get_first(graph->adjacency_list);
         aj_list_item != opal_list_get_end(graph->adjacency_list);
         aj_list_item  = opal_list_get_next(aj_list_item)) {
        aj_list = (opal_adjacency_list_t *) aj_list_item;
        /**
         * for all the edges in the adjacency list
         */
        edge_item = opal_list_get_first(aj_list->edges);
        while (edge_item != opal_list_get_end(aj_list->edges)) {
            edge = (opal_graph_edge_t *)edge_item;
            edge_item  = opal_list_get_next(edge_item);

            /**
             * if the edge is ended in the vertex
             */
            if (edge->end == vertex) {
                /* Delete this edge  */
                opal_list_remove_item(edge->in_adj_list->edges, (opal_list_item_t*)edge);
                /* distract this edge */
                OBJ_RELEASE(edge);
            }
        }
    }
}

/**
 * This graph API adds a vertex to graph. The most common use
 * for this API is while building a graph.
 *
 * @param graph The graph that the vertex will be added to.
 * @param vertex The vertex we want to add.
 */
void opal_graph_add_vertex(opal_graph_t *graph, opal_graph_vertex_t *vertex)
{
   opal_adjacency_list_t *aj_list;
   opal_list_item_t *item;

   /**
    * Find if this vertex already exists in the graph.
    */
   for (item = opal_list_get_first(graph->adjacency_list);
        item != opal_list_get_end(graph->adjacency_list);
        item  = opal_list_get_next(item)) {
       aj_list = (opal_adjacency_list_t *) item;
       if (aj_list->vertex == vertex) {
           /* If this vertex exists, dont do anything. */
           return;
       }
   }
   /* Construct a new adjacency list */
   aj_list = OBJ_NEW(opal_adjacency_list_t);
   aj_list->vertex = vertex;
   /* point the vertex to the adjacency list of the vertex (for easy searching) */
   vertex->in_adj_list = aj_list;
   /* Append the new creates adjacency list to the graph */
   opal_list_append(graph->adjacency_list, (opal_list_item_t*)aj_list);
   /* point the vertex to the graph it belongs to (mostly for debug uses)*/
   vertex->in_graph = graph;
   /* increase the number of vertices in the graph */
   graph->number_of_vertices++;
}


/**
 * This graph API adds an edge (connection between two
 * vertices) to a graph. The most common use
 * for this API is while building a graph.
 *
 * @param graph The graph that this edge will be added to.
 * @param edge The edge that we want to add.
 *
 * @return int Success or error. this API can return an error if
 *         one of the vertices is not in the graph.
 */
int opal_graph_add_edge(opal_graph_t *graph, opal_graph_edge_t *edge)
{
    opal_adjacency_list_t *aj_list, *start_aj_list= NULL;
    opal_list_item_t *item;
    bool start_found = false, end_found = false;


    /**
     * find the vertices that this edge should connect.
     */
    for (item = opal_list_get_first(graph->adjacency_list);
         item != opal_list_get_end(graph->adjacency_list);
         item  = opal_list_get_next(item)) {
        aj_list = (opal_adjacency_list_t *) item;
        if (aj_list->vertex == edge->start) {
            start_found = true;
            start_aj_list = aj_list;
        }
        if (aj_list->vertex == edge->end) {
            end_found = true;
        }
    }
    /**
     * if one of the vertices either the start or the end is not
     * found - return an error.
     */
    if (false == start_found && false == end_found) {
        return OPAL_ERROR;
    }
    /* point the edge to the adjacency list of the start vertex (for easy search) */
    edge->in_adj_list=start_aj_list;
    /* append the edge to the adjacency list of the start vertex */
    opal_list_append(start_aj_list->edges, (opal_list_item_t*)edge);
    /* increase the graph size */
    graph->number_of_edges++;
    return OPAL_SUCCESS;
}


/**
 * This graph API removes an edge (a connection between two
 * vertices) from the graph. The most common use of this API is
 * while destructing a graph or while removing vertices from a
 * graph. while removing vertices from a graph, we should also
 * remove the connections from and to the vertices that we are
 * removing.
 *
 * @param graph The graph that this edge will be remove from.
 * @param edge the edge that we want to remove.
 */
void opal_graph_remove_edge (opal_graph_t *graph, opal_graph_edge_t *edge)
{
    /* remove the edge from the list it belongs to */
    opal_list_remove_item(edge->in_adj_list->edges, (opal_list_item_t*)edge);
    /* decrees the number of edges in the graph */
    graph->number_of_edges--;
    /* Note that the edge is not destructed - the caller should destruct this edge. */
}

/**
 * This graph API remove a vertex from graph. The most common
 * use for this API is while distracting a graph or while
 * removing relevant vertices from a graph.
 *
 * @param graph The graph that the vertex will be remove from.
 * @param vertex The vertex we want to remove.
 */

void opal_graph_remove_vertex(opal_graph_t *graph, opal_graph_vertex_t *vertex)
{
    opal_adjacency_list_t *adj_list;

    /* do not need to remove all the edges of this vertex and destruct them as
     * they will be released in the destructor for adj_list */
    adj_list = vertex->in_adj_list;
    /**
     * remove the adjscency list of this vertex from the graph and
     * destruct it.
     */
    opal_list_remove_item(graph->adjacency_list, (opal_list_item_t*)adj_list);
    OBJ_RELEASE(adj_list);
    /**
     * delete all the edges that connected *to* the vertex.
     */
    delete_all_edges_conceded_to_vertex(graph, vertex);
    /* destruct the vertex */
    OBJ_RELEASE(vertex);
    /* decrease the number of vertices in the graph. */
    graph->number_of_vertices--;
}

/**
 * This graph API tell us if two vertices are adjacent
 *
 * @param graph The graph that the vertices belongs to.
 * @param vertex1 first vertex.
 * @param vertex2 second vertex.
 *
 * @return uint32_t the weight of the connection between the two
 *         vertices or infinity if the vertices are not
 *         connected.
 */
uint32_t opal_graph_adjacent(opal_graph_t *graph, opal_graph_vertex_t *vertex1, opal_graph_vertex_t *vertex2)
{
    opal_adjacency_list_t *adj_list;
    opal_list_item_t *item;
    opal_graph_edge_t *edge;

    /**
     * Verify that the first vertex belongs to the graph.
     */
    if (graph != vertex1->in_graph) {
        OPAL_OUTPUT((0,"opal_graph_adjacent 1 Vertex1 %p not in the graph %p\n",(void *)vertex1,(void *)graph));
        return DISTANCE_INFINITY;
    }
    /**
     * Verify that the second vertex belongs to the graph.
     */
    if (graph != vertex2->in_graph) {
        OPAL_OUTPUT((0,"opal_graph_adjacent 2 Vertex2 %p not in the graph %p\n",(void *)vertex2,(void *)graph));
        return DISTANCE_INFINITY;
    }
    /**
     * If the first vertex and the second vertex are the same
     * vertex, the distance between the is 0.
     */
    if (vertex1 == vertex2) {
        return 0;
    }
    /**
     * find the second vertex in the adjacency list of the first
     * vertex.
     */
    adj_list = (opal_adjacency_list_t *) vertex1->in_adj_list;
    for (item = opal_list_get_first(adj_list->edges);
         item != opal_list_get_end(adj_list->edges);
         item  = opal_list_get_next(item)) {
        edge = (opal_graph_edge_t *)item;
        if (edge->end == vertex2) {
            /* if the second vertex was found in the adjacency list of the first one, return the weight */
            return edge->weight;
        }
    }
    /* if the second vertex was not found in the adjacency list of the first one, return infinity */
    return DISTANCE_INFINITY;
}

/**
 * This Graph API returns the order of the graph (number of
 * vertices)
 *
 * @param graph
 *
 * @return int
 */
int opal_graph_get_order(opal_graph_t *graph)
{
    return graph->number_of_vertices;
}

/**
 * This Graph API returns the size of the graph (number of
 * edges)
 *
 * @param graph
 *
 * @return int
 */
int opal_graph_get_size(opal_graph_t *graph)
{
    return graph->number_of_edges;
}

/**
 * This graph API finds a vertex in the graph according the
 * vertex data.
 * @param graph the graph we searching in.
 * @param vertex_data the vertex data we are searching according
 *                    to.
 *
 * @return opal_graph_vertex_t* The vertex founded or NULL.
 */
opal_graph_vertex_t *opal_graph_find_vertex(opal_graph_t *graph, void *vertex_data)
{
    opal_adjacency_list_t *aj_list;
    opal_list_item_t *item;

    /**
     * Run on all the vertices of the graph
     */
    for (item = opal_list_get_first(graph->adjacency_list);
         item != opal_list_get_end(graph->adjacency_list);
         item  = opal_list_get_next(item)) {
        aj_list = (opal_adjacency_list_t *) item;
        if (NULL != aj_list->vertex->compare_vertex) {
            /* if the vertex data of a vertex is equal to the vertex data */
            if (0 == aj_list->vertex->compare_vertex(aj_list->vertex->vertex_data, vertex_data)) {
                /* return the found vertex */
                return aj_list->vertex;
            }
        }
    }
    /* if a vertex is not found, return NULL */
    return NULL;
}


/**
 * This graph API returns an array of pointers of all the
 * vertices in the graph.
 *
 *
 * @param graph
 * @param vertices_list an array of pointers of all the
 *         vertices in the graph vertices.
 *
 * @return int returning the graph order (the
 *                    number of vertices in the returned array)
 */
int opal_graph_get_graph_vertices(opal_graph_t *graph, opal_pointer_array_t *vertices_list)
{
    opal_adjacency_list_t *aj_list;
    opal_list_item_t *item;
    int i;

    /**
     * If the graph order is 0, return NULL.
     */
    if (0 == graph->number_of_vertices) {
        return 0;
    }
    /* Run on all the vertices of the graph */
    for (item = opal_list_get_first(graph->adjacency_list), i = 0;
         item != opal_list_get_end(graph->adjacency_list);
         item  = opal_list_get_next(item), i++) {
        aj_list = (opal_adjacency_list_t *) item;
        /* Add the vertex to the vertices array */
        opal_pointer_array_add(vertices_list,(void *)aj_list->vertex);
    }
    /* return the vertices list */
    return graph->number_of_vertices;
}

/**
 * This graph API returns all the adjacents of a vertex and the
 * distance (weight) of those adjacents and the vertex.
 *
 * @param graph
 * @param vertex The reference vertex
 * @param adjacents An allocated pointer array of vertices and
 *                  their distance from the reference vertex.
 *                  Note that this pointer should be free after
 *                  usage by the user
 *
 * @return int the number of adjacents in the list.
 */
int opal_graph_get_adjacent_vertices(opal_graph_t *graph, opal_graph_vertex_t *vertex, opal_value_array_t *adjacents)
{
    opal_adjacency_list_t *adj_list;
    opal_graph_edge_t *edge;
    int adjacents_number;
    opal_list_item_t *item;
    vertex_distance_from_t distance_from;
    int i;

    /**
     * Verify that the vertex belongs to the graph.
     */
    if (graph != vertex->in_graph) {
        OPAL_OUTPUT((0,"Vertex %p not in the graph %p\n", (void *)vertex, (void *)graph));
        return 0;
    }
    /**
     * find the adjacency list that this vertex belongs to
     */
    adj_list = (opal_adjacency_list_t *) vertex->in_adj_list;
    /* find the number of adjcents of this vertex */
    adjacents_number = opal_list_get_size(adj_list->edges);
    /* Run on all the edges from this vertex */
    for (item = opal_list_get_first(adj_list->edges), i = 0;
         item != opal_list_get_end(adj_list->edges);
         item  = opal_list_get_next(item), i++) {
        edge = (opal_graph_edge_t *)item;
        /* assign vertices and their weight in the adjcents list */
        distance_from.vertex = edge->end;
        distance_from.weight = edge->weight;
        opal_value_array_append_item(adjacents, &distance_from);
    }
    /* return the number of the adjacents in the list */
    return adjacents_number;
}

/**
 * This graph API finds the shortest path between two vertices.
 *
 * @param graph
 * @param vertex1 The start vertex.
 * @param vertex2 The end vertex.
 *
 * @return uint32_t the distance between the two vertices.
 */

uint32_t opal_graph_spf(opal_graph_t *graph, opal_graph_vertex_t *vertex1, opal_graph_vertex_t *vertex2)
{
    opal_value_array_t *distance_array;
    uint32_t items_in_distance_array, spf = DISTANCE_INFINITY;
    vertex_distance_from_t *vertex_distance;
    uint32_t i;

    /**
     * Verify that the first vertex belongs to the graph.
     */
    if (graph != vertex1->in_graph) {
        OPAL_OUTPUT((0,"opal_graph_spf 1 Vertex1 %p not in the graph %p\n",(void *)vertex1,(void *)graph));
        return DISTANCE_INFINITY;
    }
    /**
     * Verify that the second vertex belongs to the graph.
     */
    if (graph != vertex2->in_graph) {
        OPAL_OUTPUT((0,"opal_graph_spf 2 Vertex2 %p not in the graph %p\n",(void *)vertex2,(void *)graph));
        return DISTANCE_INFINITY;
    }
    /**
     * Run Dijkstra algorithm on the graph from the start vertex.
     */
    distance_array = OBJ_NEW(opal_value_array_t);
    opal_value_array_init(distance_array, sizeof(vertex_distance_from_t));
    opal_value_array_reserve(distance_array,50);
    items_in_distance_array = opal_graph_dijkstra(graph, vertex1, distance_array);
    /**
     * find the end vertex in the distance array that Dijkstra
     * algorithm returned.
     */
    for (i = 0; i < items_in_distance_array; i++) {
        vertex_distance = opal_value_array_get_item(distance_array, i);
        if (vertex_distance->vertex == vertex2) {
            spf = vertex_distance->weight;
            break;
        }
    }
    OBJ_RELEASE(distance_array);
    /* return the distance (weight) to the end vertex */
    return spf;
}

/**
 * Compare the distance between two vertex distance items. this
 * function is used for sorting an array of vertices distance by
 * qsort function.
 *
 * @param item1 a void pointer to vertex distance structure
 * @param item2 a void pointer to vertex distance structure
 *
 * @return int 1 - the first item weight is higher then the
 *         second item weight. 0 - the weights are equal. -1 -
 *         the second item weight is higher the the first item
 *         weight.
 */
static int compare_vertex_distance(const void *item1, const void *item2)
{
    vertex_distance_from_t *vertex_dist1, *vertex_dist2;

    /* convert the void pointers to vertex distance pointers. */
    vertex_dist1 = (vertex_distance_from_t *)item1;
    vertex_dist2 = (vertex_distance_from_t *)item2;

    /* If the first item weight is higher then the second item weight return 1*/
    if (vertex_dist1->weight > vertex_dist2->weight) {
        return 1;
    }
    /* If they are equal return 0 */
    else if (vertex_dist1->weight == vertex_dist2->weight) {
        return 0;
    }
    /* if you reached here then the second item weight is higher the the first item weight */
    return -1;
}


/**
 * This graph API returns the distance (weight) from a reference
 * vertex to all other vertices in the graph using the Dijkstra
 * algorithm
 *
 * @param graph
 * @param vertex The reference vertex.
 * @param distance_array An array of vertices and
 *         their distance from the reference vertex.
 *
 * @return uint32_t the size of the distance array
 */
uint32_t opal_graph_dijkstra(opal_graph_t *graph, opal_graph_vertex_t *vertex, opal_value_array_t *distance_array)
{
    int graph_order;
    vertex_distance_from_t *Q, *q_start, *current_vertex;
    opal_list_item_t *adj_list_item;
    opal_adjacency_list_t *adj_list;
    int number_of_items_in_q;
    int i;
    uint32_t weight;


    /**
     * Verify that the reference vertex belongs to the graph.
     */
    if (graph != vertex->in_graph) {
        OPAL_OUTPUT((0,"opal:graph:dijkstra: vertex %p not in the graph %p\n",(void *)vertex,(void *)graph));
        return 0;
    }
    /* get the order of the graph and allocate a working queue accordingly */
    graph_order = opal_graph_get_order(graph);
    Q = (vertex_distance_from_t *)malloc(graph_order * sizeof(vertex_distance_from_t));
    /* assign a pointer to the start of the queue */
    q_start = Q;
    /* run on all the vertices of the graph */
    for (adj_list_item = opal_list_get_first(graph->adjacency_list), i=0;
         adj_list_item != opal_list_get_end(graph->adjacency_list);
         adj_list_item  = opal_list_get_next(adj_list_item), i++) {
        adj_list = (opal_adjacency_list_t *)adj_list_item;
        /* insert the vertices pointes to the working queue */
        Q[i].vertex = adj_list->vertex;
        /**
         * assign an infinity distance to all the vertices in the queue
         * except the reference vertex which its distance should be 0.
         */
        if (Q[i].vertex == vertex) {
            Q[i].weight = 0;
        }
        else {
            Q[i].weight = DISTANCE_INFINITY;
        }
    }
    number_of_items_in_q = i;
    /* sort the working queue according the distance from the reference vertex */
    qsort(q_start, number_of_items_in_q, sizeof(vertex_distance_from_t), compare_vertex_distance);
    /* while the working queue is not empty */
    while (number_of_items_in_q > 0) {
        /* start to work with the first vertex in the working queue */
        current_vertex = q_start;
        /* remove the first vertex from the queue */
        q_start++;
        /* decrees the number of vertices in the queue */
        number_of_items_in_q--;
        /* find the distance of all other vertices in the queue from the first vertex in the queue */
        for (i = 0; i < number_of_items_in_q; i++) {
            weight = opal_graph_adjacent(graph, current_vertex->vertex, q_start[i].vertex);
            /**
             * if the distance from the first vertex in the queue to the I
             * vertex in the queue plus the distance of the first vertex in
             * the queue from the referenced vertex is smaller than the
             * distance of the I vertex from the referenced vertex, assign
             * the lower distance to the I vertex.
             */
            if (current_vertex->weight + weight < q_start[i].weight) {
                q_start[i].weight = weight + current_vertex->weight;
            }
        }
        /* sort again the working queue */
        qsort(q_start, number_of_items_in_q, sizeof(vertex_distance_from_t), compare_vertex_distance);
    }
    /* copy the working queue the the returned distance array */
    for (i = 0; i < graph_order-1; i++) {
        opal_value_array_append_item(distance_array, (void *)&(Q[i+1]));
    }
    /* free the working queue */
    free(Q);
    /* assign the distance array size. */
    return graph_order - 1;
}


/**
 * This graph API duplicates a graph. Note that this API does
 * not copy the graph but builds a new graph while coping just
 * the vertex data.
 *
 * @param dest The new created graph.
 * @param src The graph we want to duplicate.
 */
void opal_graph_duplicate(opal_graph_t **dest, opal_graph_t *src)
{
    opal_adjacency_list_t *aj_list;
    opal_list_item_t *aj_list_item, *edg_item;
    opal_graph_vertex_t *vertex;
    opal_graph_edge_t *edge, *new_edge;

    /* construct a new graph */
    *dest = OBJ_NEW(opal_graph_t);
    /* Run on all the vertices of the src graph */
    for (aj_list_item = opal_list_get_first(src->adjacency_list);
         aj_list_item != opal_list_get_end(src->adjacency_list);
         aj_list_item  = opal_list_get_next(aj_list_item)) {
        aj_list = (opal_adjacency_list_t *) aj_list_item;
        /* for each vertex in the src graph, construct a new vertex */
        vertex = OBJ_NEW(opal_graph_vertex_t);
        /* associate the new vertex to a vertex from the original graph */
        vertex->sibling = aj_list->vertex;
        /* associate the original vertex to the new constructed vertex */
        aj_list->vertex->sibling = vertex;
        /* allocate space to vertex data in the new vertex */
        if (NULL != aj_list->vertex->alloc_vertex_data) {
            vertex->vertex_data = aj_list->vertex->alloc_vertex_data();
            vertex->alloc_vertex_data = aj_list->vertex->alloc_vertex_data;
        }
        /* copy the vertex data from the original vertex  to the new vertex */
        if (NULL != aj_list->vertex->copy_vertex_data) {
            aj_list->vertex->copy_vertex_data(&(vertex->vertex_data), aj_list->vertex->vertex_data);
            vertex->copy_vertex_data = aj_list->vertex->copy_vertex_data;
        }
        /* copy all the fields of the original vertex to the new vertex. */
        vertex->free_vertex_data = aj_list->vertex->free_vertex_data;
        vertex->print_vertex = aj_list->vertex->print_vertex;
        vertex->compare_vertex = aj_list->vertex->compare_vertex;
        vertex->in_graph = *dest;
        /* add the new vertex to the new graph */
        opal_graph_add_vertex(*dest, vertex);
    }
    /**
     * Now, copy all the edges from the source graph
     */
    /* Run on all the adjscency lists in the graph */
    for (aj_list_item = opal_list_get_first(src->adjacency_list);
         aj_list_item != opal_list_get_end(src->adjacency_list);
         aj_list_item  = opal_list_get_next(aj_list_item)) {
        aj_list = (opal_adjacency_list_t *) aj_list_item;
        /* for all the edges in the adjscency list */
        for (edg_item = opal_list_get_first(aj_list->edges);
             edg_item != opal_list_get_end(aj_list->edges);
             edg_item = opal_list_get_next(edg_item)) {
            edge = (opal_graph_edge_t *)edg_item;
            /* construct new edge for the new graph */
            new_edge = OBJ_NEW(opal_graph_edge_t);
            /* copy the edge weight from the original edge */
            new_edge->weight = edge->weight;
            /* connect the new edge according to start and end associations to the vertices of the src graph */
            new_edge->start = edge->start->sibling;
            new_edge->end = edge->end->sibling;
            /* add the new edge to the new graph */
            opal_graph_add_edge(*dest, new_edge);
        }
    }
}

/**
 * This graph API prints a graph - mostly for debug uses.
 * @param graph
 */
void opal_graph_print(opal_graph_t *graph)
{
    opal_adjacency_list_t *aj_list;
    opal_list_item_t *aj_list_item;
    opal_graph_edge_t *edge;
    opal_list_item_t *edge_item;
    char *tmp_str1, *tmp_str2;
    bool need_free1, need_free2;

    /* print header */
    opal_output(0, "      Graph         ");
    opal_output(0, "====================");
    /* run on all the vertices of the graph */
    for (aj_list_item = opal_list_get_first(graph->adjacency_list);
         aj_list_item != opal_list_get_end(graph->adjacency_list);
         aj_list_item  = opal_list_get_next(aj_list_item)) {
        aj_list = (opal_adjacency_list_t *) aj_list_item;
        /* print vertex data to temporary string*/
        if (NULL != aj_list->vertex->print_vertex) {
            need_free1 = true;
            tmp_str1 = aj_list->vertex->print_vertex(aj_list->vertex->vertex_data);
        }
        else {
            need_free1 = false;
            tmp_str1 = "";
        }
        /* print vertex */
        opal_output(0, "V(%s) Connections:",tmp_str1);
        /* run on all the edges of the vertex */
        for (edge_item = opal_list_get_first(aj_list->edges);
             edge_item != opal_list_get_end(aj_list->edges);
             edge_item  = opal_list_get_next(edge_item)) {
            edge = (opal_graph_edge_t *)edge_item;
            /* print the vertex data of the vertex in the end of the edge to a temporary string */
            if (NULL != edge->end->print_vertex) {
                need_free2 = true;
                tmp_str2 = edge->end->print_vertex(edge->end->vertex_data);
            }
            else {
                need_free2 = false;
                tmp_str2 = "";
            }
            /* print the edge */
            opal_output(0, "    E(%s -> %d -> %s)",tmp_str1, edge->weight, tmp_str2);
            if (need_free2) {
                free(tmp_str2);
            }
        }
        if (need_free1) {
            free(tmp_str1);
        }
    }
}

