#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <graph.h>
#include <ctype.h>
#include <queue.h>
#include <dlist.h>
#include <string.h>

#define MAXNODENAME 40
#define BUFSIZE 400

/*
 * is:connected.c: The main program for traversing a graph for "Obligatorisk uppgift 4" in the "Datastructures and
 * algorithms" course at the Department of Computing Science, Umea University.
 *
 * Authors: Ellen Horneij tfy22ehj
 *          Jakob Nyström tfy22jnm
 *          Marc Meunier tfy22mmr
 *
 * Version information:
 *   v1.0  2025-05-25: First public version.
 *   v2.0  2025-06-02: Fixed traversing
 *   v3.0  2025-07-29: Fixed error messages 
 *
 */

// ===========HELP FUNCTIONS ============
/**
 * @brief Find position of first non-whitespace character.
 *
 * @param s - string to search.
 * @return int - Returns the position of the first non-whitespace character, or -1 if not found.
 */
int first_non_white_space(const char *s)
{
    int i = 0; // Start at first char.

    // Advance as long as we're loooking at white-space until we hit EOL.
    while (s[i] && isspace(s[i]))
    {
        i++;
    }
    // We found a non-whitespace char.
    if (s[i])
    {
        // It was a proper characted. Return its position.
        return i;
    }
    else
    {
        // It was the null terminator. Return fail.
        return -1;
    }
}

/**
 * @brief Determine if the string is blank.
 *
 * @param s - string to search.
 * @return true if the line contains only whitespace chars.
 * @return false if the line contains at least one non-whitespace char.
 */

bool line_is_blank(const char *s)
{
    // Line is blank if it only contained white-space chars.
    return first_non_white_space(s) < 0;
}

/**
 * @brief Determine if the string is a comment line.
 *
 * @param s - string to search.
 * @return true if the line is a comment line.
 * @return false if the line is not a comment line.
 *
 * A comment line has a hash sign '#' as the first non-whitespace char on the line.
 */
bool line_is_comment(const char *s)
{
    int i = first_non_white_space(s);
    return (i >= 0 && s[i] == '#');
}

/**
 * @brief Extract node names from a line from the map file.
 *
 * @param buf - Input buffer.
 * @param n1 - Output buffer for the first node name. Must be at least MAXNODENAME+1 long.
 * @param n2 - Ditto for the second node name.
 * @return int - Returns the number of correctly parsed node names. If the return value is 2, both n1
 * and n2 contain node names. If the return value is less than 2, parsing of at least one node name
 * failed, in which case the content of n1 and n2 are undefined.
 */
int parse_map_line(const char *buf, char *n1, char *n2)
{
    // Create a format string the will do the work.
    char fmt[20];
    // This will generate the format string " %40s %40s" if MAXNODENAME is 40.
    snprintf(fmt, sizeof(fmt), " %%%ds %%%ds", MAXNODENAME, MAXNODENAME);

    // sscanf does all the necessary parsing.
    // Node names must be separated by whitespace.
    // Whitespace before the first node name is allowed.
    // Anything after the second node name is ignored.
    int n = sscanf(buf, fmt, n1, n2);

    // The return value from sscanf contains the number of properly parsed format codes, i.e. the
    // number of node names.
    return n;
}

/**
 * FUNCTION TO TRAVERSE THE GRAPH
 * @g: pointer to the graph
 * @src: pointer to source node
 * @dest: pointer to destination node
 */
bool find_path(graph *g, node *src, node *dest)
{

    // Mark the starting node as seen
    g = graph_node_set_seen(g, src, true);
    dlist *neighbours = dlist_empty(NULL);
    // Put it in an empty queue
    queue *q = queue_enqueue(queue_empty(NULL), src);
    while (!queue_is_empty(q))
    {
        // pick the first node from queue
        src = queue_front(q);
        q = queue_dequeue(q);

        // if node equals destinatiom node, return true
        if (nodes_are_equal(src, dest))
        {
            // Deallocate the queue and list
            queue_kill(q);
            dlist_kill(neighbours);
            return true;
        }
        // remove list before making new.
        if (neighbours != NULL)
        {
            dlist_kill(neighbours);
        }
        // get the neighbours
        neighbours = graph_neighbours(g, src);
        dlist_pos p = dlist_first(neighbours);

        // Iterate over the lists of neigbours
        while (!dlist_is_end(neighbours, p))
        {

            if (!graph_node_is_seen(g, dlist_inspect(neighbours, p)))
            {
                // Mark unseen node as seen and put in queue
                g = graph_node_set_seen(g, dlist_inspect(neighbours, p), true);
                q = queue_enqueue(q, dlist_inspect(neighbours, p));
            }
            p = dlist_next(neighbours, p);
        }
    }
    // Deallocate the list and the queue
    dlist_kill(neighbours);
    queue_kill(q);
    return false;
}

/**
 * check_node_name - function used to find errors in node name
 *
 * Returns: Exit faliure if error in map file
 */
