#   Copyright (c) 2020 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.

from __future__ import absolute_import

import numpy as np
from paddle2onnx.constant import dtypes
from paddle2onnx.op_mapper import OpMapper as op_mapper
from paddle2onnx.op_mapper import mapper_helper
import copy
import six
import paddle


@op_mapper('set_value')
class SetValue():
    support_opset_version_range = (11, 15)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        axes = node.attr('axes')
        steps, is_steps_tensor = mapper_helper.get_node_attr_value(
            graph,
            node,
            'steps',
            'StepsTensor',
            'StepsTensorList',
            return_list=True,
            dtype=dtypes.ONNX.INT64)

        starts, is_starts_tensor = mapper_helper.get_node_attr_value(
            graph,
            node,
            'starts',
            'StartsTensor',
            'StartsTensorList',
            return_list=True,
            dtype=dtypes.ONNX.INT64)

        ends, is_ends_tensor = mapper_helper.get_node_attr_value(
            graph,
            node,
            'ends',
            'EndsTensor',
            'EndsTensorList',
            return_list=True,
            dtype=dtypes.ONNX.INT64)

        contain_step_bigger_than_1 = False
        for i in steps:
            contain_step_bigger_than_1 = i > 1
            if not isinstance(i, int) or contain_step_bigger_than_1:
                contain_step_bigger_than_1 = True
                break
        condition = is_steps_tensor or is_starts_tensor or is_ends_tensor or contain_step_bigger_than_1
        assert not condition, "Currently not supported convert now"

        input_x_shape = node.input_shape('Input', 0)
        onnx_paddings = [0] * len(input_x_shape) * 2
        value_shape = list(copy.copy(node.input_shape('Input', 0)))
        for i in range(len(axes)):
            axis = axes[i]
            if starts[i] < 0:
                starts[i] = starts[i] + input_x_shape[i]
            if ends[i] < 0:
                ends[i] = ends[i] + input_x_shape[i]
            onnx_paddings[axis] = starts[i]
            value_shape[axis] = value_shape[axis] - onnx_paddings[axis]
            onnx_paddings[axis + len(input_x_shape)] = input_x_shape[
                axis] - ends[i]
            if onnx_paddings[axis + len(input_x_shape)] < 0:
                onnx_paddings[axis + len(input_x_shape)] = 0
            value_shape[axis] = value_shape[axis] - onnx_paddings[axis + len(
                input_x_shape)]
        dtype_paddle = node.input_dtype('Input', 0)
        dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[dtype_paddle]
        value_tensor = None
        shape = node.attr('shape')
        if len(shape) > 0:
            dtypes_list = [
                'fp32_values', 'fp64_values', 'int32_values', 'int64_values',
                'bool_values'
            ]
            for i in range(len(dtypes_list)):
                value = node.attr(dtypes_list[i])
                if value is not None:
                    break
            if len(value) == 1:
                total_nums = 1
                for i in value_shape:
                    total_nums *= i
                value = value * total_nums
                value_tensor = mapper_helper.constant_helper(
                    graph, dtype_paddle, value, shape=value_shape)
            else:
                value_tensor = mapper_helper.constant_helper(
                    graph, dtype_paddle, value, shape=shape)
        else:
            value_tensor = node.input('ValueTensor', 0)
        MAX_FLOAT32 = 3.402823466E+38
        max_node = graph.make_node(
            'Constant', attrs={'dtype': dtype,
                               'value': [MAX_FLOAT32]})
        pads_node = graph.make_node(
            'Constant',
            attrs={'dtype': dtypes.ONNX.INT64,
                   'value': onnx_paddings})
        value_pad_node = graph.make_node(
            'Pad', inputs=[value_tensor, pads_node, max_node])

        condition_dtype = graph.make_node(
            "Equal", inputs=[value_pad_node, max_node])
        condition_node = graph.make_node(
            'Cast', inputs=[condition_dtype], to=dtypes.ONNX.BOOL)
        graph.make_node(
            "Where",
            inputs=[condition_node, node.input('Input', 0), value_pad_node],
            outputs=node.output('Out'))


@op_mapper('one_hot_v2')
class OneHotV2():
    support_opset_version_range = (9, )

    @classmethod
    def opset_9(cls, graph, node, **kw):
        allow_out_of_range = node.attr('allow_out_of_range')
        assert not allow_out_of_range, "allow_out_of_range can not be true in one_hot_v2."
        in_dtype_paddle = node.input_dtype('X', 0)
        in_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[in_dtype_paddle]
        out_dtype = node.output_dtype('Out', 0)
        out_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[out_dtype]
        inputs = node.input('X', 0)
        if in_dtype_paddle == paddle.int32:
            inputs = graph.make_node(
                'Cast', inputs=[inputs], to=dtypes.ONNX.INT64)
            in_dtype = dtypes.ONNX.INT64
        value_node = graph.make_node('Constant', dtype=out_dtype, value=[0, 1])
        depth = node.attr('depth')
        if node.input('depth_tensor', 0) is not None:
            depth_node = node.input('depth_tensor', 0)
        else:
            depth_node = graph.make_node(
                'Constant', dtype=in_dtype, value=[depth])
        reshaped_input_node = graph.make_node(
            'OneHot',
            inputs=[inputs, depth_node, value_node],
            outputs=node.output('Out'))


@op_mapper('concat')
class Concat():
    support_opset_version_range = (4, 15)

    @classmethod
    def opset_4(cls, graph, node, **kw):
        inputs = node.input('X')

        input_dtypes = [node.input_dtype('X', i) for i in range(len(inputs))]
        inputs = mapper_helper.dtype_alignment(graph, inputs, input_dtypes)
        node_axis = node.input('AxisTensor')
        if node_axis is not None and len(node_axis) > 0:
            axis_node = node.input('AxisTensor')[0]
            try:
                axis = mapper_helper.get_value_from_parameters(graph,
                                                               axis_node)[0]
            except Exception as e:
                raise Exception(
                    "Currently does not support the axis parameter as input tensor"
                    + str(e))
        else:
            axis = node.attr('axis')
        if axis < 0:
            axis = axis + len(node.input_shape('X', 0))

        node = graph.make_node(
            'Concat', inputs=inputs, outputs=node.output('Out'), axis=axis)


@op_mapper('assign')
class Assign():
    support_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        inputs = node.input('X')
        graph.make_node('Identity', inputs=inputs, outputs=node.output('Out'))


@op_mapper('lod_reset')
class LodReset():
    support_opset_version_range = (1, )

    @classmethod
    def opset_1(cls, graph, node, **kw):
        graph.make_node(
            'Identity', inputs=node.input('X'), outputs=node.output('Out'))


@op_mapper('eye')
class Eye():
    support_opset_version_range = (9, )

    @classmethod
    def opset_9(cls, graph, node, **kw):
        num_rows = node.attr('num_rows')
        num_columns = node.attr('num_columns')
        dtype = node.output_dtype('Out', 0)
        value = [0] * num_rows * num_columns
        value_tensor = mapper_helper.constant_helper(
            graph, dtype, value, shape=[num_rows, num_columns])
        graph.make_node(
            'EyeLike', inputs=[value_tensor], outputs=node.output('Out'))


@op_mapper('stack')
class Stack():
    support_opset_version_range = (4, 15)

    @classmethod
    def opset_4(cls, graph, node, **kw):
        inputs = node.input('X')
        input_dtypes = [node.input_dtype('X', i) for i in range(len(inputs))]
        inputs = mapper_helper.dtype_alignment(graph, inputs, input_dtypes)
        axis = node.attr('axis')

        unsqueezed_inputs = list()
        for ipt in inputs:
            unsqueezed_ipt = mapper_helper.unsqueeze_helper(graph, ipt, [axis])
            unsqueezed_inputs.append(unsqueezed_ipt)
        graph.make_node(
            'Concat',
            inputs=unsqueezed_inputs,
            outputs=node.output('Y'),
            axis=axis)


@op_mapper('unstack')
class Unstack():
    support_opset_version_range = (2, 15)

    @classmethod
    def opset_2(cls, graph, node, **kw):
        axis = node.attr('axis')
        ndim = node.block.vars[node.input('X')[0]].ndim
        axis = axis + ndim if axis < 0 else axis
        output_y = mapper_helper.split_helper(
            graph,
            node.input('X'),
            axis=axis,
            split=[1] * len(node.output('Y')),
            outputs=len(node.output('Y')))

        if isinstance(output_y, six.string_types):
            output_y = [output_y]

        for i in range(len(output_y)):
            mapper_helper.squeeze_helper(graph, output_y[i], [axis],
                                         node.output('Y', i))


