import warnings

import numpy as np
from sklearn.metrics import accuracy_score
from sklearn.metrics import average_precision_score
#####################################################################################
from sklearn.metrics import confusion_matrix
from sklearn.metrics import f1_score
from sklearn.metrics import fbeta_score
from sklearn.metrics import log_loss
from sklearn.metrics import make_scorer
from sklearn.metrics import mean_squared_error, median_absolute_error, mean_absolute_error
from sklearn.metrics import mean_squared_log_error
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import roc_auc_score


def balanced_accuracy_score(y_true, y_pred, sample_weight=None,
                            adjusted=False):
    C = confusion_matrix(y_true, y_pred, sample_weight=sample_weight)
    with np.errstate(divide='ignore', invalid='ignore'):
        per_class = np.diag(C) / C.sum(axis=1)
    if np.any(np.isnan(per_class)):
        warnings.warn('y_pred contains classes not in y_true')
        per_class = per_class[~np.isnan(per_class)]
    score = np.mean(per_class)
    if adjusted:
        n_classes = len(per_class)
        chance = 1 / n_classes
        score -= chance
        score /= 1 - chance
    return score


#####################################################################################
def accu(results, y_cv):
    return (results == y_cv).astype(int).sum(axis=0) / (y_cv.shape[0])


def rmse(results, y_cv):
    return np.sqrt(np.mean((results - y_cv) ** 2, axis=0))


######## Defining objective functions for HyperOpt here ######################
### keep all Classification Scorers greater_is_better True but subtract scorer from 1 so it diminishes.
#### This is the only way HyperOpt will find the minimum - so don't change this code anytime!
def gini(truth, predictions):
    g = np.asarray(np.c_[truth, predictions, np.arange(len(truth))], dtype=np.float)
    g = g[np.lexsort((g[:, 2], -1 * g[:, 1]))]
    gs = g[:, 0].cumsum().sum() / g[:, 0].sum()
    gs -= (len(truth) + 1) / 2.
    return gs / len(truth)


def gini_sklearn(truth, predictions):
    return gini(truth, predictions) / gini(truth, truth)


def gini_meae(truth, predictions):
    score = median_absolute_error(truth, predictions)
    return score


def gini_msle(truth, predictions):
    score = np.sqrt(mean_squared_log_error(truth, predictions))
    return score


def gini_mae(truth, predictions):
    score = mean_absolute_error(truth, predictions)
    return score


def gini_mse(truth, predictions):
    score = np.sqrt(mean_squared_error(truth, predictions))
    return score


def gini_rmse(truth, predictions):
    score = np.sqrt(mean_squared_error(truth, predictions))
    return score


def gini_accuracy(truth, predictions):
    return 1 - accuracy_score(truth, predictions)


def gini_bal_accuracy(truth, predictions):
    return 1 - balanced_accuracy_score(truth, predictions)


def gini_roc(truth, predictions):
    return 1 - roc_auc_score(truth, predictions)


def gini_precision(truth, predictions):
    return 1 - precision_score(truth, predictions)


def gini_average_precision(truth, predictions):
    return 1 - average_precision_score(truth, predictions.argmax(axis=1), average='weighted')


def gini_weighted_precision(truth, predictions):
    return 1 - precision_score(truth, predictions.argmax(axis=1), average='weighted')


def gini_macro_precision(truth, predictions):
    return 1 - precision_score(truth, predictions.argmax(axis=1), average='macro')


def gini_micro_precision(truth, predictions):
    return 1 - precision_score(truth, predictions.argmax(axis=1), average='micro')


def gini_samples_precision(truth, predictions):
    return 1 - precision_score(truth, predictions.argmax(axis=1), average='samples')


def gini_f1(truth, predictions):
    return 1 - f1_score(truth, predictions)


def gini_weighted_f1(truth, predictions):
    return 1 - f1_score(truth, predictions.argmax(axis=1), average='weighted')


def gini_macro_f1(truth, predictions):
    return 1 - f1_score(truth, predictions.argmax(axis=1), average='macro')


def gini_micro_f1(truth, predictions):
    return 1 - f1_score(truth, predictions.argmax(axis=1), average='micro')


