#include <stdio.h>
#include <array_2d.h> // ska innehålla array_2d implementation
#include <graph.h>
#include <dlist.h>
#include <stdlib.h>
#include <string.h>

/*
 * graph.c: Implementation of a 2-dimensional array based graph for "Obligatorisk uppgift 4" in the "Datastructures and
 * algorithms" course at the Department of Computing Science, Umea University.
 *
 * Authors: Jakob Nyström tfy22jnm
 *          Ellen Horneij tfy22ehj
 *          Marc Meunier tfy22mmr
 *
 * Version information:
 *   v1.0  2025-05-25: First public version.
 *
 */

// ===========INTERNAL DATA TYPES ============

// Graph contains the matrix with nodes and edges, total number of nodes ans edges and
struct graph
{
    array_2d *entries;
    int nr_of_nodes;
    int nr_of_edges;
    int max_nodes;
};

/**
 * Node entry contains the value of the node, it's position and it's seen status
 */
struct node
{
    const char *value;
    int position;
    bool seen;
};

// =================== NODE COMPARISON FUNCTION ======================

/**
 * nodes_are_equal() - Check whether two nodes are equal.
 * @n1: Pointer to node 1.
 * @n2: Pointer to node 2.
 *
 * Returns: true if the nodes are considered equal, otherwise false.
 *
 */
