import numpy as np
from pathlib import Path
from os import listdir
from datetime import datetime
from phase_utils import EvaluationMode as Eval
from phase_utils import LearningProfileDescription

LP_FILE_NAME = 'lp'


###############################################################################
# Store data to disk - Phase
# Uses the class LearningProfile and not LearningProfileDescription
###############################################################################

def create_datetime_subdir(dir_path):
    """
    Create subdirectory named after the current date and time.

    Args:
        dir_path (Path): Path to directory.

    Raises:
        ValueError: If `dir_path` exists, but is not a directory.

    Returns:
        dir_path (Path): New path to subdirectory.
    """
    if not dir_path.is_dir():
        if dir_path.exists():
            raise ValueError(f"Not a directory: '{dir_path}'")
        # Create dir
        dir_path.mkdir(parents=True, exist_ok=True)

    # Create subdirectory named after the current date and time
    date_time = datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
    dir_path = Path(dir_path / date_time)
    dir_path.mkdir()
    return dir_path


def evaluation_phase(dir_path, learning_profiles):
    """
    Calculates the score for each learning profile and saves the score
    together with the MSE in .npy format.

    Files are created with the name LP_FILE_NAME-id.npy.

    Args:
        dir_path (Path): Directory path for output from learning profiles.
        learning_profiles (list): List of learning profiles.
    """
    for lp in learning_profiles:
        data = create_data(lp)
        file_path = dir_path / f"{LP_FILE_NAME}-{lp.get_id()}.npy"
        np.save(Path(file_path), data)
    return


def create_data(lp):
    """
    Creates a data array for the learning profile.

    Data Format: train_dataset_name, test_dataset_name, al_func_name,
    ml_func_name, score_arousal, score_valence, mean_score,
    MSE_arousal, MSE_valence, MSE_mean, batch_size, hyper_parameters

    Args:
        lp (LearningProfile): Contains all the data needed.

    Returns:
        np.array: list of specified Data Format.
    """
    arousal = lp.get_MSE_arousal()
    valence = lp.get_MSE_valence()

    score_arousal = np.sum(arousal)
    score_valence = np.sum(valence)

    mean = [np.mean(i) for i in zip(*[arousal, valence])]
    mean_score = np.sum(mean)

    return np.array([lp.get_train_dataset_name(),
                     lp.get_test_dataset_name(),
                     lp.get_al_function().__name__,
                     lp.get_ml_function().__name__,
                     score_arousal, score_valence, mean_score,
                     arousal, valence, mean,
                     lp.get_batch_size(),
                     lp.get_hyper_parameters()],
                    dtype=object)


###############################################################################
# Load data from disk - Phase
# Uses the class LearningProfileDescription and not LearningProfile
###############################################################################


def evaluate_all_profiles(learning_profiles=[],
                          eval=Eval.AROUSAL,
                          nr_models=-1):
    """
    Given a list of learning profiles, evaluate them and return the first
    'nr_models' profiles with the best score according to the eval method.

    Args:
        learning_profiles (list): List of learning profiles
            (using LearningProfileDescription).
        eval (Enum): method of evaluation. Defaults to Eval.AROUSAL.
        nr_models (int): number of models to include in plot.
            Defaults to -1 (All models included).

    Returns:
        List(Tuple(String, float)): List of tuples containing the
        first nr_models with the best profiles in
        decending order (based on score) and their corresponding score.
    """
    if not learning_profiles:
        raise ValueError("No learning profiles given")

    # Set eval mode for all learning profiles
    [lp.set_eval_mode(eval) for lp in learning_profiles]

    # Sort according to best score
    sorted_list = sorted(learning_profiles, key=lambda lp: lp.get_score())

    best_LPs = [(f"Best profile: {lp.get_name(True, True)} " +
                 f" h_pars: {lp.get_hyper_parameters()}",
                 lp.get_score()) for lp in sorted_list]

    min_ = min(len(sorted_list), nr_models)
    return best_LPs[:min_] if nr_models > -1 else best_LPs


def load_all_learning_profiles(dir_path):
    """
    Load all learning profiles from disk and parse them
    using LearningProfileDescription.

    Args:
        dir_path (Path): Path to directory containing learning profiles.

    Returns:
        learning_profiles (list): list of parsed learning profiles.
    """
    # Load all learning profiles in the given dir_path
    learning_profiles = []
    for file_name in listdir(dir_path):
        if LP_FILE_NAME in file_name:
            lp_loaded = np.load(Path(dir_path / file_name), allow_pickle=True)
            # Get id from the file name
            id = file_name.split(LP_FILE_NAME + '-', 1)[1]
            id = id.split('.npy')[0]
            lp = LearningProfileDescription(id, lp_loaded)
            learning_profiles.append(lp)
    return learning_profiles

import numpy as np
from pathlib import Path
from os import listdir
from datetime import datetime
from phase_utils import EvaluationMode as Eval
from phase_utils import LearningProfileDescription

LP_FILE_NAME = 'lp'


###############################################################################
# Store data to disk - Phase
# Uses the class LearningProfile and not LearningProfileDescription
###############################################################################

def create_datetime_subdir(dir_path):
    """
    Create subdirectory named after the current date and time.

    Args:
        dir_path (Path): Path to directory.

    Raises:
        ValueError: If `dir_path` exists, but is not a directory.

    Returns:
        dir_path (Path): New path to subdirectory.
    """
    if not dir_path.is_dir():
        if dir_path.exists():
            raise ValueError(f"Not a directory: '{dir_path}'")
        # Create dir
        dir_path.mkdir(parents=True, exist_ok=True)

    # Create subdirectory named after the current date and time
    date_time = datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
    dir_path = Path(dir_path / date_time)
    dir_path.mkdir()
    return dir_path


