# Copyright (c) OpenMMLab. All rights reserved.
from copy import deepcopy

import pytest
import torch

from mmgen.models.architectures.stylegan.generator_discriminator_v2 import (
    ADAStyleGAN2Discriminator, StyleGAN2Discriminator, StyleGANv2Generator)
from mmgen.models.architectures.stylegan.modules import (Blur,
                                                         ModulatedStyleConv,
                                                         ModulatedToRGB)
from mmgen.models.architectures.stylegan.mspie import (
    MSStyleGAN2Discriminator, MSStyleGANv2Generator)


class TestBlur:

    @classmethod
    def setup_class(cls):
        cls.kernel = [1, 3, 3, 1]
        cls.pad = (1, 1)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_blur_cuda(self):
        blur = Blur(self.kernel, self.pad)
        x = torch.randn((2, 3, 8, 8))
        res = blur(x)

        assert res.shape == (2, 3, 7, 7)


class TestModStyleConv:

    @classmethod
    def setup_class(cls):
        cls.default_cfg = dict(
            in_channels=3,
            out_channels=1,
            kernel_size=3,
            style_channels=5,
            upsample=True)

    def test_mod_styleconv_cpu(self):
        conv = ModulatedStyleConv(**self.default_cfg)
        input_x = torch.randn((2, 3, 4, 4))
        input_style = torch.randn((2, 5))

        res = conv(input_x, input_style)
        assert res.shape == (2, 1, 8, 8)

        _cfg = deepcopy(self.default_cfg)
        _cfg['upsample'] = False
        conv = ModulatedStyleConv(**_cfg)
        input_x = torch.randn((2, 3, 4, 4))
        input_style = torch.randn((2, 5))

        res = conv(input_x, input_style)
        assert res.shape == (2, 1, 4, 4)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_mod_styleconv_cuda(self):
        conv = ModulatedStyleConv(**self.default_cfg).cuda()
        input_x = torch.randn((2, 3, 4, 4)).cuda()
        input_style = torch.randn((2, 5)).cuda()

        res = conv(input_x, input_style)
        assert res.shape == (2, 1, 8, 8)

        _cfg = deepcopy(self.default_cfg)
        _cfg['upsample'] = False
        conv = ModulatedStyleConv(**_cfg).cuda()
        input_x = torch.randn((2, 3, 4, 4)).cuda()
        input_style = torch.randn((2, 5)).cuda()

        res = conv(input_x, input_style)
        assert res.shape == (2, 1, 4, 4)


class TestToRGB:

    @classmethod
    def setup_class(cls):
        cls.default_cfg = dict(in_channels=5, style_channels=5, out_channels=3)

    def test_torgb_cpu(self):
        model = ModulatedToRGB(**self.default_cfg)
        input_x = torch.randn((2, 5, 4, 4))
        style = torch.randn((2, 5))

        res = model(input_x, style)
        assert res.shape == (2, 3, 4, 4)

        input_x = torch.randn((2, 5, 8, 8))
        style = torch.randn((2, 5))
        skip = torch.randn(2, 3, 4, 4)
        res = model(input_x, style, skip)
        assert res.shape == (2, 3, 8, 8)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_torgb_cuda(self):
        model = ModulatedToRGB(**self.default_cfg).cuda()
        input_x = torch.randn((2, 5, 4, 4)).cuda()
        style = torch.randn((2, 5)).cuda()

        res = model(input_x, style)
        assert res.shape == (2, 3, 4, 4)

        input_x = torch.randn((2, 5, 8, 8)).cuda()
        style = torch.randn((2, 5)).cuda()
        skip = torch.randn(2, 3, 4, 4).cuda()
        res = model(input_x, style, skip)
        assert res.shape == (2, 3, 8, 8)


