import numpy as np
import scipy.stats as stats
from sklearn.utils import check_array
from .base import BaseThresholder
from .thresh_utility import normalize, cut


class QMCD(BaseThresholder):
    """QMCD class for Quasi-Monte Carlo Discreprancy thresholder.

       Use the quasi-Monte Carlo discreprancy to evaluate a non-parametric means
       to threshold scores generated by the decision_scores where outliers
       are set to any value beyond and percentile or quantile of one minus the
       descreperancy (Note** A discrepancy quantifies the distance between the
       continuous uniform distribution on a hypercube and the discrete uniform
       distribution on distinct sample points)
       
       Paramaters
       ----------

       method : str, optional (default='WD')
        {'CD', 'WD', 'MD', 'L2-star'}
        CD:      Centered Discrepancy
        WD:      Wrap-around Discrepancy
        MD:      Mix between CD/WD
        L2-star: L2-star discrepancy

       lim : str, optional (default='P')
        {'Q', 'P'}
        Q: Use quntile limiting
        P: Use percentile limiting
        

       Attributes
       ----------

       eval_: numpy array of binary labels of the training data. 0 stands
           for inliers and 1 for outliers/anomalies.

    """

    def __init__(self, method='WD', lim='P'):

        super(QMCD, self).__init__()
        self.method = method
        self.lim = lim

    def eval(self, decision):
        """Outlier/inlier evaluation process for decision scores.

        Parameters
        ----------
        decision : np.array or list of shape (n_samples)
                   which are the decision scores from a
                   outlier detection.
        
        Returns
        -------
        outlier_labels : numpy array of shape (n_samples,)
            For each observation, tells whether or not
            it should be considered as an outlier according to the
            fitted model. 0 stands for inliers and 1 for outliers.
        """

        decision = check_array(decision, ensure_2d=False)

        decision = normalize(decision)

        # Get the quasi Monte-Carlo discrepancy of the labels
        disc = stats.qmc.discrepancy(decision.reshape(-1,1), method=self.method)

        # Set the limit to either the quantile or percentile of 1-discrepancy
        if self.lim=='Q':
            limit = np.quantile(decision, 1.0-disc)
        elif self.lim=='P':
            try:
                limit = np.percentile(decision, (1.0-disc)*100, method='midpoint')
            except TypeError:
                limit = np.percentile(decision, (1.0-disc)*100, interpolation='midpoint')

        self.thresh_ = limit

        return cut(decision, limit)
