import numpy as np
import pandas as pd


def filter_events(df: pd.DataFrame, config: dict[str, any]) -> pd.DataFrame:
    """Получение табличной нотации для экспериментов из файлов OpenVibe (.csv)

    Args:
        df (pd.DataFrame): сырой файл .ov сконвертированный в .csv и загруженный в pandas
        config (dict[str, any]): словарь с расшифровкой кодов событий OpenVibe

    Returns:
        pd.DataFrame: фрейм (таблица) в нашей собственной нотации
    """

    events = []
    __temp_event = {}

    new_frame = df.copy()

    __ov_events = df[~df["Event Id"].isna()][["Event Id"]]
    for idx in range(__ov_events.shape[0]):
        __row_events = __ov_events.iloc[idx, 0].split(":")

        if config["main_actions"]["left"] in __row_events or config["main_actions"]["right"] in __row_events:
            __temp_event = {"start": __ov_events.index[idx]}
            if config["main_actions"]["left"] in __row_events:
                __temp_event["action"] = "left"
            if config["main_actions"]["right"] in __row_events:
                __temp_event["action"] = "right"

        # В записи stop повторяется дважды (как и любые другие действия)
        if config["main_actions"]["stop"] in __row_events and len(__temp_event) > 0:
            __temp_event["stop"] = __ov_events.index[idx + 1]
            events.append(__temp_event.copy())
            __temp_event = {}

    new_frame = new_frame.drop(columns=["Epoch", "Event Id", "Event Date", "Event Duration"])
    new_frame["action"] = None

    for event in events:
        new_frame.loc[event["start"] : event["stop"], "action"] = event["action"]

    new_frame["action"] = new_frame["action"].fillna("relax")
    return new_frame


def get_ranges(labels: np.ndarray) -> list[list[int, int, int]]:
    """Преобразование сырых лейблов временного ряда в отрезки для визуализации

    Args:
        labels (np.ndarray): лейблы

    Returns:
        list[list[int, int, int]]: список отрезков для визуализации, каждый отрезок определен как
            тройка "начало", "конец", "тип действия"
    """

    actions = []
    __temp_action = [0]
    __prev_action = labels[0]
    idx = 1

    while True:
        if idx + 1 > len(labels):
            __temp_action.extend([idx, __prev_action])
            actions.append(__temp_action)
            break

        if labels[idx] != __prev_action:
            __temp_action.extend([idx - 1, __prev_action])
            actions.append(__temp_action)
            __temp_action = [idx]
            __prev_action = labels[idx]

        idx += 1

    return actions


def split_epochs_binary(
    df: pd.DataFrame,
    epoch_duration: int,
    init_pause: int,
    final_pause: int,
    step: int,
    map_labels: dict[str, int],
    cols: list[str],
) -> tuple[np.ndarray, np.ndarray]:
    """Формирование бинарного датасета (из отрезков, когда есть действие!)

    Notes:
        Если нужно использовать только один отрезок на действие - установть step > [отсчеты в отрезке]

        Например, если отрезок действия 4 с (1000 отсчётов для частоты дискретизации 250 Гц), то поставив step=1001
        будет сформирован только 1 обучающий (валидационный) объект из данного действия.

    Args:
        df (pd.DataFrame): фрейм с данными
        epoch_duration (int): длительность эпохи (в отсчетах сигнала, t*f)
        init_pause (int): начальная пауза - сколько отсчетов выбросить с момента предъявления стимула
            (в отсчетах сигнала, t*f)
        final_pause (int): конечная пауза - сколько отсчетов выкинуть в конце (в отсчетах сигнала, t*f)
        step (int): шаг смещения внутри времени прдъявления стимула (в отсчетах сигнала, t*f)
        map_labels (dict[str, int]): словарь соотносящий действие и его числовое значение,
            например, {'left': 0, 'right': 1}
        cols (list[str]): имена колонок, которые будут добавлены в датасет,
            например: ['Channel 1', 'Channel 2', 'Channel 3']

    Returns:
        tuple[np.ndarray, np.ndarray]: кортеж с 2 массивами:

            * обучающие данные, размерность [количество образцов X длительность эпохи X количество каналов]

            * метки классов (в соответсвии с map_labels), размерность [количество образцов X 1]

    Raise:
        ValueError: словарь map_labels содержит информацию не о 2х классах
    """

    if len(map_labels) != 2:
        raise ValueError(f"Словарь map_labels должен содержать 2 класса, получено: {len(map_labels)}!")

    actions = get_ranges(df["action"])
    x = []
    y = []

    for action in actions:
        if action[2] in map_labels.keys():
            __low_bound = action[0] + init_pause
            __upper_bound = action[1] - final_pause - epoch_duration + 1
            for inner_idx in range(__low_bound, __upper_bound, step):
                x.append(df.loc[inner_idx : inner_idx + epoch_duration - 1, cols].values)
                y.append(map_labels[action[2]])
    return np.array(x), np.array(y)
