import argparse
import logging
import sys

import pyphen
import nltk

pyphen.language_fallback("en_US")

logger = logging.getLogger()
logger.setLevel(logging.INFO)
console_out = logging.StreamHandler(sys.stdout)
console_out.setLevel(logging.DEBUG)
logger.addHandler(console_out)


def parse_arguments():
    """
    Prosty parser argumentów w wierszu poleceń.
    :return: Tekst przeznaczony do edycji
    """
    parser = argparse.ArgumentParser(description="Odebranie tekstu przeznaczonego do edycji")
    parser.add_argument("text", metavar="input text", type=str)
    args = parser.parse_args()
    return args.text


def clean_input(text):
    """
    Funkcja oczyszczająca tekst.
    :param text: Tekst wpisany przez użytkownika
    :return: Oczyszczony tekst, pozbawiony znaków innych niż ASCII
    """
    # Aby ułatwić sobie start, zachowajmy tylko znaki ASCII.
    return str(text.encode().decode("ascii", errors="ignore"))


def preprocess_input(text):
    """
    Wyodrębnienie tokenów z oczyszczonego tekstu.
    :param text: Oczyszczony tekst
    :return: Tekst przygotowany do analizy po wyodrębnieniu zdań i słów
    """
    sentences = nltk.sent_tokenize(text)
    tokens = [nltk.word_tokenize(sentence) for sentence in sentences]
    return tokens


def compute_flesch_reading_ease(total_syllables, total_words, total_sentences):
    """
    Wyliczenie oceny czytelności na podstawie wskaźników podsumowujących.
    :param total_syllables: liczba sylab w tekście wejściowym
    :param total_words: liczba słów w tekście wejściowym
    :param total_sentences: liczba zdań w tekście wejściowym
    :return: ocena czytelności: im bardziej złożony tekst, tym niższa ocena
    """
    return (
        206.85
        - 1.015 * (total_words / total_sentences)
        - 84.6 * (total_syllables / total_words)
    )


def get_reading_level_from_flesch(flesch_score):
    """
    Wartości graniczne z artykułu https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests
    :param flesch_score:
    :return: poziom i trudność czytania w zależności od oceny Flescha
    """
    if flesch_score < 30:
        return "Bardzo trudny w czytaniu"
    elif flesch_score < 50:
        return "Trudny"
    elif flesch_score < 60:
        return "Dość trudny"
    elif flesch_score < 70:
        return "Prosty"
    elif flesch_score < 80:
        return "Dość łatwy"
    elif flesch_score < 90:
        return "Łatwy"
    else:
        return "Bardzo łatwy"


def compute_average_word_length(tokens):
    """
    Wyliczenie średniej długości słowa w zdaniu.
    :param tokens: lista słów
    :return: średnia długość słowa w liście
    """
    word_lengths = [len(word) for word in tokens]
    return sum(word_lengths) / len(word_lengths)


def compute_total_average_word_length(sentence_list):
    """
    Wyliczenie średniej długości słowa w kilku zdaniach.
    :param sentence_list: lista zdań, z których każde jest listą słów
    :return: średnia długość słowa w liście zdań
    """
    lengths = [compute_average_word_length(tokens) for tokens in sentence_list]
    return sum(lengths) / len(lengths)


def compute_total_unique_words_fraction(sentence_list):
    """
    Wyliczenie odsetka unikatowych słów.
    :param sentence_list: lista zdań, z których każde jest listą słów
    :return: odsetek unikatowych słów w zdaniach
    """
    all_words = [word for word_list in sentence_list for word in word_list]
    unique_words = set(all_words)
    return len(unique_words) / len(all_words)


def count_word_usage(tokens, word_list):
    """
    Liczba wystąpień słów w liście.
    :param tokens: lista tokenów w zdaniu
    :param word_list: lista wyszukiwanych słów
    :return: liczba wystąpień słów w liście
    """
    return len([word for word in tokens if word.lower() in word_list])


def count_word_syllables(word):
    """
    Wyliczenie sylab w słowie.
    :param word: słowo
    :return: liczba sylab wyliczona za pomocą biblioteki pyphen
    """
    dic = pyphen.Pyphen(lang="en_US")
    # zwrócenie słowa z sylabami oddzielonymi myślnikami
    hyphenated = dic.inserted(word)
    return len(hyphenated.split("-"))


