# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch

from mmpose.models.backbones.hrformer import (HRFomerModule, HRFormer,
                                              HRFormerBlock)


def test_hrformer_module():
    norm_cfg = dict(type='BN')
    block = HRFormerBlock
    # Test multiscale forward
    num_channles = (32, 64)
    num_inchannels = [c * block.expansion for c in num_channles]
    hrmodule = HRFomerModule(
        num_branches=2,
        block=block,
        num_blocks=(2, 2),
        num_inchannels=num_inchannels,
        num_channels=num_channles,
        num_heads=(1, 2),
        num_window_sizes=(7, 7),
        num_mlp_ratios=(4, 4),
        drop_paths=(0., 0.),
        norm_cfg=norm_cfg)

    feats = [
        torch.randn(1, num_inchannels[0], 64, 64),
        torch.randn(1, num_inchannels[1], 32, 32)
    ]
    feats = hrmodule(feats)

    assert len(str(hrmodule)) > 0
    assert len(feats) == 2
    assert feats[0].shape == torch.Size([1, num_inchannels[0], 64, 64])
    assert feats[1].shape == torch.Size([1, num_inchannels[1], 32, 32])

    # Test single scale forward
    num_channles = (32, 64)
    in_channels = [c * block.expansion for c in num_channles]
    hrmodule = HRFomerModule(
        num_branches=2,
        block=block,
        num_blocks=(2, 2),
        num_inchannels=num_inchannels,
        num_channels=num_channles,
        num_heads=(1, 2),
        num_window_sizes=(7, 7),
        num_mlp_ratios=(4, 4),
        drop_paths=(0., 0.),
        norm_cfg=norm_cfg,
        multiscale_output=False,
    )

    feats = [
        torch.randn(1, in_channels[0], 64, 64),
        torch.randn(1, in_channels[1], 32, 32)
    ]
    feats = hrmodule(feats)

    assert len(feats) == 1
    assert feats[0].shape == torch.Size([1, in_channels[0], 64, 64])

    # Test single branch HRFormer module
    hrmodule = HRFomerModule(
        num_branches=1,
        block=block,
        num_blocks=(1, ),
        num_inchannels=[num_inchannels[0]],
        num_channels=[num_channles[0]],
        num_heads=(1, ),
        num_window_sizes=(7, ),
        num_mlp_ratios=(4, ),
        drop_paths=(0.1, ),
        norm_cfg=norm_cfg,
    )

    feats = [
        torch.randn(1, in_channels[0], 64, 64),
    ]
    feats = hrmodule(feats)

    assert len(feats) == 1
    assert feats[0].shape == torch.Size([1, in_channels[0], 64, 64])

    # Value tests
    kwargs = dict(
        num_branches=2,
        block=block,
        num_blocks=(2, 2),
        num_inchannels=num_inchannels,
        num_channels=num_channles,
        num_heads=(1, 2),
        num_window_sizes=(7, 7),
        num_mlp_ratios=(4, 4),
        drop_paths=(0.1, 0.1),
        norm_cfg=norm_cfg,
    )

    with pytest.raises(ValueError):
        # len(num_blocks) should equal num_branches
        kwargs['num_blocks'] = [2, 2, 2]
        HRFomerModule(**kwargs)
    kwargs['num_blocks'] = [2, 2]

    with pytest.raises(ValueError):
        # len(num_blocks) should equal num_branches
        kwargs['num_channels'] = [2]
        HRFomerModule(**kwargs)
    kwargs['num_channels'] = [2, 2]

    with pytest.raises(ValueError):
        # len(num_blocks) should equal num_branches
        kwargs['num_inchannels'] = [2]
        HRFomerModule(**kwargs)
    kwargs['num_inchannels'] = [2, 2]


def test_hrformer_backbone():
    norm_cfg = dict(type='BN')
    # only have 3 stages
    extra = dict(
        drop_path_rate=0.2,
        stage1=dict(
            num_modules=1,
            num_branches=1,
            block='BOTTLENECK',
            num_blocks=(2, ),
            num_channels=(64, )),
        stage2=dict(
            num_modules=1,
            num_branches=2,
            block='HRFORMERBLOCK',
            window_sizes=(7, 7),
            num_heads=(1, 2),
            mlp_ratios=(4, 4),
            num_blocks=(2, 2),
            num_channels=(32, 64)),
        stage3=dict(
            num_modules=4,
            num_branches=3,
            block='HRFORMERBLOCK',
            window_sizes=(7, 7, 7),
            num_heads=(1, 2, 4),
            mlp_ratios=(4, 4, 4),
            num_blocks=(2, 2, 2),
            num_channels=(32, 64, 128)),
        stage4=dict(
            num_modules=3,
            num_branches=4,
            block='HRFORMERBLOCK',
            window_sizes=(7, 7, 7, 7),
            num_heads=(1, 2, 4, 8),
            mlp_ratios=(4, 4, 4, 4),
            num_blocks=(2, 2, 2, 2),
            num_channels=(32, 64, 128, 256),
            multiscale_output=True))

    with pytest.raises(ValueError):
        # len(num_blocks) should equal num_branches
        extra['stage4']['num_branches'] = 3
        HRFormer(extra=extra)
    extra['stage4']['num_branches'] = 4

    # Test HRFormer-S
    model = HRFormer(extra=extra, norm_cfg=norm_cfg)
    model.init_weights()
    model.train()

    imgs = torch.randn(1, 3, 64, 64)
    feats = model(imgs)
    assert len(feats) == 4
    assert feats[0].shape == torch.Size([1, 32, 16, 16])
    assert feats[3].shape == torch.Size([1, 256, 2, 2])

    # Test single scale output and model
    # without relative position bias
    extra['stage4']['multiscale_output'] = False
    extra['with_rpe'] = False
    model = HRFormer(extra=extra, norm_cfg=norm_cfg)
    model.init_weights()
    model.train()

    imgs = torch.randn(1, 3, 64, 64)
    feats = model(imgs)
    assert len(feats) == 1
    assert feats[0].shape == torch.Size([1, 32, 16, 16])
