import os
from pathlib import Path
import pandas as pd
from lime.lime_tabular import LimeTabularExplainer

from ml_editor.data_processing import get_split_by_author

FEATURE_DISPLAY_NAMES = {
    "num_questions": "częstość znaków zapytania",
    "num_periods": "częstość kropek",
    "num_commas": "częstość przecinków",
    "num_exclam": "częstość wykrzykników",
    "num_quotes": "częstość apostrofów",
    "num_colon": "częstość dwukropków",
    "num_semicolon": "częstość średników",
    "num_stops": "częstość stop-słów",
    "num_words": "liczba słów",
    "num_chars": "liczba znaków",
    "num_diff_words": "różnorodność słownictwa",
    "avg_word_len": "złożoność słownictwa",
    "polarity": "pozytywne nastawienie emocjonalne",
    "ADJ": "częstość przymiotników",
    "ADP": "częstość przyimków",
    "ADV": "częstość przysłówków",
    "AUX": "częstość czasowników posiłkowych",
    "CONJ": "częstość spójników koordynacyjnych",
    "DET": "częstość określników",
    "INTJ": "częstość wykrzykników",
    "NOUN": "częstość rzeczowników",
    "NUM": "częstość liczebników",
    "PART": "częstość partykuł",
    "PRON": "częstość zaimków",
    "PROPN": "częstość nazw własnych",
    "PUNCT": "częstość znaków interpunkcyjnych",
    "SCONJ": "częstość spójników podrzędnych",
    "SYM": "częstość symboli",
    "VERB": "częstość czasowników",
    "X": "częstość innych słów",
}

POS_NAMES = {
    "ADJ": "przymiotnik",
    "ADP": "przyimek",
    "ADV": "przysłówek",
    "AUX": "czasownik posiłkowy",
    "CONJ": "spójnik koordynujący",
    "DET": "określnik",
    "INTJ": "wykrzyknik",
    "NOUN": "rzeczownik",
    "NUM": "liczebnik",
    "PART": "partykuła",
    "PRON": "zaimek",
    "PROPN": "nazwa własna",
    "PUNCT": "znak interpunkcyjny",
    "SCONJ": "spójnik podrzędny",
    "SYM": "symbol",
    "VERB": "czasownik",
    "X": "inne",
}

FEATURE_ARR = [
    "num_questions",
    "num_periods",
    "num_commas",
    "num_exclam",
    "num_quotes",
    "num_colon",
    "num_stops",
    "num_semicolon",
    "num_words",
    "num_chars",
    "num_diff_words",
    "avg_word_len",
    "polarity",
]
FEATURE_ARR.extend(POS_NAMES.keys())


def get_explainer():
    """
    Przygotowanie narzędzia LIME przy użyciu naszych danych. Funkcja jest
    szybka, więc nie trzeba jej serializować.
    :return: obiekt LIME
    """
    curr_path = Path(os.path.dirname(__file__))
    data_path = Path("../data/writers_with_features.csv")
    df = pd.read_csv(curr_path / data_path)
    train_df, test_df = get_split_by_author(df, test_size=0.2, random_state=40)
    explainer = LimeTabularExplainer(
        train_df[FEATURE_ARR].values,
        feature_names=FEATURE_ARR,
        class_names=["low", "high"],
    )
    return explainer


EXPLAINER = get_explainer()


def simplify_order_sign(order_sign):
    """
    Uproszczenie znaków, aby wyświetlane informacje były bardziej czytelne dla użytkownika.
    :param order_sign: wejściowy operator porównania
    :return: operator upraszczający
    """
    if order_sign in ["<=", "<"]:
        return "<"
    if order_sign in [">=", ">"]:
        return ">"
    return order_sign


def get_recommended_modification(simple_order, impact):
    """
    Utworzenie ciągu zalecenia za pomocą operatora i typu wpływu.
    :param simple_order: operator upraszczający
    :param impact: informacja, czy zmiana ma pozytywny, czy negatywny wpływ
    :return: sformatowany ciąg zalecenia
    """
    bigger_than_threshold = simple_order == ">"
    has_positive_impact = impact > 0

    if bigger_than_threshold and has_positive_impact:
        return "Nie zmniejszaj"
    if not bigger_than_threshold and not has_positive_impact:
        return "Zwiększ"
    if bigger_than_threshold and not has_positive_impact:
        return "Zmniejsz"
    if not bigger_than_threshold and has_positive_impact:
        return "Nie zwiększaj"


def parse_explanations(exp_list):
    """
    Przekształcenie wyjaśnień zwróconych przez narzędzie LIME na format czytelny dla użytkownika.
    :param exp_list: wyjaśnienia zwrócone przez narzędzie LIME
    :return: tablica słowników zawierających ciągi  prezentowane użytkownikowi
    """
    parsed_exps = []
    for feat_bound, impact in exp_list:
        conditions = feat_bound.split(" ")

        # Pomijamy podwójne warunki, np. 1 <= a < 3, ponieważ trudniej jest
        # na ich podstawie formułować zalecenia.
        if len(conditions) == 3:
            feat_name, order, threshold = conditions

            simple_order = simplify_order_sign(order)
            recommended_mod = get_recommended_modification(simple_order, impact)

            parsed_exps.append(
                {
                    "feature": feat_name,
                    "feature_display_name": FEATURE_DISPLAY_NAMES[feat_name],
                    "order": simple_order,
                    "threshold": threshold,
                    "impact": impact,
                    "recommendation": recommended_mod,
                }
            )
    return parsed_exps


def get_recommendation_string_from_parsed_exps(exp_list):
    """
    Wygenerowanie tekstu zalecenia, który można wyświetlić w aplikacji Flask.
    :param exp_list: tablica słowników zawierających wyjaśnienia
    :return: tekst wyjaśnienia w formacie HTML
    """
    recommendations = []
    for i, feature_exp in enumerate(exp_list):
        recommendation = "%s %s" % (
            feature_exp["recommendation"],
            feature_exp["feature_display_name"],
        )
        font_color = "green"
        if feature_exp["recommendation"] in ["Zwiększ", "Zmnijesz"]:
            font_color = "red"
        rec_str = """<font color="%s">%s) %s</font>""" % (
            font_color,
            i + 1,
            recommendation,
        )
        recommendations.append(rec_str)
    rec_string = "<br/>".join(recommendations)
    return rec_string