@op_mapper('expand_as_v2')
class ExpandAsV2():
    support_opset_version_range = (8, 15)

    @classmethod
    def opset_8(cls, graph, node, **kw):
        target_shape = node.attr('target_shape')
        if node.input('target_tensor', 0) is not None:
            target_shape = graph.make_node(
                'Shape', inputs=[node.input('target_tensor', 0)])
        elif target_shape is not None:
            target_shape = graph.make_node(
                'Constant',
                attrs={'dtype': dtypes.ONNX.INT64,
                       'value': target_shape})
        else:
            raise Exception(
                "Not find attribute: 'target_shape' or tensor 'target_tensor'")
        node = graph.make_node(
            'Expand',
            inputs=[node.input('X', 0), target_shape],
            outputs=node.output('Out'))


@op_mapper('expand_v2')
class ExpandV2():
    support_opset_version_range = (8, 15)

    @classmethod
    def opset_8(cls, graph, node, **kw):
        expand_shape, _ = mapper_helper.get_node_attr_value(
            graph,
            node,
            'shape',
            'Shape',
            'expand_shapes_tensor',
            dtype=dtypes.ONNX.INT64)

        input_shape = node.input_shape('X', 0)
        input_shape_node = graph.make_node('Shape', inputs=node.input('X', 0))

        node_shape = node.attr('shape')
        node_shape_tensor = node.input('Shape')
        node_shape_tensor_list = node.input('expand_shapes_tensor')
        if node_shape_tensor is not None and len(node_shape_tensor) > 0:
            diff = node.input_shape('Shape', 0)[0] - len(input_shape)
        elif node_shape_tensor_list is not None and \
                len(node_shape_tensor_list) > 0:
            diff = len(node_shape_tensor_list) - len(input_shape)
        elif node_shape is not None and len(node_shape) > 0:
            diff = len(node_shape) - len(input_shape)
            expand_shape = graph.make_node(
                'Constant', dtype=dtypes.ONNX.INT64, value=expand_shape)

        if diff > 0:
            one_node = graph.make_node(
                'Constant',
                attrs={'dtype': dtypes.ONNX.INT64,
                       'value': [1] * diff})
            input_shape_node = graph.make_node(
                'Concat', inputs=[one_node, input_shape_node], axis=0)

        if graph.opset_version < 12:
            input_shape_node = graph.make_node(
                'Cast', inputs=[input_shape_node], to=dtypes.ONNX.FLOAT)
            expand_shape = graph.make_node(
                'Cast', inputs=[expand_shape], to=dtypes.ONNX.FLOAT)
            shape = graph.make_node(
                'Max', inputs=[input_shape_node, expand_shape])
            shape = graph.make_node(
                'Cast', inputs=[shape], to=dtypes.ONNX.INT64)
        else:
            shape = graph.make_node(
                'Max', inputs=[input_shape_node, expand_shape])
        node = graph.make_node(
            'Expand',
            inputs=[node.input('X', 0), shape],
            outputs=node.output('Out'))


@op_mapper('shape')
class Shape():
    support_opset_version_range = (6, 15)

    @classmethod
    def opset_6(cls, graph, node, **kw):
        shape_node = graph.make_node('Shape', inputs=node.input('Input'))
        graph.make_node(
            'Cast',
            inputs=[shape_node],
            outputs=node.output('Out'),
            to=dtypes.ONNX.INT32)


@op_mapper('size')
class Numel():
    supports_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        size_node = graph.make_node('Size', inputs=node.input('Input'))
        mapper_helper.unsqueeze_helper(graph, size_node, [0],
                                       node.output('Out'))


@op_mapper('split')
class Split():
    support_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        sections = node.attr('sections')
        axis = cls.get_axis(graph, node)
        if isinstance(sections, list) and len(sections) == 1:
            graph.make_node(
                'Identity', inputs=node.input('X'), outputs=node.output('Out'))
        else:
            if len(sections) > 0:
                input_shape = node.block.vars[node.input('X')[0]].shape
                section_index = [
                    i for i, val in enumerate(sections) if val == -1
                ]
                if input_shape[axis] != -1 and len(section_index) == 1:
                    sections[section_index[0]] = input_shape[axis] - sum(
                        sections) - 1
                mapper_helper.split_helper(
                    graph,
                    node.input('X'),
                    axis=axis,
                    split=sections,
                    outputs=node.output('Out'))
            else:
                graph.make_node(
                    'Split',
                    inputs=node.input('X'),
                    outputs=node.output('Out'),
                    axis=axis)

    @classmethod
    def get_axis(cls, graph, node):
        if len(node.input('AxisTensor')) > 0:
            axis_node = node.input('AxisTensor')[0]
            # When axis is tensor, only int32 and int64 are supported
            if axis_node not in graph.parameters:
                raise Exception(
                    "Currently does not support the axis parameter as input tensor!"
                )
            else:
                axis = graph.parameters[axis_node].attribute[0].t.int32_data
                if axis is None or len(axis) < 1:
                    axis = graph.parameters[axis_node].attribute[
                        0].t.int64_data[0]
        else:
            axis = node.attr('axis')
        return axis


@op_mapper(['roll'])
class Roll():
    support_opset_version_range = (4, 15)

    @classmethod
    def roll(cls, graph, node, input_x, dims, shifts):
        for i in range(len(dims)):
            if graph.opset_version >= 10 and isinstance(shifts,
                                                        six.string_types):
                to_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype(
                    'ShiftsTensor', 0)]
                const_i = graph.make_node('Constant', dtype=to_dtype, value=i)
                const_0 = graph.make_node('Constant', dtype=to_dtype, value=0)
                shift_node = graph.make_node(
                    'Gather', inputs=[shifts, const_i], axis=0)
                shift_node = graph.make_node(
                    "Sub", inputs=[const_0, shift_node])
                shift_node = mapper_helper.unsqueeze_helper(graph, shift_node,
                                                            [0])
            elif graph.opset_version < 10 and isinstance(shifts,
                                                         six.string_types):
                raise Exception(
                    "shifts of roll is Tensor, please try with higher onnx opset_version>=10."
                )
            else:
                shift_node = [-shifts[i]]
                to_dtype = dtypes.ONNX.INT64
            shapes = []
            shape = mapper_helper.slice_helper(
                graph, input_x, [dims[i]], shift_node, [60000], dtype=to_dtype)
            shapes.append(shape)
            shape = mapper_helper.slice_helper(
                graph, input_x, [dims[i]], [0], shift_node, dtype=to_dtype)
            shapes.append(shape)
            input_x = graph.make_node('Concat', inputs=shapes, axis=dims[i])
        return input_x

    @classmethod
    def flatten(cls, graph, node):
        dims = len(node.input_shape('X', 0))
        start_axis = 0
        end_axis = dims - 1
        shape_node = graph.make_node('Shape', inputs=node.input('X'))
        if end_axis < dims - 1:
            slice1 = mapper_helper.slice_helper(
                graph, shape_node, axes=[0], starts=[0], ends=[start_axis])
            slice3 = mapper_helper.slice_helper(
                graph, shape_node, axes=[0], starts=[end_axis + 1],
                ends=[dims])
            slices = [
                slice1, graph.make_node(
                    'Constant', value=[-1], dtype=dtypes.ONNX.INT64), slice3
            ]
        else:
            slice1 = mapper_helper.slice_helper(
                graph, shape_node, axes=[0], starts=[0], ends=[start_axis])
            slices = [
                slice1, graph.make_node(
                    'Constant', value=[-1], dtype=dtypes.ONNX.INT64)
            ]
        final_shape = graph.make_node('Concat', inputs=slices, axis=0)
        output = graph.make_node(
            'Reshape', inputs=[node.input('X')[0], final_shape])
        return output

    @classmethod
    def opset_4(cls, graph, node, **kw):
        dims = node.attr('axis')
        shifts = node.attr('shifts')
        input_x = node.input('X')[0]
        input_shape = node.input_shape('X', 0)
        shifts_node = node.input('ShiftsTensor')
        if len(dims) > 0:
            axes = [
                axis + len(input_shape) if axis < 0 else axis
                for i, axis in enumerate(dims)
            ]
            if shifts_node is not None and len(shifts_node) > 0:
                shifts = shifts_node[0]
            else:
                for i in range(0, len(axes)):
                    if input_shape[axes[i]] > 0:
                        assert -input_shape[axes[i]] <= shifts[i] <= input_shape[axes[i]], \
                            "the value of shifts in axis is less than the value of input_shape in axis."

            input_x = cls.roll(graph, node, input_x, axes, shifts)
            graph.make_node(
                'Identity', inputs=[input_x], outputs=node.output('Out'))
        else:
            if shifts_node is not None and len(shifts_node) > 0:
                shifts = shifts_node[0]
            input_x = cls.flatten(graph, node)
            input_x = cls.roll(graph, node, input_x, [0], shifts)
            shape_node = graph.make_node(
                'Constant',
                attrs={'dtype': dtypes.ONNX.INT64,
                       'value': list(input_shape)})
            graph.make_node(
                'Reshape',
                inputs=[input_x, shape_node],
                outputs=node.output('Out'))


