import gymnasium as gym
import numpy as np
import os
from pathlib import Path
import unittest

from ray.rllib.models.preprocessors import GenericPixelPreprocessor
from ray.rllib.models.torch.modules.convtranspose2d_stack import ConvTranspose2DStack
from ray.rllib.utils.framework import try_import_torch, try_import_tf
from ray.rllib.utils.images import imread

torch, nn = try_import_torch()
tf1, tf, tfv = try_import_tf()


class TestConvTranspose2DStack(unittest.TestCase):
    """Tests our ConvTranspose2D Stack modules/layers."""

    def test_convtranspose2d_stack(self):
        """Tests, whether the conv2d stack can be trained to predict an image."""
        batch_size = 128
        input_size = 1
        module = ConvTranspose2DStack(input_size=input_size)
        preprocessor = GenericPixelPreprocessor(
            gym.spaces.Box(0, 255, (64, 64, 3), np.uint8), options={"dim": 64}
        )
        optim = torch.optim.Adam(module.parameters(), lr=0.0001)

        rllib_dir = Path(__file__).parent.parent.parent
        img_file = os.path.join(rllib_dir, "tests/data/images/obstacle_tower.png")
        img = imread(img_file)
        # Preprocess.
        img = preprocessor.transform(img)
        # Make channels first.
        img = np.transpose(img, (2, 0, 1))
        # Add batch rank and repeat.
        imgs = np.reshape(img, (1,) + img.shape)
        imgs = np.repeat(imgs, batch_size, axis=0)
        # Move to torch.
        imgs = torch.from_numpy(imgs)
        init_loss = loss = None
        for _ in range(10):
            # Random inputs.
            inputs = torch.from_numpy(
                np.random.normal(0.0, 1.0, (batch_size, input_size))
            ).float()
            distribution = module(inputs)
            # Construct a loss.
            loss = -torch.mean(distribution.log_prob(imgs))
            if init_loss is None:
                init_loss = loss
            print("loss={}".format(loss))
            # Minimize loss.
            loss.backward()
            optim.step()
        self.assertLess(loss, init_loss)


if __name__ == "__main__":
    import pytest
    import sys

    sys.exit(pytest.main(["-v", __file__]))
