/**
 * @file test_graph.cpp
 *
 * CopyRight F. J. Madrid-Cuevas <fjmadrid@uco.es>
 *
 * Sólo se permite el uso de este código en la docencia de las asignaturas sobre
 * Estructuras de Datos de la Universidad de Córdoba.
 *
 * Está prohibido su uso para cualquier otro objetivo.
 */

#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
#include <exception>
#include <vector>

#include <graph.hpp>
#include "item.hpp"

int main(int argc, const char *argv[])
{
    int exit_code = EXIT_SUCCESS;
    try
    {
        if (argc != 2)
        {
            std::cerr << "Usage: " << argv[0] << " filename" << std::endl;
            return EXIT_FAILURE;
        }
        std::ifstream input_file(argv[1]);
        if (!input_file)
        {
            std::cerr << "Error: could not open input filename '" << argv[1] << "'." << std::endl;
            return EXIT_FAILURE;
        }

        WGraph<Char> graph;

        std::cout << std::unitbuf;

        std::string buffer;
        std::istringstream input;
        while (std::getline(input_file, buffer))
        {
            input.str(buffer);
            input.clear();
            std::string command;

            input >> command;
            if (!input || command == "" || command[0] == '#')
                continue;

            if (command == "CREATE")
            {
                size_t capacity;
                int is_directed;
                if (!(input >> capacity >> is_directed))
                    throw std::runtime_error("Wrong command");
                std::cout << "Creating a "
                          << (is_directed ? "directed" : "undirected")
                          << " graph with capacity = "
                          << capacity << " ... ";
                graph = WGraph<Char>(capacity, is_directed != 0);
                std::cout << " ok." << std::endl;
            }
            else if (command == "IS_DIRECTED")
            {
                std::cout << "Is the graph directed? ";
                std::cout << (graph.is_directed() ? "Y" : "N") << std::endl;
            }
            else if (command == "CAPACITY")
            {
                std::cout << "Graph capacity: ";
                std::cout << graph.capacity() << std::endl;
            }
            else if (command == "ORDER")
            {
                std::cout << "Graph order: ";
                std::cout << graph.order() << std::endl;
            }
            else if (command == "SIZE")
            {
                std::cout << "Graph size: ";
                std::cout << graph.size() << std::endl;
            }
            else if (command == "IS_EMPTY")
            {
                std::cout << "Is the graph empty? ";
                std::cout << (graph.is_empty() ? "Y" : "N") << std::endl;
            }
            else if (command == "IS_FULL")
            {
                std::cout << "Is the graph full? ";
                std::cout << (graph.is_full() ? "Y" : "N") << std::endl;
            }
            else if (command == "ADD_VERTEX")
            {
                Char data;
                if (!(input >> data))
                    throw std::runtime_error("Wrong command");
                std::cout << "Adding new vertex with data: " << data << " ... ";
                auto v = graph.add_vertex(data);
                std::cout << " vertex's label: " << v->label() << std::endl;
            }
            else if (command == "WEIGHT_BY_LABELS")
            {
                size_t u_label, v_label;
                if (!(input >> u_label >> v_label))
                    throw std::runtime_error("Wrong command");
                std::cout << "Weight of edge ("
                          << graph.item(u_label).key() << ","
                          << graph.item(v_label).key() << ") is ";
                std::cout << graph.weight(u_label, v_label) << std::endl;
            }
            else if (command == "SET_WEIGHT_BY_LABELS")
            {
                size_t u_label, v_label;
                float weight;
                if (!(input >> u_label >> v_label >> weight))
                    throw std::runtime_error("Wrong command");
                std::cout << "Setting weight of edge ("
                          << graph.item(u_label).key() << ","
                          << graph.item(v_label).key() << ") to "
                          << weight << " ... ";
                graph.set_weight(u_label, v_label, weight);
                std::cout << "ok." << std::endl;
            }
            else if (command == "IS_ADJACENT_BY_LABELS")
            {
                size_t u_label, v_label;
                if (!(input >> u_label >> v_label))
                    throw std::runtime_error("Wrong command");
                std::cout << "Is vertex "
                          << graph.item(u_label).key() << " adjacent to "
                          << graph.item(v_label).key() << "? ";
                std::cout << (graph.is_adjacent(u_label, v_label) ? "Y" : "N") << std::endl;
            }
            else if (command == "SET_ITEM_VERTEX_BY_LABEL")
            {
                size_t u_label;
                Char new_item;
                if (!(input >> u_label >> new_item))
                    throw std::runtime_error("Wrong command");
                std::cout << "Setting item value of vertex "
                          << graph.item(u_label).key() << " to "
                          << new_item << " ... ";
                graph.set_item(u_label, new_item);
                std::cout << "ok." << std::endl;
            }
            else if (command == "SET_VISITED_VERTEX_BY_LABEL")
            {
                size_t u_label;
                bool new_state;
                if (!(input >> u_label >> new_state))
                    throw std::runtime_error("Wrong command");
                std::cout << "Setting visited state of vertex "
                          << graph.item(u_label).key() << " to "
                          << (new_state ? "Y" : "N") << " ... ";
                graph.set_visited(u_label, new_state);
                std::cout << "ok." << std::endl;
            }
            else if (command == "PRINT_VERTEX_BY_LABEL")
            {
                size_t u_label;
                if (!(input >> u_label))
                    throw std::runtime_error("Wrong command");
                std::cout << "Getting vertex with label " << u_label << " ... ";
                auto v = graph.vertex(u_label);
                std::cout << " item: " << v->item() << " visited: "
                          << (v->is_visited() ? "Y" : "N") << std::endl;
            }
            else if (command == "PRINT_VERTICES")
            {
                std::cout << "Graph's vertices:" << std::endl;
                std::cout << " [";
                for (auto v = graph.vertices_begin(); v != graph.vertices_end(); ++v)
                {
                    std::cout << ' ' << v->label() << ':' << v->item();
                }
                std::cout << " ]" << std::endl;
            }
            else if (command == "PRINT_EDGE_BY_LABELS")
            {
                size_t u_label, v_label;
                if (!(input >> u_label >> v_label))
                    throw std::runtime_error("Wrong command");
                std::cout << "Getting edge ("
                          << graph.item(u_label).key() << ","
                          << graph.item(v_label).key() << ") ... ";
                auto e = graph.edge(u_label, v_label);
                std::cout << " weight: " << e->item() << std::endl;
            }
            else if (command == "PRINT_EDGES")
            {
                std::cout << "Graph's edges:" << std::endl;
                std::cout << " [";
                for (auto v = graph.vertices_begin(); v != graph.vertices_end(); ++v)
                    for (auto e = graph.edges_begin(v); e != graph.edges_end(v); ++e)
                    {
                        if (!graph.is_directed() && e->first()->label() > e->second()->label())
                            continue; // avoid printing twice the same edge in undirected graphs
                        std::cout << ' ' << '('
                                  << e->first()->item().key() << ','
                                  << e->second()->item().key() << "):"
                                  << e->item();
                    }
                std::cout << " ]" << std::endl;
            }
            else if (command == "PRINT_WEIGHTS")
            {
                std::cout << "Graph's weights:" << std::endl;
                graph.weights().fold(std::cout);
                std::cout << std::endl;
            }
            else if (command == "RESET")
            {
                std::cout << "Resetting the graph ... ";
                graph.reset(false);
                std::cout << "ok." << std::endl;
            }
            else if (command == "FOLD")
            {
                std::cout << "Folding graph: " << std::endl;
                std::cout << graph;
                std::cout << std::endl;
            }
            else if (command == "UNFOLD")
            {
                std::cout << "Unfolding graph ...";
                input_file >> graph;
                std::cout << " ok." << std::endl;
            }
            else
            {
                std::cerr << "Error: command unknown '" << command << "'."
                          << std::endl;
                return EXIT_FAILURE;
            }
        }
    }
    catch (std::runtime_error &e)
    {
        std::cerr << "Run time exception: " << e.what() << std::endl;
        exit_code = EXIT_FAILURE;
    }
    catch (...)
    {
        std::cerr << "Caught unknown exception!." << std::endl;
        exit_code = EXIT_FAILURE;
    }
    return exit_code;
}
