
from __future__ import annotations
from typing import NoReturn
from . import LinearRegression
from ...base import BaseEstimator
import numpy as np


class PolynomialFitting(BaseEstimator):
    """
    Polynomial Fitting using Least Squares estimation
    """
    def __init__(self, k: int) -> PolynomialFitting:
        """
        Instantiate a polynomial fitting estimator

        Parameters
        ----------
        k : int
            Degree of polynomial to fit
        """
        super().__init__()
        self._k = k
        self._linear = LinearRegression(include_intercept=True)

    def _fit(self, X: np.ndarray, y: np.ndarray) -> NoReturn:
        """
        Fit Least Squares model to polynomial transformed samples

        Parameters
        ----------
        X : ndarray of shape (n_samples, n_features)
            Input data to fit an estimator for

        y : ndarray of shape (n_samples, )
            Responses of input data to fit to
        """
        self._linear._fit(self.__transform(X), y)

    def _predict(self, X: np.ndarray) -> np.ndarray:
        """
        Predict responses for given samples using fitted estimator

        Parameters
        ----------
        X : ndarray of shape (n_samples, n_features)
            Input data to predict responses for

        Returns
        -------
        responses : ndarray of shape (n_samples, )
            Predicted responses of given samples
        """
        return self._linear._predict(self.__transform(X))

    def _loss(self, X: np.ndarray, y: np.ndarray) -> float:
        """
        Evaluate performance under MSE loss function

        Parameters
        ----------
        X : ndarray of shape (n_samples, n_features)
            Test samples

        y : ndarray of shape (n_samples, )
            True labels of test samples

        Returns
        -------
        loss : float
            Performance under MSE loss function
        """
        return self._linear._loss(self.__transform(X), y)

    def __transform(self, X: np.ndarray) -> np.ndarray:
        """
        Transform given input according to the univariate polynomial transformation

        Parameters
        ----------
        X: ndarray of shape (n_samples,)

        Returns
        -------
        transformed: ndarray of shape (n_samples, k+1)
            Vandermonde matrix of given samples up to degree k
        """
        return np.vander(X, self._k+1, increasing=True)


# from _future_ import annotations
# from typing import NoReturn
# from . import LinearRegression
# from ...base import BaseEstimator
# import numpy as np


# class PolynomialFitting(BaseEstimator):
#     """
#     Polynomial Fitting using Least Squares estimation
#     """
#     def _init_(self, k: int) -> PolynomialFitting:
#         """
#         Instantiate a polynomial fitting estimator

#         Parameters
#         ----------
#         k : int
#             Degree of polynomial to fit
#         """
#         super()._init_()
#         self.model = LinearRegression()
#         self.k = k

#     def _fit(self, X: np.ndarray, y: np.ndarray) -> NoReturn:
#         """
#         Fit Least Squares model to polynomial transformed samples

#         Parameters
#         ----------
#         X : ndarray of shape (n_samples, n_features)
#             Input data to fit an estimator for

#         y : ndarray of shape (n_samples, )
#             Responses of input data to fit to
#         """
#         self.model.fit(self._transform(X), y)

#     def _predict(self, X: np.ndarray) -> np.ndarray:
#         """
#         Predict responses for given samples using fitted estimator

#         Parameters
#         ----------
#         X : ndarray of shape (n_samples, n_features)
#             Input data to predict responses for

#         Returns
#         -------
#         responses : ndarray of shape (n_samples, )
#             Predicted responses of given samples
#         """
#         return self.model.predict(self._transform(X))

#     def _loss(self, X: np.ndarray, y: np.ndarray) -> float:
#         """
#         Evaluate performance under MSE loss function

#         Parameters
#         ----------
#         X : ndarray of shape (n_samples, n_features)
#             Test samples

#         y : ndarray of shape (n_samples, )
#             True labels of test samples

#         Returns
#         -------
#         loss : float
#             Performance under MSE loss function
#         """
#         return self.model.loss(self._transform(X), y)

#     def __transform(self, X: np.ndarray) -> np.ndarray:
#         """
#         Transform given input according to the univariate polynomial transformation

#         Parameters
#         ----------
#         X: ndarray of shape (n_samples,)

#         Returns
#         -------
#         transformed: ndarray of shape (n_samples, k+1)
#             Vandermonde matrix of given samples up to degree k
#         """
#         return np.vander(X, self.k, increasing=True)