from datetime import timedelta
from sklearn.metrics import auc
from collections import Counter, OrderedDict

import functools
import numpy.ma as ma
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import pathlib
import glob
import _pickle as pickle


LOG_EPS = -1e150

report_begin_str = "\\documentclass[a4paper]{article} \n \\usepackage[english]{babel} \n \\usepackage[utf8x]" \
                       "{inputenc} \n \\usepackage[T1]{fontenc} \n \\usepackage[a4paper, top = 3cm, bottom = 2cm, " \
                       "left = 3cm, right = 3cm, marginparwidth = 1.75cm]{geometry}"
report_being_str2 = "\\usepackage{amsmath} \n \\usepackage{amsthm} \n \\usepackage{amsfonts} \n \\usepackage{graphicx} \n \\usepackage[colorinlistoftodos]{todonotes} \n \\usepackage[colorlinks = true, allcolors = blue]{hyperref} \n \\usepackage{multirow} \n \\usepackage{siunitx, etoolbox} \n \\usepackage{subcaption} \n \\usepackage{tikz} \n \\usepackage{standalone} \n \\usetikzlibrary{arrows, automata} \n \\usepackage{listings} \n " \
                    "\\usepackage{color} \n \\usepackage{textcomp} \n \\theoremstyle{definition} \n \\newtheorem{definition}{Definition} \n \\sisetup{ \n table - align - uncertainty = true, \n separate - uncertainty = true, \n} \n \\renewrobustcmd{\\bfseries}{\\fontseries{b}\\selectfont} \n \\renewrobustcmd {\\boldmath}{} \n \\begin{document}"

###################### TEXT MANIPULATION METHODS #######################
def chop_microseconds(delta):
    return delta - timedelta(microseconds=delta.microseconds)


def convert_to_bold(string):
    return '\033[1m' + string + '\033[0m'


def convert_to_bold_for_latex(string):
    return "\\textbf{" + string + "}"


def remove_digits(s):
    return ''.join(i for i in s if not i.isdigit())


def remove_whitespaces_head_and_tail(s):
    if len(s) != 0:
        # remove leading and trailing whitespace
        if s[0] == ' ':
            s = s[1:]
        if s[-1] == ' ':
            s = s[:-1]
    return s


def set_precision(val, prec=10):
    return round(val, prec)


def llhoods_with_precision(llhoods, prec=40):
    for i in range(len(llhoods)):
        llhoods[i] = set_precision(llhoods[i], prec)
    return llhoods


def contains_all(_str, _list):
    res = True
    for s in _str:
        if s not in _list:
            res = False
            break
    return res
    # return 0 not in [s in list for s in str]

###################### STABLE CALCULATION METHODS #######################
def log_sum_probs(log_p1, log_p2):
    log_mx = np.max([log_p1, log_p2])

    return log_mx + np.log(np.exp(log_p1 - log_mx) + np.exp(log_p2 - log_mx))


def log_weighted_sum_probs(pi_1, log_p1, pi_2, log_p2, pi_3, log_p3):

    x_1 = np.log(pi_1) + log_p1
    x_2 = np.log(pi_2) + log_p2
    x_3 = np.log(pi_3) + log_p3

    xs = [x_1, x_2, x_3]
    log_mx = np.max(xs, axis=0)

    sm = (log_p1!=LOG_EPS) * np.exp(x_1 - log_mx) + (log_p2!=LOG_EPS) * np.exp(x_2 - log_mx) + (log_p3!=LOG_EPS) * np.exp(x_3 - log_mx)

    return log_mx + np.log(sm)


def log_weighted_sum_probs_check(pi_2, log_p2, pi_3, log_p3):

    x_2 = np.log(pi_2) + log_p2
    x_3 = np.log(pi_3) + log_p3

    xs = [x_2, x_3]
    log_mx = np.max(xs, axis=0)

    sm = (log_p2!=LOG_EPS) * np.exp(x_2 - log_mx) + (log_p3!=LOG_EPS) * np.exp(x_3 - log_mx)

    return log_mx + np.log(sm)

