#%%
import numpy as np
from DataPreparation import CreditCardPreparation, AirlineDataPreparation

from tools.DataVisualization import TrendPlot

from graphviz import Graph
import statistics

from river import tree
from river import preprocessing
from river import linear_model
from river import ensemble

from tqdm import tqdm

class RiverHTClassifierStudyPipeline:

    def __init__(self, random_seed):
        """
        Initializing River Hoeffding Classifier will automatically initialize the training data
        Base on DataPreparation class, invoke method: `get_splitted_train_test_pd_df_data`
        will return splitted training dataset and testing dataset
        """
        self._train_features, self._test_features, self._train_target, self._test_target = \
            AirlineDataPreparation().get_splitted_train_test_pd_df_data(random_seed=random_seed)

    def process_and_evaluate_model_recall_rate(self):

        print('Training Features Shape: ', self._train_features.shape)
        print('Training Target Shape: ', self._train_target.shape)
        print('Testing Features Shape: ', self._test_features.shape)
        print('Testing Target Shape: ', self._test_target.shape)

        # self._model = tree.HoeffdingTreeClassifier(
        #     # grace_period=10, # let it smaller is better
        #     # split_criterion='gini',
        #     # split_confidence=1e-5,
        #     # leaf_prediction='nb', # best performance
        #     # nb_threshold=0, # best performance
        # )

        self._model = tree.HoeffdingAdaptiveTreeClassifier(
            # grace_period=10,
            # split_confidence=1e-5,
            # leaf_prediction='nb',
            # nb_threshold=10,
            # seed=0
        )

        # self._model = ensemble.AdaptiveRandomForestClassifier(
        #     n_models=10,
        #     max_depth=20,
        #     # grace_period=1,
        #     # split_criterion='gini',
        #     # split_confidence=0.01,
        #     # leaf_prediction='nb',
        #     # nb_threshold=0,
        #     # seed=0
        # )

        incremental_num_trained_data = []
        incremental_acc_list = []
        incremental_recall_rate_list = []
        num_data_run = 0
        for index, raw in tqdm(self._train_features.iterrows(), total=self._train_features.shape[0]):
            self._model.learn_one(raw, self._train_target[index])
            num_data_run += 1

            #==================================#
            # Inter-training process check out #
            #==================================#
            if num_data_run % 5000 == 0:
                print("online ML trained #{} rows of data".format(num_data_run))
                acc, recall = self.run_pred()
                incremental_num_trained_data.append(num_data_run)
                incremental_acc_list.append(acc)
                incremental_recall_rate_list.append(recall)
            # if num_data_run > 10000:
            #     break
        # acc, recall = self.run_pred()
        # incremental_num_trained_data.append(num_data_run)
        # incremental_acc_list.append(acc)
        # incremental_recall_rate_list.append(recall)

        #%%
        # self._model.draw()
        #%%

        return incremental_num_trained_data, incremental_acc_list, incremental_recall_rate_list

    def run_pred(self):
        test_data_len = len(self._test_features)
        correct_cnt = 0
        hf_pred = []
        for index, raw in tqdm(self._test_features.iterrows(), total=self._test_features.shape[0]):
        # for index, raw in self._test_features.iterrows():
            test_pred = self._model.predict_one(raw)
            hf_pred.append(test_pred)
            if test_pred == self._test_target[index]:
                correct_cnt += 1

        ##======================================##
        # Figure out the model prediction result #
        ##======================================##

        test_target = np.array(self._test_target)

        # Simply Prediction
        correct_cnt = 0
        high_risk_cnt = 0
        pred_high_risk_cnt = 0
        pickup_high_risk_cnt = 0
        for i in range(test_data_len):
            if test_target[i] == 1:
                high_risk_cnt += 1
                if hf_pred[i] == test_target[i]:
                    pickup_high_risk_cnt += 1
            if hf_pred[i] == 1:
                pred_high_risk_cnt += 1
            if hf_pred[i] == test_target[i]:
                correct_cnt += 1

        print("Total Testing Data: {}".format(test_data_len))
        print("Total correct predict: {}".format(correct_cnt))

        print("Total Target events: ", str(high_risk_cnt))
        print("Predict Target events: ", str(pred_high_risk_cnt))
        print("Picked Up Target events: ", str(pickup_high_risk_cnt))
        print("Acc: {:.2f} %".format(correct_cnt / test_data_len * 100))
        print("Recall rate: {:.2f} %".format(pickup_high_risk_cnt/high_risk_cnt * 100))

        accuracy = correct_cnt / test_data_len * 100
        recall_rate = pickup_high_risk_cnt/high_risk_cnt

        return accuracy, recall_rate

if __name__ == "__main__":
    import time
    acc_random_result = []
    recall_random_result = []
    for i_random_seed in range(42, 45):
        print(i_random_seed)
        study_pipeline = RiverHTClassifierStudyPipeline(random_seed=i_random_seed)
        inc_num_trained_data, inc_accuracy_list, inc_recall_rate_liar = \
            study_pipeline.process_and_evaluate_model_recall_rate()
        acc_random_result.append(inc_accuracy_list)
        recall_random_result.append(inc_recall_rate_liar)

    print("accuracy ratio")
    print(acc_random_result)
    print("recall rate ratio")
    print(recall_random_result)

    print("acc random result test")
    print(acc_random_result)
    test_acc_transpose = np.array(acc_random_result).T
    test_acc_transpose = test_acc_transpose.tolist()
    print("check out transpose result")
    print(test_acc_transpose)

    test_recall_transpose = np.array(recall_random_result).T
    test_recall_transpose = test_recall_transpose.tolist()

    test_acc_mean = [statistics.mean(i) for i in test_acc_transpose]
    test_acc_err = [statistics.stdev(i) for i in test_acc_transpose]

    print("Accuracy mean and std values")
    print(inc_num_trained_data)
    print(test_acc_mean)
    print(test_acc_err)

    test_rec_mean = [statistics.mean(i) for i in test_recall_transpose]
    test_rec_err = [statistics.stdev(i) for i in test_recall_transpose]

    print("recall mean and std values")
    print(test_rec_mean)
    print(test_rec_err)

    TrendPlot().plot_trend_with_error_bar(
        inc_num_trained_data,
        test_acc_mean,
        y_err=test_acc_err,
        x_label="#data for trained",
        y_label="Accuracy"
    )


#
# model = compose.Pipeline()
#
# model = scaler | log_reg
#
# model.learn_many(features_data, target_data)


# %%
