# Copyright 2020 Huawei Technologies Co., Ltd
#
# 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.
# ============================================================================
""" test cell """
import copy
import numpy as np
import pytest

import mindspore as ms
import mindspore.nn as nn
from mindspore import Tensor, Parameter
from mindspore.ops import operations as P
from mindspore.common.api import _cell_graph_executor
from mindspore.common.initializer import initializer, One


class ModA(nn.Cell):
    def __init__(self, tensor):
        super(ModA, self).__init__()
        self.weight = Parameter(tensor, name="weight")

    def construct(self, *inputs):
        pass


class ModB(nn.Cell):
    def __init__(self, tensor):
        super(ModB, self).__init__()
        self.weight = Parameter(tensor, name="weight")

    def construct(self, *inputs):
        pass


class ModC(nn.Cell):
    def __init__(self, ta, tb):
        super(ModC, self).__init__()
        self.mod1 = ModA(ta)
        self.mod2 = ModB(tb)

    def construct(self, *inputs):
        pass


class Net(nn.Cell):
    """ Net definition """
    name_len = 4
    cells_num = 3

    def __init__(self, ta, tb):
        super(Net, self).__init__()
        self.mod1 = ModA(ta)
        self.mod2 = ModB(tb)
        self.mod3 = ModC(ta, tb)

    def construct(self, *inputs):
        pass


class Net2(nn.Cell):
    def __init__(self, ta, tb):
        super(Net2, self).__init__(auto_prefix=False)
        self.mod1 = ModA(ta)
        self.mod2 = ModB(tb)
        self.mod3 = ModC(ta, tb)

    def construct(self, *inputs):
        pass