def log_weighted_sum_normalize_probs(pi_1, log_p1, pi_2, log_p2, pi_3, log_p3):

    x_1 = np.log(pi_1) + log_p1
    x_2 = np.log(pi_2) + log_p2
    x_3 = np.log(pi_3) + log_p3

    xs = [x_1, x_2, x_3]
    log_mx = np.max(xs, axis=0)

    sm = (log_p1!=LOG_EPS) * np.exp(x_1 - log_mx) + (log_p2!=LOG_EPS) * np.exp(x_2 - log_mx) + (log_p3!=LOG_EPS) * np.exp(x_3 - log_mx)
    return x_1, x_2, x_3, log_mx, sm
    # return np.exp(x_1-log_mx), np.exp(x_2-log_mx), np.exp(x_3-log_mx), sm

def log_weighted_sum_probs_min(pi_1, log_p1, pi_2, log_p2, pi_3, log_p3):

    x_1 = np.log(pi_1) + log_p1
    x_2 = np.log(pi_2) + log_p2
    x_3 = np.log(pi_3) + log_p3

    xs = [x_1, x_2, x_3]
    log_mn = np.min(xs, axis=0)

    sm = (log_p1!=LOG_EPS) * np.exp(x_1 + log_mn) + (log_p2!=LOG_EPS) * np.exp(x_2 + log_mn) + (log_p3!=LOG_EPS) * np.exp(x_3 + log_mn)

    return log_mx + np.log(sm)


def normalize_log_probs(probs):
    reduced_probs = np.exp(probs - max(probs))
    return reduced_probs/reduced_probs.sum()


def logdot(a, b):
    max_a, max_b = np.max(a), np.max(b)
    exp_a, exp_b = np.exp(a - max_a), np.exp(b - max_b)
    c = np.log(np.dot(exp_a, exp_b)) + max_a + max_b

    return c


def ma_multidot(arrays):
    return functools.reduce(ma.dot, arrays)


def multi_logdot(Xs):
    max_Xs = [np.max(X) for X in Xs]
    exp_Xs = [np.exp(X - max_X) for X, max_X in zip(Xs, max_Xs)]
    res = np.linalg.multi_dot(exp_Xs)
    res[np.where(res==0)] = LOG_EPS
    return np.log(res) + sum(max_Xs)


###############################################################
###################### DATA I/O METHODS #######################
###############################################################
def read_dataset(_data_path, _header=None):
    return pd.read_csv(_data_path, sep=',', encoding='ISO-8859-1', dtype=str, keep_default_na=False, header=_header, skipinitialspace=True)


# writing data
def write_data(data, filepath='../../automata/example.dat'):
    f = open(filepath, 'w')
    for line in data:
        f.write(str(line)+"\n")
    f.close()


def save_object(obj, filename):
    with open(filename, 'wb') as output:  # Overwrites any existing file.
        pickle.dump(obj, output)


def load_object(filename):
    with open(filename, 'rb') as output:
        obj = pickle.load(output)

    return obj


def print_to_file(txt, filename='long_my_output.txt'):
    with open(filename, 'a+') as f:
        f.write(txt + '\n')


