# -*- coding: utf-8 -*-

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from classifiers import Classifier


#######################
# Auxiliary functions #
#######################


# Evaluation metric
def update_preq_metric(s_prev, n_prev, correct, fading_factor=0.99):
    s = correct + fading_factor * s_prev
    n = 1.0 + fading_factor * n_prev
    metric = s / n

    return s, n, metric


# Plot data
def plot_results(simulation_time, lst_preq_general_accs, lst_names, out_name, flag_store=False):
    # figure
    plt.figure(figsize=(5, 5))
    plt.xlabel('Time Step', fontsize=10, weight='bold')
    plt.xticks(np.arange(0, simulation_time + 50, int(simulation_time / 5)), fontsize=15)
    plt.xlim(0, simulation_time)
    plt.ylabel('Accuracy', fontsize=10, weight='bold')
    plt.yticks(np.arange(0.0, 1.1, 0.2), fontsize=15)
    plt.ylim(0, 1)
    plt.grid(linestyle='dotted')

    # plot
    for i in range(len(lst_preq_general_accs)):
        plt.plot(range(simulation_time), lst_preq_general_accs[i], label=lst_names[i], linewidth=1.0)
    leg = plt.legend(ncol=1, loc='lower right', fontsize=12)
    leg.get_frame().set_alpha(0.9)

    # store
    if flag_store:
        plt.savefig(out_name)
    # plt.show()


# Load data
def load_data(dataset_name):
    data = None
    if 'circle' in dataset_name:
        data = pd.read_csv('circle.csv').values

    return data

#######
# OGD #
#######
# theta = 1.0: OGD
# theta < 1.0: OGD with active learning


def ogd(data_params, cls_params, theta=1.0):
    # fix random state / seed
    random_state = np.random.RandomState(seed=0)

    # get data parameters
    dataset_name = data_params['dataset_name']
    num_features = data_params['num_features']

    # load data
    data = load_data(dataset_name)

    # create classifier
    cls = Classifier(num_features, cls_params)

    # prequential evaluation
    preq_general_accs = []
    preq_general_acc_n = 0.0
    preq_general_acc_s = 0.0

    # start
    simulation_time = data.shape[0]
    for t in range(simulation_time):
        if t % 500 == 0:
            print('Time step: ', t)

        # receive example
        xy = data[t, :]
        x = xy[:-1]
        x = np.reshape(x, (1, len(x)))  # reshape vector (d,) to 1-d matrix (1,d)

        # predict class label
        y_hat_prob = cls.model.predict(x=x, verbose=0)
        y_hat_class = np.around(y_hat_prob)

        # AL strategy
        y = xy[-1]  # class label (ground truth)
        if random_state.rand() <= theta:
            # update (train) classifier
            y = np.reshape(y, (1, 1))  # reshape from (1,) to (1,1)
            cls.train_cls(x, y)

        # check if prediction is correct
        correct = 1 if y == y_hat_class else 0

        # update accuracy
        preq_general_acc_s, preq_general_acc_n, preq_general_acc = \
            update_preq_metric(preq_general_acc_s, preq_general_acc_n, correct)
        preq_general_accs.append(preq_general_acc)

    return preq_general_accs

################
# Exercise 1.1 #
################


# data parameters
circle_params = {'dataset_name': 'circle', 'num_features': 2}

# classifier parameters
nn_params1 = {'cls_name': 'neural_network', 'learning_rate': 0.1, 'momentum': 0.9, 'hidden_units': 8,
              'hidden_activation_fun': 'tanh'}
nn_params2 = {'cls_name': 'neural_network', 'learning_rate': 0.01, 'momentum': 0.9, 'hidden_units': 8,
              'hidden_activation_fun': 'tanh'}
nn_params3 = {'cls_name': 'neural_network', 'learning_rate': 0.001, 'momentum': 0.9, 'hidden_units': 8,
              'hidden_activation_fun': 'tanh'}

# ogd
legend_names = ['0.1', '0.01', '0.001']
lst_nn_params = [nn_params1, nn_params2, nn_params3]
results = [ogd(data_params=circle_params, cls_params=nn_params) for nn_params in lst_nn_params]
plot_results(simulation_time=5000, lst_preq_general_accs=results, lst_names=legend_names, out_name='ex1_1.png',
             flag_store=True)

################
# Exercise 1.2 #
################


# classifier parameters
nn_params1 = {'cls_name': 'neural_network', 'learning_rate': 0.01, 'momentum': 0.9, 'hidden_units': 8,
              'hidden_activation_fun': 'tanh'}
nn_params2 = {'cls_name': 'neural_network', 'learning_rate': 0.01, 'momentum': 0.9, 'hidden_units': 8,
              'hidden_activation_fun': 'sigmoid'}
nn_params3 = {'cls_name': 'neural_network', 'learning_rate': 0.01, 'momentum': 0.9, 'hidden_units': 8,
              'hidden_activation_fun': 'relu'}

# ogd
legend_names = ['tanh', 'sigmoid', 'relu']
lst_nn_params = [nn_params1, nn_params2, nn_params3]
results = [ogd(data_params=circle_params, cls_params=nn_params) for nn_params in lst_nn_params]
plot_results(simulation_time=5000, lst_preq_general_accs=results, lst_names=legend_names, out_name='ex1_2.png',
             flag_store=True)

################
# Exercise 2.1 #
################


# classifier parameters
nn_params = {'cls_name': 'neural_network', 'learning_rate': 0.01, 'momentum': 0.9, 'hidden_units': 8,
             'hidden_activation_fun': 'tanh'}

# ogd
thetas = ['1.0', '0.5', '0.1']
results = [ogd(data_params=circle_params, cls_params=nn_params, theta=float(th)) for th in thetas]
plot_results(simulation_time=5000, lst_preq_general_accs=results, lst_names=thetas, out_name='ex2_1.png',
             flag_store=True)
