import sklearn.decomposition as skl_decomposition

import Orange.data
from Orange.misc.wrapper_meta import WrapperMeta
from Orange.projection import SklProjector, Projection

__all__ = ["PCA", "SparsePCA", "RandomizedPCA", "IncrementalPCA"]


class PCA(SklProjector):
    __wraps__ = skl_decomposition.PCA
    name = 'pca'

    def __init__(self, n_components=None, copy=True, whiten=False, preprocessors=None):
        super().__init__(preprocessors=preprocessors)
        self.params = vars()

    def fit(self, X, Y=None):
        proj = self.__wraps__(**self.params)
        proj = proj.fit(X, Y)
        return PCAModel(proj, self.domain)


class SparsePCA(SklProjector):
    __wraps__ = skl_decomposition.SparsePCA
    name = 'sparse pca'

    def __init__(self, n_components=None, alpha=1, ridge_alpha=0.01,
                 max_iter=1000, tol=1e-8, method='lars', n_jobs=1, U_init=None,
                 V_init=None, verbose=False, random_state=None, preprocessors=None):
        super().__init__(preprocessors=preprocessors)
        self.params = vars()

    def fit(self, X, Y=None):
        proj = self.__wraps__(**self.params)
        proj = proj.fit(X, Y)
        return PCAModel(proj, self.domain)


class RandomizedPCA(SklProjector):
    __wraps__ = skl_decomposition.RandomizedPCA
    name = 'randomized pca'

    def __init__(self, n_components=None, copy=True, iterated_power=3,
                 whiten=False, random_state=None, preprocessors=None):
        super().__init__(preprocessors=preprocessors)
        self.params = vars()

    def fit(self, X, Y=None):
        proj = self.__wraps__(**self.params)
        proj = proj.fit(X, Y)
        return PCAModel(proj, self.domain)


class PCAModel(Projection, metaclass=WrapperMeta):
    def __init__(self, proj, domain):
        def pca_variable(i):
            def linear_combination():
                return ' + '.join(['({} - {}) * {}'.format(a.to_sql(), m, w)
                                   for a, m, w in zip(
                        domain.attributes, self.mean_, self.components_[:, i])])
            v = Orange.data.ContinuousVariable('PC %d' % i)
            v.compute_value = Projector(self, i)
            v.to_sql = linear_combination
            return v

        super().__init__(proj=proj)
        self.n_components = self.components_.shape[0]
        self.domain = Orange.data.Domain(
            [pca_variable(i) for i in range(self.n_components)],
             domain.class_vars, domain.metas)


class IncrementalPCA(SklProjector):
    __wraps__ = skl_decomposition.IncrementalPCA
    name = 'incremental pca'

    def __init__(self, n_components=None, whiten=False, copy=True,
                 batch_size=None, preprocessors=None):
        super().__init__(preprocessors=preprocessors)
        self.params = vars()

    def fit(self, X, Y=None):
        proj = self.__wraps__(**self.params)
        proj = proj.fit(X, Y)
        return IncrementalPCAModel(proj, self.domain)


class IncrementalPCAModel(PCAModel):
    def partial_fit(self, data):
        if isinstance(data, Orange.data.Storage):
            self.proj.partial_fit(data.X)
        else:
            self.proj.partial_fit(data)
        self.__dict__.update(self.proj.__dict__)
        return self


class Projector:
    def __init__(self, projection, feature):
        self.projection = projection
        self.feature = feature
        self.transformed = None

    def __call__(self, data):
        if data.domain != self.projection.pre_domain:
            data = data.from_table(self.projection.pre_domain, data)
        self.transformed = self.projection.transform(data.X)
        return self.transformed[:, self.feature]

    def __getstate__(self):
        d = dict(self.__dict__)
        d['transformed'] = None
        return d

@@ -1705,162 +1705,217 @@
 ass 
-PCAModel(Projection, metaclass=WrapperMeta):%0A    def __init__(self, proj, domain):%0A        def pca_variable(i):%0A         
+_LinearCombination:%0A    def __init__(self, attrs, weights, mean=None):%0A        self.attrs = attrs%0A        self.weights = weights%0A        self.mean = mean%0A%0A
    
+ 
 def 
-linear_combination():%0A    
+__call__(self):%0A        if self.mean is None:%0A
     
@@ -1944,19 +1944,11 @@
 oin(
-%5B'(%7B%7D - %7B%7D)
+'%7B%7D
  * %7B
@@ -1957,16 +1957,19 @@
 .format(
+w, 
 a.to_sql
@@ -1974,19 +1974,10 @@
 ql()
-, m, w)%0A   
+)%0A
     
@@ -2006,19 +2006,14 @@
     
-  
 for a,
- m,
  w i
@@ -2022,49 +2022,146 @@
 zip(
-%0A                        domain.attribute
+self.attrs, self.weights))%0A        return ' + '.join('%7B%7D * (%7B%7D - %7B%7D)'.format(w, a.to_sql(), m, w)%0A            for a, m, w in zip(self.attr
 s, s
@@ -2172,36 +2172,144 @@
 mean
-_
 , self.
-components_%5B:, i%5D)%5D)
+weights))%0A%0A%0Aclass PCAModel(Projection, metaclass=WrapperMeta):%0A    def __init__(self, proj, domain):%0A        def pca_variable(i):
 %0A   
@@ -2441,16 +2441,16 @@
 l = 
-l
+_L
 inear
-_c
+C
 ombi
@@ -2451,24 +2451,130 @@
 rCombination
+(%0A                domain.attributes, self.components_%5B:, i%5D,%0A                getattr(self, 'mean_', None))
 %0A           

Fix pickling of PCAModel.