bool nodes_are_equal(const node *n1, const node *n2)
{
    // Uses strcomp to compare the nodes
    int cmp = strcmp(n1->value, n2->value);

    // Returns true if strcomp returns 0 meaning that the nodes are equal, if else false
    if (cmp == 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}
// ===========INTERNAL FUNCTION IMPLEMENTATIONS ============

/**
 * node_create() - Allocates the memory and creates a node entry
 * @s: the value of the node
 *
 * Returns: The pointer to node entry with value s
 */
node *node_create(const char *s)
{
    // Allocate the memory for the node entry
    node *n = calloc(1, sizeof(*n));

    // Populate the entry.
    n->seen = false;
    
    char *tmp = malloc(strlen(s) + 1);
    if (tmp != NULL)
    {
        strcpy(tmp, s);
        n->value = tmp;
    }
    else
    {
        // Handle memory allocation failure
        free(n);
        return NULL;
    }
    return n;
}

/**
 * table_entry_kill() - Return the memory allocated to a table entry.
 * @e: The table entry to deallocate.
 *
 * Returns: Nothing.
 */
void node_kill(node *v)
{
    //node *n = v; // Convert the pointer (useful if debugging the code)
    if (v == NULL) {
        return;  // Nothing to free if v is NULL
    }

    // free strings
    free((void*)v->value);

    // All we need to do is to deallocate the struct.
    free(v);
}

// =================== GRAPH STRUCTURE INTERFACE ======================

/**
 * graph_empty() - Create an empty graph.
 * @max_nodes: The maximum number of nodes the graph can hold.
 *
 * Returns: A pointer to the new graph
 */

graph *graph_empty(int max_nodes)
{
    // Allocate the graph memory
    graph *g = calloc(1, sizeof(graph));

    // Create the array for the entries
    g->entries = array_2d_create(0, max_nodes, 0, max_nodes + 1, NULL);
    g->nr_of_nodes = 0;
    g->nr_of_edges = 0;
    g->max_nodes = max_nodes;
    return g;
}

/**
 * graph_is_empty() - Check if a graph is empty, i.e. has no nodes.
 * @g: Graph to check.
 *
 * Returns: True if graph is empty, otherwise false.
 */
bool graph_is_empty(const graph *g)
{
    // Returns true if nr_of_nodes is empty
    return g->nr_of_nodes == 0;
}

/**
 * graph_has_edges() - Check if a graph has any edges.
 * @g: Graph to check.
 *
 * Returns: True if graph has any edges, otherwise false.
 */
bool graph_has_edges(const graph *g)
{
    // Returns true if nr_of_edges is not empty
    return g->nr_of_edges != 0;
}

/**
 * graph_insert_node() - Inserts a node with the given name into the graph.
 * @g: Graph to manipulate.
 * @s: Node name.
 *
 * Creates a new node with a copy of the given name and puts it into
 * the graph.
 *
 * Returns: The modified graph.
 */
graph *graph_insert_node(graph *g, const char *s)
{
    // Define variables and create a node entry with s for comparisons
    int index = 0;
    int count = 0;
    node *m = node_create(s);

    // Check for duplicates among the already taken slots
    while (index < g->max_nodes && count < g->nr_of_nodes)
    {
        // Inspect the table entry
        node *n = array_2d_inspect_value(g->entries, index, 0);

        // Check if the node already exists
        if (n != NULL && nodes_are_equal(n, m))
        {
            // If the node is a duplicate, exit the loop and skips the insertion loop
            node_kill(m);
            return g;
        }
        if (n != NULL)
        {
            // Update how many taken slots that have been checked
            count++;
        }
        // Continue with the next position.
        index++;
    }
    // If no duplicate is found, insert the node in the first available slot

    while (index < g->max_nodes)
    {
        // Check for empty slot
        node *n = array_2d_inspect_value(g->entries, index, 0);
        if (n == NULL)
        {
            // Input node in the empty slot and update variables before exiting the loop
            m->position = index;
            m->seen = false;
            array_2d_set_value(g->entries, m, index, 0);
            g->nr_of_nodes++;
            break;
        }
        index++;
    }

    // Return the modified graph
    return g;
}

/**
 * graph_find_node() - Find a node stored in the graph.
 * @g: Graph to manipulate.
 * @s: Node identifier, e.g. a char *.
 *
 * Returns: A pointer to the found node, or NULL.
 */
node *graph_find_node(const graph *g, const char *s)
{
    int index = 0;
    int count = 0;
    node *m = node_create(s);

    // Loop over the node list
    while (index < g->max_nodes && count < g->nr_of_nodes)
    {
        // Check if a node with value s exists and return it, if not continue to next node
        node *n = array_2d_inspect_value(g->entries, index, 0);
        if (n != NULL && nodes_are_equal(n, m))
        {
            // Deallocate comparison node
            node_kill(m);
            return n;
        }
        if (n != NULL)
        {
            count++;
        }
        index++;
    }

    // If no match is found, deallocate comparison node and return NULL
    node_kill(m);
    return NULL;
}

/**
 * graph_node_is_seen() - Return the seen status for a node.
 * @g: Graph storing the node.
 * @n: Node in the graph to return seen status for.
 *
 * Returns: The seen status for the node.
 */
bool graph_node_is_seen(const graph *g, const node *n)
{
    // Check if node exists and return its seen-status
    node *m = graph_find_node(g, n->value);
    if (m != NULL)
    {
        return m->seen;
    }

    // Return NULL if the node is not found
    return false;
}

/**
 * graph_node_set_seen() - Set the seen status for a node.
 * @g: Graph storing the node.
 * @n: Node in the graph to set seen status for.
 * @s: Status to set.
 *
 * Returns: The modified graph.
 */
graph *graph_node_set_seen(graph *g, node *n, bool seen)
{
    // Check if node exists and update its seen-status
    node *m = graph_find_node(g, n->value);
    if (m != NULL)
    {
        m->seen = seen;
    }

    // Return the modified graph
    return g;
}

/**
 * graph_reset_seen() - Reset the seen status on all nodes in the graph.
 * @g: Graph to modify.
 *
 * Returns: The modified graph.
 */
graph *graph_reset_seen(graph *g)
{
    int index = 0;
    int count = 0;

    // Iterates over all nodes and sets the seen-status to FALSE
    while (index < g->max_nodes && count < g->nr_of_nodes)
    {
        node *n = array_2d_inspect_value(g->entries, index, 0);
        if (n != NULL)
        {
            n->seen = false;
            count++;
        }
        index++;
    }
    return g;
}

/**
 * graph_insert_edge() - Insert an edge into the graph.
 * @g: Graph to manipulate.
 * @n1: Source node (pointer) for the edge.
 * @n2: Destination node (pointer) for the edge.
 *
 * NOTE: Undefined unless both nodes are already in the graph.
 *
 * Returns: The modified graph.
 */
graph *graph_insert_edge(graph *g, node *n1, node *n2)
{
    // Checks if both source and destination exists
    if (graph_find_node(g, n1->value) != NULL && graph_find_node(g, n2->value) != NULL)
    {
        // Inserts n2 into the edge list of n1 and updates the nr_of_edges
        array_2d_set_value(g->entries, n2, n1->position, n2->position + 1);
        g->nr_of_edges++;
    }
    return g;
}

/**
 * graph_delete_node() - Remove a node from the graph.
 * @g: Graph to manipulate.
 * @n: Node to remove from the graph.
 *
 * Returns: The modified graph.
 *
 * NOTE: Undefined if the node is not in the graph.
 */
graph *graph_delete_node(graph *g, node *n)
{
    // Removes the node and all of its destinations
    for (int i = 0; i < g->max_nodes; i++)
    {
        array_2d_set_value(g->entries, NULL, n->position, 1);
    }

    // Updatess the nr_of_nodes and returns the modified graph
    g->nr_of_nodes--;
    return g;
}

/**
 * graph_delete_edge() - Remove an edge from the graph.
 * @g: Graph to manipulate.
 * @n1: Source node (pointer) for the edge.
 * @n2: Destination node (pointer) for the edge.
 *
 * Returns: The modified graph.
 *
 * NOTE: Undefined if the edge is not in the graph.
 */
graph *graph_delete_edge(graph *g, node *n1, node *n2)
{
    // Removes n2 from n1's destination list
    array_2d_set_value(g->entries, NULL, n1->position, n2->position + 1);
    g->nr_of_edges--;
    return g;
}

/**
 * graph_neighbours() - Return a list of neighbour nodes.
 * @g: Graph to inspect.
 * @n: Node to get neighbours for.
 *
 * Returns: A pointer to a list of nodes. Note: The list must be
 * dlist_kill()-ed after use.
 */
dlist *graph_neighbours(const graph *g, const node *n)
{
    // Creates the return list and gets its first position
    dlist *l = dlist_empty(NULL);
    dlist_pos pos = dlist_first(l);

    if (n != NULL)
    {
        for (int i = 1; i <= g->max_nodes + 1; i++)
        {
            // If a destination exists add it to the list of neigbours and update the list position
            node *m = array_2d_inspect_value(g->entries, n->position, i);
            if (m != NULL)
            {
                dlist_insert(l, m, pos);
                pos = dlist_next(l, pos);
            }
        }
    }

    // Return the list of neigbours
    return l;
}

/**
 * graph_kill() - Destroy a given graph.
 * @g: Graph to destroy.
 *
 * Return all dynamic memory used by the graph.
 *
 * Returns: Nothing.
 */
void graph_kill(graph *g)
{
    // Iterate over the list of nodes and deallocate them
    for (int i = 0; i < g->max_nodes; i++)
    {

        node *n = array_2d_inspect_value(g->entries, i, 0);
        node_kill(n);
    }

    // free the array and the graph
    array_2d_kill(g->entries);
    free(g);
}

/**
 * graph_print() - Iterate over the graph elements and print their values.
 * @g: Graph to inspect.
 *
 * Iterates over the graph and prints its contents.
 *
 * Returns: Nothing.
 */
void graph_print(const graph *g)
{
    // Itirate over the list and print the value
    for (int i = 0; i < g->max_nodes; i++)
    {
        for (int j = 0; j < g->max_nodes + 1; j++)
        {
            node *n = array_2d_inspect_value(g->entries, i, j);

            // Check if there is a value to print
            if (n != NULL && j == 0)
            {
                // Print the value
                printf("[%s] -> ", n->value);
            }
            else if (j == 0)
            {
                // Print no value
                printf("[###] -> ");
            }
            else if (n != NULL)
            {
                // Print the value
                printf("[%s] ", n->value);
            }
            else
            {
                // Print no value
                printf("[###] ");
            }
        }
        printf("\n");
    }
}
