import numpy as np
from sklearn.decomposition import PCA


class ChannelWisePrincipalComponentAnalysis:
    """ Channel wise PCA application. Creates PCA objects respective to the
        number of channels.
        Attr:
            n_components(int): Number of components in PCA
            copy(bool): Saves the matrix if  True and updates on each fit()
            whiten(bool): Whitens the PCA matrix to form a tight frame
            svd_solver(string): SV Decomposition solver method
            tol=var_tol(float): Unfortunately I re-implemented it
                Tolerance to the singular values of the matrix
            random_state(seed): Random state seed
            num_ch(int): number of channels in expected data
            var_tol(float): Tolerance to the variance
         """

    def __init__(self, n_components=None, var_tol=0, copy=True, whiten=False,
                 svd_solver='auto', tol=0.0, iterated_power='auto',
                 random_state=None, num_ch=1):

        self.list_pca = []
        for idx in range(num_ch):
            self.list_pca.append(PCA(n_components=n_components, copy=copy,
                                     whiten=whiten,
                                     svd_solver=svd_solver, tol=tol,
                                     iterated_power=iterated_power,
                                     random_state=random_state))
        self.var_tol = var_tol
        self.num_ch = num_ch

    def fit(self, x, y=None, var_tol=None):
        """ Inherits PCA fit() function from scikit-learn. Fits the
            transformation matrix wrt. tolerance to each PCA.
            Args:
                x(ndarray[float]): C x N x k data array
                y(ndarray[int]): N x k observation (class) array
                    N is number of samples k is dimensionality of features
                    C is number of channels
                var_tol(float): Threshold to remove lower variance dims.
                """

        if var_tol:
            self.var_tol = var_tol

        for i in range(self.num_ch):
            self.list_pca[i].fit(x[i, :, :], y)
            max_sv = self.list_pca[i].singular_values_[0]
            self.list_pca[i].n_components = np.sum(self.list_pca[i].singular_values_ >= max_sv * self.var_tol)
            try:
                self.list_pca[i].fit(x[i, :, :], y)
            except Exception as e:
                raise e

    def transform(self, x, y=None):
        f_vector = []
        for i in range(self.num_ch):
            # TODO: Observe that scikit learn PCA does not accept y
            f_vector.append(self.list_pca[i].transform(x[i, :, :]))

        return np.concatenate(f_vector, axis=1)

    def fit_transform(self, x, y=None, var_tol=None):
        """ Fits parameters wrt. the input matrix and outputs corresponding
            reduced form feature vector.
            Args:
                x(ndarray[float]): C x N x k data array
                y(ndarray[int]): N x k observation (class) array
                    N is number of samples k is dimensionality of features
                    C is number of channels
                var_tol(float): Threshold to remove lower variance dims.
            Return:
                y(ndarray(float)): N x ( sum_i (C x k')) data array
                    where k' is the new dimension for each PCA
                """

        self.fit(x, var_tol=var_tol)
        return self.transform(x)


class DummyDimReduction:
    """ Just concatenation without any PCA
        Attr:
        none(None): nothing
         """

    def __init__(self):
        self.none = None

    def fit(self, x, y=None, var_tol=None):
        self.none = None

    def transform(self, x, y=None):
        self.none = None
        num_ch = x.shape[0]
        f_vector = []
        for i in range(num_ch):
            f_vector.append(x[i, :, :])

        return np.concatenate(f_vector, axis=1)

    def fit_transform(self, x, y=None, var_tol=None):
        arg = self.transform(x, y)
        return arg
