# -*- coding: utf-8 -*-
# @Author: Aliaksei Halachkin
# @Date:   2016-10-25 22:20:37
# @Last Modified by:   Aliaksei Halachkin
# @Last Modified time: 2017-04-22 19:26:42

import numpy as np
from sklearn.metrics import confusion_matrix
from sklearn import metrics
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib import colors


class MulticlassMetrics:
    """
    Wrapper around sklearn multiclass classification metrics 
    Attributes:
        precision,
        recall,
        fscore,
        support (dict): key - class, value - score

        (x)_precision,
        (x)_recall,
        (x)_fscore, where (x) is macro, micro or weighted


    """
    def __init__(self, y_true, y_pred, target_names=None):

        if target_names is None:
            target_names = sorted(set(y_true).union(set(y_pred)))
        self.target_names = target_names

        self.y_true = y_true
        self.y_pred = y_pred
        self.binary = len(set(self.target_names)) < 3

        self.confusion_matrix = self._inst_confusion_matrix
        self.plot_confusion_matrix = self._inst_plot_confusion_matrix
        
        self._cmp_scores()

    def _cmp_scores(self):
        y_true = self.y_true
        y_pred = self.y_pred
        target_names = self.target_names
        #compute combined scores for each class
        comb_prfs = metrics.precision_recall_fscore_support(y_true,
                                                            y_pred,
                                                            labels=target_names)

        self.precision = {}
        self.recall = {}
        self.fscore = {}
        self.support = {}
        for i, cl in enumerate(target_names):
            self.precision[cl] = comb_prfs[0][i]
            self.recall[cl] = comb_prfs[1][i] 
            self.fscore[cl] = comb_prfs[2][i] 
            self.support[cl] = comb_prfs[3][i]

        self.comb_prfs = comb_prfs    

        #macro scores for each class
        macro_prfs = metrics.precision_recall_fscore_support(y_true,
                                                             y_pred,
                                                             labels=target_names,
                                                             pos_label=None,
                                                             average='macro')
        self.macro_precision = macro_prfs[0]
        self.macro_recall = macro_prfs[1]
        self.macro_fscore = macro_prfs[2]
        self.macro_prfs = macro_prfs

        #micro scores for each class
        micro_prfs = metrics.precision_recall_fscore_support(y_true,
                                                             y_pred,
                                                             labels=target_names,
                                                             pos_label=None,
                                                             average='micro')
        self.micro_precision = micro_prfs[0]
        self.micro_recall = micro_prfs[1]
        self.micro_fscore = micro_prfs[2]

        self.micro_prfs = micro_prfs

        #weighted scores for each class
        weighted_prfs = metrics.precision_recall_fscore_support(y_true,
                                                             y_pred,
                                                             labels=target_names,
                                                             pos_label=None,
                                                             average='weighted')
        self.weighted_precision = weighted_prfs[0]
        self.weighted_recall = weighted_prfs[1]
        self.weighted_fscore = weighted_prfs[2]

        self.weighted_prfs = weighted_prfs


    def _inst_confusion_matrix(self, normalization=None):
        return MulticlassMetrics.confusion_matrix(self.y_true, self.y_pred,
                                                  normalization)


    @staticmethod 
    def confusion_matrix(y_true, y_pred, normalization=None):
        if normalization is None:
            return confusion_matrix(y_true, y_pred)
        elif normalization == 0:
            cm = confusion_matrix(y_true, y_pred)
            return cm.astype('float') / cm.sum(axis=0)
        else:
            cm = confusion_matrix(y_true, y_pred)
            return cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]


    #http://scikit-learn.org/stable/_downloads/plot_confusion_matrix.py
    def _inst_plot_confusion_matrix(self,
                              normalization=None,
                              title = "Confusion matrix", 
                              cmap=plt.cm.Blues,
                              fontsize = 8):
        """Plots the confusion matrix cm
        Confusion matrix indexing:
        C_{i, j} is equal to the number of observations known to 
        be in group i but predicted to be in group j.
        """
        MulticlassMetrics.plot_confusion_matrix(
                          self.y_true, self.y_pred, self.target_names,
                          normalization, title, cmap, fontsize)

    @staticmethod 
    def plot_confusion_matrix(y_true, y_pred, target_names,
                              normalization=None,
                              title = "Confusion matrix", 
                              cmap=plt.cm.Blues,
                              fontsize = 8):
        """Plots the confusion matrix cm
        Confusion matrix indexing:
        C_{i, j} is equal to the number of observations known to 
        be in group i but predicted to be in group j.
        """
        cm = MulticlassMetrics.confusion_matrix(y_true, y_pred, normalization)

        plt.imshow(cm , interpolation='nearest', cmap=cmap)
        plt.title(title)
        target_names = map(str, map(int, target_names))
        tick_marks = np.arange(len(target_names))
        plt.xticks(tick_marks, target_names, rotation=90) 
        plt.yticks(tick_marks, target_names)
        thresh = cm.max()*0.7
        #print values
        for i, row in enumerate(cm):
            for j, c in enumerate(row):
                if c > thresh  : color = 'w'
                else: color = 'b'
                if isinstance(c, float):
                    c = round(c,2)
                plt.text(j, i, c, fontsize=fontsize, 
                                  ha='center', va='center', color=color)
        plt.ylabel('True label')
        plt.xlabel('Predicted label')