def gini_samples_f1(truth, predictions):
    return 1 - f1_score(truth, predictions.argmax(axis=1), average='samples')


def gini_log_loss(truth, predictions):
    return log_loss(truth, predictions, normalize=True)


def gini_recall(truth, predictions):
    return 1 - recall_score(truth, predictions)


def gini_weighted_recall(truth, predictions):
    return 1 - recall_score(truth, predictions.argmax(axis=1), average='weighted')


def gini_samples_recall(truth, predictions):
    return 1 - recall_score(truth, predictions.argmax(axis=1), average='samples')


def gini_macro_recall(truth, predictions):
    return 1 - recall_score(truth, predictions.argmax(axis=1), average='macro')


def gini_micro_recall(truth, predictions):
    return 1 - recall_score(truth, predictions.argmax(axis=1), average='micro')


def gini_roc_auc(truth, predictions):
    return 1 - roc_auc_score(truth, predictions.argmax(axis=1), average='macro')


# calculate f2-measure
def f2_measure(y_true, y_pred):
    return fbeta_score(y_true, y_pred, beta=2)


### keep all Regression Scorers greater_is_better True since it leaves them as is and minimizes them
meae_scorer = make_scorer(gini_meae, greater_is_better=True)
msle_scorer = make_scorer(gini_msle, greater_is_better=True)
mae_scorer = make_scorer(gini_mae, greater_is_better=True)
mse_scorer = make_scorer(gini_mse, greater_is_better=True)

### keep all Classification Scorers greater_is_better True but subtract scorer from 1 so it diminishes.
#### This is the only way HyperOpt will find the minimum - so don't change this code anytime!
accuracy_scorer = make_scorer(gini_accuracy, greater_is_better=True, needs_proba=False)
bal_accuracy_scorer = make_scorer(gini_bal_accuracy, greater_is_better=True, needs_proba=False)

gini_scorer = make_scorer(gini_sklearn, greater_is_better=True, needs_proba=True)
roc_scorer = make_scorer(gini_roc, greater_is_better=True, needs_proba=True)

precision_scorer = make_scorer(gini_precision, greater_is_better=True, needs_proba=False)
average_precision_scorer = make_scorer(gini_average_precision, greater_is_better=True, needs_proba=True)
weighted_precision_scorer = make_scorer(gini_weighted_precision, greater_is_better=True, needs_proba=True)
macro_precision_scorer = make_scorer(gini_macro_precision, greater_is_better=True, needs_proba=True)
micro_precision_scorer = make_scorer(gini_micro_precision, greater_is_better=True, needs_proba=True)
samples_precision_scorer = make_scorer(gini_samples_precision, greater_is_better=True, needs_proba=True)

f1_scorer = make_scorer(gini_f1, greater_is_better=True, needs_proba=False)
weighted_f1_scorer = make_scorer(gini_weighted_f1, greater_is_better=True, needs_proba=True)
macro_f1_scorer = make_scorer(gini_macro_f1, greater_is_better=True, needs_proba=True)
micro_f1_scorer = make_scorer(gini_micro_f1, greater_is_better=True, needs_proba=True)
samples_f1_scorer = make_scorer(gini_samples_f1, greater_is_better=True, needs_proba=True)

recall_scorer = make_scorer(gini_recall, greater_is_better=True, needs_proba=False)
weighted_recall_scorer = make_scorer(gini_weighted_recall, greater_is_better=True, needs_proba=True)
macro_recall_scorer = make_scorer(gini_macro_recall, greater_is_better=True, needs_proba=True)
micro_recall_scorer = make_scorer(gini_micro_recall, greater_is_better=True, needs_proba=True)
samples_recall_scorer = make_scorer(gini_samples_recall, greater_is_better=True, needs_proba=True)

roc_auc_scorer = make_scorer(gini_roc_auc, greater_is_better=True, needs_proba=True)
### Leave the log-loss scorer as greater_is_better True since it keeps sign and minimizes it.
logloss_scorer = make_scorer(gini_log_loss, greater_is_better=True, needs_proba=True)
##########################################################################################