def evaluation_phase(dir_path, learning_profiles):
    """
    Calculates the score for each learning profile and saves the score
    together with the MSE in .npy format.

    Files are created with the name LP_FILE_NAME-id.npy.

    Args:
        dir_path (Path): Directory path for output from learning profiles.
        learning_profiles (list): List of learning profiles.
    """
    for lp in learning_profiles:
        data = create_data(lp)
        file_path = dir_path / f"{LP_FILE_NAME}-{lp.get_id()}.npy"
        np.save(Path(file_path), data)
    return


def create_data(lp):
    """
    Creates a data array for the learning profile.

    Data Format: train_dataset_name, test_dataset_name, al_func_name,
    ml_func_name, score_arousal, score_valence, mean_score,
    MSE_arousal, MSE_valence, MSE_mean, batch_size, hyper_parameters

    Args:
        lp (LearningProfile): Contains all the data needed.

    Returns:
        np.array: list of specified Data Format.
    """
    arousal = lp.get_MSE_arousal()
    valence = lp.get_MSE_valence()

    score_arousal = np.sum(arousal)
    score_valence = np.sum(valence)

    mean = [np.mean(i) for i in zip(*[arousal, valence])]
    mean_score = np.sum(mean)

    return np.array([lp.get_train_dataset_name(),
                     lp.get_test_dataset_name(),
                     lp.get_al_function().__name__,
                     lp.get_ml_function().__name__,
                     score_arousal, score_valence, mean_score,
                     arousal, valence, mean,
                     lp.get_batch_size(),
                     lp.get_hyper_parameters()],
                    dtype=object)


###############################################################################
# Load data from disk - Phase
# Uses the class LearningProfileDescription and not LearningProfile
###############################################################################


def evaluate_all_profiles(learning_profiles=[],
                          eval=Eval.AROUSAL,
                          nr_models=-1):
    """
    Given a list of learning profiles, evaluate them and return the first
    'nr_models' profiles with the best score according to the eval method.

    Args:
        learning_profiles (list): List of learning profiles
            (using LearningProfileDescription).
        eval (Enum): method of evaluation. Defaults to Eval.AROUSAL.
        nr_models (int): number of models to include in plot.
            Defaults to -1 (All models included).

    Returns:
        List(Tuple(String, float)): List of tuples containing the
        first nr_models with the best profiles in
        decending order (based on score) and their corresponding score.
    """
    if not learning_profiles:
        raise ValueError("No learning profiles given")

    # Set eval mode for all learning profiles
    [lp.set_eval_mode(eval) for lp in learning_profiles]

    # Sort according to best score
    sorted_list = sorted(learning_profiles, key=lambda lp: lp.get_score())

    best_LPs = [(f"Best profile: {lp.get_name(True, True)} " +
                 f" h_pars: {lp.get_hyper_parameters()}",
                 lp.get_score()) for lp in sorted_list]

    min_ = min(len(sorted_list), nr_models)
    return best_LPs[:min_] if nr_models > -1 else best_LPs


def load_all_learning_profiles(dir_path):
    """
    Load all learning profiles from disk and parse them
    using LearningProfileDescription.

    Args:
        dir_path (Path): Path to directory containing learning profiles.

    Returns:
        learning_profiles (list): list of parsed learning profiles.
    """
    # Load all learning profiles in the given dir_path
    learning_profiles = []
    for file_name in listdir(dir_path):
        if LP_FILE_NAME in file_name:
            lp_loaded = np.load(Path(dir_path / file_name), allow_pickle=True)
            # Get id from the file name
            id = file_name.split(LP_FILE_NAME + '-', 1)[1]
            id = id.split('.npy')[0]
            lp = LearningProfileDescription(id, lp_loaded)
            learning_profiles.append(lp)
    return learning_profiles

############################################################################### # Store data to disk - Phase # Uses the class LearningProfile and not LearningProfileDescription ############################################################################### Create subdirectory named after the current date and time.      Args:         dir_path (Path): Path to directory.      Raises:         ValueError: If `dir_path` exists, but is not a directory.      Returns:         dir_path (Path): New path to subdirectory. # Create dir # Create subdirectory named after the current date and time Calculates the score for each learning profile and saves the score     together with the MSE in .npy format.      Files are created with the name LP_FILE_NAME-id.npy.      Args:         dir_path (Path): Directory path for output from learning profiles.         learning_profiles (list): List of learning profiles. Creates a data array for the learning profile.      Data Format: train_dataset_name, test_dataset_name, al_func_name,     ml_func_name, score_arousal, score_valence, mean_score,     MSE_arousal, MSE_valence, MSE_mean, batch_size, hyper_parameters      Args:         lp (LearningProfile): Contains all the data needed.      Returns:         np.array: list of specified Data Format. ############################################################################### # Load data from disk - Phase # Uses the class LearningProfileDescription and not LearningProfile ############################################################################### Given a list of learning profiles, evaluate them and return the first     'nr_models' profiles with the best score according to the eval method.      Args:         learning_profiles (list): List of learning profiles             (using LearningProfileDescription).         eval (Enum): method of evaluation. Defaults to Eval.AROUSAL.         nr_models (int): number of models to include in plot.             Defaults to -1 (All models included).      Returns:         List(Tuple(String, float)): List of tuples containing the         first nr_models with the best profiles in         decending order (based on score) and their corresponding score. # Set eval mode for all learning profiles # Sort according to best score Load all learning profiles from disk and parse them     using LearningProfileDescription.      Args:         dir_path (Path): Path to directory containing learning profiles.      Returns:         learning_profiles (list): list of parsed learning profiles. # Load all learning profiles in the given dir_path # Get id from the file name