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

def dtheta(x,y,dx,dy,r2):
    """Calculate dtheta for the intergrand"""
    return (x*dy - y*dx)/r2


class WIND(BaseThresholder):
    """WIND class for topological Winding number thresholder.

       Use the topological winding number (with respect to the origin) to
       evaluate a non-parametric means to threshold scores generated by
       the decision_scores where outliers are set to any value beyond the
       mean intersection point calculated from the winding number
       
       Paramaters
       ----------

       Attributes
       ----------

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

    """

    def __init__(self):

        super(WIND, self).__init__()

    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)

        # Create a normal distribution and normalize
        norm = np.random.normal(loc=0.0, scale=1.0, size=decision.shape)
        norm = normalize(norm)

        # Create a KDE of the labels and the normal distribution
        # Generate KDE
        val_data, dat_range = gen_kde(decision,0,1,len(decision)*3)
        val_norm, _ = gen_kde(norm,0,1,len(decision)*3)

        # Get the rsquared value
        r2 = val_data**2 + val_norm**2

        val_data = val_data/np.max(val_data)
        val_norm = val_norm/np.max(val_norm)

        # Find the first derivatives of the decision and norm kdes
        # with respect to the decision scores
        deriv_data = np.gradient(val_data, dat_range[1]-dat_range[0])
        deriv_norm = np.gradient(val_norm, dat_range[1]-dat_range[0])

        # Compute intergrand
        integrand = dtheta(val_data,val_norm,deriv_data,deriv_norm,r2)

        # Intergrate to find winding numbers mean intersection point
        limit = integrate.simpson(integrand)/np.sum((val_data+val_norm)/2)

        self.thresh_ = limit 
          
        return cut(decision, limit)
