"""mobile net v2"""
from mindspore import nn
from mindspore.nn.layer import combined
from mindspore.ops import operations as P


def make_divisible(input_x, div_by=8):
    return int((input_x + div_by) // div_by)


def _conv_bn(in_channel,
             out_channel,
             ksize,
             stride=1):
    """Get a conv2d batchnorm and relu layer."""
    return nn.SequentialCell(
        [combined.Conv2d(in_channel,
                         out_channel,
                         kernel_size=ksize,
                         stride=stride,
                         batchnorm=True)])


class InvertedResidual(nn.Cell):
    def __init__(self, inp, oup, stride, expend_ratio):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        assert stride in [1, 2]

        hidden_dim = int(inp * expend_ratio)
        self.use_res_connect = self.stride == 1 and inp == oup
        if expend_ratio == 1:
            self.conv = nn.SequentialCell([
                combined.Conv2d(hidden_dim,
                                hidden_dim,
                                3,
                                stride,
                                group=hidden_dim,
                                batchnorm=True,
                                activation='relu6'),
                combined.Conv2d(hidden_dim, oup, 1, 1,
                                batchnorm=True)
            ])
        else:
            self.conv = nn.SequentialCell([
                combined.Conv2d(inp, hidden_dim, 1, 1,
                                batchnorm=True,
                                activation='relu6'),
                combined.Conv2d(hidden_dim,
                                hidden_dim,
                                3,
                                stride,
                                group=hidden_dim,
                                batchnorm=True,
                                activation='relu6'),
                combined.Conv2d(hidden_dim, oup, 1, 1,
                                batchnorm=True)
            ])
        self.add = P.TensorAdd()

    def construct(self, input_x):
        out = self.conv(input_x)
        if self.use_res_connect:
            out = self.add(input_x, out)
        return out


class MobileNetV2(nn.Cell):
    def __init__(self, num_class=1000, input_size=224, width_mul=1.):
        super(MobileNetV2, self).__init__()
        block = InvertedResidual
        input_channel = 32
        last_channel = 1280
        inverted_residual_setting = [
            [1, 16, 1, 1],
            [6, 24, 2, 2],
            [6, 32, 3, 2],
            [6, 64, 4, 2],
            [6, 96, 3, 1],
            [6, 160, 3, 2],
            [6, 230, 1, 1],
        ]
        if width_mul > 1.0:
            last_channel = make_divisible(last_channel * width_mul)
        self.last_channel = last_channel
        features = [_conv_bn(3, input_channel, 3, 2)]

        for t, c, n, s in inverted_residual_setting:
            out_channel = make_divisible(c * width_mul) if t > 1 else c
            for i in range(n):
                if i == 0:
                    features.append(block(input_channel, out_channel, s, t))
                else:
                    features.append(block(input_channel, out_channel, 1, t))
                input_channel = out_channel

        features.append(_conv_bn(input_channel, self.last_channel, 1))

        self.features = nn.SequentialCell(features)
        self.mean = P.ReduceMean(keep_dims=False)
        self.classifier = combined.Dense(self.last_channel, num_class)

    def construct(self, input_x):
        out = input_x
        out = self.features(out)
        out = self.mean(out, (2, 3))
        out = self.classifier(out)
        return out
