import torch


def generate_noise(max_norm, parameter, noise_multiplier, noise_type, device):
    """
    A noise generation function that can utilize different distributions for noise generation.

    @param max_norm
        The maximum norm of the per-sample gradients. Any gradient with norm
        higher than this will be clipped to this value.
    @param parameter
        The parameter, based on which the dimension of the noise tensor
        will be determined
    @param noise_multiplier
        The ratio of the standard deviation of the Gaussian noise to
        the L2-sensitivity of the function to which the noise is added
    @param noise_type
        Sets the distribution for the noise generation.
        See generate_noise for supported strings.
    @param device
        The device used for calculations and needed for tensor definition.

    @return
        a tensor of noise in the same shape as ``parameter``.
    """
    if noise_multiplier > 0:
        mean = 0
        scale_scalar = noise_multiplier * max_norm

        scale = torch.full(size=parameter.shape, fill_value=scale_scalar, dtype=torch.float32, device=device)

        if noise_type.lower() in ["normal", "gauss", "gaussian"]:
            dist = torch.distributions.normal.Normal(mean, scale)
        elif noise_type.lower() in ["laplace", "laplacian"]:
            dist = torch.distributions.laplace.Laplace(mean, scale)
        elif noise_type.lower() in ["exponential"]:
            rate = 1 / scale
            dist = torch.distributions.exponential.Exponential(rate)
        else:
            dist = torch.distributions.normal.Normal(mean, scale)

        noise = dist.sample()

        return noise
    return 0.0