# Copyright (c) OpenMMLab. All rights reserved.
from unittest import TestCase

import numpy as np

from mmpose.codecs import SimCCLabel  # noqa: F401
from mmpose.registry import KEYPOINT_CODECS


class TestSimCCLabel(TestCase):

    # name and configs of all test cases
    def setUp(self) -> None:
        self.configs = [
            (
                'simcc gaussian',
                dict(
                    type='SimCCLabel',
                    input_size=(192, 256),
                    smoothing_type='gaussian',
                    sigma=6.0,
                    simcc_split_ratio=2.0),
            ),
            (
                'simcc smoothing',
                dict(
                    type='SimCCLabel',
                    input_size=(192, 256),
                    smoothing_type='standard',
                    sigma=5.0,
                    simcc_split_ratio=3.0,
                    label_smooth_weight=0.1),
            ),
            (
                'simcc one-hot',
                dict(
                    type='SimCCLabel',
                    input_size=(192, 256),
                    smoothing_type='standard',
                    sigma=5.0,
                    simcc_split_ratio=3.0),
            ),
            (
                'simcc dark',
                dict(
                    type='SimCCLabel',
                    input_size=(192, 256),
                    smoothing_type='gaussian',
                    sigma=6.0,
                    simcc_split_ratio=2.0,
                    use_dark=True),
            ),
            (
                'simcc separated sigmas',
                dict(
                    type='SimCCLabel',
                    input_size=(192, 256),
                    smoothing_type='gaussian',
                    sigma=(4.9, 5.66),
                    simcc_split_ratio=2.0),
            ),
        ]

        # The bbox is usually padded so the keypoint will not be near the
        # boundary
        keypoints = (0.1 + 0.8 * np.random.rand(1, 17, 2)) * [192, 256]
        keypoints = np.round(keypoints).astype(np.float32)
        keypoints_visible = np.ones((1, 17), dtype=np.float32)
        self.data = dict(
            keypoints=keypoints, keypoints_visible=keypoints_visible)

    def test_encode(self):
        keypoints = self.data['keypoints']
        keypoints_visible = self.data['keypoints_visible']

        for name, cfg in self.configs:
            codec = KEYPOINT_CODECS.build(cfg)

            encoded = codec.encode(keypoints, keypoints_visible)

            self.assertEqual(encoded['keypoint_x_labels'].shape,
                             (1, 17, int(192 * codec.simcc_split_ratio)),
                             f'Failed case: "{name}"')
            self.assertEqual(encoded['keypoint_y_labels'].shape,
                             (1, 17, int(256 * codec.simcc_split_ratio)),
                             f'Failed case: "{name}"')
            self.assertEqual(encoded['keypoint_weights'].shape, (1, 17),
                             f'Failed case: "{name}"')

    def test_decode(self):
        for name, cfg in self.configs:
            codec = KEYPOINT_CODECS.build(cfg)

            simcc_x = np.random.rand(1, 17, int(192 * codec.simcc_split_ratio))
            simcc_y = np.random.rand(1, 17, int(256 * codec.simcc_split_ratio))

            keypoints, scores = codec.decode(simcc_x, simcc_y)

            self.assertEqual(keypoints.shape, (1, 17, 2),
                             f'Failed case: "{name}"')
            self.assertEqual(scores.shape, (1, 17), f'Failed case: "{name}"')

        # test decode_visibility
        cfg = cfg.copy()
        cfg['decode_visibility'] = True
        codec = KEYPOINT_CODECS.build(cfg)

        simcc_x = np.random.rand(1, 17, int(
            192 * codec.simcc_split_ratio)) * 10
        simcc_y = np.random.rand(1, 17, int(
            256 * codec.simcc_split_ratio)) * 10
        keypoints, scores = codec.decode(simcc_x, simcc_y)

        self.assertEqual(len(scores), 2)
        self.assertEqual(scores[0].shape, (1, 17), f'Failed case: "{name}"')
        self.assertEqual(scores[1].shape, (1, 17), f'Failed case: "{name}"')
        self.assertGreaterEqual(scores[1].min(), 0.0)
        self.assertLessEqual(scores[1].max(), 1.0)

    def test_cicular_verification(self):
        keypoints = self.data['keypoints']
        keypoints_visible = self.data['keypoints_visible']

        for name, cfg in self.configs:
            codec = KEYPOINT_CODECS.build(cfg)

            encoded = codec.encode(keypoints, keypoints_visible)
            keypoint_x_labels = encoded['keypoint_x_labels']
            keypoint_y_labels = encoded['keypoint_y_labels']

            _keypoints, _ = codec.decode(keypoint_x_labels, keypoint_y_labels)

            self.assertTrue(
                np.allclose(keypoints, _keypoints, atol=5.),
                f'Failed case: "{name}"')

    def test_errors(self):
        cfg = dict(
            type='SimCCLabel',
            input_size=(192, 256),
            smoothing_type='uniform',
            sigma=1.0,
            simcc_split_ratio=2.0)

        with self.assertRaisesRegex(ValueError,
                                    'got invalid `smoothing_type`'):
            _ = KEYPOINT_CODECS.build(cfg)

        # invalid label_smooth_weight in smoothing
        cfg = dict(
            type='SimCCLabel',
            input_size=(192, 256),
            smoothing_type='standard',
            sigma=1.0,
            simcc_split_ratio=2.0,
            label_smooth_weight=1.1)

        with self.assertRaisesRegex(ValueError,
                                    '`label_smooth_weight` should be'):
            _ = KEYPOINT_CODECS.build(cfg)

        # invalid label_smooth_weight for gaussian
        cfg = dict(
            type='SimCCLabel',
            input_size=(192, 256),
            smoothing_type='gaussian',
            sigma=1.0,
            simcc_split_ratio=2.0,
            label_smooth_weight=0.1)

        with self.assertRaisesRegex(ValueError,
                                    'is only used for `standard` mode.'):
            _ = KEYPOINT_CODECS.build(cfg)