class TestStyleGAN2Generator:

    @classmethod
    def setup_class(cls):
        cls.default_cfg = dict(
            out_size=64, style_channels=16, num_mlps=4, channel_multiplier=1)

    def test_stylegan2_g_cpu(self):
        # test default config
        g = StyleGANv2Generator(**self.default_cfg)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        truncation_mean = g.get_mean_latent()
        res = g(
            None,
            num_batches=2,
            randomize_noise=False,
            truncation=0.7,
            truncation_latent=truncation_mean)
        assert res.shape == (2, 3, 64, 64)

        res = g.style_mixing(2, 2, truncation_latent=truncation_mean)
        assert res.shape[2] == 64

        random_noise = g.make_injected_noise()
        res = g(
            None,
            num_batches=1,
            injected_noise=random_noise,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        random_noise = g.make_injected_noise()
        res = g(
            None, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        styles = [torch.randn((1, 16)) for _ in range(2)]
        res = g(
            styles, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        res = g(
            torch.randn,
            num_batches=1,
            injected_noise=None,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        g.eval()
        assert g.default_style_mode == 'single'

        g.train()
        assert g.default_style_mode == 'mix'

        with pytest.raises(AssertionError):
            styles = [torch.randn((1, 6)) for _ in range(2)]
            _ = g(styles, injected_noise=None, randomize_noise=False)

        cfg_ = deepcopy(self.default_cfg)
        cfg_['out_size'] = 256
        g = StyleGANv2Generator(**cfg_)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 256, 256)

        # set mix_prob as 1.0 and 0 to force cover lines
        cfg_ = deepcopy(self.default_cfg)
        cfg_['mix_prob'] = 1
        g = StyleGANv2Generator(**cfg_)
        res = g(torch.randn, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        cfg_ = deepcopy(self.default_cfg)
        cfg_['mix_prob'] = 1
        g = StyleGANv2Generator(**cfg_)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        cfg_ = deepcopy(self.default_cfg)
        cfg_['mix_prob'] = 0
        g = StyleGANv2Generator(**cfg_)
        res = g(torch.randn, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        cfg_ = deepcopy(self.default_cfg)
        cfg_['mix_prob'] = 0
        g = StyleGANv2Generator(**cfg_)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_fp16_stylegan2_G_cuda(self):

        g = StyleGANv2Generator(**self.default_cfg, num_fp16_scales=2).cuda()
        res = g(None, num_batches=2)
        assert res.dtype == torch.float32

        g = StyleGANv2Generator(**self.default_cfg, fp16_enabled=True).cuda()
        res = g(None, num_batches=2)
        assert res.dtype == torch.float32

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_g_cuda(self):
        # test default config
        g = StyleGANv2Generator(**self.default_cfg).cuda()
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        random_noise = g.make_injected_noise()
        res = g(
            None,
            num_batches=1,
            injected_noise=random_noise,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        random_noise = g.make_injected_noise()
        res = g(
            None, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        styles = [torch.randn((1, 16)).cuda() for _ in range(2)]
        res = g(
            styles, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        res = g(
            torch.randn,
            num_batches=1,
            injected_noise=None,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        g.eval()
        assert g.default_style_mode == 'single'

        g.train()
        assert g.default_style_mode == 'mix'

        with pytest.raises(AssertionError):
            styles = [torch.randn((1, 6)).cuda() for _ in range(2)]
            _ = g(styles, injected_noise=None, randomize_noise=False)

        cfg_ = deepcopy(self.default_cfg)
        cfg_['out_size'] = 256
        g = StyleGANv2Generator(**cfg_).cuda()
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 256, 256)


class TestMSStyleGAN2Generator:

    @classmethod
    def setup_class(cls):
        cls.default_cfg = dict(
            out_size=64, style_channels=16, num_mlps=4, channel_multiplier=1)

    def test_msstylegan2_g_cpu(self):
        # test default config
        g = MSStyleGANv2Generator(**self.default_cfg)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        random_noise = g.make_injected_noise()
        res = g(
            None,
            num_batches=1,
            injected_noise=random_noise,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        random_noise = g.make_injected_noise()
        res = g(
            None, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        styles = [torch.randn((1, 16)) for _ in range(2)]
        res = g(
            styles, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        res = g(
            torch.randn,
            num_batches=1,
            injected_noise=None,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        g.eval()
        assert g.default_style_mode == 'single'

        g.train()
        assert g.default_style_mode == 'mix'

        with pytest.raises(AssertionError):
            styles = [torch.randn((1, 6)) for _ in range(2)]
            _ = g(styles, injected_noise=None, randomize_noise=False)

        cfg_ = deepcopy(self.default_cfg)
        cfg_['out_size'] = 256
        g = MSStyleGANv2Generator(**cfg_)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 256, 256)

        g = MSStyleGANv2Generator(deconv2conv=True, **self.default_cfg)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        g = MSStyleGANv2Generator(deconv2conv=True, **self.default_cfg)
        truncation_mean = g.get_mean_latent()
        res = g(
            None,
            num_batches=2,
            randomize_noise=False,
            chosen_scale=2,
            truncation=0.7,
            truncation_latent=truncation_mean)
        assert res.shape == (2, 3, 96, 96)

        res = g.style_mixing(2, 2, truncation_latent=truncation_mean)
        assert res.shape[2] == 64

        g = MSStyleGANv2Generator(
            no_pad=True, deconv2conv=True, interp_pad=4, **self.default_cfg)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        g = MSStyleGANv2Generator(
            deconv2conv=True, up_after_conv=True, **self.default_cfg)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        g = MSStyleGANv2Generator(
            deconv2conv=True, up_after_conv=True, **self.default_cfg)
        res = g(None, num_batches=2, chosen_scale=4)
        assert res.shape == (2, 3, 128, 128)

        g = MSStyleGANv2Generator(
            deconv2conv=True, up_after_conv=True, **self.default_cfg)
        res = g(None, num_batches=2, chosen_scale=(4, 4))
        assert res.shape == (2, 3, 128, 128)

        g = MSStyleGANv2Generator(
            head_pos_encoding=dict(
                type='SPE', embedding_dim=256, padding_idx=0, init_size=128),
            **self.default_cfg)
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        g = MSStyleGANv2Generator(
            head_pos_encoding=dict(
                type='SPE', embedding_dim=256, padding_idx=0, init_size=128),
            interp_head=True,
            **self.default_cfg)
        res = g(None, num_batches=1, chosen_scale=(4, 4))
        assert res.shape == (1, 3, 128, 128)

        g = MSStyleGANv2Generator(
            head_pos_encoding=dict(type='CatersianGrid'), **self.default_cfg)
        res = g(None, num_batches=2, chosen_scale=(4, 4))
        assert res.shape == (2, 3, 128, 128)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_ms_g_cuda(self):
        # test default config
        g = MSStyleGANv2Generator(**self.default_cfg).cuda()
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 64, 64)

        random_noise = g.make_injected_noise()
        res = g(
            None,
            num_batches=1,
            injected_noise=random_noise,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        random_noise = g.make_injected_noise()
        res = g(
            None, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        styles = [torch.randn((1, 16)).cuda() for _ in range(2)]
        res = g(
            styles, num_batches=1, injected_noise=None, randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        res = g(
            torch.randn,
            num_batches=1,
            injected_noise=None,
            randomize_noise=False)
        assert res.shape == (1, 3, 64, 64)

        g.eval()
        assert g.default_style_mode == 'single'

        g.train()
        assert g.default_style_mode == 'mix'

        with pytest.raises(AssertionError):
            styles = [torch.randn((1, 6)).cuda() for _ in range(2)]
            _ = g(styles, injected_noise=None, randomize_noise=False)

        cfg_ = deepcopy(self.default_cfg)
        cfg_['out_size'] = 256
        g = MSStyleGANv2Generator(**cfg_).cuda()
        res = g(None, num_batches=2)
        assert res.shape == (2, 3, 256, 256)


class TestStyleGANv2Disc:

    @classmethod
    def setup_class(cls):
        cls.default_cfg = dict(in_size=64, channel_multiplier=1)

    def test_stylegan2_disc_cpu(self):
        d = StyleGAN2Discriminator(**self.default_cfg)
        img = torch.randn((2, 3, 64, 64))
        score = d(img)
        assert score.shape == (2, 1)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_stylegan2_disc_cuda(self):
        d = StyleGAN2Discriminator(**self.default_cfg).cuda()
        img = torch.randn((2, 3, 64, 64)).cuda()
        score = d(img)
        assert score.shape == (2, 1)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_fp16_stylegan2_disc_cuda(self):
        d = StyleGAN2Discriminator(
            **self.default_cfg, num_fp16_scales=2).cuda()
        img = torch.randn((2, 3, 64, 64)).cuda()
        score = d(img)
        assert score.shape == (2, 1)
        assert score.dtype == torch.float32


class TestADAStyleGAN2Discriminator:

    @classmethod
    def setup_class(cls):
        aug_kwargs = {
            'xflip': 1,
            'rotate90': 1,
            'xint': 1,
            'scale': 1,
            'rotate': 1,
            'aniso': 1,
            'xfrac': 1,
            'brightness': 1,
            'contrast': 1,
            'lumaflip': 1,
            'hue': 1,
            'saturation': 1
        }
        cls.default_cfg = dict(
            in_size=64,
            input_bgr2rgb=True,
            data_aug=dict(
                type='ADAAug',
                update_interval=2,
                aug_pipeline=aug_kwargs,
                ada_kimg=100))

    def test_ada_stylegan2_disc_cpu(self):
        d = ADAStyleGAN2Discriminator(**self.default_cfg)
        img = torch.randn((2, 3, 64, 64))
        score = d(img)
        assert score.shape == (2, 1)

        # test ada p update
        curr_iter = 0
        batch_size = 2
        score = torch.tensor([1., 1.])
        d.ada_aug.log_buffer[0] += 2
        d.ada_aug.log_buffer[1] += score.sign().sum()
        d.ada_aug.update(iteration=curr_iter, num_batches=batch_size)
        assert d.ada_aug.aug_pipeline.p == 0.

        curr_iter += 1
        d.ada_aug.log_buffer[0] += 2
        d.ada_aug.log_buffer[1] += score.sign().sum()
        d.ada_aug.update(iteration=curr_iter, num_batches=batch_size)
        assert d.ada_aug.aug_pipeline.p == 4.0000e-05

        # test with p=1.
        d.ada_aug.aug_pipeline.p.copy_(torch.tensor(1.))
        img = torch.randn((2, 3, 64, 64))
        score = d(img)
        assert score.shape == (2, 1)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_ada_stylegan2_disc_cuda(self):
        d = ADAStyleGAN2Discriminator(**self.default_cfg).cuda()
        img = torch.randn((2, 3, 64, 64)).cuda()
        score = d(img)
        assert score.shape == (2, 1)

        # test ada p update
        curr_iter = 0
        batch_size = 2
        score = torch.tensor([1., 1.]).cuda()
        d.ada_aug.log_buffer[0] += 2
        d.ada_aug.log_buffer[1] += score.sign().sum()
        d.ada_aug.update(iteration=curr_iter, num_batches=batch_size)
        assert d.ada_aug.aug_pipeline.p == 0.

        curr_iter += 1
        d.ada_aug.log_buffer[0] += 2
        d.ada_aug.log_buffer[1] += score.sign().sum()
        d.ada_aug.update(iteration=curr_iter, num_batches=batch_size)
        assert d.ada_aug.aug_pipeline.p == 4.0000e-05

        # test with p=1.
        d.ada_aug.aug_pipeline.p.copy_(torch.tensor(1.))
        img = torch.randn((2, 3, 64, 64)).cuda()
        score = d(img)
        assert score.shape == (2, 1)


class TestMSStyleGANv2Disc:

    @classmethod
    def setup_class(cls):
        cls.default_cfg = dict(in_size=64, channel_multiplier=1)

    def test_msstylegan2_disc_cpu(self):
        d = MSStyleGAN2Discriminator(**self.default_cfg)
        img = torch.randn((2, 3, 64, 64))
        score = d(img)
        assert score.shape == (2, 1)

        d = MSStyleGAN2Discriminator(
            with_adaptive_pool=True, **self.default_cfg)
        img = torch.randn((2, 3, 64, 64))
        score = d(img)
        assert score.shape == (2, 1)

    @pytest.mark.skipif(not torch.cuda.is_available(), reason='requires cuda')
    def test_msstylegan2_disc_cuda(self):
        d = MSStyleGAN2Discriminator(**self.default_cfg).cuda()
        img = torch.randn((2, 3, 64, 64)).cuda()
        score = d(img)
        assert score.shape == (2, 1)

        d = MSStyleGAN2Discriminator(
            with_adaptive_pool=True, **self.default_cfg).cuda()
        img = torch.randn((2, 3, 64, 64)).cuda()
        score = d(img)
        assert score.shape == (2, 1)
