from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.preprocessing import StandardScaler


class StandardizedFeature(BaseEstimator, TransformerMixin):
    """
    An sklearn-style transformer that takes an ordered sequence of ``Block`` objects
    and returns a 2D array of features generated by ``feature`` whose values
    have been standardized by mean and/or standard deviation.

    Args:
        feature (``Features``): Instance of a ``Features`` class.
        scaler (:class:`StandardScaler`): Instance of ``StandardScaler``. If
            None, default options will be used to instantiate one.
    """

    def __init__(self, feature, scaler=None):
        self.feature = feature
        if scaler is None:
            self.scaler = StandardScaler()
        else:
            self.scaler = scaler

    def fit(self, blocks, y=None):
        """
        Args:
            blocks (List[Block]): as output by :class:`Blockifier.blockify`
            y (None): This isn't used, it's only here for API consistency.

        Returns:
            :class:`StandardizedFeature`: an instance of this class with the
                ``self.scaler`` attribute fit to the ``blocks`` data

        Note:
            When fitting the :class:`StandardScaler` object, you'll probably
                want to determine the mean and/or std of *multiple* HTML files'
                blocks, rather than just a single observation. To do that, just
                concatenate all of the blocks together in a single iterable.

            In contrast, you'll typically apply :meth:`transform` to a *single*
                HTML file's blocks at a time.
        """
        feature_array = self.feature.fit_transform(blocks)
        self.scaler = self.scaler.fit(feature_array)
        return self

    def transform(self, blocks, y=None):
        """
        Transform an ordered sequence of blocks into a 2D features matrix with
        shape (num blocks, num features) and standardized feature values.

        Args:
            blocks (List[Block]): as output by :class:`Blockifier.blockify`
            y (None): This isn't used, it's only here for API consistency.

        Returns:
            `np.ndarray`: 2D array of shape (num blocks, num sub-features),
                where ``blocks`` data has been transformed by ``self.feature``
                and optionally standardized by ``self.scaler``.
        """
        return self.scaler.transform(self.feature.transform(blocks))
