import torch
import numpy as np

from PIL import Image
from diffusers import DiffusionPipeline


class TextConditionalDDPMPipeline(DiffusionPipeline):
    model_cpu_offload_seq = "unet"
    def __init__(self, unet, scheduler):
        super().__init__()
        self.register_modules(unet=unet, scheduler=scheduler)

    @torch.no_grad()
    def __call__(self, encoder_hidden_states, batch_size=1, generator=None, num_inference_steps=100):
        # Sample gaussian noise to begin loop
        if isinstance(self.unet.config.sample_size, int):
            image_shape = (
                batch_size,
                3,
                self.unet.config.sample_size,
                self.unet.config.sample_size,
            )
        else:
            image_shape = (batch_size, 3, *self.unet.config.sample_size)

        image = torch.randn(image_shape, generator=generator, device=self.device)
        # set step values
        self.scheduler.set_timesteps(num_inference_steps)

        for t in self.progress_bar(self.scheduler.timesteps):
            # 1. predict noise model_output
            model_output = self.unet(image, t, encoder_hidden_states).sample
            # 2. compute previous image: x_t -> x_t-1
            image = self.scheduler.step(model_output, t, image).prev_sample

        image = (image.detach().permute(0, 2, 3, 1) / 2 + 0.5).clamp(0, 1) * 255
        image = image.cpu().numpy()
        return [Image.fromarray(image[i].astype(np.uint8), mode='RGB') for i in range(image.shape[0])]