@op_mapper(['slice', 'strided_slice'])
class Slice():
    support_opset_version_range = (1, 15)

    @classmethod
    def decrease_axis(cls, node):
        # tensor[i,:] will decrease rank of origin input, example:
        # paddle.slice() will not decrease rank of origin input
        # if input shape is [2, 3], input[0, :] will generate output with shape [3], not [1, 3].
        # paddle.slice(input, 0, 1, 0) will  generate output with shape [1, 3], not [3].

        decrease_axis = node.attr('decrease_axis')
        if len(decrease_axis) == 0:
            return None
        if node.output_shape('Out', 0) == [0]:
            return decrease_axis
        if len(node.input_shape('Input', 0)) > len(node.output_shape('Out', 0)):
            return decrease_axis
        return None

    @classmethod
    def opset_1(cls, graph, node, **kw):
        axes = node.attr('axes')
        strides, strides_is_tensor = mapper_helper.get_node_attr_value(
            graph, node, 'strides', 'StridesTensor', 'StridesTensorList', True)
        strides = [1] * len(axes) if strides is None else strides
        steps = [i for i, val in enumerate(strides) if val == 1]
        assert len(steps) == len(axes), \
            "Slice in onnx(opset<10) not support attribute 'step', Try converting with opset_version >=10"

        starts, start_is_tensor = mapper_helper.get_node_attr_value(
            graph, node, 'starts', 'StartsTensor', 'StartsTensorList', True)
        ends, end_is_tensor = mapper_helper.get_node_attr_value(
            graph, node, 'ends', 'EndsTensor', 'EndsTensorList', True)

        assert not strides_is_tensor and not start_is_tensor and not end_is_tensor, \
            "Slice in onnx(opset<10) not support attribute 'steps','starts' or 'ends' which have tensor value, " \
            "Try converting with opset_version >=10 "

        decrease_axis = cls.decrease_axis(node)
        if decrease_axis is None:
            graph.make_node(
                "Slice",
                inputs=[node.input('Input')[0]],
                outputs=node.output('Out'),
                axes=axes,
                starts=starts,
                ends=ends)
        else:
            sliced = graph.make_node(
                "Slice",
                inputs=[node.input('Input')[0]],
                axes=axes,
                starts=starts,
                ends=ends)
            mapper_helper.squeeze_helper(graph, sliced, decrease_axis,
                                         node.output('Out'))

    @classmethod
    def opset_10(cls, graph, node, **kw):
        axes = node.attr('axes')
        strides, _ = mapper_helper.get_node_attr_value(
            graph,
            node,
            'strides',
            'StridesTensor',
            'StridesTensorList',
            dtype=dtypes.ONNX.INT64)
        strides = [1] * len(axes) if strides is None else strides

        starts, _ = mapper_helper.get_node_attr_value(
            graph,
            node,
            'starts',
            'StartsTensor',
            'StartsTensorList',
            dtype=dtypes.ONNX.INT64)
        ends, _ = mapper_helper.get_node_attr_value(
            graph,
            node,
            'ends',
            'EndsTensor',
            'EndsTensorList',
            dtype=dtypes.ONNX.INT64)

        if isinstance(starts, list):
            starts_node = graph.make_node(
                'Constant',
                attrs={'dtype': dtypes.ONNX.INT64,
                       'value': starts})
        else:
            starts_node = starts
        if isinstance(ends, list):
            ends_node = graph.make_node(
                'Constant', attrs={'dtype': dtypes.ONNX.INT64,
                                   'value': ends})
        else:
            ends_node = ends

        if isinstance(strides, list):
            strides_node = graph.make_node(
                'Constant',
                attrs={'dtype': dtypes.ONNX.INT64,
                       'value': strides})
        else:
            strides_node = strides

        steps_node = strides_node
        axes_node = graph.make_node(
            'Constant', attrs={'dtype': dtypes.ONNX.INT64,
                               'value': axes})

        decrease_axis = cls.decrease_axis(node)
        if decrease_axis is None:
            sliced = graph.make_node(
                "Slice",
                inputs=[
                    node.input('Input')[0], starts_node, ends_node, axes_node,
                    steps_node
                ],
                outputs=node.output('Out'))
        else:
            sliced = graph.make_node(
                "Slice",
                inputs=[
                    node.input('Input')[0], starts_node, ends_node, axes_node,
                    steps_node
                ])
            mapper_helper.squeeze_helper(graph, sliced, decrease_axis,
                                         node.output('Out'))


@op_mapper(['sequence_expand'])
class SequenceExpand():
    support_opset_version_range = ()

    @classmethod
    def opset_1(cls, graph, node, **kw):
        graph.make_node(
            'Identity', inputs=node.input('X'), outputs=node.output('Out'))


@op_mapper(['expand'])
class Expand():
    support_opset_version_range = (6, 15)

    @classmethod
    def opset_6(cls, graph, node, **kw):
        expand_times, _ = mapper_helper.get_node_attr_value(
            graph,
            node,
            'expand_times',
            'ExpandTimes',
            'expand_times_tensor',
            dtype=dtypes.ONNX.INT64)

        if isinstance(expand_times, list):
            expand_times = graph.make_node(
                'Constant',
                attrs={'dtype': dtypes.ONNX.INT64,
                       'value': expand_times})

        graph.make_node(
            "Tile",
            inputs=[node.input('X', 0), expand_times],
            outputs=node.output('Out'))


@op_mapper(['tile'])
class Tile():
    support_opset_version_range = (6, 15)

    @classmethod
    def opset_6(cls, graph, node, **kw):
        repeat_times, _ = mapper_helper.get_node_attr_value(
            graph,
            node,
            'repeat_times',
            'RepeatTimes',
            'repeat_times_tensor',
            dtype=dtypes.ONNX.INT64)

        if isinstance(repeat_times, list):
            repeat_times = graph.make_node(
                'Constant',
                attrs={'dtype': dtypes.ONNX.INT64,
                       'value': repeat_times})

        graph.make_node(
            "Tile",
            inputs=[node.input('X', 0), repeat_times],
            outputs=node.output('Out'))


@op_mapper('range')
class Range():
    support_opset_version_range = (11, 15)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        start = node.input('Start', 0)
        end = node.input('End', 0)
        step = node.input('Step', 0)
        start_t = mapper_helper.squeeze_helper(graph, start, [0])
        end_t = mapper_helper.squeeze_helper(graph, end, [0])
        step_t = mapper_helper.squeeze_helper(graph, step, [0])
        graph.make_node(
            "Range",
            inputs=[start_t, end_t, step_t],
            outputs=node.output('Out'))


@op_mapper('fill_constant')
class Constant():
    support_opset_version_range = (1, 15)

    @classmethod
    def check_int_type(cls, dtype):
        if dtype in [dtypes.ONNX.INT16, dtypes.ONNX.INT32, dtypes.ONNX.INT64]:
            return True
        return False

    @classmethod
    def opset_1(cls, graph, node, **kw):
        value = node.attr('value')
        dtype = node.attr('dtype')
        value_is_scalar_tensor = False
        if 'ValueTensor' in node.inputs and len(node.input('ValueTensor')) > 0:
            rank = len(node.input_shape("ValueTensor", 0))
            if rank == 1 and node.input_shape("ValueTensor", 0)[0] == 1:
                value_is_scalar_tensor = True
                value = node.input("ValueTensor")[0]
            else:
                raise Exception(
                    "paddle.full with tensor value parameter is not supported yet."
                )

        shape, is_shape_tensor = mapper_helper.get_node_attr_value(
            graph,
            node,
            'shape',
            'ShapeTensor',
            'ShapeTensorList',
            dtype=dtypes.ONNX.INT64)

        if graph.opset_version >= 9 and (is_shape_tensor or
                                         value_is_scalar_tensor):
            if not is_shape_tensor:
                shape = graph.make_node(
                    'Constant', dtype=dtypes.ONNX.INT64, value=shape)
            input_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[dtype]
            if not value_is_scalar_tensor and cls.check_int_type(input_dtype):
                to_dtype = dtypes.ONNX.DOUBLE
                outputs = None
            else:
                to_dtype = input_dtype
                outputs = node.output('Out')

            if value_is_scalar_tensor:
                base_value = graph.make_node(
                    'ConstantOfShape',
                    inputs=shape,
                    attrs={'dims': [1],
                           'dtype': to_dtype,
                           'value': 0})
                node2 = graph.make_node(
                    "Add", inputs=[base_value, value], outputs=outputs)
            else:
                node2 = graph.make_node(
                    'ConstantOfShape',
                    inputs=shape,
                    outputs=outputs,
                    attrs={'dims': [1],
                           'dtype': to_dtype,
                           'value': value})

            if not value_is_scalar_tensor and cls.check_int_type(input_dtype):
                graph.make_node(
                    'Cast',
                    inputs=node2,
                    outputs=node.output('Out'),
                    attrs={'to': input_dtype})
        else:
            assert not is_shape_tensor and not value_is_scalar_tensor, \
                "Currently op ['fill_constant'] does not support in onnx(opset<9) when 'shape' or 'fill_value' has " \
                "tensor, Try converting with opset_version >=9 "

            value = np.ones(shape) * value
            value = value.astype(dtypes.DTYPE_PADDLE_NUMPY_MAP[dtype])
            value = value.flatten().tolist()

            graph.make_node(
                'Constant',
                inputs=[],
                outputs=node.output('Out'),
                attrs={
                    'dims': shape,
                    'dtype': dtypes.DTYPE_PADDLE_ONNX_MAP[dtype],
                    'value': value
                })


