# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
sys.path.append("../")
import numpy as np
import unittest
import paddle
from paddleslim.nas import ofa
from paddleslim.nas.ofa import OFA, RunConfig, DistillConfig
from paddleslim.nas.ofa.convert_super import supernet
from paddleslim.nas.ofa.convert_super import Convert, supernet


class ModelV1(paddle.nn.Layer):
    def __init__(self, name=''):
        super(ModelV1, self).__init__()
        self.model = paddle.nn.Sequential(
            paddle.nn.Conv2D(3, 12, 16), paddle.nn.ReLU())
        self.cls = self.create_parameter(
            attr=paddle.ParamAttr(
                name=name + 'cls',
                initializer=paddle.nn.initializer.Assign(
                    paddle.zeros(shape=(2, 12, 17, 17)))),
            shape=(2, 12, 17, 17))

    def forward(self, inputs):
        return self.cls + self.model(inputs)


class ModelShortcut(paddle.nn.Layer):
    def __init__(self):
        super(ModelShortcut, self).__init__()
        self.conv1 = paddle.nn.Sequential(
            paddle.nn.Conv2D(3, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())
        self.branch1 = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 12, 1),
            paddle.nn.BatchNorm2D(12),
            paddle.nn.ReLU(),
            paddle.nn.Conv2D(
                12, 12, 1, groups=12),
            paddle.nn.BatchNorm2D(12),
            paddle.nn.ReLU(),
            paddle.nn.Conv2D(
                12, 12, 1, groups=12),
            paddle.nn.BatchNorm2D(12),
            paddle.nn.ReLU())
        self.branch2 = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 12, 1),
            paddle.nn.BatchNorm2D(12),
            paddle.nn.ReLU(),
            paddle.nn.Conv2D(
                12, 12, 1, groups=12),
            paddle.nn.BatchNorm2D(12),
            paddle.nn.ReLU(),
            paddle.nn.Conv2D(12, 12, 1),
            paddle.nn.BatchNorm2D(12),
            paddle.nn.ReLU())
        self.out = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())

    def forward(self, x):
        x = self.conv1(x)
        y = self.branch1(x)
        y = x + y
        z = self.branch2(y)
        z = z + y
        z = self.out(z)
        return z


class ModelElementwise(paddle.nn.Layer):
    def __init__(self):
        super(ModelElementwise, self).__init__()
        self.conv1 = paddle.nn.Sequential(
            paddle.nn.Conv2D(3, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())
        self.conv2 = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 24, 3),
            paddle.nn.BatchNorm2D(24), paddle.nn.ReLU())
        self.conv3 = paddle.nn.Sequential(
            paddle.nn.Conv2D(24, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())
        self.out = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 6, 1),
            paddle.nn.BatchNorm2D(6), paddle.nn.ReLU())

    def forward(self, x):
        d = paddle.randn(shape=[2, 12, x.shape[2], x.shape[3]], dtype='float32')
        d = paddle.nn.functional.softmax(d)

        x = self.conv1(x)
        x = x + d
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.out(x)
        return x


class ModelMultiExit(paddle.nn.Layer):
    def __init__(self):
        super(ModelMultiExit, self).__init__()
        self.conv1 = paddle.nn.Sequential(
            paddle.nn.Conv2D(3, 12, 3),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())
        self.block1 = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 24, 7),
            paddle.nn.BatchNorm2D(24),
            paddle.nn.ReLU(),
            paddle.nn.MaxPool2D(
                kernel_size=3, stride=2, padding=0),
            paddle.nn.Conv2D(24, 24, 7),
            paddle.nn.BatchNorm2D(24),
            paddle.nn.ReLU(),
            paddle.nn.MaxPool2D(
                kernel_size=3, stride=2, padding=0))
        self.block2 = paddle.nn.Sequential(
            paddle.nn.Conv2D(24, 24, 1),
            paddle.nn.BatchNorm2D(24),
            paddle.nn.ReLU(),
            paddle.nn.MaxPool2D(
                kernel_size=3, stride=2, padding=1))

        self.out1 = paddle.nn.Sequential(
            paddle.nn.Conv2D(24, 24, 1),
            paddle.nn.BatchNorm2D(24), paddle.nn.ReLU())

        self.out2 = paddle.nn.Sequential(
            paddle.nn.Conv2D(48, 24, 7),
            paddle.nn.BatchNorm2D(24),
            paddle.nn.ReLU(),
            paddle.nn.Conv2D(24, 24, 3),
            paddle.nn.BatchNorm2D(24), paddle.nn.ReLU())

    def forward(self, x):
        x = self.conv1(x)

        b1 = self.block1(x)
        adapt = paddle.nn.UpsamplingBilinear2D(size=[b1.shape[2], b1.shape[2]])
        b2 = self.block2(b1)
        up = adapt(b2)
        y1 = self.out1(b1)
        y2 = paddle.concat([b1, up], axis=1)
        y2 = self.out2(y2)
        return [y1, y2]


class ModelInputDict(paddle.nn.Layer):
    def __init__(self):
        super(ModelInputDict, self).__init__()
        self.conv0 = paddle.nn.Sequential(
            paddle.nn.Conv2D(3, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())
        self.conv1 = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())
        self.conv2 = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())
        self.conv3 = paddle.nn.Sequential(
            paddle.nn.Conv2D(12, 12, 1),
            paddle.nn.BatchNorm2D(12), paddle.nn.ReLU())

    def forward(self, x, data):
        x = self.conv1(self.conv0(x))
        y = self.conv2(x)
        y = y + data['data']
        return self.conv3(y)