void check_node_name(char *n1, char *n2, char *data)
{
        int k = parse_map_line(data, n1, n2);

        //Print error if number of correct node names is not 2
        if (k != 2)
        {
            fprintf(stderr, "Error reading the file\n");
            exit(EXIT_FAILURE);
        }
        //Check for non alphanumeric characters
        for (int j = 0; j < strlen(n1); j++)
        {
            if (isalnum(n1[j])==0)
            {
                fprintf(stderr, "Error non alphanumeric\n");
                exit(EXIT_FAILURE);                
            }
        }
        for (int i = 0; i < strlen(n2); i++)
        {
            if (isalnum(n2[i])==0)
            {
                fprintf(stderr, "Error non alphanumeric\n");
                exit(EXIT_FAILURE);                
            }
        }

}

/**
 * insert_nodes - insert the nodes to the graph
 * Returns: A graph with the input nodes inserted
 */
graph *insert_nodes(graph *g, char *n1, char *n2)
{
        g = graph_insert_node(g, n1);
        g = graph_insert_node(g, n2);

        // Get pointers to the nodes from the graph
        node *m1 = graph_find_node(g, n1);
        node *m2 = graph_find_node(g, n2);

        // Check that both nodes exist
        if (m1 == NULL || m2 == NULL)
        {
            fprintf(stderr, "Error: node not found for edge: %s - %s\n", n1, n2);
            exit(EXIT_FAILURE);
        }

        // Insert the edges between the source and destination nodes and between the nodes themselves
        g = graph_insert_edge(g, m1, m1);
        g = graph_insert_edge(g, m2, m2);
        g = graph_insert_edge(g, m1, m2);
    

    return g;
}

/**
 * graph_from_file - reads the file and constructs a graph with it's contents
 * Returns: A pointer to the constructed graph
 */
graph *graph_from_file(int argc, char *argv[])
{
    char data[40];

    // Open the file
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
        return NULL;
    }
    const char *filename = argv[1];

    // Print error message if file did not read correctly an exit program
    FILE *fptr = fopen(filename, "r");
    if (fptr == NULL)
    {
        perror("Error opening file\n");
        return NULL;
    }

    // Read the first line of the map-file
    fgets(data, 40, fptr);

    // Ignore comments and blank rows
    while (line_is_blank(data) || line_is_comment(data))
    {
        fgets(data, 40, fptr);
    }

    //Check that first non blank or coment line is a digit 
    for (int i = 0; i < strlen(data) - 1; i++)
    {
        if (!isdigit(data[i]))
        {
            fprintf(stderr, "Error: first row is not a number\n");
            exit(EXIT_FAILURE);
        }
    }
    // Read the number of edges in the map-file and create an empty graph
    int nr_of_edges = atoi(data);
    graph *g = graph_empty(2 * nr_of_edges);

    // Iterates over the edge imputs
    for (int i = 0; i < nr_of_edges; i++)
    {
        // Read the data, parse the data and insert it into nodes in the graph
        fgets(data, 40, fptr);
        char n1[MAXNODENAME + 1];
        char n2[MAXNODENAME + 1];
        check_node_name(n1,n2,data);
        g = insert_nodes(g,n1,n2);
    }
    // Close the file and return the graph
    fclose(fptr);
    return g;
}

/**
 * main - The main program that handles user inputs and runs all of the help functions
 *
 * Returns: 0 if program exits normally
 *
 */
int main(int argc, char *argv[])
{
    // Read the file and insert it into a graph
    graph *g = graph_from_file(argc, argv);

    // If file opening fails
    if (g == NULL)
    {
        return EXIT_FAILURE;
    }

    char src[MAXNODENAME + 1];
    char dest[MAXNODENAME + 1];
    char fmt[20];
    char buf[100];

    // Print the user interface
    printf("Enter origin and destination (quit to exit): ");

    // This will generate the format string " %40s %40s" if MAXNODENAME is 40.
    snprintf(fmt, sizeof(fmt), " %%%ds %%%ds", MAXNODENAME, MAXNODENAME);

    while (fgets(buf, sizeof(buf), stdin) != NULL)
    {
        // Reads the input from the user
        sscanf(buf, fmt, src, dest);

        // Quit the program if the user enters "quit"
        if (strcmp(src, "quit") == 0)
        {
            break;
        }

        // Check that the nodes exist in the graph
        node *nsrc = graph_find_node(g, src);
        node *ndest = graph_find_node(g, dest);
        if (nsrc == NULL || ndest == NULL)
        {
            printf("One or both nodes not found in the graph.\n");
        }
        // If the nodes exist check if there is a path and print the result
        else if (find_path(g, nsrc, ndest))
        {
            printf("There is a path from %s to %s.\n\n", src, dest);
            g = graph_reset_seen(g);
        }
        else
        {
            printf("There is no path from %s to %s.\n\n", src, dest);
            g = graph_reset_seen(g);
        }
        printf("Enter origin and destination (quit to exit): ");
    }

    // Print exit message and kill the graph
    printf("Normal exit.\n");
    graph_kill(g);
    return 0;
}