def create_folders(model, _start_over_report):
    """ Creates folders for a column in a dataset
    :param _start_over_report: True/False. True allows creating a new report and False allows appending to the existing report
    :return:
    """
    main_folder = model.experiment_config.current_experiment_folder

    # Create inputs, outputs, and results folders
    pathlib.Path(main_folder).mkdir(parents=True, exist_ok=True)
    pathlib.Path(main_folder + '/inputs' ).mkdir(parents=True, exist_ok=True)
    pathlib.Path(main_folder + '/outputs').mkdir(parents=True, exist_ok=True)
    pathlib.Path(main_folder + '/results').mkdir(parents=True, exist_ok=True)

    # Either creates a new file or appends to an existing one
    if _start_over_report:
        with open(model.experiment_config.main_experiments_folder + '/' + model.experiment_config.dataset_name
                          + '/report.tex', 'w') as f:
            # Prints latex file structure
            print(report_begin_str, file=f)
            print(report_being_str2, file=f)

            # Prints title, and section header, etc.
            print('\\title{The Report for the File Named '+ model.experiment_config.dataset_name +'} \n \\maketitle', file=f)
            print('The number of columns is ' + str(model.data.shape[1]) + '.', file=f)
            print('The number of rows is ' + str(model.data.shape[0]) + '.', file=f)
            print('\section{Column Name: ' + model.experiment_config.current_column_name.replace("_","\_")+ '}', file=f)
    else:
        with open(model.experiment_config.main_experiments_folder + '/' + model.experiment_config.dataset_name + '/report.tex', 'a') as f:
            print('\section{Column Name: ' + model.experiment_config.current_column_name.replace("_","\_") + '}', file=f)


# copies some columns directly
def copy_columns_between_dicts(dict_source, dict_target, columns):
    for column in columns:
        dict_target[column] = dict_source[column]
    return dict_target


###############################################################
#################### VISUALIZATION METHODS ####################
###############################################################
def plot_matrix(X, title='Title', xlabel='xlabel', ylabel='ylabel', figsize=None, vmax_=None, xticklabels=None, yticklabels=None, cmap=plt.cm.gray_r):
    if figsize is None:
        plt.figure(figsize=(18,6))
    else:
        plt.figure(figsize=figsize)
    if vmax_ is None:
        VMAX = np.max(X)
    else:
        VMAX = vmax_
    if yticklabels is None:
        pass
    else:
        plt.yticks(range(len(yticklabels)), yticklabels)

    if xticklabels is None:
        pass
    else:
        plt.xticks(range(len(xticklabels)), xticklabels, rotation=90)

    plt.imshow(X, interpolation='none', vmax=VMAX, vmin=0, aspect='auto', cmap=cmap)
    plt.colorbar()
    plt.xlabel(xlabel, fontsize=20)
    plt.ylabel(ylabel, fontsize=20)
    plt.title(title, fontsize=20)
    plt.show()


def plot_normal_type_histogram(x, current_experiment_folder):
    plt.figure()
    plt.hist(np.array(x), bins=20)
    plt.ylabel('data values')
    plt.savefig(current_experiment_folder + '/outputs/histogram.eps', dpi=1000)
    plt.close()


def bar_plot_type_posteriors(x, y, current_experiment_folder, _display=False, _save=False):
    # plots the column posterior in order to show the prediction for column's type
    N = len(y)
    y_pos = np.arange(N)
    plt.figure(figsize=(N*1.2,4))
    plt.bar(y_pos, x, align='center', alpha=0.5, width=0.4)
    plt.xticks(y_pos, y)
    plt.ylim(0, 1)
    plt.ylabel('Probability')
    plt.title('Posterior of column type: p(t=k|X)')
    if _save:
        plt.savefig(current_experiment_folder + '/outputs/type_posteriors.eps', dpi=1000)
    if _display:
        plt.show()
    else:
        plt.close()

# source: http://scipy-cookbook.readthedocs.io/items/Matplotlib_HintonDiagrams.html
def _blob(x, y, area, colour):
    """
    Draws a square-shaped blob with the given area (< 1) at
    the given coordinates.
    """
    hs = np.sqrt(area) / 2
    xcorners = np.array([x - hs, x + hs, x + hs, x - hs])
    ycorners = np.array([y - hs, y - hs, y + hs, y + hs])
    plt.fill(xcorners, ycorners, colour, edgecolor=colour)



