import numpy as np
from view.python_core.misc import class_mixer
from view.python_core.flags import FlagsManager


class CalculatorDefault(object):

    def __init__(self):

        super().__init__()

    def subsetter(self, data):

        return data

    def get_min(self, data):

        subsetted_data = self.subsetter(data)
        return self.min(subsetted_data)

    def get_max(self, data):

        subsetted_data = self.subsetter(data)
        return self.max(subsetted_data)

    def min(self, data):

        pass

    def max(self, data):

        pass

    def get_data_limit(self, data):

        return self.get_min(data), self.get_max(data)


class CalculatorFixedMin(CalculatorDefault):

    def __init__(self, SO_MV_scalemin, **kwargs):

        super().__init__(**kwargs)
        self.SO_MV_scalemin = SO_MV_scalemin

    def min(self, data):

        return self.SO_MV_scalemin


class CalculatorFixedMax(CalculatorDefault):

    def __init__(self, SO_MV_scalemax, **kwargs):

        super().__init__(**kwargs)
        self.SO_MV_scalemax = SO_MV_scalemax

    def max(self, data):

        return self.SO_MV_scalemax


class CalculatorNormalMax(CalculatorDefault):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def max(self, data):

        return data.max()


class CalculatorNormalMin(CalculatorDefault):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def min(self, data):
        return data.min()


class CalculatorPercentileMax(CalculatorDefault):

    def __init__(self, percentile_value_from_top, **kwargs):
        super().__init__(**kwargs)
        self.percentile_value = percentile_value_from_top

    def max(self, data):
        # method was set to 'nearest' because of numpy bug: https://github.com/numpy/numpy/issues/21524
        return np.percentile(data, 100 - self.percentile_value, method='nearest')


class CalculatorPercentileMin(CalculatorDefault):

    def __init__(self, percentile_value_from_bottom, **kwargs):
        super().__init__(**kwargs)
        self.percentile_value = percentile_value_from_bottom

    def min(self, data):
        # method was set to 'nearest' because of numpy bug: https://github.com/numpy/numpy/issues/21524
        return np.percentile(data, self.percentile_value, method='nearest')


class SquareSubsetter2D(CalculatorDefault):

    def __init__(self, fractional_margin, **kwargs):

        super().__init__(**kwargs)
        self.fractional_margin = fractional_margin

    def subsetter(self, data):

        xborder = int(data.shape[0] * self.fractional_margin)
        yborder = int(data.shape[1] * self.fractional_margin)
        return data[xborder: data.shape[0] - xborder, yborder: data.shape[1] - yborder]


class SquareSubsetter3D(CalculatorDefault):

    def __init__(self, fractional_margin, **kwargs):

        super().__init__(**kwargs)
        self.fractional_margin = fractional_margin

    def subsetter(self, data):

        xborder = int(data.shape[0] * self.fractional_margin)
        yborder = int(data.shape[1] * self.fractional_margin)
        return data[xborder: data.shape[0] - xborder, yborder: data.shape[1] - yborder, :]


class AreaSubsetter2D(CalculatorDefault):

    def __init__(self, frame_mask, **kwargs):

        super().__init__(**kwargs)
        self.frame_mask = frame_mask

    def subsetter(self, data):

        return np.ma.MaskedArray(data=data, mask=~self.frame_mask)


class AreaSubsetter3D(CalculatorDefault):

    def __init__(self, frame_mask, **kwargs):

        super().__init__(**kwargs)
        self.frame_mask = frame_mask

    def subsetter(self, data):

        mask_3D = np.stack([self.frame_mask] * data.shape[-1], axis=2)
        return np.ma.MaskedArray(data=data, mask=~mask_3D)


def get_data_limit_decider_3D(flags: FlagsManager, frame_mask):

    min_max_decider = flags["mv_individualScale"] % 10

    if flags["mv_percentileScale"] == 1:
        min_class, max_class = CalculatorPercentileMin, CalculatorPercentileMax
        min_kwargs, max_kwargs = [{"percentile_value_from_bottom": flags["mv_percentileValue"]},
                                  {"percentile_value_from_top": flags["mv_percentileValue"]}]
    else:
        min_class, max_class = CalculatorNormalMin, CalculatorNormalMax
        min_kwargs, max_kwargs = [{}, {}]

    if min_max_decider in [0, 1]:

        mixed_class = class_mixer(CalculatorFixedMin, CalculatorFixedMax)
        return mixed_class(SO_MV_scalemin=flags["SO_MV_scalemin"], SO_MV_scalemax=flags["SO_MV_scalemax"])

    elif min_max_decider == 2:

        mixed_class = class_mixer(min_class, max_class)
        return mixed_class(**min_kwargs, **max_kwargs)

    elif min_max_decider == 3:

        mixed_class = class_mixer(SquareSubsetter3D, min_class, max_class)
        return mixed_class(fractional_margin=flags["mv_indiScale3factor"], **min_kwargs, **max_kwargs)

    elif min_max_decider == 4:

        mixed_class = class_mixer(CalculatorFixedMin, max_class)
        return mixed_class(SO_MV_scalemin=flags["SO_MV_scalemin"], **max_kwargs)

    elif min_max_decider == 5:

        mixed_class = class_mixer(AreaSubsetter3D, min_class, max_class)
        return mixed_class(frame_mask=frame_mask, **min_kwargs, **max_kwargs)

    elif min_max_decider == 6:

        mixed_class = class_mixer(AreaSubsetter3D, CalculatorFixedMin, max_class)
        return mixed_class(frame_mask=frame_mask, SO_MV_scalemin=flags["SO_MV_scalemin"], **max_kwargs)

    else:
        raise NotImplementedError


def get_data_limit_decider_2D(flags: FlagsManager, frame_mask):

    min_max_decider = flags["SO_individualScale"] % 10

    if flags["SO_percentileScale"] == 1:
        min_class, max_class = CalculatorPercentileMin, CalculatorPercentileMax
        min_kwargs, max_kwargs = [{"percentile_value_from_bottom": flags["SO_percentileValue"]},
                                  {"percentile_value_from_top": flags["SO_percentileValue"]}]
    else:
        min_class, max_class = CalculatorNormalMin, CalculatorNormalMax
        min_kwargs, max_kwargs = [{}, {}]

    if min_max_decider in [0, 1]:

        mixed_class = class_mixer(CalculatorFixedMin, CalculatorFixedMax)
        return mixed_class(SO_MV_scalemin=flags["SO_MV_scalemin"], SO_MV_scalemax=flags["SO_MV_scalemax"])

    elif min_max_decider == 2:

        mixed_class = class_mixer(min_class, max_class)
        return mixed_class(**min_kwargs, **max_kwargs)

    elif min_max_decider == 3:

        mixed_class = class_mixer(SquareSubsetter2D, min_class, max_class)
        return mixed_class(fractional_margin=flags["SO_indiScale3factor"], **min_kwargs, **max_kwargs)

    elif min_max_decider == 4:

        mixed_class = class_mixer(CalculatorFixedMin, max_class)
        return mixed_class(SO_MV_scalemin=flags["SO_MV_scalemin"], **max_kwargs)

    elif min_max_decider == 5:

        mixed_class = class_mixer(AreaSubsetter2D, min_class, max_class)
        return mixed_class(frame_mask=frame_mask, **min_kwargs, **max_kwargs)

    elif min_max_decider == 6:

        mixed_class = class_mixer(AreaSubsetter2D, CalculatorFixedMin, max_class)
        return mixed_class(frame_mask=frame_mask, SO_MV_scalemin=flags["SO_MV_scalemin"], **max_kwargs)

    else:
        raise NotImplementedError
