/**
 * @file trie_node.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 <cassert>
#include <sstream>
#include <cstdint>
#include "utf8_utils.hpp"
#include "trie_node.hpp"

const std::map<std::string, TrieNode::Ref>::const_iterator &TrieNode::current() const
{
    return curr_;
}

void TrieNode::set_current(const std::map<std::string, TrieNode::Ref>::const_iterator &new_current)
{
    curr_=new_current;
}

const std::map<std::string, TrieNode::Ref> &TrieNode::children() const
{
    return dict_;
}

std::map<std::string, TrieNode::Ref> &TrieNode::children()
{
    return dict_;
}

void TrieNode::set_children(const std::map<std::string, Ref> &new_children)
{
    dict_.insert(new_children.begin(), new_children.end());
}

TrieNode::TrieNode()
{
    is_key_ = false;
    curr_ = dict_.end();
    
    assert(is_key() == false);
    assert(!current_exists());
};

TrieNode::Ref TrieNode::create()
{
    return std::shared_ptr<TrieNode>(new TrieNode());
}

bool TrieNode::is_key() const
{
    return is_key_;
}

size_t TrieNode::num_children() const
{
    return dict_.size();
}

bool TrieNode::has(const std::string &k) const
{
    // Solo nos interesa saber si la clave existe en el mapa de hijos
    return dict_.find(k) != dict_.end();
}

const TrieNode::Ref &
TrieNode::child(const std::string &k) const
{
    assert(has(k));

    return dict_.at(k);
}

bool TrieNode::current_exists() const
{
    return curr_ != dict_.end();
}

const TrieNode::Ref &
TrieNode::current_node() const
{
    return curr_->second;
}

const std::string &TrieNode::current_symbol() const
{
    return curr_->first;
}

void TrieNode::set_is_key_state(bool new_state)
{
    is_key_=new_state;

    assert(is_key() == new_state);
}

bool TrieNode::find_child(const std::string &s)
{
    curr_ = dict_.find(s);
    bool found = (curr_ != dict_.end());

    assert(found || !current_exists());
    assert(!found || current_symbol() == s);
    return found;
}

void TrieNode::goto_first_child()
{
    curr_=dict_.begin();
}

void TrieNode::goto_next_child()
{
    assert(current_exists());

    ++curr_;
}

void TrieNode::set_child(const std::string &k, const Ref &node)
{
    assert(node != nullptr);

    dict_[k] = node;
    curr_ = dict_.find(k);


    assert(current_exists());
    assert(current_symbol() == k);
    assert(current_node() == node);
}

void TrieNode::compact()
{
    // Primero compactamos recursivamente todos los hijos (postorden).
    goto_first_child();
    while (current_exists()){
        current_node()->compact();
        goto_next_child();
    }

    // Ahora reconstruimos los hijos fusionando cadenas lineales.
    goto_first_child();
    std::map<std::string, Ref> new_children;

    while (current_exists()){
        std::string edge = current_symbol();
        Ref child = current_node();

        // Fusionamos mientras el hijo no sea clave Y tenga exactamente un único nieto.
        // Condición con &&: ambas deben cumplirse a la vez.
        //   - !is_key(): si fuera clave, no podemos saltárnoslo (sería una palabra válida).
        //   - num_children()==1: si tuviese más de un hijo, es una bifurcación y no se puede fusionar.
        while (!(child->is_key()) && (child->num_children() == 1)){
            child->goto_first_child();
            edge = edge + child->current_symbol(); // concatenamos la etiqueta del arco
            child = child->current_node();         // bajamos un nivel
        }
        new_children[edge] = child;
        goto_next_child();
    }

    // Sustituimos los hijos originales por los nuevos arcos compactados.
    dict_ = new_children;
    curr_ = dict_.end();

    assert(!current_exists());
}

std::ostream &
operator<<(std::ostream &out, const TrieNode &n) noexcept
{
    out << "[ " << (n.is_key() ? "T" : "F");

    for (const auto& pair : n.children())
    {
        out << " [ '" << pair.first << "' " << *(pair.second) << " ]";
    }

    out << " ]";

    return out;
}

std::istream &operator>>(std::istream &in, TrieNode &node) noexcept(false)
{
    char c;

    // 1. Leer el '[' inicial
    if (!(in >> c) || c != '[') throw std::runtime_error("Wrong input format");

    // 2. Leer estado 'T' o 'F'
    if (!(in >> c) || (c != 'T' && c != 'F')) throw std::runtime_error("Wrong input format");
    node.set_is_key_state(c == 'T');

    std::map<std::string, TrieNode::Ref> new_children;

    // 3. Bucle continuo leyendo el siguiente caracter válido
    while (in >> c) 
    {
        // Si encontramos el corchete de cierre, el nodo padre ha terminado correctamente
        if (c == ']') {
            node.set_children(new_children);
            return in;
        }

        // Si no es el fin del padre, obligatoriamente DEBE ser un nuevo bloque de hijo
        if (c != '[') throw std::runtime_error("Wrong input format");

        std::string symbol;
        
        // Leemos el símbolo
        if (!read_quoted_string(in, symbol)) throw std::runtime_error("Wrong input format");

        // Rellenamos el hijo recursivamente
        TrieNode::Ref child = TrieNode::create();
        if (!(in >> *child)) throw std::runtime_error("Wrong input format");

        // Leer el ']' que cierra específicamente este bloque de hijo
        if (!(in >> c) || c != ']') throw std::runtime_error("Wrong input format");

        new_children[symbol] = child;
    }

    // 4. Si llegamos aquí, significa que el flujo (stream) se quedó sin texto (EOF) 
    // ANTES de que pudiéramos encontrar el ']' que cerraba al padre.
    throw std::runtime_error("Wrong input format");
}