# def plot_hinton(W, method=None, _max_value=None, xticklabels=None, yticklabels=None, path=None):
#     """
#     Draws a Hinton diagram for visualizing a weight matrix.
#     Temporarily disables matplotlib interactive mode if it is on,
#     otherwise this takes forever.
#     """
#
#     reenable = False
#     if plt.isinteractive():
#         plt.ioff()
#     plt.clf()
#
#     if reenable:
#         plt.ion()
#
#     special.hinton(W, max_value=_max_value)
#     if xticklabels is not None:
#         plt.xticks(np.arange(len(xticklabels)), xticklabels, rotation=90, fontsize=13)
#     if yticklabels is not None:
#         plt.yticks(np.arange(len(xticklabels)), yticklabels, fontsize=13)
#
#     plt.xlabel('true type', fontsize=17)
#     plt.ylabel('predicted type', fontsize=17)
#
#     if path is not None:
#         plt.savefig(path, dpi=1000, bbox_inches="tight")
#
#     # plt.show()

def plot_roc(fpr, tpr, _type, _method='', _path='experiments/0_predictions/roc.eps', _save=False, _show=True):
    roc_auc = auc(fpr, tpr)
    plt.figure()
    plt.plot(fpr, tpr, color='darkorange', label='ROC curve '+_method+'(area = %0.2f)' % roc_auc)
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('ROC for ' + _type)
    plt.legend(loc="lower right")
    if _show:
        plt.show()
    if _save:
        plt.savefig(_path, dpi=1000)

def plot_roc_multiple(Xs, _type, _path='experiments/0_predictions/roc.eps', _save=False, _show=True):
    plt.figure()
    for method in list(Xs.keys()):
        fpr = Xs[method][_type]['fpr']
        tpr = Xs[method][_type]['tpr']
        roc_auc = auc(fpr, tpr)

        plt.plot(fpr, tpr, label='ROC curve ' + method + ' (area = %0.2f)' % roc_auc)

    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('ROC for ' + _type)
    plt.legend(loc="lower right")
    if _show:
        plt.show()
    if _save:
        plt.savefig(_path, dpi=1000)


def plot_roc_multiple_dots(Xs, _type, _path='experiments/0_predictions/roc.eps', _save=False, _show=True):
        plt.figure()
        tprs = []
        fprs = []
        groups = []
        for method in list(Xs.keys()):
            tprs.append(Xs[method][_type][0])
            fprs.append(Xs[method][_type][1])
            groups.append(method)

        df = pd.DataFrame({
            'x': fprs,
            'y': tprs,
            'group': groups
        })

        sns.regplot(data=df, x="x", y="y", fit_reg=False, marker="+", color="skyblue")

        plt.xlim([0.0, 1.0])
        plt.ylim([0.0, 1.05])
        plt.xlabel('False Positive Rate')
        plt.ylabel('True Positive Rate')
        plt.title('ROC for ' + _type)
        if _show:
            plt.show()
        if _save:
            plt.savefig(_path, dpi=1000)


###############################################################
#################### LATEX METHODS ############################
###############################################################
def print_figure_latex(column_name, f):
    print("\\begin{figure}[!h] \n \\centering \n \\includegraphics[width = \\textwidth]{" + column_name +
          "/outputs/type_posteriors.eps} \n \\caption{The posterior probability distribution of the column type for column named " +
          column_name.replace("_", "\_") + ".} \n \\label{fig:" + column_name+ "} \n \\end{figure}",file=f)


def print_line_latex(txt, f):
    print(txt, file=f)