@op_mapper(['lookup_table_v2', 'lookup_table'])
class Embedding():
    support_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        ids = node.input('Ids', 0)
        if node.type == 'lookup_table' and node.input_shape('Ids', 0)[-1] == 1:
            ids = mapper_helper.squeeze_helper(graph,
                                               node.input('Ids', 0), [-1])
        padding_idx = node.attr('padding_idx')
        input_shape = node.input_shape('W', 0)
        if padding_idx != -1:
            key = node.input('W', 0)
            if -1 in input_shape:
                assert False, "opset version < 11 do not support padding_idx !=-1 and weight is tensor with dynamic shape, please set opset version > 11 or use input_spec to set input shape"
            else:
                data = np.ones(shape=input_shape, dtype=np.float32)
                data[padding_idx] = 0.0
                dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('W', 0)]
                constant = graph.make_node(
                    'Constant',
                    dtype=dtype,
                    dims=input_shape,
                    value=data.flatten().tolist())
                weight_node = graph.make_node(
                    'Mul', inputs=[node.input('W', 0), constant])
                graph.make_node(
                    'Gather',
                    inputs=[weight_node, ids],
                    outputs=node.output('Out'))
        else:
            graph.make_node(
                'Gather',
                inputs=[node.input('W', 0), ids],
                outputs=node.output('Out'))

    @classmethod
    def opset_11(cls, graph, node, **kw):
        ids = node.input('Ids', 0)
        if node.type == 'lookup_table' and node.input_shape('Ids', 0)[-1] == 1:
            ids = mapper_helper.squeeze_helper(graph,
                                               node.input('Ids', 0), [-1])

        padding_idx = node.attr('padding_idx')
        input_shape = node.input_shape('W', 0)
        if padding_idx != -1:
            if -1 in input_shape:
                replace_shape = list(copy.copy(input_shape))
                del (replace_shape[0])
                replace_data = graph.make_node(
                    'Constant',
                    dtype=dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('W',
                                                                        0)],
                    dims=replace_shape,
                    value=[0.0] * np.prod(replace_shape))
                index = graph.make_node(
                    'Constant', dtype=dtypes.ONNX.INT64, value=[padding_idx])
                Scatter_node = graph.make_node(
                    'ScatterND',
                    inputs=[node.input('W', 0), index, replace_data])
                graph.make_node(
                    'Gather',
                    inputs=[Scatter_node, ids],
                    outputs=node.output('Out'))
            else:
                data = np.ones(shape=input_shape, dtype=np.float32)
                data[padding_idx] = 0.0
                dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('W', 0)]
                constant = graph.make_node(
                    'Constant',
                    dtype=dtype,
                    dims=input_shape,
                    value=data.flatten().tolist())
                weight_node = graph.make_node(
                    'Mul', inputs=[node.input('W', 0), constant])
                graph.make_node(
                    'Gather',
                    inputs=[weight_node, ids],
                    outputs=node.output('Out'))
        else:
            graph.make_node(
                'Gather',
                inputs=[node.input('W', 0), ids],
                outputs=node.output('Out'))


@op_mapper('fill_constant_batch_size_like')
class FillConstantBatchSizeLike():
    support_opset_version_range = (9, 12)

    @classmethod
    def opset_10(cls, graph, node, **kw):
        out_shape = node.attr('shape')
        input_dim_idx = node.attr('input_dim_idx')
        output_dim_idx = node.attr('output_dim_idx')

        del out_shape[output_dim_idx]
        out_shape.insert(0, 1)

        dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[node.attr('dtype')]
        if node.attr("str_value") is not None and node.attr("str_value") != "":
            value = eval(node.attr("str_value"))
        else:
            value = node.attr('value')
        input_shape = node.input_shape('Input', 0)
        constant = graph.make_node(
            'Constant',
            dtype=dtype,
            dims=out_shape,
            value=[value] * np.prod(out_shape))

        shape = graph.make_node('Shape', inputs=node.input('Input'))
        start = graph.make_node(
            'Constant', dtype=dtypes.ONNX.INT64, value=[input_dim_idx])
        end = graph.make_node(
            'Constant', dtype=dtypes.ONNX.INT64, value=[input_dim_idx + 1])
        batch = graph.make_node('Slice', inputs=[shape, start, end])
        repeat = batch
        if len(out_shape) > 1:
            repeat = graph.make_node(
                'Constant',
                dtype=dtypes.ONNX.INT64,
                value=[1] * (len(out_shape) - 1))
            repeat = graph.make_node('Concat', inputs=[batch, repeat], axis=-1)
        if output_dim_idx == 0:
            graph.make_node(
                'Tile', inputs=[constant, repeat], outputs=node.output('Out'))
        else:
            out = graph.make_node('Tile', inputs=[constant, repeat])
            perm = list(range(len(out_shape)))
            del perm[0]
            perm.insert(output_dim_idx, 0)
            graph.make_node(
                'Transpose',
                inputs=[out],
                perm=perm,
                outputs=node.output('Out'))


@op_mapper('fill_any_like')
class FullLike():
    '''
    fill_any_like is kernel for paddle op::full_like & ones_like
    '''
    support_opset_version_range = (9, 15)

    @classmethod
    def opset_9(cls, graph, node, **kw):
        shape_node = graph.make_node('Shape', inputs=node.input('X'))
        value = node.attr('value')
        dtype = node.attr('dtype')
        input_dtype = node.input_dtype('X', 0)
        if dtype is None:
            dtype = input_dtype
        np_dtype = dtypes.DTYPE_PADDLE_STR_MAP[dtype]
        onnx_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[dtype]
        graph.make_node(
            'ConstantOfShape',
            inputs=[shape_node],
            outputs=node.output('Out'),
            dims=[1],
            dtype=onnx_dtype,
            value=np.array(value).astype(np_dtype))


@op_mapper('fill_zeros_like')
class FullZeroLike():
    '''
    fill_zeros_like is kernel for paddle op::zeros_like
    '''
    support_opset_version_range = (9, 15)

    @classmethod
    def opset_9(cls, graph, node, **kw):
        shape_node = graph.make_node('Shape', inputs=node.input('X'))
        value = 0
        dtype = node.attr('dtype')
        input_dtype = node.input_dtype('X', 0)
        if dtype is None:
            dtype = input_dtype
        np_dtype = dtypes.DTYPE_PADDLE_STR_MAP[dtype]
        onnx_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[dtype]
        graph.make_node(
            'ConstantOfShape',
            inputs=[shape_node],
            outputs=node.output('Out'),
            dims=[1],
            dtype=onnx_dtype,
            value=np.array(value).astype(np_dtype))


@op_mapper('gather_nd')
class Gather_nd():
    support_opset_version_range = (11, 15)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        data = node.input('X', 0)
        index = node.input('Index', 0)
        index_dtype = node.input_dtype('Index', 0)
        index_node = None
        if index_dtype != paddle.int64:
            index_node = graph.make_node(
                'Cast', inputs=[node.input('Index', 0)], to=dtypes.ONNX.INT64)
        else:
            index_node = index
        graph.make_node(
            'GatherND', inputs=[data, index_node], outputs=node.output('Out'))


@op_mapper('gather')
class Gather():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        axis = node.attr('axis')
        if node.input('Axis', 0) != None:
            axis_node = node.input('Axis', 0)
            try:
                axis = mapper_helper.get_value_from_parameters(graph,
                                                               axis_node)[0]
            except Exception as e:
                raise Exception(
                    "Currently does not support the axis parameter as input tensor"
                    + str(e))
        if axis is None:
            axis = 0
        if len(node.input_shape('Index', 0)) == 1:
            # gather
            graph.make_node(
                'Gather',
                inputs=[node.input('X', 0), node.input('Index', 0)],
                outputs=node.output('Out'),
                attrs={'axis': axis})
        else:
            raise Exception(
                "please try to convert OP:gather(indices's rank >1) with opset_version >= 11."
            )

    @classmethod
    def opset_11(cls, graph, node, **kw):
        axis = node.attr('axis')
        if node.input('Axis', 0) != None:
            axis_node = node.input('Axis', 0)
            try:
                axis = mapper_helper.get_value_from_parameters(graph,
                                                               axis_node)[0]
            except Exception as e:
                raise Exception(
                    "Currently does not support the axis parameter as input tensor"
                    + str(e))
        if axis is None:
            axis = 0
        if len(node.input_shape('Index', 0)) == 1:
            # gather
            graph.make_node(
                'Gather',
                inputs=[node.input('X', 0), node.input('Index', 0)],
                outputs=node.output('Out'),
                attrs={'axis': axis})
        else:
            # gather_nd
            index_dtype = node.input_dtype('Index', 0)
            if index_dtype != paddle.int64:
                index_node = graph.make_node(
                    'Cast',
                    inputs=[node.input('Index', 0)],
                    to=dtypes.ONNX.INT64)
                graph.make_node(
                    'GatherND',
                    inputs=[node.input('X', 0), index_node],
                    outputs=node.output('Out'))
            else:
                graph.make_node(
                    'GatherND',
                    inputs=[node.input('X', 0), node.input('Index', 0)],
                    outputs=node.output('Out'))


