from .pixelwise import bleach_correct_pixelwise
from ...idl_translation_core.bleach_correction import fitlogdecay, get_bleach_weights
import numpy as np
import platform
import multiprocessing as mp


class NoBleachCompensator(object):

    def __init__(self):

        super().__init__()

    def apply(self, stack_xyt: np.ndarray, area_mask: np.ndarray):
        """
        Apply bleach correction to the movie `stack_xyt`
        :param numpy.ndarray stack_xyt: 3D, format XYT
        :param numpy.ndarray area: 2D, formay XY
        :return: bleach corrected movie
        :rtype: numpy.ndarray, same shape and format as `stack_xyt`
        """

        return stack_xyt, None


class BaseBleachCompensator(NoBleachCompensator):

    def __init__(self, flags, p1_metadata, movie_size):
        """
        :param FlagsManager flags:
        :param pandas.Series p1_metadata: experimental metadata
        :param tuple movie_size: raw data size, format XYT
        :return: an object that can be used to apply bleach compensation
        """

        super().__init__()

        self.weights = get_bleach_weights(
            flags=flags, p1_metadata=p1_metadata, movie_size=movie_size,
            exclude_stimulus=flags["LE_BleachExcludeStimulus"])


class PixelWiseBleachCompensatorParallel(BaseBleachCompensator):

    def __init__(self, flags, p1_metadata, movie_size):
        """
        :param FlagsManager flags:
        :param pandas.Series p1_metadata: experimental metadata
        :return: an object that can be used to apply bleach compensation
        """

        super().__init__(flags, p1_metadata, movie_size)
        self.ncpu = mp.cpu_count()

    def apply(self, stack_xyt: np.ndarray, area_mask: np.ndarray):
        """
        Apply bleach correction to the movie `stack_xyt`
        :param numpy.ndarray stack_xyt: 3D, format XYT
        :param numpy.ndarray area_mask: 2D, formay XY
        :return: bleach corrected movie
        :rtype: numpy.ndarray, same shape and format as `stack_xyt`
        """

        return bleach_correct_pixelwise(
            movie=stack_xyt, weights=self.weights, area=area_mask, ncpu=self.ncpu)


class PixelWiseBleachCompensator1CPU(PixelWiseBleachCompensatorParallel):

    def __init__(self, flags, p1_metadata, movie_size):
        """
        :param FlagsManager flags:
        :param pandas.Series p1_metadata: experimental metadata
        :return: an object that can be used to apply bleach compensation
        """

        super().__init__(flags, p1_metadata, movie_size)
        self.ncpu = 1


class UniformBleachCompensator(BaseBleachCompensator):

    def __init__(self, flags, p1_metadata, movie_size):

        super().__init__(flags, p1_metadata, movie_size)
        self.background_frames = p1_metadata.background_frames
        self.show_results = not flags["VIEW_batchmode"]
        self.measurement_label = p1_metadata['ex_name']

    def apply(self, stack_xyt: np.ndarray, area_mask: np.ndarray):

        # converting data temporarily to txy format as it is easier to divide the movie by
        # a frame in this format
        stack_txy = np.moveaxis(stack_xyt, source=-1, destination=0)

        # take average over background frames for all pixels
        F0_frame = stack_xyt[:, :, self.background_frames[0]: self.background_frames[1] + 1].mean(axis=2)

        # divide each frame by F0_frame (background frame)
        F_by_F0_txy = stack_txy / F0_frame

        # apply area_mask
        F_by_F0_txy_masked = F_by_F0_txy * area_mask.astype(int)

        # average each frame to make a curve
        # (some pixels in F0_frame might have values of 0, so F_by_F0_txy might have nans)
        curve = np.nanmean(F_by_F0_txy_masked, axis=(1, 2))

        # apply bleach correction to curve and return the parameters A, K and C
        fitted_curve, (A, K, C) = fitlogdecay(lineIn=curve, weights=self.weights, showresults=self.show_results, measurement_label=self.measurement_label)

        # converting to xyt as it is easier to subtract a trace from all pixels in this format
        F_by_F0_xyt = np.moveaxis(F_by_F0_txy, source=0, destination=-1)

        # the third component ensures that the average intensity of each pixel is not
        # affected by the bleach correction applied
        corrected_F_by_F0_xyt = F_by_F0_xyt - fitted_curve + fitted_curve.mean()

        # converting to txy as it easier to multiply by a frame in this format
        corrected_F_by_F0_txy = np.moveaxis(corrected_F_by_F0_xyt, source=-1, destination=0)

        corrected_raw_txy = corrected_F_by_F0_txy * F0_frame

        # converting back to our stadard format xyt
        raw_corrected = np.moveaxis(corrected_raw_txy, source=0, destination=-1)

        return raw_corrected, (A, K, C)


def get_bleach_compensator(flags, p1_metadata, movie_size):
    """
    Get an object whose "apply" method can apply bleach compensation to a movie
    :param FlagsManager flags:
    :param pandas.Series p1_metadata: experimental metadata
    :return: an object that can be used to apply bleach compensation
    """

    if flags["LE_BleachCorrMethod"] == "None":

        return NoBleachCompensator()

    if flags["LE_BleachCorrMethod"] == "log_pixelwise_1cpu":

        return PixelWiseBleachCompensator1CPU(flags, p1_metadata, movie_size)

    if flags["LE_BleachCorrMethod"] == "log_pixelwise_parallel":

        return PixelWiseBleachCompensatorParallel(flags, p1_metadata, movie_size)

    elif flags["LE_BleachCorrMethod"] == "log_uniform":

        return UniformBleachCompensator(flags, p1_metadata, movie_size)
    else:
        raise NotImplementedError