def print_row_type_dist_table_latex(current_experiment_folder, num_normal_cells, num_missings, num_catch_alls, num_data):
    table_row_type_dist = pd.DataFrame(columns=['', 'Number of Entries', 'Proportion to the Total Num. of Rows'])

    table_row_type_dist.loc[0] = ['Row Type', 'Number of Entries', 'Proportion to the Total Num. of Rows']
    table_row_type_dist.loc[1] = ['Normal', num_normal_cells, round(1. * num_normal_cells / num_data, 2)]
    table_row_type_dist.loc[2] = ['Missing', num_missings, round((1. * num_missings) / num_data, 2)]
    table_row_type_dist.loc[3] = ['Catch-all', num_catch_alls, round((1. * num_catch_alls) / num_data, 2)]

    with open(current_experiment_folder + "/outputs/table_row_type_dist.tex", "w") as f:
        f.write("\\begin{tabular}{|" + " | ".join(["c"] * len(table_row_type_dist.columns)) + "|}\n")
        for i, row in table_row_type_dist.iterrows():
            f.write("\hline ")
            if i == 0:
                f.write(" & ".join(["\\bfseries " + str(x) for x in row.values]) + " \\\\\n")
            else:
                f.write(" & ".join([str(x) for x in row.values]) + " \\\\\n")
        f.write("\hline ")
        f.write("\\end{tabular}")


def print_statistics_table_latex(x, current_experiment_folder):
    table_histogram = pd.DataFrame(columns=['Min.', 'Max.', 'Mean', 'Std'])
    table_histogram.loc[0] = ['Min.', 'Max.', 'Avg.', 'Std.']
    table_histogram.loc[1] = [round(np.min(x), 2), round(np.max(x), 2),
                              round(np.mean(x), 2), round(np.std(x), 2)]
    with open(current_experiment_folder + "/outputs/table_histogram_detail.tex", "w") as f:
        f.write("\\begin{tabular}{|" + " | ".join(["c"] * len(table_histogram.columns)) + "|}\n")
        for i, row in table_histogram.iterrows():
            f.write("\hline ")
            if i == 0:
                f.write(" & ".join(["\\bfseries " + str(x) for x in row.values]) + " \\\\\n")
            else:
                f.write(" & ".join([str(x) for x in row.values]) + " \\\\\n")
        f.write("\hline ")
        f.write("\\end{tabular}")


def print_table_latex(x, current_experiment_folder):
    table_histogram = pd.DataFrame(columns=['Min.', 'Max.', 'Mean', 'Std'])
    table_histogram.loc[0] = ['Min.', 'Max.', 'Avg.', 'Std.']
    table_histogram.loc[1] = [round(np.min(x), 2), round(np.max(x), 2),
                              round(np.mean(x), 2), round(np.std(x), 2)]
    with open(current_experiment_folder + "/outputs/table_histogram_detail.tex", "w") as f:
        f.write("\\begin{tabular}{|" + " | ".join(["c"] * len(table_histogram.columns)) + "|}\n")
        for i, row in table_histogram.iterrows():
            f.write("\hline ")
            if i == 0:
                f.write(" & ".join(["\\bfseries " + str(x) for x in row.values]) + " \\\\\n")
            else:
                f.write(" & ".join([str(x) for x in row.values]) + " \\\\\n")
        f.write("\hline ")
        f.write("\\end{tabular}")