def count_sentence_syllables(tokens):
    """
    Wyliczenie sylab w zdaniu.
    :param tokens: lista słów i znaków przestankowych
    :return: liczba sylab w zdaniu
    """
    # Tokenizer traktuje znak przestankowy jako słowo, więc usuwamy je.
    punctuation = ".,!?/"
    return sum(
        [
            count_word_syllables(word)
            for word in tokens
            if word not in punctuation
        ]
    )


def count_total_syllables(sentence_list):
    """
    Wyliczenie sylab w liście zdań.
    :param sentence_list: lista zdań, z których każde jest listą słów
    :return: liczba sylab w liście zdań
    """
    return sum(
        [count_sentence_syllables(sentence) for sentence in sentence_list]
    )


def count_words_per_sentence(sentence_tokens):
    """
    Wyliczenie słów w zdaniu.
    :param sentence_tokens: lista słów i znaków przestankowych
    :return: liczba słów w zdaniu
    """
    punctuation = ".,!?/"
    return len([word for word in sentence_tokens if word not in punctuation])


def count_total_words(sentence_list):
    """
    Wyliczenie słów w liście zdań.
    :param sentence_list: lista zdań, z których każde jest listą słów
    :return: liczba słów w liście zdań
    """
    return sum(
        [count_words_per_sentence(sentence) for sentence in sentence_list]
    )


def get_suggestions(sentence_list):
    """
    Funkcja zwracająca ciąg znaków zawierający sugestie
    :param sentence_list: lista zdań, z których każde jest listą słów
    :return: sugestie poprawienia wprowadzanych danych
    """
    told_said_usage = sum(
        (count_word_usage(tokens, ["told", "said"]) for tokens in sentence_list)
    )
    but_and_usage = sum(
        (count_word_usage(tokens, ["but", "and"]) for tokens in sentence_list)
    )
    wh_adverbs_usage = sum(
        (
            count_word_usage(
                tokens,
                [
                    "when",
                    "where",
                    "why",
                    "whence",
                    "whereby",
                    "wherein",
                    "whereupon",
                ],
            )
            for tokens in sentence_list
        )
    )
    result_str = ""
    adverb_usage = "Użycie przysłówków: %s told/said, %s but/and, %s przysłówków wh" % (
        told_said_usage,
        but_and_usage,
        wh_adverbs_usage,
    )
    result_str += adverb_usage
    average_word_length = compute_total_average_word_length(sentence_list)
    unique_words_fraction = compute_total_unique_words_fraction(sentence_list)

    word_stats = "Średnia długość słowa: %.2f, odsetek unikatowych słów: %.2f" % (
        average_word_length,
        unique_words_fraction,
    )
    # Użycie podziału wiersza HTML w celu wyświetlenia wyników na stronie
    result_str += "<br/>"
    result_str += word_stats

    number_of_syllables = count_total_syllables(sentence_list)
    number_of_words = count_total_words(sentence_list)
    number_of_sentences = len(sentence_list)

    syllable_counts = "Liczba sylab: %d, słów: %d, zdań: %d" % (
        number_of_syllables,
        number_of_words,
        number_of_sentences,
    )
    result_str += "<br/>"
    result_str += syllable_counts

    flesch_score = compute_flesch_reading_ease(
        number_of_syllables, number_of_words, number_of_sentences
    )

    flesch = "Liczba sylab: %d, indeks Flescha: %.2f, %s" % (
        number_of_syllables,
        flesch_score,
        get_reading_level_from_flesch(flesch_score),
    )

    result_str += "<br/>"
    result_str += flesch

    return result_str


def get_recommendations_from_input(txt):
    """
    Funkcja oczyszczająca i wstępnie przetwarzająca wejściowy ciąg znaków
    oraz generująca heurystycznie zalecenia. 
    :param txt: tekst wejściowy
    :return: zalecenia dotyczące tekstu wejściowego
    """
    processed = clean_input(txt)
    tokenized_sentences = preprocess_input(processed)
    suggestions = get_suggestions(tokenized_sentences)
    return suggestions


if __name__ == "__main__":
    input_text = parse_arguments()
    print(get_recommendations_from_input(input_text))
