// Letras de una palabra en UTF-8. Este archivo se les da hecho: lo central del laboratorio es la
// tabla y el corrector, no la codificación.
//
// Un std::string guarda bytes, y una letra como "á" ocupa dos. Si un corrector cambiara un char a
// la vez, partiría esa letra y probaría cadenas que ni siquiera son UTF-8 válido. Por eso todo el
// corrector trabaja con letras: cada letra es un std::string con los bytes de un carácter.
#ifndef UTF8_H
#define UTF8_H

#include <array>
#include <cstddef>
#include <string>
#include <vector>

// Cuántos bytes tiene el carácter que empieza con este byte. 0 si el byte no puede empezar uno.
inline std::size_t utf8_length(char first) {
    unsigned char c = static_cast<unsigned char>(first);
    if (c < 0x80) return 1;
    if (c >= 0xC2 && c <= 0xDF) return 2;
    if (c >= 0xE0 && c <= 0xEF) return 3;
    if (c >= 0xF0 && c <= 0xF4) return 4;
    return 0;
}

// Parte una palabra en letras: "año" da {"a", "ñ", "o"}. Un byte inválido queda como una letra.
inline std::vector<std::string> split_letters(const std::string& word) {
    std::vector<std::string> letters;
    std::size_t i = 0;
    while (i < word.size()) {
        std::size_t length = utf8_length(word[i]);
        if (length == 0 || i + length > word.size()) {
            length = 1;
        }
        letters.push_back(word.substr(i, length));
        i += length;
    }
    return letters;
}

// Une letras otra vez en una palabra.
inline std::string join_letters(const std::vector<std::string>& letters) {
    std::string word;
    for (const std::string& letter : letters) {
        word += letter;
    }
    return word;
}

// Las 33 letras que aparecen en palabras.txt.
inline const std::array<std::string, 33>& alphabet() {
    static const std::array<std::string, 33> letters = {
        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q",
        "r", "s", "t", "u", "v", "w", "x", "y", "z", "á", "é", "í", "ñ", "ó", "ú", "ü"};
    return letters;
}

// Si los bytes en word[i] son una letra (a-z, A-Z o una letra de Latin-1, como á o Ñ), la agrega
// en minúscula a `out` y devuelve cuántos bytes ocupaba. Si no, devuelve 0.
inline std::size_t take_lowercase_letter(const std::string& text, std::size_t i, std::string& out) {
    unsigned char c = static_cast<unsigned char>(text[i]);
    if (c >= 'a' && c <= 'z') {
        out += static_cast<char>(c);
        return 1;
    }
    if (c >= 'A' && c <= 'Z') {
        out += static_cast<char>(c + ('a' - 'A'));
        return 1;
    }
    // En UTF-8, U+00C0 a U+00FF son 0xC3 seguido de 0x80 a 0xBF. Las mayúsculas llegan hasta
    // 0x9E y su minúscula está 0x20 más arriba. 0x97 y 0xB7 son los signos de multiplicar y dividir.
    if (c == 0xC3 && i + 1 < text.size()) {
        unsigned char d = static_cast<unsigned char>(text[i + 1]);
        if (d >= 0x80 && d <= 0xBF && d != 0x97 && d != 0xB7) {
            out += static_cast<char>(c);
            out += static_cast<char>(d <= 0x9E ? d + 0x20 : d);
            return 2;
        }
    }
    return 0;
}

// Saca las palabras de un texto, en minúsculas: lo que no es letra las separa.
inline std::vector<std::string> extract_words(const std::string& text) {
    std::vector<std::string> words;
    std::string current;
    std::size_t i = 0;
    while (i < text.size()) {
        std::size_t taken = take_lowercase_letter(text, i, current);
        if (taken > 0) {
            i += taken;
        } else {
            if (!current.empty()) {
                words.push_back(current);
                current.clear();
            }
            i++;
        }
    }
    if (!current.empty()) {
        words.push_back(current);
    }
    return words;
}

#endif