def evaluate_types(_dataset_name, _ptype, _header=None,):
    predicted_types = _ptype.predicted_types
    dataset_path    = '../data/' + _dataset_name + '.csv'
    annotation_path = '../annotations/' + _dataset_name + '.csv'

    df = pd.read_csv(dataset_path, sep=',', encoding='ISO-8859-1', dtype=str, header=_header, keep_default_na=False, skipinitialspace=True)
    annotations = pd.read_csv(annotation_path, sep=',', encoding='ISO-8859-1', dtype=str, keep_default_na=False)

    true_values = annotations['Type'].values.tolist()
    true_values = [true_value.split('-')[0] for true_value in true_values]

    predictions = predicted_types.values()
    predictions = [prediction.replace('date-eu', 'date').replace('date-iso-8601', 'date').replace('date-non-std-subtype','date').replace('date-non-std','date') for prediction in predictions]

    column_names = list(predicted_types.keys())

    correct_, false_ = 0., 0.
    for i, (prediction, true_value) in enumerate(zip(predictions, true_values)):
        column_name = column_names[i]
        unique_vals, unique_vals_counts = np.unique([str(int_element) for int_element in df[df.columns[i]].tolist()], return_counts=True)
        if prediction == true_value:
            correct_ += 1
        else:
            false_ += 1
            print('column name : ', column_names[i])
            indices = _ptype.normal_types[column_name]
            print('\tsome normal data values: ', [unique_vals[ind] for ind in indices][:20])
            print('\ttheir counts: ', [unique_vals_counts[ind] for ind in indices][:20])

            indices = _ptype.missing_types[column_name]
            if len(indices) !=0 :
                print('\tsome missing data values: ', [unique_vals[ind] for ind in indices][:20])
                print('\ttheir counts: ', [unique_vals_counts[ind] for ind in indices][:20])

            indices = _ptype.anomaly_types[column_name]
            if len(indices) !=0 :
                print('\tsome anomalous data values: ', [unique_vals[ind] for ind in indices][:20])
                print('\ttheir counts: ', [unique_vals_counts[ind] for ind in indices][:20])

            print('\ttrue/annotated type : ', true_value, '\n\tpredicted type : ', prediction)
            print('\tposterior probs: ', _ptype.p_t_columns[list(_ptype.p_t_columns.keys())[i]])
            print('\ttypes: ', list(_ptype.types.values()), '\n')


    print('correct/total = ', round(correct_/len(column_names),2), '(' + str(int(correct_)) + '/' + str(len(column_names)) + ')')


###### added later - needs a pass over
def not_vector(X):
    return np.array([not x for x in X])


def get_type_counts(predictions, annotations, _types=['boolean', 'date', 'float', 'integer', 'string']):
    dataset_counts = OrderedDict()
    total_test = {t: 0 for t in _types}

    for dataset_name in annotations:

        true_values = annotations[dataset_name]
        ptype_predictions = predictions[dataset_name].values()

        ptype_predictions = [prediction.replace('date-eu', 'date').replace('date-iso-8601', 'date').replace('date-non-std-subtype', 'date').replace('date-non-std', 'date') for
                             prediction in ptype_predictions]

        ignored_columns = np.where((np.array(true_values) != 'all identical') & (np.array(true_values) != 'gender') & (np.array(ptype_predictions) != 'all identical') & (
            np.array(ptype_predictions) != 'unknown'))[0]

        counts = Counter(np.array(true_values)[ignored_columns])
        for t in _types:
            if t not in list(counts.keys()):
                counts[t] = 0

        # Counters are unordered, so for deterministic output we sort via a list
        dataset_counts[dataset_name] = dict(sorted(counts.items()))

        total_test = {t: total_test[t] + dataset_counts[dataset_name][t] for t in _types}

    total_cols = sum([total_test[t] for t in ['boolean', 'date', 'float', 'integer', 'string']])

    return [total_test, dataset_counts, total_cols]


def evaluate_model_type(annotations, predictions):
    types = ['integer', 'string', 'float', 'boolean', 'date']
    type_rates = {t: {'TP': 0, 'FP': 0, 'TN': 0, 'FN': 0} for t in types}

    predictions = [prediction.replace('date-eu', 'date').replace('date-iso-8601', 'date').replace('date-non-std-subtype', 'date').replace('date-non-std', 'date') for
                         prediction in predictions]

    # find columns whose types are not supported by ptype
    ignored_columns = np.where((np.array(annotations) != 'all identical') &
                               (np.array(annotations) != 'gender') &
                               (np.array(predictions) != 'all identical') &
                               (np.array(predictions) != 'unknown'))[0]

    for t in types:
        # print(t)
        # print(ignored_columns)
        # print(annotations)
        # print(predictions)
        y_true = (np.array(annotations) == t)[ignored_columns]
        y_score = (np.array(predictions) == t)[ignored_columns]

        type_rates[t]['TP'] = sum(y_true * y_score)
        type_rates[t]['FP'] = sum(not_vector(y_true) * y_score)
        type_rates[t]['TN'] = sum(not_vector(y_true) * not_vector(y_score))
        type_rates[t]['FN'] = sum(y_true * not_vector(y_score))

    return type_rates


