"""Compute intensity features in labeled image."""
import warnings

import numpy as np


def compute_intensity_features(
        im_label, im_intensity, num_hist_bins=10,
        rprops=None, feature_list=None):
    """Calculate intensity features from an intensity image.

    Parameters
    ----------
    im_label : array_like
        A labeled mask image wherein intensity of a pixel is the ID of the
        object it belongs to. Non-zero values are considered to be foreground
        objects.

    im_intensity : array_like
        Intensity image.

    num_hist_bins: int, optional
        Number of bins used to computed the intensity histogram of an object.
        Histogram is used to energy and entropy features. Default is 10.

    rprops : output of skimage.measure.regionprops, optional
        rprops = skimage.measure.regionprops( im_label ). If rprops is not
        passed then it will be computed inside which will increase the
        computation time.

    feature_list : list, default is None
        list of intensity features to return.
        If none, all intensity features are returned.

    Returns
    -------
    fdata: pandas.DataFrame
        A pandas dataframe containing the intensity features listed below for
        each object/label.

    Notes
    -----
    List of intensity features computed by this function:

    Intensity.Min : float
        Minimum intensity of object pixels.

    Intensity.Max : float
        Maximum intensity of object pixels.

    Intensity.Mean : float
        Mean intensity of object pixels

    Intensity.Median : float
        Median intensity of object pixels

    Intensity.MeanMedianDiff : float
        Difference between mean and median intensities of object pixels.

    Intensity.Std : float
        Standard deviation of the intensities of object pixels

    Intensity.IQR: float
        Inter-quartile range of the intensities of object pixels

    Intensity.MAD: float
        Median absolute deviation of the intensities of object pixels

    Intensity.Skewness : float
        Skewness of the intensities of object pixels. Value is 0 when all
        intensity values are equal.

    Intensity.Kurtosis : float
        Kurtosis of the intensities of object pixels. Value is -3 when all
        values are equal.

    Intensity.HistEnergy : float
        Energy of the intensity histogram of object pixels

    Intensity.HistEntropy : float
        Entropy of the intensity histogram of object pixels.

    References
    ----------
    .. [#] Daniel Zwillinger and Stephen Kokoska. "CRC standard probability
       and statistics tables and formulae," Crc Press, 1999.

    """
    import pandas as pd
    import scipy.stats
    from skimage.measure import regionprops

    default_feature_list = [
        'Intensity.Min',
        'Intensity.Max',
        'Intensity.Mean',
        'Intensity.Median',
        'Intensity.MeanMedianDiff',
        'Intensity.Std',
        'Intensity.IQR',
        'Intensity.MAD',
        'Intensity.Skewness',
        'Intensity.Kurtosis',
        'Intensity.HistEnergy',
        'Intensity.HistEntropy',
    ]

    # List of feature names
    if feature_list is None:
        feature_list = default_feature_list
    else:
        assert all(j in default_feature_list for j in feature_list), \
            'Some feature names are not recognized.'

    # compute object properties if not provided
    if rprops is None:
        rprops = regionprops(im_label)

    # create pandas data frame containing the features for each object
    numFeatures = len(feature_list)
    numLabels = len(rprops)
    fdata = pd.DataFrame(np.zeros((numLabels, numFeatures)),
                         columns=feature_list)

    # conditionally execute calculations if x in the features list
    def _conditional_execution(feature, func, *args, **kwargs):
        if feature in feature_list:
            with warnings.catch_warnings():
                warnings.simplefilter('ignore', RuntimeWarning)
                fdata.at[i, feature] = func(*args, **kwargs)

    def _return_input(x):
        return x

    for i in range(numLabels):
        if rprops[i] is None:
            continue

        # get intensities of object pixels
        pixelIntensities = np.sort(
            im_intensity[rprops[i].coords[:, 0], rprops[i].coords[:, 1]],
        )

        # simple descriptors
        meanIntensity = np.mean(pixelIntensities)
        medianIntensity = np.median(pixelIntensities)
        _conditional_execution('Intensity.Min', np.min, pixelIntensities)
        _conditional_execution('Intensity.Max', np.max, pixelIntensities)
        _conditional_execution('Intensity.Mean', _return_input, meanIntensity)
        _conditional_execution(
            'Intensity.Median', _return_input, medianIntensity)
        _conditional_execution(
            'Intensity.MeanMedianDiff', _return_input,
            meanIntensity - medianIntensity)
        _conditional_execution('Intensity.Std', np.std, pixelIntensities)
        _conditional_execution(
            'Intensity.Skewness', scipy.stats.skew, pixelIntensities)
        _conditional_execution(
            'Intensity.Kurtosis', scipy.stats.kurtosis, pixelIntensities)

        # inter-quartile range
        _conditional_execution(
            'Intensity.IQR', scipy.stats.iqr, pixelIntensities)

        # median absolute deviation
        _conditional_execution(
            'Intensity.MAD', np.median,
            np.abs(pixelIntensities - medianIntensity))

        # histogram-based features
        if any(j in feature_list for j in [
                'Intensity.HistEntropy', 'Intensity.HistEnergy']):

            # compute intensity histogram
            hist, bins = np.histogram(pixelIntensities, bins=num_hist_bins)
            prob = hist / np.sum(hist, dtype=np.float32)

            # entropy and energy
            _conditional_execution(
                'Intensity.HistEntropy', scipy.stats.entropy, prob)
            _conditional_execution('Intensity.HistEnergy', np.sum, prob**2)

    return fdata
