/**
 * @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;
        VertexIterator<Char> v_it;
        EdgeIterator<Char> e_it;
        const_VertexIterator<Char> cv_it;
        const_EdgeIterator<Char> ce_it;

        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 == "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 == "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 == "VERTEX_ITERATOR_BEGIN")
            {
                bool is_const;
                if (!(input >> is_const))
                    throw std::runtime_error("Wrong command");
                std::cout << "Move vertex iterator " << (is_const ? "(const) " : "") << "to begin ... ";
                if (is_const)
                    cv_it = graph.vertices_begin();
                else
                    v_it = graph.vertices_begin();
                std::cout << "ok." << std::endl;
            }
            else if (command == "IS_VERTEX_ITERATOR_END")
            {
                bool is_const;
                if (!(input >> is_const))
                    throw std::runtime_error("Wrong command");
                std::cout << "Is vertex iterator " << (is_const ? "(const) " : "") << "at end? ";
                if (is_const)
                    std::cout << (cv_it == graph.vertices_end() ? "Y" : "N") << std::endl;
                else
                    std::cout << (v_it == graph.vertices_end() ? "Y" : "N") << std::endl;
            }
            else if (command == "PRINT_VERTEX_ITERATOR")
            {
                bool is_const;
                if (!(input >> is_const))
                    throw std::runtime_error("Wrong command");
                std::cout << "Vertex iterator " << (is_const ? "(const) " : "") << "points to vertex: ";
                if (is_const)
                {
                    auto v = *cv_it;
                    std::cout << v.label() << ":" << v.item() << " visited: "
                              << (v.is_visited() ? "Y" : "N") << std::endl;
                }
                else
                {
                    auto v = *v_it;
                    std::cout << v.label() << ":" << v.item() << " visited: "
                              << (v.is_visited() ? "Y" : "N") << std::endl;
                }
            }
            else if (command == "VERTEX_ITERATOR_NEXT")
            {
                bool is_const;
                bool pre_increment;
                if (!(input >> is_const >> pre_increment))
                    throw std::runtime_error("Wrong command");
                std::cout << "Move vertex iterator " << (is_const ? "(const) " : "") << "to next ("
                          << (pre_increment ? "pre" : "post") << " increment) ... ";
                if (is_const)
                {
                    if (pre_increment)
                        ++cv_it;
                    else
                    {
                        auto old_it = cv_it;
                        auto tmp = cv_it++;
                        if (old_it != tmp)
                            throw std::runtime_error("Error: post-increment operator of const_VertexIterator does not return the old value.");
                    }
                }
                else
                {
                    if (pre_increment)
                        ++v_it;
                    else
                    {
                        auto old_it = v_it;
                        auto tmp = v_it++;
                        if (old_it != tmp)
                            throw std::runtime_error("Error: post-increment operator of VertexIterator does not return the old value.");
                    }
                }
                std::cout << "ok." << std::endl;
            }
            else if (command == "FIND_VERTEX")
            {
                bool is_const;
                Char data;
                if (!(input >> is_const >> data))
                    throw std::runtime_error("Wrong command");
                std::cout << "Finding vertex" << (is_const ? " (const)" : "") << " with data " << data << " ... ";
                if (is_const)
                    cv_it = graph.find_vertex(data);
                else
                    v_it = graph.find_vertex(data);
                std::cout << "finished." << std::endl;
            }
            else if (command == "EDGE_ITERATOR_BEGIN")
            {
                bool is_const;
                if (!(input >> is_const))
                    throw std::runtime_error("Wrong command");
                std::cout << "Move edge iterator " << (is_const ? "(const) " : "") << "to begin ... ";
                if (is_const)
                    ce_it = graph.edges_begin(cv_it);
                else
                    e_it = graph.edges_begin(v_it);
                std::cout << "ok." << std::endl;
            }
            else if (command == "IS_EDGE_ITERATOR_END")
            {
                bool is_const;
                if (!(input >> is_const))
                    throw std::runtime_error("Wrong command");
                std::cout << "Is edge iterator " << (is_const ? "(const) " : "") << "at end? ";
                if (is_const)
                    std::cout << (ce_it == graph.edges_end(cv_it) ? "Y" : "N") << std::endl;
                else
                    std::cout << (e_it == graph.edges_end(v_it) ? "Y" : "N") << std::endl;
            }
            else if (command == "PRINT_EDGE_ITERATOR")
            {
                bool is_const;
                if (!(input >> is_const))
                    throw std::runtime_error("Wrong command");
                std::cout << "Edge iterator " << (is_const ? "(const) " : "") << "points to edge: ";
                if (is_const)
                {
                    auto e = *ce_it;
                    std::cout << e.first()->item().key() << ":"
                              << e.second()->item().key()
                              << " weight: " << e.item()
                              << std::endl;
                }
                else
                {
                    auto e = *e_it;
                    std::cout << e.first()->item().key() << ":"
                              << e.second()->item().key()
                              << " weight: " << e.item()
                              << std::endl;
                }
            }
            else if (command == "EDGE_ITERATOR_NEXT")
            {
                bool is_const;
                bool pre_increment;
                if (!(input >> is_const >> pre_increment))
                    throw std::runtime_error("Wrong command");
                std::cout << "Move edge iterator " << (is_const ? "(const) " : "") << "to next ("
                          << (pre_increment ? "pre" : "post") << " increment) ... ";
                if (is_const)
                {
                    if (pre_increment)
                        ++ce_it;
                    else
                    {
                        auto old_it = ce_it;
                        auto tmp = ce_it++;
                        if (old_it != tmp)
                            throw std::runtime_error("Error: post-increment operator of const_VertexIterator does not return the old value.");
                    }
                }
                else
                {
                    if (pre_increment)
                        ++e_it;
                    else
                    {
                        auto old_it = e_it;
                        auto tmp = e_it++;
                        if (old_it != tmp)
                            throw std::runtime_error("Error: post-increment operator of VertexIterator does not return the old value.");
                    }
                }
                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;
}
