class ModelModifier:
    def modify(self, prior_model_definition: dict):
        """
        modifies parameters in place

        Parameters
        ----------
        prior_model_definition: dict
            this is the parameters dict as from the prior
        """
        pass

    def parameters(self):
        return {}


class DictModifier(ModelModifier):
    def __init__(self, modification):
        self.modification = modification

    def parameters(self):
        return self.modification

    def modify(self, prior_model_definition: dict):
        self.recursive_modify(prior_model_definition, self.modification)

    @staticmethod
    def recursive_modify(original: dict, modification: dict):
        for key, value in modification.items():
            if not isinstance(original[key], dict):
                original[key] = value
            else:
                DictModifier.recursive_modify(original[key], value)


class NoiseModifier(ModelModifier):
    def __init__(self, noise, noise_key):
        self.noise = noise
        self.noise_key = noise_key

    def modify(self, prior_model_definition: dict):
        prior_model_definition['noise']['parameters'][self.noise_key] = self.noise

    def parameters(self):
        return {"noise": self.noise}


class UniformNoiseSetter(ModelModifier):
    def __init__(self, noise_center, noise_range):
        assert 0 < noise_center - noise_range / 2 < noise_center + noise_range / 2 < 1, "Noise not in [0, 1] anymore"
        self.noise_center = noise_center
        self.noise_range = noise_range

    def modify(self, prior_model_definition: dict):
        prior_model_definition['noise']['parameters']['fraction_remove_and_add'] = {
          "type": "uniform",
          "args": [
              self.noise_center - self.noise_range/2,
              self.noise_range
          ]
        }

    def parameters(self):
        return {"noise_center": self.noise_center,
                "noise_range": self.noise_range}


class SubsamplingModifier(ModelModifier):
    def __init__(self, subsampling, subsampling_key):
        self.subsampling = subsampling
        self.subsampling_key = subsampling_key

    def modify(self, prior_model_definition: dict):
        if self.subsampling_key is not None:
            prior_model_definition['noise']['parameters'][self.subsampling_key] = self.subsampling

    def parameters(self):
        if self.subsampling_key is None:
            return {}
        return {"subsampling": self.subsampling}


class AggregateModifier(ModelModifier):
    def __init__(self, modifiers):
        self.modifiers = list(modifiers)

    def __getstate__(self):
        return self.modifiers

    def __setstate__(self, state):
        self.modifiers = state

    def modify(self, prior_model_definition: dict):
        for modifier in self.modifiers:
            modifier.modify(prior_model_definition)

    def parameters(self):
        par = {}
        for modifier in self.modifiers:
            par.update(modifier.parameters())
        return par

    def __getattr__(self, item):
        for modifier in self.modifiers:
            try:
                return getattr(modifier, item)
            except AttributeError:
                pass
        raise AttributeError(item)


def NoiseSubsamplingModifier(noise, subsampling, noise_key, subsampling_key):
    """
    Convenience for modifying noise and subsampling

    Returns
    -------

    AggregateModifier
    """
    return AggregateModifier([NoiseModifier(noise, noise_key), SubsamplingModifier(subsampling, subsampling_key)])