import numpy
import time



class SimulatedCamera():
    """
    Camera simulation for debugging purposes.
    """

    def __init__(self, camera_config = None, size_x=1280, size_y=960, number_of_dead_pixels=100, noise=0.1,
                 beam_size_x=100, beam_size_y=20):
        """
        Initialize the camera simulation.
        :param size_x: Image width.
        :param size_y: Image height.
        :param number_of_dead_pixels: Number of simulated dead pixels.
        :param noise: How much noise to introduce.
        :param beam_size_x: Beam width.
        :param beam_size_y: Beam height.
        """
        self.camera_config = camera_config
        self.channel_image = None
        self.width_raw = size_x
        self.height_raw = size_y
        self.noise = noise  # double {0,1} noise amplification factor
        self.dead_pixels = self._generate_dead_pixels(number_of_dead_pixels)
        self.beam_size_x = beam_size_x
        self.beam_size_y = beam_size_y

    def _generate_dead_pixels(self, number_of_dead_pixel):
        dead_pixels = numpy.zeros((self.height_raw, self.width_raw))

        for _ in range(number_of_dead_pixel):
            x = numpy.random.randint(0, self.height_raw)
            y = numpy.random.randint(0, self.width_raw)
            dead_pixels[x, y] = 1

        return dead_pixels

    def get_image(self, raw=False):
        """
        Get the simulated image.
        :param raw: If true, return a simulated camera wihtout the beam (just noise).
        :return: Camera image.
        """

        if raw:
            image = numpy.zeros((self.size_y, self.size_x))
        else:
            beam_x = numpy.linspace(-self.beam_size_x + numpy.random.rand(),
                                    self.beam_size_x + numpy.random.rand(),
                                    self.height_raw)
            beam_y = numpy.linspace(-self.beam_size_y + numpy.random.rand(),
                                    self.beam_size_y + numpy.random.rand(),
                                    self.width_raw)
            x, y = numpy.meshgrid(beam_y, beam_x)
            image = numpy.exp(-(x ** 2 + y ** 2))

        # Add some noise
        if self.noise:
            image += numpy.random.random((self.height_raw, self.width_raw)) * self.noise

        # Add dead pixels
        image += self.dead_pixels

        image.clip(0, 0.9, out=image)
        image *= (numpy.power(2, 16) - 1)

        return self._get_image(image, raw=raw)

    def _get_image(self, value, raw=False):

        if value is None:
            return None

        # Convert type - we are using f because of better performance
        # floats (32bit-ones) are way faster to calculate than 16 bit ints, actually even faster than native
        # int type (32/64uint) since we can leverage SIMD instructions (SSE/SSE2 on Intels).
        value = value.astype('u2').astype(numpy.float32)

        # Shape image
        value = value[:(self.width_raw * self.height_raw)].reshape((self.height_raw, self.width_raw))

        # Return raw image without any corrections
        if raw:
            return value

        if self.camera_config is not None:
            # Correct image
            if self.camera_config.parameters["mirror_x"]:
                value = numpy.fliplr(value)

            if self.camera_config.parameters["mirror_y"]:
                value = numpy.flipud(value)
            value = numpy.rot90(value, self.camera_config.parameters["rotate"])
        
        return numpy.ascontiguousarray(value)



simulated_camera = None
def new_simulated_camera( camera_config = None, size_x=1280, size_y=960, number_of_dead_pixels=100, noise=0.1, beam_size_x=100, beam_size_y=20):
    global simulated_camera
    simulated_camera =  SimulatedCamera(camera_config, size_x, size_y, number_of_dead_pixels, noise,beam_size_x, beam_size_y)
    return simulated_camera


def get_image(raw=False):
    return simulated_camera.get_image(raw)
    