def get_evaluations(_annotations, _predictions, methods=['ptype',]):

    dataset_names = list(_annotations.keys())
    types = ['boolean', 'date', 'float', 'integer', 'string']

    Js = {}
    overall_accuracy = {method: 0 for method in methods}
    for t in types:

        J = {}
        for method in methods:

            tp, fp, fn = .0, .0, .0
            for dataset_name in dataset_names:
                temp = evaluate_model_type(_annotations[dataset_name],
                                           _predictions[dataset_name].values())
                tp += temp[t]['TP']
                fp += temp[t]['FP']
                fn += temp[t]['FN']

            overall_accuracy[method] += tp
            J[method] = float_2dp(tp / (tp + fp + fn))
        Js[t] = J

    return Js, overall_accuracy


def run_experiment(total_cols,
                   dataset_names,
                   types=None):
    if types is None:
        types = {1: 'integer', 2: 'string', 3: 'float', 4: 'boolean',
                 5: 'date-iso-8601', 6: 'date-eu',
                 7: 'date-non-std-subtype', 8: 'date-non-std'}
    ptype = Ptype(_types=types)
    for dataset_name in dataset_names:
        df = read_dataset(dataset_name, TEMP_PATHS)
        ptype.run_inference(_data_frame=df, _dataset_name=dataset_name)

    df, overall_accuracy = get_evaluations(_annotations_path=_annotations_path, _predictions_path=_predictions_path, _training=False, _sets=test_datasets)

    overall_accuracy_to_print = {method: "{:.2f}".format(overall_accuracy[method] / (total_cols)) for method in overall_accuracy}
    return [df, overall_accuracy_to_print]


def get_datasets():
    dataset_names = []
    for file in glob.glob("data/*.csv"):
        dataset_names.append(file.split('/')[-1])

    return dataset_names


def float_2dp(n: float):
    """Round a float to 2 decimal places, preserving float-hood. Probably a better way to do this."""
    return np.float64("{:.2f}".format(n))


def evaluate_predictions(annotations, type_predictions):
    ### the column type counts of the datasets
    [_, dataset_counts, total_cols] = get_type_counts(type_predictions, annotations)
    df = pd.DataFrame(dataset_counts, columns=dataset_counts.keys())
    column_type_counts = 'tests/column_type_counts.csv'
    expected = pd.read_csv(column_type_counts, index_col=0)
    if not(expected.equals(df)):
        df.to_csv(path_or_buf=column_type_counts + '.new')
        raise Exception(f'{column_type_counts} comparison failed.')

    Js, overall_accuracy = get_evaluations(annotations, type_predictions)
    overall_accuracy_to_print = {method: {'overall-accuracy': float_2dp(overall_accuracy[method] / total_cols)} for method in overall_accuracy}
    print('overall accuracy: ', overall_accuracy_to_print)
    print('Jaccard index values: ', {t:Js[t]['ptype'] for t in Js})

    df1 = pd.DataFrame.from_dict(Js, orient='index')
    df2 = pd.DataFrame.from_dict(overall_accuracy_to_print, orient='index').T
    df = df2.append(df1)
    column_type_evaluations = 'tests/column_type_evaluations.csv'
    expected = pd.read_csv(column_type_evaluations, index_col=0)
    if not(expected.equals(df)):
        df.to_csv(path_or_buf=column_type_evaluations + '.new')
        raise Exception(f'{column_type_evaluations} comparison failed.')
