#include "directed_graph.h"
#include "ui.h"
#include <unordered_set>
#include <unordered_map>
#include <iostream>

UI::UI(){
    // nothing to do
}

UI::~UI(){
    // nothing to do
}

void UI::run(){
    int command;
    while(true){
        if(this->_graph.numVertices() == 0)
            std::cout << std::endl << "Graph is empty" << std::endl;
        std::cout << std::endl;
        std::cout << "1. Load graph from file" << std::endl;
        std::cout << "2. Save graph to file" << std::endl;
        std::cout << "3. Generate a graph" << std::endl;
        std::cout << "4. Add vertex" << std::endl;
        std::cout << "5. Remove vertex" << std::endl;
        std::cout << "6. Add edge" << std::endl;
        std::cout << "7. Remove edge" << std::endl;
        std::cout << "8. Get in degree" << std::endl;
        std::cout << "9. Get out degree" << std::endl;
        std::cout << "10. Get cost" << std::endl;
        std::cout << "11. Set cost" << std::endl;
        std::cout << "12. Get number of vertices" << std::endl;
        std::cout << "13. Get number of edges" << std::endl;
        std::cout << "14. Get vertices" << std::endl;
        std::cout << "15. Get edges" << std::endl;
        std::cout << "16. Get inbounds" << std::endl;
        std::cout << "17. Get outbounds" << std::endl;
        std::cout << "18. Futeti-l " << std::endl;
        std::cout << "0. Exit" << std::endl;
        std::cout << "Command: ";
        std::cin >> command;
        std::cout << std::endl;
        switch(command){
            case 1:{
                std::string filename;
                std::cout << "Filename: ";
                std::cin >> filename;
                try{
                    this->_graph = read_from_file(filename);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 2:{
                std::string filename;
                std::cout << "Filename: ";
                std::cin >> filename;
                try{
                    write_to_file(this->_graph, filename);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 3:{
                int num_vertices, num_edges;
                std::cout << "Number of vertices: ";
                std::cin >> num_vertices;
                std::cout << "Number of edges: ";
                std::cin >> num_edges;
                try{
                    this->_graph = random_graph(num_vertices, num_edges);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 4:{
                int v;
                std::cout << "Vertex: ";
                std::cin >> v;
                try{
                    this->_graph.addVertex(v);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 5:{
                int v;
                std::cout << "Vertex: ";
                std::cin >> v;
                try{
                    this->_graph.removeVertex(v);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 6:{
                int v1, v2, w;
                std::cout << "Vertex 1: ";
                std::cin >> v1;
                std::cout << "Vertex 2: ";
                std::cin >> v2;
                std::cout << "Weight: ";
                std::cin >> w;
                try{
                    this->_graph.addEdge(v1, v2, w);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 7:{
                int v1, v2;
                std::cout << "Vertex 1: ";
                std::cin >> v1;
                std::cout << "Vertex 2: ";
                std::cin >> v2;
                try{
                    this->_graph.removeEdge(v1, v2);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 8:{
                int v;
                std::cout << "Vertex: ";
                std::cin >> v;
                try{
                    std::cout << "In degree: " << this->_graph.inDegree(v) << std::endl;
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 9:{
                int v;
                std::cout << "Vertex: ";
                std::cin >> v;
                try{
                    std::cout << "Out degree: " << this->_graph.outDegree(v) << std::endl;
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 10:{
                int v1, v2;
                std::cout << "Vertex 1: ";
                std::cin >> v1;
                std::cout << "Vertex 2: ";
                std::cin >> v2;
                try{
                    std::cout << "Cost: " << this->_graph.weight(v1, v2) << std::endl;
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 11:{
                int v1, v2, w;
                std::cout << "Vertex 1: ";
                std::cin >> v1;
                std::cout << "Vertex 2: ";
                std::cin >> v2;
                std::cout << "Weight: ";
                std::cin >> w;
                try{
                    this->_graph.weight(v1, v2, w);
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 12:{
                std::cout << "Number of vertices: " << this->_graph.numVertices() << std::endl;
                break;
            }
            case 13:{
                std::cout << "Number of edges: " << this->_graph.numEdges() << std::endl;
                break;
            }
            case 14:{
                std::unordered_set<int> vertices = this->_graph.vertices();
                for(int vertex : vertices){
                    std::cout << vertex << " ";
                }
                std::cout << std::endl;
                break;
            }
            case 15:{
                std::unordered_map<std::pair<int, int>, int> edges = this->_graph.edges();
                for(std::pair<std::pair<int, int>, int> edge : edges){
                    std::cout << edge.first.first << " " << edge.first.second << " " << edge.second << std::endl;
                }
                break;
            }
            case 16:{
                int v;
                std::cout << "Vertex: ";
                std::cin >> v;
                try{
                    std::unordered_set<int> inbounds = this->_graph.inbounds(v);
                    for(int vertex : inbounds){
                        std::cout << vertex << " ";
                    }
                    std::cout << std::endl;
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 17:{
                int v;
                std::cout << "Vertex: ";
                std::cin >> v;
                try{
                    std::unordered_set<int> outbounds = this->_graph.outbounds(v);
                    for(int vertex : outbounds){
                        std::cout << vertex << " ";
                    }
                    std::cout << std::endl;
                }
                catch(std::invalid_argument& e){
                    std::cout << e.what() << std::endl;
                }
                break;
            }
            case 18:{
                std::cout<<"Inbound: \n";
                std::unordered_set<int> vertices = this->_graph.vertices();
                for(int vertex : vertices){
                    std::cout << vertex << ": ";
                    for(int in : this->_graph.inbounds(vertex)){
                        std::cout << in << " ";
                    }
                    std::cout << std::endl;
                }
                std::cout << std::endl;
                vertices = this->_graph.vertices();
                std::cout<<"Outbound: \n";
                for(int vertex : vertices){
                    std::cout << vertex << ": ";
                    for(int out : this->_graph.outbounds(vertex)){
                        std::cout << out << " ";
                    }
                    std::cout << std::endl;
                }
                std::cout << std::endl;
                std::cout<<"Edges: \n";
                std::unordered_map<std::pair<int, int>, int> edges = this->_graph.edges();
                for(std::pair<std::pair<int, int>, int> edge : edges){
                    std::cout << edge.first.first << " " << edge.first.second << " " << edge.second << std::endl;
                }
                break;
            }
            case 0:{
                return;
            }
            default:{
                std::cout << "Invalid command" << std::endl;
                break;
            }
        }
    }
}