import sklearn
import pandas
import seaborn as sns
import matplotlib.pyplot as pyplot
from functools import reduce
# import numpy as np


def metrics_from_prediction_and_label(labels, predictions, verbose=False):

    measures = {
        "accuracy": sklearn.metrics.accuracy_score(labels, predictions),
        "balanced_accuracy": sklearn.metrics.balanced_accuracy_score(labels, predictions),
        "precision_micro": sklearn.metrics.precision_score(labels, predictions, average='micro'),
        "precision_macro": sklearn.metrics.precision_score(labels, predictions, average='macro'),
        "precision_weighted": sklearn.metrics.precision_score(labels, predictions, average='weighted'),
        "recall_micro": sklearn.metrics.recall_score(labels, predictions, average='micro'),
        "recall_macro": sklearn.metrics.recall_score(labels, predictions, average='macro'),
        "recall_weighted": sklearn.metrics.recall_score(labels, predictions, average='weighted'),
        "f1_score_micro": sklearn.metrics.f1_score(labels, predictions, average='micro'),
        "f1_score_macro": sklearn.metrics.f1_score(labels, predictions, average='macro'),
        "f1_score_weighted": sklearn.metrics.f1_score(labels, predictions, average='weighted')
    }

    try:
        measures["roc_auc_weighted"] = multi_class_roc_auc_score(labels, predictions, 'weighted')
        measures["roc_auc_macro"] = multi_class_roc_auc_score(labels, predictions, 'macro')
        measures["roc_auc_micro"] = multi_class_roc_auc_score(labels, predictions, 'micro')
    except ValueError:
        print("Warning: Roc auc score can not be calculated ...")

    try:
        # note we use the average precision at different threshold values as the auc of the pr-curve
        # and not the auc-pr-curve with the trapezoidal rule / linear interpolation because it could be too optimistic
        measures["auc_prc_weighted"] = multi_class_prc_auc_score(labels, predictions, 'weighted')
        measures["auc_prc_macro"] = multi_class_prc_auc_score(labels, predictions, 'macro')
        measures["auc_prc_micro"] = multi_class_prc_auc_score(labels, predictions, 'micro')
    except ValueError:
        print("Warning: Auc prc score can not be calculated ...")

    save_confusion_matrix(labels, predictions)

    report = save_classification_report(labels, predictions)
    classes = list(sorted(set(labels)))
    for pos_class in classes:
        measures[str(pos_class) + "_precision"] = report[str(pos_class)]['precision']
        measures[str(pos_class) + "_recall"] = report[str(pos_class)]['recall']
        measures[str(pos_class) + "_f1-score"] = report[str(pos_class)]['f1-score']
        measures[str(pos_class) + "_support"] = report[str(pos_class)]['support']

        if pos_class == 1:
            neg_class = 0
        else:
            neg_class = 1

        tp, fp, tn, fn = calculate_cm_states(labels, predictions, pos_class, neg_class)

        measures[str(pos_class) + "_tp"] = tp
        measures[str(pos_class) + "_fp"] = fp
        measures[str(pos_class) + "_tn"] = tn
        measures[str(pos_class) + "_fn"] = fn


        if tn + fp == 0:
            pass
        else:
            # Specificity or true negative rate
            measures[str(pos_class) + "_tnr"] = tn / (tn + fp)

            # Fall out or false positive rate
            measures[str(pos_class) + "_fpr"] = fp / (fp + tn)

        if tn + fn == 0:
            pass
        else:
            # Negative predictive value
            measures[str(pos_class) + "_npv"] = tn / (tn + fn)

        if tp + fn == 0:
            pass
        else:
            # False negative rate
            measures[str(pos_class) + "_fnr"] = fn / (tp + fn)

        if tp + fp == 0:
            pass
        else:
            # False discovery rate
            measures[str(pos_class) + "_fdr"] = fp / (tp + fp)

    return measures


def calculate_cm_states(labels, predictions, pos_class, neg_class):

    tp = 0
    fp = 0
    tn = 0
    fn = 0

    for i in range(len(predictions)):
        if labels[i] == predictions[i] == pos_class:
            tp += 1
        if predictions[i] == pos_class and labels[i] != predictions[i]:
            fp += 1
        if labels[i] == predictions[i] == neg_class:
            tn += 1
        if predictions[i] == neg_class and labels[i] != predictions[i]:
            fn += 1

    return tp, fp, tn, fn


def save_classification_report(labels, predictions):
    return sklearn.metrics.classification_report(y_true=labels, y_pred=predictions, output_dict=True)


def multi_class_roc_auc_score(label, predict, average):
    label_binarizer = sklearn.preprocessing.LabelBinarizer()
    label_binarizer.fit(label)
    label = label_binarizer.transform(label)
    predict = label_binarizer.transform(predict)

    return sklearn.metrics.roc_auc_score(label, predict, average=average)


def multi_class_prc_auc_score(label, predict, average):
    label_binarizer = sklearn.preprocessing.LabelBinarizer()
    label_binarizer.fit(label)
    label = label_binarizer.transform(label)
    predict = label_binarizer.transform(predict)

    return sklearn.metrics.average_precision_score(label, predict, average=average)


def label_binarizer(labels):

    for index in range(0, len(labels)):
        if labels[index] >= 0.5:
            labels[index] = 1.0
        else:
            labels[index] = 0.0

    return labels