@op_mapper('squeeze2')
class Squeeze():
    support_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        shape = node.input_shape('X', 0)
        ret = [i for i, val in enumerate(shape) if val > 1]
        if len(ret) == len(shape):
            graph.make_node(
                'Identity', inputs=node.input('X'), outputs=node.output('Out'))
        else:
            axes = cls.compute_axes(graph, node)
            if len(axes) > 0:
                axes.sort()
                mapper_helper.squeeze_helper(graph,
                                             node.input('X', 0), axes,
                                             node.output('Out'))
            else:
                graph.make_node(
                    'Squeeze',
                    inputs=[node.input('X', 0)],
                    outputs=node.output('Out'))

    @classmethod
    def compute_axes(cls, graph, node):
        shape = node.input_shape('X', 0)
        axes = node.attr('axes')
        if len(axes) > 0:
            axes = [
                axis + len(shape) if axis < 0 else axis
                for i, axis in enumerate(axes)
            ]
        return axes


@op_mapper('assign_value')
class Assign():
    support_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        if len(node.input_names) > 0:
            graph.make_node(
                'Identity', inputs=node.input('X'), outputs=node.output('Out'))
        else:
            parameters = {}
            value = np.array(node.attr('fp32_values'))
            if value is None or value.size < 1:
                value = np.array(node.attr('int32_values'))
            if value is None or value.size < 1:
                value = np.array(node.attr('int64_values'))
            parameter = {
                'data': value,
                'dtype': node.output_dtype("Out", 0),
                'shape': node.attr('shape')
            }
            parameters[node.output('Out', 0)] = parameter
            graph.build_parameters(parameters)


@op_mapper('transpose2')
class Transpose():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_7(cls, graph, node, **kw):
        graph.make_node(
            'Transpose',
            inputs=node.input('X'),
            outputs=node.output('Out'),
            perm=node.attr('axis'))


@op_mapper('flatten2')
class Flatten():
    support_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        input_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('X', 0)]
        if input_dtype in [dtypes.ONNX.INT32, dtypes.ONNX.INT64
                           ] and graph.opset_version < 9:
            raise Exception(
                "int32 or int64 not supported in onnx <9, please try with higher onnx opset_version>=9."
            )

        graph.make_node(
            'Flatten',
            inputs=node.input('X'),
            outputs=node.output('Out'),
            axis=node.attr('axis'))


@op_mapper('flatten_contiguous_range')
class FlattenContiguousRange():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_7(cls, graph, node, **kw):
        dims = len(node.input_shape('X', 0))
        start_axis = node.attr('start_axis')
        end_axis = node.attr('stop_axis')
        shape_node = graph.make_node('Shape', inputs=node.input('X'))
        if start_axis < 0:
            start_axis += dims
        if end_axis < 0:
            end_axis += dims
        if start_axis == 0 and end_axis == dims - 1:
            final_shape = graph.make_node(
                'Constant', value=[-1], dtype=dtypes.ONNX.INT64)
        elif start_axis == 0:
            slice_end = mapper_helper.slice_helper(
                graph, shape_node, axes=[0], starts=[end_axis + 1],
                ends=[dims])
            slices = [
                graph.make_node(
                    'Constant', value=[-1], dtype=dtypes.ONNX.INT64), slice_end
            ]
            final_shape = graph.make_node('Concat', inputs=slices, axis=0)
        elif end_axis == dims - 1:
            slice_start = mapper_helper.slice_helper(
                graph, shape_node, axes=[0], starts=[0], ends=[start_axis])
            slices = [
                slice_start, graph.make_node(
                    'Constant', value=[-1], dtype=dtypes.ONNX.INT64)
            ]
            final_shape = graph.make_node('Concat', inputs=slices, axis=0)
        else:
            slice_start = mapper_helper.slice_helper(
                graph, shape_node, axes=[0], starts=[0], ends=[start_axis])
            slice_end = mapper_helper.slice_helper(
                graph, shape_node, axes=[0], starts=[end_axis + 1],
                ends=[dims])
            slices = [
                slice_start, graph.make_node(
                    'Constant', value=[-1], dtype=dtypes.ONNX.INT64), slice_end
            ]
            final_shape = graph.make_node('Concat', inputs=slices, axis=0)
        graph.make_node(
            'Reshape',
            inputs=[node.input('X')[0], final_shape],
            outputs=node.output('Out'))


@op_mapper('reshape2')
class Reshape():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_7(cls, graph, node, **kw):
        shape_name = 'ShapeTensor'
        if shape_name not in node.inputs or len(node.input(shape_name)) == 0:
            shape_name = 'Shape'
        if shape_name not in node.inputs or len(node.input(shape_name)) == 0:
            if node.attr('shape') is None or len(node.attr('shape')) == 0:
                raise Exception("shape tensor and shape attrubite all unkown.")
        if len(node.input(shape_name)) > 1:
            dims = []
            for i in range(len(node.input(shape_name))):
                dim = node.input(shape_name)[i]
                dim = graph.make_node(
                    'Cast', inputs=[dim], to=dtypes.ONNX.INT64)
                dims.append(dim)
            shape = graph.make_node('Concat', inputs=dims, axis=-1)
            graph.make_node(
                'Reshape',
                inputs=[node.input('X')[0], shape],
                outputs=node.output('Out'))
        elif len(node.input(shape_name)) == 1:
            cast_shape_node = graph.make_node(
                'Cast', inputs=node.input(shape_name), to=dtypes.ONNX.INT64)
            graph.make_node(
                'Reshape',
                inputs=[node.input('X')[0], cast_shape_node],
                outputs=node.output('Out'))
        elif node.attr('shape') is not None and len(node.attr('shape')) > 0:
            shape_node = graph.make_node(
                'Constant',
                attrs={
                    'dtype': dtypes.ONNX.INT64,
                    'value': node.attr('shape')
                })
            reshape_node = graph.make_node(
                'Reshape',
                inputs=[node.input('X')[0], shape_node],
                outputs=node.output('Out'))


@op_mapper('unsqueeze2')
class Unsqueeze():
    support_opset_version_range = (1, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        axes = cls.get_axes(graph, node)
        mapper_helper.unsqueeze_helper(graph,
                                       node.input('X'), axes,
                                       node.output('Out'))

    @classmethod
    def opset_13(cls, graph, node, **kw):
        axes_node = cls.get_axes(graph, node, return_node=True)
        graph.make_node(
            'Unsqueeze',
            inputs=node.input('X') + [axes_node],
            outputs=node.output('Out'))

    @classmethod
    def get_axes(cls, graph, node, return_node=False):
        axes_node = None
        ndim = node.block.vars[node.input('X')[0]].ndim
        if len(node.attr('axes')) > 0:
            axes = node.attr('axes')
        else:
            axes_node = node.input('AxesTensor')[0]
            if axes_node is not None and graph.opset_version > 12 and return_node:
                return axes_node
            try:
                axes = mapper_helper.get_value_from_parameters(graph, axes_node)
            except Exception as e:
                raise Exception(
                    "Currently does not support the axes parameter as input tensor in onnx(opset<13), "
                    "Try converting with opset_version >=13 " + str(e))
        # axes is list of non-negative integers
        axes = [
            axis + ndim + i + 1 if axis < 0 else axis
            for i, axis in enumerate(axes)
        ]

        axes_copy = axes.copy()
        assert sorted(
            axes) == axes_copy, "axes must be arranged in the following order"
        assert len(set(axes)) == len(axes), "axes have duplicate axis"

        if return_node:
            if axes_node is None:
                axes_node = graph.make_node(
                    'Constant',
                    attrs={'dtype': dtypes.ONNX.INT64,
                           'value': axes})
            return axes_node
        return axes


@op_mapper('reciprocal')
class Reciprocal():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        graph.make_node(
            'Reciprocal', inputs=node.input('X'), outputs=node.output('Out'))


@op_mapper('cast')
class Cast():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        graph.make_node(
            'Cast',
            inputs=node.input('X'),
            outputs=node.output('Out'),
            to=dtypes.DTYPE_PADDLE_ONNX_MAP[node.attr('out_dtype')])


@op_mapper('linspace')
class Linspace():
    support_opset_version_range = (9, 15)

    @classmethod
    def opset_9(cls, graph, node, **kw):
        start = node.input('Start', 0)
        stop = node.input('Stop', 0)
        num = node.input('Num', 0)
        dtype = node.attr('dtype')

        start = graph.make_node('Cast', inputs=[start], to=dtypes.ONNX.FLOAT)
        stop = graph.make_node('Cast', inputs=[stop], to=dtypes.ONNX.FLOAT)

        sub_a_node = graph.make_node('Sub', inputs=[stop, start])

        one_node = graph.make_node(
            'Constant',
            dtype=dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('Num', 0)],
            value=[1])

        sub_b_node = graph.make_node('Sub', inputs=[num, one_node])

        sub_b_float_node = graph.make_node(
            'Cast', inputs=[sub_b_node], to=dtypes.ONNX.FLOAT)

        step = graph.make_node('Div', inputs=[sub_a_node, sub_b_float_node])

        range_tensor = graph.make_node(
            'Cast', inputs=[num], to=dtypes.ONNX.INT64)

        one_like_node = graph.make_node(
            'ConstantOfShape',
            inputs=[range_tensor],
            dtype=dtypes.ONNX.FLOAT,
            value=[1])

        none_zero_node = graph.make_node('NonZero', inputs=[one_like_node])

        trans_none_zero_node = graph.make_node(
            'Transpose', inputs=[none_zero_node], perm=[1, 0])

        trans_squeeze = mapper_helper.squeeze_helper(graph,
                                                     trans_none_zero_node, [1])

        trans_squeeze = graph.make_node(
            'Cast', inputs=[trans_squeeze], to=dtypes.ONNX.FLOAT)

        mul_node = graph.make_node('Mul', inputs=[trans_squeeze, step])

        add_node = graph.make_node('Add', inputs=[mul_node, start])
        graph.make_node(
            'Cast',
            inputs=[add_node],
            outputs=node.output('Out'),
            to=dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('Start', 0)])


