/**
 * @file test_trie.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 <fstream>
#include <sstream>
#include <vector>
#include <exception>
#include <string>
#include <algorithm>

#include "trie.hpp"

#if defined WIN32 || defined _WIN32 || defined __WIN32__ || defined WIN64 || defined _WIN64 || defined __WIN64 || defined _WINDOWS
#include <windows.h>
#endif

int prefix_draw_subtrie(std::ostream &out, Trie &n)
{
    static int count = 0;
    int p_label = count++;
    out << 'n' << p_label << "[label=\"\"" << (n.is_key() ? ",style=filled, fillcolor=lightgray" : "") << "]" << std::endl;
    n.goto_first_symbol();
    while (n.current_exists())
    {
        Trie child = n.current();
        int c_label = prefix_draw_subtrie(out, child);
        out << 'n' << p_label << " -> " << 'n' << c_label << " [label=\"" << n.current_symbol() << "\"]" << std::endl;
        n.goto_next_symbol();
    }
    return p_label;
}

static void draw_trie(std::ostream &out, Trie &tree)
{
    out << "strict digraph Tree {" << std::endl;

    if (!tree.is_empty())
        prefix_draw_subtrie(out, tree);

    out << "}" << std::endl;
}

int main(int argc, const char *argv[])
{
    int exit_code = EXIT_SUCCESS;
#if defined WIN32 || defined _WIN32 || defined __WIN32__ || defined WIN64 || defined _WIN64 || defined __WIN64 || defined _WINDOWS
    SetConsoleOutputCP(CP_UTF8);
#endif

    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;
            std::cerr << "Usage: " << argv[0] << " filename" << std::endl;
            return EXIT_FAILURE;
        }
        Trie trie;
        std::string command;
        std::string line;

        while (std::getline(input_file, line))
        {
            if (line.empty() || line[0] == '#')
                continue;
            std::istringstream input_line(line);
            input_line >> command;
            if (command == "IS_EMPTY")
            {
                std::cout << "Is it empty? " << (trie.is_empty() ? "yes." : "no.") << std::endl;
            }
            else if (command == "PREFIX")
            {
                std::cout << "Trie's prefix: '" << trie.prefix()
                          << "'." << std::endl;
            }
            else if (command == "IS_KEY")
            {
                std::cout << "Is trie's prefix a key? " << (trie.is_key() ? "yes." : "no.") << std::endl;
            }
            else if (command == "INSERT")
            {
                std::string new_key;
                input_line.ignore(); // remove blank.
                std::getline(input_line, new_key);
                trie.insert(new_key);
                std::cout << "Inserted key: '" << new_key << "'." << std::endl;
            }
            else if (command == "HAS")
            {
                std::string key;
                input_line.ignore(); // remove blank.
                std::getline(input_line, key);
                if (!input_line)
                {
                    std::cerr << "Error: wrong input format." << std::endl;
                    return EXIT_FAILURE;
                }
                std::cout << "Has '" << key << "' key?: "
                          << (trie.has(key) ? "yes." : "no.") << std::endl;
            }
            else if (command == "SUBTRIE")
            {
                std::string prefix;
                input_line.ignore(); // remove blank.
                std::getline(input_line, prefix);
                if (!input_line)
                {
                    std::cerr << "Error: worng input format." << std::endl;
                    return EXIT_FAILURE;
                }
                std::cout << "Finding subtrie for prefix '"
                          << prefix << "' ... ";
                trie = trie.child(prefix);
                std::cout << (trie.is_empty() ? "(not found)" : "(found)")
                          << std::endl;
            }
            else if (command == "CURRENT_EXISTS")
            {
                std::cout << "Current exist? "
                          << (trie.current_exists() ? "yes." : "no.")
                          << std::endl;
            }
            else if (command == "CURRENT_SYMBOL")
            {
                std::cout << "Current symbol '"
                          << trie.current_symbol() << "'." << std::endl;
            }
            else if (command == "CURRENT_PREFIX")
            {
                std::cout << "Current trie's prefix '" << trie.current().prefix() << "'." << std::endl;
            }
            else if (command == "GOTO_FIRST_SYMBOL")
            {
                std::cout << "Going to first symbol ... ";
                trie.goto_first_symbol();
                std::cout << " ok." << std::endl;
            }
            else if (command == "GOTO_NEXT_SYMBOL")
            {
                std::cout << "Going to next symbol ... ";
                trie.goto_next_symbol();
                std::cout << " ok." << std::endl;
            }
            else if (command == "FIND_SYMBOL")
            {
                std::string symbol;
                input_line >> symbol;
                std::cout << "Finding symbol '" << symbol << "' ... ";
                std::cout << (trie.find_symbol(symbol) ? "found." : "not found.");
                std::cout << std::endl;
            }
            else if (command == "RETRIEVE")
            {
                std::cout << "Rerieving keys with prefix '" << trie.prefix()
                          << "' :";
                std::vector<std::string> keys;
                trie.retrieve(keys);
                std::sort(std::begin(keys), std::end(keys));
                for (size_t i = 0; i < keys.size(); ++i)
                    std::cout << " '" << keys[i] << "'";
                std::cout << std::endl;
            }
            else if (command == "FOLD")
            {
                std::cout << "Fold trie: ";
                std::cout << trie << std::endl;
            }
            else if (command == "UNFOLD")
            {
                std::cout << "Unfolding ... ";
                try
                {
                    input_line >> trie;
                    std::cout << " ok." << std::endl;
                }
                catch (std::runtime_error &e)
                {
                    std::cout << "Runtime exception: '" << e.what() << "'." << std::endl;
                }
            }
            else if (command == "COMPACT")
            {
                std::cout << "Compacting the trie ... ";
                trie.compact();
                std::cout << " ok." << std::endl;
            }
            else if (command == "DRAW")
            {
                std::string filename;
                input_line >> filename;
                if (!input_line)
                    throw std::runtime_error("Error: wrong input format.");
                std::ofstream output_file(filename);
                if (!output_file)
                    throw std::runtime_error("Error: could not open output filename '" + filename + "'.");
                draw_trie(output_file, trie);
                output_file << std::endl;
            }
            else
            {
                std::cerr << "Error: command unknown '" << command << "'." << std::endl;
                return EXIT_FAILURE;
            }
        }
    }
    catch (std::runtime_error &e)
    {
        std::cerr << "Runtime exception: " << e.what() << std::endl;
        exit_code = EXIT_FAILURE;
    }
    catch (...)
    {
        std::cerr << "Catched unknown exception!." << std::endl;
        exit_code = EXIT_FAILURE;
    }
    return exit_code;
}