def save_confusion_matrix(labels, predictions, path="../../../results/cm.pdf"):

    classes = sklearn.utils.multiclass.unique_labels(labels, predictions)
    cms = []
    cm = sklearn.metrics.confusion_matrix(labels, predictions)
    cm_df = pandas.DataFrame(cm, index=classes, columns=classes)
    cms.append(cm_df)

    def prettify(n):
        """
        if n > 1000000:
            return str(np.round(n / 1000000, 1)) + 'M'
        elif n > 1000:
            return str(np.round(n / 1000, 1)) + 'K'
        else:
            return str(n)
        """
        return str(n)

    cm = reduce(lambda x, y: x.add(y, fill_value=0), cms)
    annot = cm.applymap(prettify)
    cm = (cm.T / cm.sum(axis=1)).T
    fig, g = pyplot.subplots(figsize=(7, 4.5))
    g = sns.heatmap(cm, annot=annot, fmt='', cmap='Blues', cbar=False, rasterized=True, linewidths=0.1)
    _ = g.set(ylabel='Actual', xlabel='Prediction')

    for _, spine in g.spines.items():
        spine.set_visible(True)

    pyplot.xticks(rotation=45)
    fig.tight_layout()
    fig.savefig(path)
    pyplot.close()
[((115, 11, 115, 101), 'sklearn.metrics.classification_report', 'sklearn.metrics.classification_report', (), '', False, 'import sklearn\n'), ((119, 22, 119, 60), 'sklearn.preprocessing.LabelBinarizer', 'sklearn.preprocessing.LabelBinarizer', ({}, {}), '()', False, 'import sklearn\n'), ((124, 11, 124, 73), 'sklearn.metrics.roc_auc_score', 'sklearn.metrics.roc_auc_score', (), '', False, 'import sklearn\n'), ((128, 22, 128, 60), 'sklearn.preprocessing.LabelBinarizer', 'sklearn.preprocessing.LabelBinarizer', ({}, {}), '()', False, 'import sklearn\n'), ((133, 11, 133, 83), 'sklearn.metrics.average_precision_score', 'sklearn.metrics.average_precision_score', (), '', False, 'import sklearn\n'), ((149, 14, 149, 73), 'sklearn.utils.multiclass.unique_labels', 'sklearn.utils.multiclass.unique_labels', ({(149, 53, 149, 59): 'labels', (149, 61, 149, 72): 'predictions'}, {}), '(labels, predictions)', False, 'import sklearn\n'), ((151, 9, 151, 62), 'sklearn.metrics.confusion_matrix', 'sklearn.metrics.confusion_matrix', ({(151, 42, 151, 48): 'labels', (151, 50, 151, 61): 'predictions'}, {}), '(labels, predictions)', False, 'import sklearn\n'), ((152, 12, 152, 64), 'pandas.DataFrame', 'pandas.DataFrame', (), '', False, 'import pandas\n'), ((169, 13, 169, 46), 'matplotlib.pyplot.subplots', 'pyplot.subplots', (), '', True, 'import matplotlib.pyplot as pyplot\n'), ((170, 8, 170, 103), 'seaborn.heatmap', 'sns.heatmap', (), '', True, 'import seaborn as sns\n'), ((176, 4, 176, 30), 'matplotlib.pyplot.xticks', 'pyplot.xticks', (), '', True, 'import matplotlib.pyplot as pyplot\n'), ((179, 4, 179, 18), 'matplotlib.pyplot.close', 'pyplot.close', ({}, {}), '()', True, 'import matplotlib.pyplot as pyplot\n'), ((12, 20, 12, 71), 'sklearn.metrics.accuracy_score', 'sklearn.metrics.accuracy_score', ({(12, 51, 12, 57): 'labels', (12, 59, 12, 70): 'predictions'}, {}), '(labels, predictions)', False, 'import sklearn\n'), ((13, 29, 13, 89), 'sklearn.metrics.balanced_accuracy_score', 'sklearn.metrics.balanced_accuracy_score', ({(13, 69, 13, 75): 'labels', (13, 77, 13, 88): 'predictions'}, {}), '(labels, predictions)', False, 'import sklearn\n'), ((14, 27, 14, 96), 'sklearn.metrics.precision_score', 'sklearn.metrics.precision_score', (), '', False, 'import sklearn\n'), ((15, 27, 15, 96), 'sklearn.metrics.precision_score', 'sklearn.metrics.precision_score', (), '', False, 'import sklearn\n'), ((16, 30, 16, 102), 'sklearn.metrics.precision_score', 'sklearn.metrics.precision_score', (), '', False, 'import sklearn\n'), ((17, 24, 17, 90), 'sklearn.metrics.recall_score', 'sklearn.metrics.recall_score', (), '', False, 'import sklearn\n'), ((18, 24, 18, 90), 'sklearn.metrics.recall_score', 'sklearn.metrics.recall_score', (), '', False, 'import sklearn\n'), ((19, 27, 19, 96), 'sklearn.metrics.recall_score', 'sklearn.metrics.recall_score', (), '', False, 'import sklearn\n'), ((20, 26, 20, 88), 'sklearn.metrics.f1_score', 'sklearn.metrics.f1_score', (), '', False, 'import sklearn\n'), ((21, 26, 21, 88), 'sklearn.metrics.f1_score', 'sklearn.metrics.f1_score', (), '', False, 'import sklearn\n'), ((22, 29, 22, 94), 'sklearn.metrics.f1_score', 'sklearn.metrics.f1_score', (), '', False, 'import sklearn\n')]