@op_mapper('clip')
class Clip():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        min_value = node.attr('min')
        max_value = node.attr('max')
        if node.input('Max', 0) is None or len(node.input('Max')) == 0:
            max_ = max_value
        else:
            max_ = node.input('Max', 0)
        if node.input('Min', 0) is None or len(node.input('Min')) == 0:
            min_ = min_value
        else:
            min_ = node.input('Min', 0)
        mapper_helper.clip_helper(graph, node,
                                  node.input('X', 0), max_, min_,
                                  node.output('Out', 0))


@op_mapper(['pad2d', 'pad3d'])
class Pad():
    support_opset_version_range = (1, 12)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        if node.attr('mode') == 'replicate':
            mode = 'edge'
        elif node.attr('mode') == 'circular':
            raise Exception("The padding mode = circular is not supported, " \
                            "Please try the other three ways")
        else:
            mode = node.attr('mode')
        pads = cls.convert_padding(node, **kw)
        if pads is None:
            key = node.input('Paddings', 0)
            padding = None
            if key in graph.parameters.keys():
                paddings = graph.parameters[key].attribute[0].t.int32_data
                if node.attr('data_format') == 'NCHW':
                    pads = [
                        0, 0, paddings[0], paddings[2], 0, 0, paddings[1],
                        paddings[3]
                    ]
                elif node.attr('data_format') == 'NHWC':
                    pads = [
                        0, paddings[0], paddings[2], 0, 0, paddings[1],
                        paddings[3], 0
                    ]
                elif node.attr('data_format') == 'NCDHW':
                    pads = [
                        0, 0, paddings[4], paddings[2], paddings[0], 0, 0,
                        paddings[5], paddings[3], paddings[1]
                    ]
                elif node.attr('data_format') == 'NDHWC':
                    pads = [
                        0, paddings[4], paddings[2], paddings[0], 0, 0,
                        paddings[5], paddings[3], paddings[1], 0
                    ]
            else:
                raise Exception("In Pad op, padding can not be tensor" \
                                "Please set opset version >= 11")

        value = None
        if node.attr('pad_value') is not None:
            value = node.attr('pad_value')
        elif node.attr('value') is not None:
            value = node.attr('value')
        graph.make_node(
            'Pad',
            inputs=node.input('X'),
            outputs=node.output('Out'),
            mode=mode,
            value=value,
            pads=pads)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        pads = cls.convert_padding(node, **kw)
        if node.attr('mode') == 'replicate':
            mode = 'edge'
        elif node.attr('mode') == 'circular':
            raise Exception("The padding mode = circular is not supported, " \
                            "Please try the other three ways")
        else:
            mode = node.attr('mode')
        pads_node = None
        if isinstance(pads, list):
            pads_node = graph.make_node(
                'Constant', attrs={'dtype': dtypes.ONNX.INT64,
                                   'value': pads})
        else:
            key = node.input('Paddings', 0)
            padding = None
            if key in graph.parameters.keys():
                paddings = graph.parameters[key].attribute[0].t.int32_data
                onnx_paddings = None
                if node.attr('data_format') == 'NCHW':
                    onnx_paddings = [
                        0, 0, paddings[0], paddings[2], 0, 0, paddings[1],
                        paddings[3]
                    ]
                elif node.attr('data_format') == 'NHWC':
                    onnx_paddings = [
                        0, paddings[0], paddings[2], 0, 0, paddings[1],
                        paddings[3], 0
                    ]
                elif node.attr('data_format') == 'NCDHW':
                    onnx_paddings = [
                        0, 0, paddings[4], paddings[2], paddings[0], 0, 0,
                        paddings[5], paddings[3], paddings[1]
                    ]
                elif node.attr('data_format') == 'NDHWC':
                    onnx_paddings = [
                        0, paddings[4], paddings[2], paddings[0], 0, 0,
                        paddings[5], paddings[3], paddings[1], 0
                    ]

                pads_node = graph.make_node(
                    'Constant',
                    attrs={'dtype': dtypes.ONNX.INT64,
                           'value': onnx_paddings})
            else:
                padding_node = node.input('Paddings', 0)
                casted_padding_node = graph.make_node(
                    'Cast', inputs=[padding_node], to=dtypes.ONNX.FLOAT)
                zero_node = None
                if node.attr('data_format') == 'NCHW' or node.attr(
                        'data_format') == 'NHWC':
                    zero_node = graph.make_node(
                        'Constant', dtype=dtypes.ONNX.FLOAT, value=[0] * 8)
                else:
                    zero_node = graph.make_node(
                        'Constant', dtype=dtypes.ONNX.FLOAT, value=[0] * 10)
                index = None
                if node.attr('data_format') == 'NCHW':
                    index = graph.make_node(
                        'Constant', dtype=dtypes.ONNX.INT32,
                        value=[2, 6, 3, 7])
                elif node.attr('data_format') == 'NHWC':
                    index = graph.make_node(
                        'Constant', dtype=dtypes.ONNX.INT32,
                        value=[1, 5, 2, 6])
                elif node.attr('data_format') == 'NCDHW':
                    index = graph.make_node(
                        'Constant',
                        dtype=dtypes.ONNX.INT32,
                        value=[4, 9, 3, 8, 2, 7])
                elif node.attr('data_format') == 'NDHWC':
                    index = graph.make_node(
                        'Constant',
                        dtype=dtypes.ONNX.INT32,
                        value=[3, 8, 2, 7, 1, 6])

                float_paddle_node = graph.make_node(
                    'ScatterElements',
                    inputs=[zero_node, index, casted_padding_node])
                paddle_node = graph.make_node(
                    'Cast', inputs=[float_paddle_node], to=dtypes.ONNX.INT64)
                pads_node = paddle_node

        value = None
        if node.attr('pad_value') is not None:
            value = node.attr('pad_value')
        elif node.attr('value') is not None:
            value = node.attr('value')
        value_node = graph.make_node(
            'Constant',
            attrs={
                'dtype': dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('X', 0)],
                'value': value
            })

        graph.make_node(
            'Pad',
            inputs=node.input('X') + [pads_node, value_node],
            outputs=node.output('Out'),
            mode=mode)

    @classmethod
    def convert_padding(cls, node, **kw):
        x_shape = node.input_shape('X', 0)
        paddings = node.attr('paddings')
        if paddings == []:
            return None
        onnx_paddings = None
        if node.attr('data_format') == 'NCHW':
            onnx_paddings = [
                0, 0, paddings[0], paddings[2], 0, 0, paddings[1], paddings[3]
            ]
        elif node.attr('data_format') == 'NHWC':
            onnx_paddings = [
                0, paddings[0], paddings[2], 0, 0, paddings[1], paddings[3], 0
            ]
        elif node.attr('data_format') == 'NCDHW':
            onnx_paddings = [
                0, 0, paddings[4], paddings[2], paddings[0], 0, 0, paddings[5],
                paddings[3], paddings[1]
            ]
        elif node.attr('data_format') == 'NDHWC':
            onnx_paddings = [
                0, paddings[4], paddings[2], paddings[0], 0, 0, paddings[5],
                paddings[3], paddings[1], 0
            ]
        return onnx_paddings


