import torch
import numpy as np

from PIL import Image
from diffusers import DiffusionPipeline


class FrameConditionalDDPMPipeline(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, cond, batch_size=1, generator=None, num_inference_steps=100, save_round=0):
        # 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)
        temp_li = []
        for i, t in enumerate(self.scheduler.timesteps):
            # 1. predict noise model_output
            model_output = self.unet(image, t, cond)
            # 2. compute previous image: x_t -> x_t-1
            image = self.scheduler.step(model_output, t, image).prev_sample
            if save_round > 0 and (i % save_round) == 0:
                tmp_image = (image.detach().permute(0, 2, 3, 1) / 2 + 0.5).clamp(0, 1) * 255
                tmp_image = tmp_image.cpu().numpy()
                for j in range(tmp_image.shape[0]):
                    tmp_ij = Image.fromarray(tmp_image[j].astype(np.uint8), mode='RGB')
                    tmp_ij.save(f'{i}_{j}.png')

        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])]
