# BaseML基类，各个大类能够继承其中的基本方法
import os
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import joblib


class baseml:
    """BaseML中的继承基类,单例模式避免多次调用创建
    
    """
    def __init__(self):

        self.cwd = os.path.dirname(os.getcwd())  # 获取当前文件的绝对路径
        self.file_dirname = os.path.dirname(os.path.abspath(__file__))
        self.x_train, self.x_test, self.y_train, self.y_test, self.x_val, self.y_val = [], [], [], [], [], []
        self.X = []
        self.Y = []
        self.dataset = []
        self.model = None
        self.test_size = 0.2
        self.scaler = None

    # 采用单例，避免基类创建太多次
    def __new__(cls, *args, **kwargs):
        # print("__new__")
        if not hasattr(baseml, "_instance"):
            # print("创建新实例")
            baseml._instance = object.__new__(cls)
        return baseml._instance


    def train(self):
        # 必须要改写的类
        raise AttributeError("train function must be implemented")

    def inference(self):
        # 必须要改写的类
        raise AttributeError("inference function must be implemented")


    # 从文件加载数据集，支持csv文件和txt文件
    # def load_dataset_from_file(self, path, x_column = [], y_column = []):
    #     if type == 'csv':
    #         self.dataset = pd.read_csv(path).values # .values就转成numpy格式了
    #         X = self.dataset[:,x_column]
    #         y = self.dataset[:,y_column]
    #         self.get_data(X,y,x_column,y_column)
    #     elif type == 'txt':
    #         self.dataset = np.loadtxt(path)
    #         X = self.dataset[:,x_column]
    #         y = self.dataset[:,y_column]
    #         X = X.values
    #         y = y.values
    #         self.get_data(X,y,x_column,y_column)

    # 从数据加载数据集，支持['numpy','list','DataFrame']
    # def load_dataset_from_data(self, X, y = None, x_column = [], y_column = []):
    #     if type(X) != type(y):
    #         raise TypeError("数据格式不同，无法加载")
    #     if isinstance(X,list):
    #         X = np.array(X)
    #         y = np.array(y)
    #         self.get_data(X,y,x_column,y_column)
    #     elif isinstance(X,np.ndarray):
    #         self.get_data(X,y,x_column,y_column)
    #     elif isinstance(X,pd.DataFrame):
    #         X = X.values
    #         y = y.values
    #         self.get_data(X,y,x_column,y_column)


    def load_dataset(self, X, y = [], type = None, x_column = [], y_column = [], 
                     shuffle = True, show = False, split = False, scale = False):
        """Load the model's data set.

        Args:
            X (str|numpy|pandas|list): 自变量.
            y (str|numpy|pandas|list, optional): 目标值. 默认为 [].
            type (str, optional): X和y的输入格式, choice = ['csv', 'numpy','pandas','list','txt], 最后统一转换为numpy. 
            x_column (list, optional): X 的索引列. 默认设置为X的所有列.
            y_column (list, optional): y的索引列. 默认设置为y的所有列.
            shuffle (bool, optional): 是否对元素随机排序. 默认为True.
            show (bool, optional): 显示5条数据. 默认为True.
            split(bool, optional): 是否划分数据集为训练集和测试集. 默认为True.
            scale(bool, optional): 是否对数据进行归一化. False.

        """
        if (type == 'csv' or type == 'txt') and len(x_column) == 0:
            raise ValueError("请传入数据列号")
        if type == 'csv':
            self.dataset = pd.read_csv(X).values  # .values就转成numpy格式了
            if shuffle:
                np.random.shuffle(self.dataset)
            self.get_data(self.dataset,self.dataset,x_column,y_column,split,scale)
        elif type == 'numpy':  
            if shuffle:
                X, y = self.shuffle_data(X,y)
            self.get_data(X,y,x_column,y_column,split,scale)
        elif type == 'pandas':
            X = X.values
            y = y.values if len(y)>0 else []
            if shuffle:
                X, y = self.shuffle_data(X,y)
            self.get_data(X,y,x_column,y_column,split,scale)
        elif type == 'list':
            X = np.array(X)
            y = np.array(y) if len(y)>0 else []
            if shuffle:
                X, y = self.shuffle_data(X,y)
            self.get_data(X,y,x_column,y_column,split,scale)
        elif type == 'txt':
            self.dataset = np.loadtxt(X)
            self.dataset = self.dataset.values
            if shuffle:
                np.random.shuffle(self.dataset)
            self.get_data(self.dataset,self.dataset,x_column,y_column,split,scale)

        print("Load dataset successfully!")

        if show and len(self.x_train) >= 5:   # 显示前5条数据
            print("X")
            print(self.x_train[:5])
            print("y")
            if len(self.y_train) >= 5:
                print(self.y_train[:5])
            else:
                print("None")
        

    def save(self,path="checkpoint.pkl"):
        print("Saving model checkpoints...")
        joblib.dump(self.model, path, compress=3)
        print("Saved successfully!")
        
    
    def load(self, path):
        self.model = joblib.load(path)

    def get_data(self,X,y,x_column,y_column,split,scale):
        """通过列号获取真实的训练数据

        Args:
            X (numpy.ndarray): 自变量.
            y (numpy.ndarray.ndarraye_): 因变量.
            x_column (list): 自变量的列索引集合.
            y_column (list): 因变量的列索引集合.
        """
        if len(x_column) == 0 and len(X):
             # 如果没有赋值，那么默认选用所有列
            x_column = list(range(X.shape[1]))
        if len(y_column) == 0 and len(y):
            # 如果没有赋值，默认用y的所有列
            if y.ndim == 1:
                y_column = [0]
            else:
                y_column = list(range(y.shape[1]))

        if len(X):
            self.x_train = X[:,x_column]

        if scale:  # 对训练数据进行归一化，在聚类、部分分类的时候需要使用
            self.scaler = MinMaxScaler(feature_range=(0, 1))
            self.x_train = self.scaler.fit_transform(self.x_train)

        if len(y):  # 
            if y.ndim == 1:
                y = y.reshape(-1,1)
            self.y_train = y[:,y_column]
            if self.y_train.shape[0]:
                self.dataset = np.concatenate((self.x_train,self.y_train),axis=1) # 按列进行拼接
        else:
            self.dataset = self.x_train


        if split:   # 进行数据集划分
            self.x_train, self.x_test, self.y_train, self.y_test = train_test_split(self.x_train,  self.y_train, test_size = self.test_size, random_state=42)

    def shuffle_data(self, X, y):
        if len(X) == len(y): 
            c = list(zip(X, y))  # 保持X与y的对应关系
            np.random.shuffle(c)
            X = np.array([t[0] for t in c])
            y = np.array([t[1] for t in c])
        elif len(X) > 0 and len(y) == 0:
            np.random.shuffle(X)
        
        return X, y

    def reverse_scale(self, data):
        return self.scaler.inverse_transform(data)

        
    def get_test_data(self):
        return self.x_test, self.y_test