@op_mapper('gaussian_random')
class GaussianRandom():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_7(cls, graph, node, **kw):
        shape_input_list = node.input('ShapeTensorList')
        shape_input = None
        if len(shape_input_list) == 0:
            shape_input = node.input('ShapeTensor')
        else:
            shape_input = graph.make_node(
                "Concat", inputs=node.input('ShapeTensorList'), axis=0)
        if shape_input is None or len(shape_input) == 0:
            graph.make_node(
                'RandomNormal',
                dtype=dtypes.DTYPE_PADDLE_ONNX_MAP[node.attr('dtype')],
                outputs=node.output('Out'),
                shape=node.attr('shape'),
                seed=float(node.attr('seed')),
                mean=node.attr('mean'),
                scale=node.attr('std'))
        else:
            cast_input_shape = graph.make_node(
                'Cast', inputs=shape_input, to=dtypes.ONNX.INT64)
            zero_like_node = graph.make_node(
                'ConstantOfShape',
                inputs=cast_input_shape,
                dims=[1],
                dtype=dtypes.ONNX.FLOAT,
                value=[0])
            graph.make_node(
                'RandomNormalLike',
                dtype=dtypes.DTYPE_PADDLE_ONNX_MAP[node.attr('dtype')],
                outputs=node.output('Out'),
                inputs=zero_like_node,
                seed=float(node.attr('seed')),
                mean=node.attr('mean'),
                scale=node.attr('std'))


@op_mapper('uniform_random_batch_size_like')
class UniformRandom():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_1(cls, graph, node, **kw):
        graph.make_node(
            'RandomUniformLike',
            inputs=node.input('Input'),
            outputs=node.output('Out'),
            high=node.attr('max'),
            dtype=dtypes.DTYPE_PADDLE_ONNX_MAP[node.attr('dtype')],
            low=node.attr('min'),
            seed=float(node.attr('seed')), )


@op_mapper('uniform_random')
class UniformRandom():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_7(cls, graph, node, **kw):
        shape_input_list = node.input('ShapeTensorList')
        shape_input = None
        if len(shape_input_list) == 0:
            shape_input = node.input('ShapeTensor')
        else:
            shape_input = graph.make_node(
                "Concat", inputs=node.input('ShapeTensorList'), axis=0)
        if shape_input is None or len(shape_input) == 0:
            graph.make_node(
                'RandomUniform',
                dtype=dtypes.DTYPE_PADDLE_ONNX_MAP[node.attr('dtype')],
                outputs=node.output('Out'),
                shape=node.attr('shape'),
                seed=float(node.attr('seed')),
                low=node.attr('min'),
                high=node.attr('max'))
        else:
            cast_input_shape = graph.make_node(
                'Cast', inputs=shape_input, to=dtypes.ONNX.INT64)
            zero_like_node = graph.make_node(
                'ConstantOfShape',
                inputs=cast_input_shape,
                dtype=dtypes.ONNX.FLOAT,
                value=[0])
            graph.make_node(
                'RandomUniformLike',
                dtype=dtypes.DTYPE_PADDLE_ONNX_MAP[node.attr('dtype')],
                outputs=node.output('Out'),
                inputs=zero_like_node,
                seed=float(node.attr('seed')),
                low=node.attr('min'),
                high=node.attr('max'))


# 'bilinear_interp', 'nearest_interp', scale only support 2, 4, 6, 8, 10
@op_mapper(
    [
        'bilinear_interp', 'nearest_interp', 'bilinear_interp_v2',
        'nearest_interp_v2', 'bicubic_interp_v2', 'linear_interp_v2',
        'trilinear_interp_v2', 'trilinear_interp', 'linear_interp'
    ],
    mapper_dict={
        'bilinear_interp': 'linear',
        'nearest_interp': 'nearest',
        'bilinear_interp_v2': 'linear',
        'nearest_interp_v2': 'nearest',
        'bicubic_interp_v2': 'cubic',
        'linear_interp_v2': 'linear',
        'trilinear_interp_v2': 'linear',
        'trilinear_interp': 'linear',
        'linear_interp': 'linear',
    },
    opset_op_dict={
        9: 'Upsample',
        10: 'Resize',
    })
class Resize():
    support_opset_version_range = (9, 15)

    @classmethod
    def opset_9(cls, graph, node, **kw):
        inputs = [node.input('X')[0]]
        resize_type = kw['mapper_dict'][node.type]
        cls.waringInfo(graph, node, resize_type)
        if len(node.input('OutSize')) > 0 or len(node.input('SizeTensor')) > 0:
            output_node = cls.compute_outsize_node(
                graph, node, return_scale=True)
        elif 'Scale' in node.inputs and len(node.input('Scale')) > 0:
            output_node = cls.compute_scale_node(graph, node)
        else:
            output_node = cls.compute_attrs_node(graph, node, return_scale=True)

        inputs = inputs + output_node
        op = kw['opset_op_dict'][graph.opset_version]
        graph.make_node(
            op, inputs=inputs, outputs=node.output('Out'), mode=resize_type)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        inputs = [node.input('X')[0]]
        resize_type = kw['mapper_dict'][node.type]
        cls.waringInfo(graph, node, resize_type)
        if node.attr('align_corners'):
            coordinate_transformation_mode = 'align_corners'
        elif node.attr('align_mode') == 1 and resize_type is not 'cubic':
            coordinate_transformation_mode = 'asymmetric'
        elif resize_type == 'nearest':
            coordinate_transformation_mode = 'asymmetric'
        else:
            coordinate_transformation_mode = 'half_pixel'
        roi_node = graph.make_node(
            'Constant',
            attrs={
                'dtype': dtypes.ONNX.FLOAT,
                'value': [1, 1, 1, 1, 1, 1, 1, 1]
            })

        inputs.append(roi_node)
        if len(node.input('OutSize')) > 0 or len(node.input('SizeTensor')) > 0:
            output_node = cls.compute_outsize_node(graph, node)
        elif 'Scale' in node.inputs and len(node.input('Scale')) > 0:
            output_node = cls.compute_scale_node(graph, node)
        else:
            output_node = cls.compute_attrs_node(graph, node)
        inputs = inputs + output_node
        attrs = {
            'mode': resize_type,
            'coordinate_transformation_mode': coordinate_transformation_mode
        }
        if resize_type == 'nearest' and coordinate_transformation_mode == 'asymmetric':
            attrs['nearest_mode'] = 'floor'
        graph.make_node(
            'Resize', inputs=inputs, outputs=node.output('Out'), attrs=attrs)

    @classmethod
    def compute_outsize_node(cls, graph, node, return_scale=False):
        dtype = dtypes.ONNX.INT64
        if return_scale:
            dtype = dtypes.ONNX.FLOAT
        input_shape_node = graph.make_node('Shape', inputs=node.input('X'))
        if dtype != dtypes.ONNX.INT64:
            input_shape_node = graph.make_node(
                'Cast', inputs=[input_shape_node], to=dtype)
        shape_pre_node = mapper_helper.slice_helper(
            graph, input_shape_node, axes=[], starts=[0], ends=[2])

        out_size = [node.attr('out_d'), node.attr('out_h'), node.attr('out_w')]
        out_size = [val for val in out_size if val > 0]
        use_tensor = False
        if len(node.input('OutSize')) > 0 or len(node.input('SizeTensor')) > 0:
            use_tensor = True
        if len(out_size) > 0 and not use_tensor:
            out_size_node = graph.make_node(
                'Constant', attrs={'dtype': dtype,
                                   'value': out_size})
        else:
            out_size_node, _ = mapper_helper.get_node_attr_value(
                graph, node, None, 'OutSize', 'SizeTensor', dtype=dtype)
        out_size_node = graph.make_node(
            'Concat', inputs=[shape_pre_node, out_size_node], axis=0)

        if return_scale:
            scale_node = graph.make_node(
                'Div', inputs=[out_size_node, input_shape_node])
            return [scale_node]

        scale_empty_node = graph.make_node(
            'Constant', attrs={'dtype': dtypes.ONNX.FLOAT,
                               'value': []})
        return [scale_empty_node, out_size_node]

    @classmethod
    def compute_scale_node(cls, graph, node):
        cast_scale = graph.make_node(
            'Cast', inputs=node.input('Scale'), to=dtypes.ONNX.FLOAT)
        inputs_cocat = []
        const_node = graph.make_node(
            'Constant', attrs={'dtype': dtypes.ONNX.FLOAT,
                               'value': [1, 1]})
        inputs_cocat.append(const_node)
        scale = node.attr('scale')
        if isinstance(scale, (float, int)):
            cast_scale = [cast_scale] * (len(node.input_shape('X', 0)) - 2)
            inputs_cocat = inputs_cocat + cast_scale
        else:
            inputs_cocat = inputs_cocat + [cast_scale]
        scale_node = graph.make_node('Concat', inputs=inputs_cocat, axis=0)
        return [scale_node]

    @classmethod
    def compute_attrs_node(cls, graph, node, return_scale=False):
        out_size = [node.attr('out_d'), node.attr('out_h'), node.attr('out_w')]
        scale = node.attr('scale')
        if isinstance(scale, (float, int)):
            scale = [scale] * (len(node.input_shape('X', 0)) - 2)

        out_size = [val for val in out_size if val > 0]
        if len(out_size) > 0:
            output_node = cls.compute_outsize_node(
                graph, node, return_scale=return_scale)
            return output_node

        assert len(scale) > 0, Exception("scale size should > 0!")
        scale_node = graph.make_node(
            'Constant',
            attrs={'dtype': dtypes.ONNX.FLOAT,
                   'value': [1, 1] + scale})
        return [scale_node]

    @classmethod
    def waringInfo(cls, graph, node, resize_type):
        assert node.attrs['data_layout'] == 'NCHW', \
            "The conv data layout should be 'NCHW' , but received data format " \
            "is %s." % node.attrs['data_format']

        if graph.opset_version < 11:
            if node.attr('align_corners') or resize_type in ["cubic"]:
                raise Exception(
                    "When align_corners is true or resize_type is 'cubic', the case isn't supported in onnx(opset<=10), "
                    "Try converting with opset_version>= 11 ")
            if node.attr('align_mode') == 0 and resize_type in [
                    "bilinear", "linear", "trilinear"
            ]:
                raise Exception(
                    "When align_mode == 0 and resize_type is 'bilinear' or 'linear or 'trilinear', the case isn't "
                    "supported in onnx(opset<=10), Try converting with opset_version>= 11 "
                )