class TestOFAV2(unittest.TestCase):
    def setUp(self):
        model = ModelV1()
        sp_net_config = supernet(expand_ratio=[0.25, 0.5, 1.0])
        self.model = Convert(sp_net_config).convert(model)
        self.images = paddle.randn(shape=[2, 3, 32, 32], dtype='float32')

    def test_ofa(self):
        self.ofa_model = OFA(self.model)
        self.ofa_model.set_epoch(0)
        self.ofa_model.set_task('expand_ratio')
        out, _ = self.ofa_model(self.images)


class TestOFAV2Export(unittest.TestCase):
    def setUp(self):
        model = ModelV1(name='export')
        sp_net_config = supernet(expand_ratio=[0.25, 0.5, 1.0])
        self.model = Convert(sp_net_config).convert(model)
        self.images = paddle.randn(shape=[2, 3, 32, 32], dtype='float32')
        self.ofa_model = OFA(self.model)

    def test_export(self):
        origin_model = ModelV1(name='origin')
        net_config = {'model.0': {}}
        self.ofa_model.export(
            net_config,
            input_shapes=[1, 3, 32, 32],
            input_dtypes=['float32'],
            origin_model=origin_model)


class Testelementwise(unittest.TestCase):
    def setUp(self):
        model = ModelElementwise()
        sp_net_config = supernet(expand_ratio=[0.25, 0.5, 1.0])
        self.model = Convert(sp_net_config).convert(model)
        self.images = paddle.randn(shape=[2, 3, 32, 32], dtype='float32')

    def test_elementwise(self):
        self.ofa_model = OFA(self.model)
        self.ofa_model.set_epoch(0)
        self.ofa_model.set_task('expand_ratio')
        out, _ = self.ofa_model(self.images)
        assert list(self.ofa_model._ofa_layers.keys()) == ['conv2.0', 'conv3.0']


class TestMultiExit(unittest.TestCase):
    def setUp(self):
        self.images = paddle.randn(shape=[1, 3, 224, 224], dtype='float32')
        model = ModelMultiExit()
        sp_net_config = supernet(expand_ratio=[0.25, 0.5, 1.0])
        self.model = Convert(sp_net_config).convert(model)

    def test_multiexit(self):
        self.ofa_model = OFA(self.model)
        self.ofa_model.set_epoch(0)
        self.ofa_model.set_task('expand_ratio')
        out, _ = self.ofa_model(self.images)
        assert list(self.ofa_model._ofa_layers.keys(
        )) == ['conv1.0', 'block1.0', 'block1.4', 'block2.0', 'out2.0']


class TestShortcutSkiplayers(unittest.TestCase):
    def setUp(self):
        model = ModelShortcut()
        sp_net_config = supernet(expand_ratio=[0.5, 1.0])
        self.model = Convert(sp_net_config).convert(model)
        self.images = paddle.randn(shape=[2, 3, 32, 32], dtype='float32')
        self.init_config()
        self.ofa_model = OFA(self.model, run_config=self.run_config)
        self.ofa_model._clear_search_space(self.images)

    def init_config(self):
        default_run_config = {'skip_layers': ['branch1.6']}
        self.run_config = RunConfig(**default_run_config)

    def test_shortcut(self):
        self.ofa_model.set_epoch(0)
        self.ofa_model.set_task('expand_ratio')
        for i in range(5):
            self.ofa_model(self.images)
        assert list(self.ofa_model._ofa_layers.keys()) == ['branch2.0']


class TestShortcutSkiplayersCase1(TestShortcutSkiplayers):
    def init_config(self):
        default_run_config = {'skip_layers': ['conv1.0']}
        self.run_config = RunConfig(**default_run_config)


class TestShortcutSkiplayersCase2(TestShortcutSkiplayers):
    def init_config(self):
        default_run_config = {'skip_layers': ['branch2.0']}
        self.run_config = RunConfig(**default_run_config)

    def test_shortcut(self):
        assert list(self.ofa_model._ofa_layers.keys()) == ['conv1.0']


class TestInputDict(unittest.TestCase):
    def setUp(self):
        model = ModelInputDict()

        sp_net_config = supernet(expand_ratio=[0.5, 1.0])
        self.model = Convert(sp_net_config).convert(model)
        self.images = paddle.randn(shape=[2, 3, 32, 32], dtype='float32')
        self.images2 = {
            'data': paddle.randn(
                shape=[2, 12, 32, 32], dtype='float32')
        }
        default_run_config = {'skip_layers': ['conv1.0', 'conv2.0']}
        self.run_config = RunConfig(**default_run_config)

        self.ofa_model = OFA(self.model, run_config=self.run_config)
        self.ofa_model._clear_search_space(self.images, data=self.images2)

    def test_export(self):

        config = self.ofa_model._sample_config(
            task="expand_ratio", sample_type="smallest")
        self.ofa_model.export(
            config,
            input_shapes=[[1, 3, 32, 32], {
                'data': [1, 12, 32, 32]
            }],
            input_dtypes=['float32', 'float32'])


if __name__ == '__main__':
    unittest.main()
