import numpy
import scipy.stats


# Sensor class represents a sensor connected to the virtual device.
class Sensor:
    def __init__(self, sensor_id, name, sensor_type, minimum, maximum, sampling_rate, distribution):
        self._id = sensor_id
        self._name = name
        self._type = sensor_type
        self._minimum = minimum
        self._maximum = maximum
        self._sampling_rate = sampling_rate
        self._distribution = distribution
        self._status = False
        self._value = 0

    # Overload of the print of a sensor object.
    def __str__(self):
        return "" + str(self._id) + " " + self._name + " " + str(self._status)

    # ID getter.
    @property
    def id(self):
        return self._id

    # Name getter.
    @property
    def name(self):
        return self._name

    # Name setter.
    @name.setter
    def name(self, name):
        self._name = name

    # Type getter.
    @property
    def type(self):
        return self._type

    # Type setter.
    @type.setter
    def type(self, type):
        self._type = type

    # Minimum getter.
    @property
    def minimum(self):
        return self._minimum

    # Minimum setter.
    @minimum.setter
    def minimum(self, minimum):
        self._minimum = minimum

    # Maximum getter.
    @property
    def maximum(self):
        return self._maximum

    # Maximum setter.
    @maximum.setter
    def maximum(self, maximum):
        self._maximum = maximum

    # Sampling rate getter.
    @property
    def sampling_rate(self):
        return self._sampling_rate

    # Sampling rate setter.
    @sampling_rate.setter
    def sampling_rate(self, sampling_rate):
        self._sampling_rate = sampling_rate

    # Distribution getter.
    @property
    def distribution(self):
        return self._distribution

    # Distribution setter.
    @distribution.setter
    def distribution(self, distribution):
        self._distribution = distribution

    # Value getter.
    @property
    def value(self):
        return self._value

    # Function for generate a random integer value following a normal distribution.
    def normalDistributionInteger(self):
        x = numpy.arange(self._minimum, self._maximum + 1)
        sigma = numpy.std(x)
        xU, xL = x + 0.5, x - 0.5
        prob = scipy.stats.norm.cdf(xU, scale=3*sigma) - scipy.stats.norm.cdf(xL, scale=3*sigma)
        prob = prob / prob.sum()  # normalize the probabilities so their sum is 1
        self._value = numpy.random.choice(x, p=prob)

    # Function for generate a float integer value following a normal distribution.
    def normalDistributionFloat(self):
        x = numpy.linspace(round(self._minimum, 2), round(self._maximum + 1, 2))
        sigma = numpy.std(x)
        xU, xL = x + 0.50, x - 0.50
        prob = scipy.stats.norm.cdf(xU, scale=3*sigma) - scipy.stats.norm.cdf(xL, scale=3*sigma)
        prob = prob / prob.sum()  # normalize the probabilities so their sum is 1
        self._value = round(numpy.random.choice(x, p=prob), 2)

    # Function for generate a random integer value following a uniform distribution.
    def uniformDistributionInteger(self):
        self._value = numpy.random.randint(self._minimum, self._maximum + 1)

    # Function for generate a random float value following a normal distribution.
    def uniformDistributionFloat(self):
        x = numpy.random.uniform(self.minimum, self._maximum + 1)
        # Round the generated number with 2 decimals.
        self._value = round(x, 2)

    # Activate the sensor.
    def activate(self):
        self._status = True

    # Deactivate the sensor.
    def deactivate(self):
        self._status = False

    # Return the status of the sensor.
    def isActive(self):
        return self._status

    # Assign a random value to the value attribute depending from the distribution and type.
    def randomValue(self):
        if self._status:
                if (self._distribution == 'normal') and (self._type == 'integer'):
                    self.normalDistributionInteger()
                elif (self._distribution == 'normal') and (self._type == 'float'):
                    self.normalDistributionFloat()
                elif (self._distribution == 'uniform') and (self._type == 'integer'):
                    self.uniformDistributionInteger()
                elif (self._distribution == 'uniform') and (self._type == 'float'):
                    self.uniformDistributionFloat()
        else:
            print("Device", self._id, "is not active, please activate it before")