class ConvNet(nn.Cell):
    """ ConvNet definition """
    image_h = 224
    image_w = 224
    output_ch = 64

    def __init__(self, num_classes=10):
        super(ConvNet, self).__init__()
        self.conv1 = nn.Conv2d(3, ConvNet.output_ch, kernel_size=7, stride=2, pad_mode="pad", padding=3)
        self.bn1 = nn.BatchNorm2d(ConvNet.output_ch)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode="same")
        self.flatten = nn.Flatten()
        self.fc = nn.Dense(
            int(ConvNet.image_h * ConvNet.image_w * ConvNet.output_ch / (4 * 4)),
            num_classes)

    def construct(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        x = self.flatten(x)
        x = self.fc(x)
        return x


def test_basic():
    ta = Tensor(np.ones([2, 3]))
    tb = Tensor(np.ones([1, 4]))
    n = Net(ta, tb)
    names = list(n.parameters_dict().keys())
    assert len(names) == n.name_len
    assert names[0] == "mod1.weight"
    assert names[1] == "mod2.weight"
    assert names[2] == "mod3.mod1.weight"
    assert names[3] == "mod3.mod2.weight"


def test_parameter_name():
    """ test_parameter_name """
    ta = Tensor(np.ones([2, 3]))
    tb = Tensor(np.ones([1, 4]))
    n = Net(ta, tb)
    names = []
    for m in n.parameters_and_names():
        if m[0]:
            names.append(m[0])
    assert names[0] == "mod1.weight"
    assert names[1] == "mod2.weight"
    assert names[2] == "mod3.mod1.weight"
    assert names[3] == "mod3.mod2.weight"


def test_cell_name():
    """ test_cell_name """
    ta = Tensor(np.ones([2, 3]))
    tb = Tensor(np.ones([1, 4]))
    n = Net(ta, tb)
    n.insert_child_to_cell('modNone', None)
    names = []
    for m in n.cells_and_names():
        if m[0]:
            names.append(m[0])
    assert names[0] == "mod1"
    assert names[1] == "mod2"
    assert names[2] == "mod3"
    assert names[3] == "mod3.mod1"
    assert names[4] == "mod3.mod2"


def test_cells():
    ta = Tensor(np.ones([2, 3]))
    tb = Tensor(np.ones([1, 4]))
    n = Net(ta, tb)
    ch = list(n.cells())
    assert len(ch) == n.cells_num


def test_exceptions():
    """ test_exceptions """
    t = Tensor(np.ones([2, 3]))

    class ModError(nn.Cell):
        def __init__(self, tensor):
            self.weight = Parameter(tensor, name="weight")
            super(ModError, self).__init__()

        def construct(self, *inputs):
            pass

    with pytest.raises(AttributeError):
        ModError(t)

    class ModError1(nn.Cell):
        def __init__(self, tensor):
            super().__init__()
            self.weight = Parameter(tensor, name="weight")
            self.weight = None
            self.weight = ModA(tensor)

        def construct(self, *inputs):
            pass

    with pytest.raises(TypeError):
        ModError1(t)

    class ModError2(nn.Cell):
        def __init__(self, tensor):
            super().__init__()
            self.mod = ModA(tensor)
            self.mod = None
            self.mod = tensor

        def construct(self, *inputs):
            pass

    with pytest.raises(TypeError):
        ModError2(t)

    m = nn.Cell()
    assert m.construct() is None


def test_cell_copy():
    net = ConvNet()
    copy.deepcopy(net)


def test_del():
    """ test_del """
    ta = Tensor(np.ones([2, 3]))
    tb = Tensor(np.ones([1, 4]))
    n = Net(ta, tb)
    names = list(n.parameters_dict().keys())
    assert len(names) == n.name_len
    del n.mod1
    names = list(n.parameters_dict().keys())
    assert len(names) == n.name_len - 1
    with pytest.raises(AttributeError):
        del n.mod1.weight
    del n.mod2.weight
    names = list(n.parameters_dict().keys())
    assert len(names) == n.name_len - 2
    with pytest.raises(AttributeError):
        del n.mod


def test_add_attr():
    """ test_add_attr """
    ta = Tensor(np.ones([2, 3]))
    tb = Tensor(np.ones([1, 4]))
    p = Parameter(ta, name="weight")
    m = nn.Cell()
    m.insert_param_to_cell('weight', p)

    with pytest.raises(TypeError):
        m.insert_child_to_cell("network", p)

    with pytest.raises(KeyError):
        m.insert_param_to_cell('', p)
    with pytest.raises(KeyError):
        m.insert_param_to_cell('a.b', p)
    m.insert_param_to_cell('weight', p)
    with pytest.raises(KeyError):
        m.insert_child_to_cell('', ModA(ta))
    with pytest.raises(KeyError):
        m.insert_child_to_cell('a.b', ModB(tb))

    with pytest.raises(TypeError):
        m.insert_child_to_cell('buffer', tb)
    with pytest.raises(TypeError):
        m.insert_param_to_cell('w', ta)
    with pytest.raises(TypeError):
        m.insert_child_to_cell('m', p)

    class ModAddCellError(nn.Cell):
        def __init__(self, tensor):
            self.mod = ModA(tensor)
            super().__init__()

        def construct(self, *inputs):
            pass

    with pytest.raises(AttributeError):
        ModAddCellError(ta)


def test_apply():
    """
    Feature: Cell.apply.
    Description: Verify Cell.apply.
    Expectation: No exception.
    """
    net = nn.SequentialCell(nn.Dense(2, 2), nn.Dense(2, 2))

    def func(cell):
        if isinstance(cell, nn.Dense):
            cell.weight.set_data(initializer(One(), cell.weight.shape, cell.weight.dtype))

    net.apply(func)

    target = np.ones((2, 2), ms.dtype_to_nptype(net[0].weight.dtype))
    assert np.allclose(target, net[0].weight.asnumpy())
    assert np.allclose(target, net[1].weight.asnumpy())


def test_train_eval():
    m = nn.Cell()
    assert not m.training
    m.set_train()
    assert m.training
    m.set_train(False)
    assert not m.training


def test_stop_update_name():
    ta = Tensor(np.ones([2, 3]))
    tb = Tensor(np.ones([1, 4]))
    n = Net2(ta, tb)
    names = list(n.parameters_dict().keys())
    assert names[0] == "weight"
    assert names[1] == "mod1.weight"
    assert names[2] == "mod2.weight"


class ModelName(nn.Cell):
    def __init__(self, tensor):
        super(ModelName, self).__init__()
        self.w2 = Parameter(tensor, name="weight")
        self.w1 = Parameter(tensor, name="weight")
        self.w3 = Parameter(tensor, name=None)
        self.w4 = Parameter(tensor, name=None)

    def construct(self, *inputs):
        pass


def test_cell_names():
    ta = Tensor(np.ones([2, 3]))
    mn = ModelName(ta)
    with pytest.raises(ValueError):
        _cell_graph_executor.compile(mn)


class TestKwargsNet(nn.Cell):
    def construct(self, p1, p2, p3=False, p4=False):
        if p3:
            return p1
        if p4:
            return P.Add()(p1, p2)
        return p2


def test_kwargs_default_value1():
    """
    Feature: Supports Cell kwargs inputs.
    Description: Pass kwargs.
    Expectation: No exception.
    """
    x = Tensor([[1], [2], [3]], ms.float32)
    y = Tensor([[4], [5], [6]], ms.float32)
    net = TestKwargsNet()
    res = net(x, y, p4=True)
    print(res)


def test_kwargs_default_value2():
    """
    Feature: Supports Cell kwargs inputs.
    Description: Pass kwargs.
    Expectation: No exception.
    """
    x = Tensor([[[[1.0, 2.0], [3.0, 4.0]]]], ms.float32)
    nn_op = nn.ResizeBilinear()
    res = nn_op(x, (4, 4), align_corners=True)
    print(res)