@op_mapper('pixel_shuffle')
class PixelShuffle():
    support_opset_version_range = (11, 15)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        upscale_factor = node.attr('upscale_factor')

        node = graph.make_node(
            'DepthToSpace',
            inputs=node.input('X'),
            outputs=node.output('Out'),
            blocksize=upscale_factor,
            mode='CRD')


@op_mapper('scatter')
class Scatter():
    support_opset_version_range = (11, 15)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        ids = node.input('Ids', 0)
        input_dtype = dtypes.DTYPE_PADDLE_ONNX_MAP[node.input_dtype('Ids', 0)]
        if input_dtype != dtypes.ONNX.INT64:
            ids = graph.make_node('Cast', inputs=[ids], to=dtypes.ONNX.INT64)

        shape = graph.make_node(
            'Constant',
            value=[node.input_shape('Ids', 0)[0], 1],
            dtype=dtypes.ONNX.INT64)
        reshape_index = graph.make_node('Reshape', inputs=[ids, shape])
        if not node.attr('overwrite'):
            raise Exception("overwrite = False not support yet.")
        else:
            graph.make_node(
                'ScatterND',
                inputs=[
                    node.input('X', 0), reshape_index, node.input('Updates', 0)
                ],
                outputs=node.output('Out'))


@op_mapper('scatter_nd_add')
class ScatterndAdd():
    support_opset_version_range = (11, 12)

    @classmethod
    def opset_11(cls, graph, node, **kw):
        shape = graph.make_node('Shape', inputs=node.input('X', 0))
        zero_like_node = graph.make_node(
            'ConstantOfShape',
            inputs=[shape],
            dims=[1],
            dtype=dtypes.ONNX.FLOAT,
            value=[0])
        add_node = graph.make_node(
            'ScatterND',
            inputs=[
                zero_like_node, node.input('Index', 0), node.input('Updates', 0)
            ], )
        graph.make_node(
            'Add',
            inputs=[node.input('X', 0), add_node],
            outputs=node.output('Out'))


@op_mapper('meshgrid')
class Meshgrid():
    support_opset_version_range = (8, 15)

    @classmethod
    def opset_8(cls, graph, node, **kw):
        tensors = [t for t in list(node.input('X'))]
        tensors_shape = [graph.make_node('Shape', inputs=t) for t in tensors]
        out_shape = graph.make_node('Concat', inputs=tensors_shape, axis=0)
        out = []
        for i, t in enumerate(tensors):
            shape_i = [
                graph.make_node(
                    'Constant',
                    attrs={'dtype': dtypes.ONNX.INT64,
                           'value': [1]})
            ] * len(tensors)
            shape_i[i] = tensors_shape[i]
            t_reshaped = graph.make_node(
                'Reshape',
                inputs=[t, graph.make_node(
                    'Concat', inputs=shape_i, axis=0)])
            out.append(
                graph.make_node(
                    'Expand',
                    inputs=[t_reshaped, out_shape],
                    outputs=node.output('Out')[i]))


@op_mapper('flip')
class Flip():
    support_opset_version_range = (7, 15)

    @classmethod
    def opset_7(cls, graph, node, **kw):
        inputs = node.input('X')
        x_dtype = node.input_dtype('X', 0)
        if x_dtype == paddle.bool or x_dtype == paddle.float64:
            inputs = [
                graph.make_node(
                    "Cast", inputs=inputs, to=dtypes.ONNX.FLOAT)
            ]
        axes = node.attr("axis")
        if not isinstance(axes, list):
            axes = [axes]
        input_shape = node.input_shape('X', 0)

        for i, axis in enumerate(axes):
            if axis < 0:
                axes[i] += len(input_shape)
            assert input_shape[
                axis] > 0, "The dimension in axis of input must be fixed for flip operator, but now the input shape({}) in axis({}) is unknow.".format(
                    input_shape, axis)

        temp_input = inputs[0]
        for i, axis in enumerate(axes):
            if input_shape[axis] == 1:
                if i != len(axes) - 1:
                    continue
                else:
                    if x_dtype == paddle.bool or x_dtype == paddle.float64:
                        graph.make_node(
                            "Cast",
                            inputs=[temp_input],
                            outputs=node.output("Out"),
                            to=dtypes.DTYPE_PADDLE_ONNX_MAP[x_dtype])
                    else:
                        graph.make_node(
                            "Identity",
                            inputs=[temp_input],
                            outputs=node.output("Out"))
            else:
                splits = graph.make_node(
                    "Split",
                    inputs=[temp_input],
                    outputs=input_shape[axis],
                    axis=axis,
                    split=[1] * input_shape[axis])
                reversed_splits = splits[::-1]
                if i != len(axes) - 1:
                    temp_input = graph.make_node(
                        "Concat", inputs=reversed_splits, axis=axis)
                else:
                    if x_dtype == paddle.bool or x_dtype == paddle.float64:
                        out = graph.make_node(
                            "Concat", inputs=reversed_splits, axis=axis)
                        graph.make_node(
                            "Cast",
                            inputs=[out],
                            outputs=node.output("Out"),
                            to=dtypes.DTYPE_PADDLE_ONNX_MAP[x_dtype])
                    else:
                        graph.make_node(
                            "Concat",
                            inputs=reversed_splits,
                            outputs=node.output("Out"),
                            axis=axis)

    @classmethod
    def opset_13(cls, graph, node, **kw):
        inputs = node.input('X')
        x_dtype = node.input_dtype('X', 0)
        if x_dtype == paddle.bool or x_dtype == paddle.float64:
            inputs = [
                graph.make_node(
                    "Cast", inputs=inputs, to=dtypes.ONNX.FLOAT)
            ]
        axes = node.attr("axis")
        if not isinstance(axes, list):
            axes = [axes]
        input_shape = node.input_shape('X', 0)

        for i, axis in enumerate(axes):
            if axis < 0:
                axes[i] += len(input_shape)
            assert input_shape[
                axis] > 0, "The dimension in axis of input must be fixed for flip operator, but now the input shape({}) in axis({}) is unknow.".format(
                    input_shape, axis)

        temp_input = inputs[0]
        for i, axis in enumerate(axes):
            if input_shape[axis] == 1:
                if i != len(axes) - 1:
                    continue
                else:
                    if x_dtype == paddle.bool or x_dtype == paddle.float64:
                        graph.make_node(
                            "Cast",
                            inputs=[temp_input],
                            outputs=node.output("Out"),
                            to=dtypes.DTYPE_PADDLE_ONNX_MAP[x_dtype])
                    else:
                        graph.make_node(
                            "Identity",
                            inputs=[temp_input],
                            outputs=node.output("Out"))
            else:
                split = graph.make_node(
                    'Constant',
                    attrs={
                        'dtype': dtypes.ONNX.INT64,
                        'value': [1] * input_shape[axis]
                    })
                splits = graph.make_node(
                    "Split",
                    inputs=[temp_input, split],
                    outputs=input_shape[axis],
                    axis=axis)
                reversed_splits = splits[::-1]
                if i != len(axes) - 1:
                    temp_input = graph.make_node(
                        "Concat", inputs=reversed_splits, axis=axis)
                else:
                    if x_dtype == paddle.bool or x_dtype == paddle.float64:
                        out = graph.make_node(
                            "Concat", inputs=reversed_splits, axis=axis)
                        graph.make_node(
                            "Cast",
                            inputs=[out],
                            outputs=node.output("Out"),
                            to=dtypes.DTYPE_PADDLE_ONNX_MAP[x_dtype])
                    else:
                        graph.make_node(
                            "Concat",
                            inputs=reversed_splits,
                            outputs=node.output("Out"),
                            axis=axis)
