/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
 * CANN Open Software License Agreement Version 2.0 (the "License").
 * Please refer to the License for details. You may not use this file except in compliance with the License.
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
 * See LICENSE in the root of the software repository for the full text of the License.
 */

#include "onnx_common.h"
#include "op_nn_proto_extend.h"
#include "pooling/max_pool3_d/op_graph/max_pool3_d_proto.h"
#include "pooling/max_pool_v3/op_graph/max_pool_v3_proto.h"

extern "C" {
typedef int32_t rtError_t;
static const int32_t RT_ERROR_NONE = 0;
rtError_t rtGetSocVersion(char* version, const uint32_t maxLen);
}

using namespace ge;
namespace domi {
static const int OUTPUT_SIZE = 1;
static const int LEN_ATTR_1D = 1;
struct TbeAttr {
    bool trans_2d = false;
    std::vector<int64_t> ksize;
    std::vector<int64_t> strides;
    std::string padding_mode;
    std::vector<int64_t> pads;
    int64_t ceil_mode;
    std::vector<int64_t> dilation;
};

struct OnnxAttr {
    bool trans_2d = false;
    std::string auto_pad = "NOTSET";
    int64_t ceil_mode = 0;
    std::vector<int64_t> dilations;
    std::vector<int64_t> kernel_shape;
    std::vector<int64_t> pads;
    std::vector<int64_t> strides;
};

static Status UpdateOnnxAttrFromOnnx(const ge::onnx::NodeProto* node, OnnxAttr& onnx_attr)
{
    for (const auto& attr : node->attribute()) {
        if (attr.name() == "kernel_shape" && attr.type() == ge::onnx::AttributeProto::INTS) {
            for (int i = 0; i < attr.ints_size(); i++) {
                onnx_attr.kernel_shape.push_back(attr.ints(i));
            }
            if (attr.ints_size() == LEN_ATTR_1D) {
                onnx_attr.kernel_shape.push_back(1);
                onnx_attr.trans_2d = true;
            }
        } else if (attr.name() == "strides" && attr.type() == ge::onnx::AttributeProto::INTS) {
            for (int i = 0; i < attr.ints_size(); i++) {
                onnx_attr.strides.push_back(attr.ints(i));
            }
            if (attr.ints_size() == LEN_ATTR_1D) {
                onnx_attr.strides.push_back(1);
            }
        } else if (attr.name() == "auto_pad" && attr.type() == ge::onnx::AttributeProto::STRING) {
            onnx_attr.auto_pad = attr.s();
        } else if (attr.name() == "pads" && attr.type() == ge::onnx::AttributeProto::INTS) {
            unsigned int len = attr.ints_size();
            if (len & 1) {
                OP_LOGE("MaxPool",
                        "the length of pads must be even, such as [x1_begin, x2_begin...x1_end, x2_end,...]");
                return FAILED;
            }
            for (unsigned int i = 0; i < len / 2; i++) {
                onnx_attr.pads.push_back(attr.ints(i));
                onnx_attr.pads.push_back(attr.ints(i + len / 2));
            }
            if (len / 2 == LEN_ATTR_1D) {
                onnx_attr.pads.push_back(0);
                onnx_attr.pads.push_back(0);
            }
        } else if (attr.name() == "ceil_mode" && attr.type() == ge::onnx::AttributeProto::INT) {
            onnx_attr.ceil_mode = attr.i();
        } else if (attr.name() == "dilations" && attr.type() == ge::onnx::AttributeProto::INTS) {
            for (int i = 0; i < attr.ints_size(); i++) {
                onnx_attr.dilations.push_back(attr.ints(i));
            }
            if (attr.ints_size() == LEN_ATTR_1D) {
                onnx_attr.dilations.push_back(attr.ints(0));
                onnx_attr.trans_2d = true;
            }
        } else if (attr.name() == "storage_order" && attr.type() == ge::onnx::AttributeProto::INT && attr.i() == 1) {
            OP_LOGE("MaxPool", "only support storage_order=0, but 1 is obtained now.");
            return FAILED;
        }
    }
    if (onnx_attr.kernel_shape.size() == 0) {
        OP_LOGE("MaxPool", "kernel_shape is required attribute, but NONE is obtained now.");
        return FAILED;
    }
    return SUCCESS;
}

static void MaybeChangeAttr(std::vector<int64_t>& value, int64_t length, int64_t num)
{
    if (value.empty()) {
        value = std::vector<int64_t>(length, num);
    } else if (length == 4 && num != 0) {
        value.resize(length);
        value[3] = value[1];
        value[2] = value[0];
        value[1] = 1;
        value[0] = 1;
    }
}

static Status ParseParamsMaxPool(const Message* op_src, ge::Operator& op_dest)
{
    const ge::onnx::NodeProto* node = dynamic_cast<const ge::onnx::NodeProto*>(op_src);
    if (nullptr == node) {
        OP_LOGE("MaxPool", "Dynamic cast op_src to NodeProto failed.");
        return FAILED;
    }
    int op_output_size = node->output_size();
    if (op_output_size != OUTPUT_SIZE) {
        OP_LOGE("MaxPool", "The output of Indices is not support, transforming failed.");
        return FAILED;
    }

    op_dest.DynamicInputRegister("x", 1);
    op_dest.DynamicOutputRegister("y", 1);
    op_dest.SetAttr("original_type", "ai.onnx::11::MaxPool");
    OnnxAttr node_attr;
    if (UpdateOnnxAttrFromOnnx(node, node_attr) != SUCCESS) {
        return FAILED;
    }

    int64_t dims = node_attr.kernel_shape.size();
    if (dims != 2 && dims != 3) {
        OP_LOGE("MaxPool", "Only support 2D/3D, but the length of kernel_shape is %ld", dims);
        return FAILED;
    }

    op_dest.SetAttr("name", node->name());
    op_dest.SetAttr("dims", dims);
    op_dest.SetAttr("ceil_mode", node_attr.ceil_mode);
    op_dest.SetAttr("trans_2d", node_attr.trans_2d);
    std::map<string, string> padding_mode = {
        {"NOTSET", "CALCULATED"}, {"SAME_UPPER", "SAME"}, {"SAME_LOWER", "SAME"}, {"VALID", "VALID"}};
    op_dest.SetAttr("padding_mode", padding_mode[node_attr.auto_pad]);

    MaybeChangeAttr(node_attr.kernel_shape, dims == 2 ? dims + 2 : dims, 1);
    op_dest.SetAttr("ksize", node_attr.kernel_shape);

    MaybeChangeAttr(node_attr.strides, dims == 2 ? dims + 2 : dims, 1);
    op_dest.SetAttr("strides", node_attr.strides);

    MaybeChangeAttr(node_attr.pads, dims * 2, 0);
    op_dest.SetAttr("pads", node_attr.pads);

    if (dims == 3) {
        MaybeChangeAttr(node_attr.dilations, dims + 2, 1);
    } else {
        MaybeChangeAttr(node_attr.dilations, dims * 2, 1);
    }
    op_dest.SetAttr("dilation", node_attr.dilations);

    return SUCCESS;
}

static bool IsRegBasePlatform()
{
    char socVersion[256] = {0};
    if (rtGetSocVersion(socVersion, sizeof(socVersion)) != RT_ERROR_NONE) {
        OP_LOGE("MaxPool", "Get soc version failed, fallback to aicpu path.");
        return false;
    }
    std::string socStr(socVersion);
    bool isRegbase = (socStr.find("Ascend950") != std::string::npos || socStr.find("MC62") != std::string::npos);
    OP_LOGD("MaxPool", "IsRegBasePlatform check: soc_version=%s, is_regbase=%d.", socStr.c_str(), isRegbase);
    return isRegbase;
}

static Status UpdateTbeAttrFromOp(const Operator& op, TbeAttr& tbe_attr, int dims)
{
    if (op.GetAttr("ceil_mode", tbe_attr.ceil_mode) != SUCCESS) {
        OP_LOGE("MaxPool", "get ceil_mode from op failed");
        return FAILED;
    };
    if (op.GetAttr("padding_mode", tbe_attr.padding_mode) != SUCCESS) {
        OP_LOGE("MaxPool", "get padding_mode from op failed");
        return FAILED;
    };
    if (op.GetAttr("ksize", tbe_attr.ksize) != SUCCESS) {
        OP_LOGE("MaxPool", "get ksize from op failed");
        return FAILED;
    };
    if (op.GetAttr("strides", tbe_attr.strides) != SUCCESS) {
        OP_LOGE("MaxPool", "get strides from op failed");
        return FAILED;
    };
    if (op.GetAttr("pads", tbe_attr.pads) != SUCCESS) {
        OP_LOGE("MaxPool", "get pads from op failed");
        return FAILED;
    };
    if (op.GetAttr("dilation", tbe_attr.dilation) != SUCCESS) {
        OP_LOGE("MaxPool", "get dilation from op failed");
        return FAILED;
    };
    if (op.GetAttr("trans_2d", tbe_attr.trans_2d) != SUCCESS) {
        OP_LOGW("MaxPool", "get trans_2d from op failed, use default.");
    };
    return SUCCESS;
}

static void GenAicpuOp(Operator& op, std::vector<int64_t> ksize, std::vector<int64_t> strides,
                       std::vector<int64_t> pads, std::string padding_mode, Operator& input, std::string ori_name)
{
    std::vector<int64_t> ksize_transpose = {ksize[0], ksize[2], ksize[3], ksize[1]};
    std::vector<int64_t> strides_transpose = {strides[0], strides[2], strides[3], strides[1]};
    auto transposeIn = op::TransposeD(ori_name + "_permuteIn").set_input_x(input).set_attr_perm({0, 2, 3, 1});

    std::vector<int32_t> pads_vector(8, 0);
    bool use_pad = false;
    for (size_t i = 0; i < pads.size(); i++) {
        pads_vector[i + 2] = static_cast<int32_t>(pads[i]);
        if (pads[i] != 0) {
            use_pad = true;
        }
    }
    if (use_pad) {
        int64_t len = pads_vector.size();
        std::vector<int64_t> dims_pad = {len};
        ge::Tensor pads_tensor = Vec2Tensor(pads_vector, dims_pad, ge::DT_INT32, ge::FORMAT_NHWC);
        auto paddings = op::Const(ori_name + "_paddings").set_attr_value(pads_tensor);

        float tmp_const = -65504.0f;
        ge::Tensor scalar_const_value = CreateScalar(tmp_const, ge::DT_FLOAT, ge::FORMAT_NHWC);
        auto constant_values = op::Const(ori_name + "_constant_values").set_attr_value(scalar_const_value);

        auto padV2 = op::PadV2(ori_name + "_PadV2")
                         .set_input_x(transposeIn)
                         .set_input_paddings(paddings)
                         .set_input_constant_values(constant_values);

        op = op::MaxPool(ori_name + "_MaxPool")
                 .set_input_x(padV2)
                 .set_attr_ksize(ksize_transpose)
                 .set_attr_strides(strides_transpose)
                 .set_attr_padding(padding_mode)
                 .set_attr_data_format("NHWC");
    } else {
        op = op::MaxPool(ori_name + "_MaxPool")
                 .set_input_x(transposeIn)
                 .set_attr_ksize(ksize_transpose)
                 .set_attr_strides(strides_transpose)
                 .set_attr_padding(padding_mode)
                 .set_attr_data_format("NHWC");
    }
}

static Status UpdateFormat(Operator& op, Format format)
{
    ge::TensorDesc orgTensorX = op.GetInputDesc("x");
    orgTensorX.SetOriginFormat(format);
    orgTensorX.SetFormat(format);
    auto ret = op.UpdateInputDesc("x", orgTensorX);
    if (ret != ge::GRAPH_SUCCESS) {
        OP_LOGE(GetOpName(op).c_str(), "update input x format failed.");
        return FAILED;
    }
    OP_LOGD(GetOpName(op).c_str(), "update input x format success, now is %d", op.GetInputDesc("x").GetFormat());

    ge::TensorDesc orgTensorY = op.GetOutputDesc("y");
    orgTensorY.SetOriginFormat(format);
    orgTensorY.SetFormat(format);
    ret = op.UpdateOutputDesc("y", orgTensorY);
    if (ret != ge::GRAPH_SUCCESS) {
        OP_LOGE(GetOpName(op).c_str(), "update output y format failed.");
        return FAILED;
    }
    OP_LOGD(GetOpName(op).c_str(), "update output y format success, now is %d", op.GetOutputDesc("y").GetFormat());
    return SUCCESS;
}

static Status ParseOpToGraphMaxPool(const Operator& op, Graph& graph)
{
    std::string ori_name;
    if (op.GetAttr("name", ori_name) != SUCCESS) {
        OP_LOGE(GetOpName(op).c_str(), "get name from op failed.");
        return FAILED;
    }

    int dims = 0;
    if (op.GetAttr("dims", dims) != SUCCESS) {
        OP_LOGE("MaxPool", "get dims from op failed");
        return FAILED;
    }
    TbeAttr tbe_attr;
    if (UpdateTbeAttrFromOp(op, tbe_attr, dims) != SUCCESS) {
        return FAILED;
    }

    ge::Operator data0 = op::Data(ori_name + "_data0").set_attr_index(0);
    std::vector<Operator> inputs{data0};
    std::vector<std::pair<Operator, std::vector<size_t>>> outputs;

    if (dims == 2) {
        if (tbe_attr.trans_2d) {
            ge::Operator::OpListInt axes = {3};
            data0 = op::Unsqueeze(ori_name + "_UnsqueezeX").set_input_x(data0).set_attr_axes(axes);
        }
        bool isBigKernel = tbe_attr.ksize[2] * tbe_attr.ksize[3] > 255 ||
                           (tbe_attr.strides[2] > 63 || tbe_attr.strides[3] > 63);
        if (!IsRegBasePlatform() && isBigKernel) {
            Operator aicpu_op;
            GenAicpuOp(aicpu_op, tbe_attr.ksize, tbe_attr.strides, tbe_attr.pads, "VALID", data0, ori_name);

            if (UpdateFormat(aicpu_op, ge::FORMAT_NHWC) != SUCCESS) {
                return FAILED;
            }
            ge::Operator transposeOut = op::TransposeD(ori_name + "_permuteOut")
                                            .set_input_x(aicpu_op)
                                            .set_attr_perm({0, 3, 1, 2});
            if (tbe_attr.trans_2d) {
                ge::Operator::OpListInt axis = {3};
                transposeOut = op::Squeeze(ori_name + "_SqueezeTranspose")
                                   .set_input_x(transposeOut)
                                   .set_attr_axis(axis);
            }
            ge::TensorDesc orgTensorY = transposeOut.GetOutputDesc("y");
            orgTensorY.SetOriginFormat(ge::FORMAT_NCHW);
            orgTensorY.SetFormat(ge::FORMAT_NCHW);
            auto ret = transposeOut.UpdateOutputDesc("y", orgTensorY);
            if (ret != ge::GRAPH_SUCCESS) {
                OP_LOGE(transposeOut.GetName().c_str(), "update output y format failed.");
                return FAILED;
            }
            OP_LOGD(transposeOut.GetName().c_str(), "update output y format success, now is %d",
                    transposeOut.GetOutputDesc("y").GetFormat());
            outputs.emplace_back(transposeOut, std::vector<std::size_t>{0});
        } else {
            ge::Operator maxpoolv3 = op::MaxPoolV3(ori_name + "_MaxPoolV3")
                                         .set_input_x(data0)
                                         .set_attr_ksize(tbe_attr.ksize)
                                         .set_attr_strides(tbe_attr.strides)
                                         .set_attr_padding_mode(tbe_attr.padding_mode)
                                         .set_attr_pads(tbe_attr.pads)
                                         .set_attr_ceil_mode(tbe_attr.ceil_mode == 1)
                                         .set_attr_data_format("NCHW");
            if (tbe_attr.trans_2d) {
                ge::Operator::OpListInt axis = {3};
                maxpoolv3 = op::Squeeze(ori_name + "_SqueezeMaxpoolv3").set_input_x(maxpoolv3).set_attr_axis(axis);
            }
            if (UpdateFormat(maxpoolv3, ge::FORMAT_NCHW) != SUCCESS) {
                return FAILED;
            }
            outputs.emplace_back(maxpoolv3, std::vector<std::size_t>{0});
        }
    } else {
        auto maxpool3d = op::MaxPool3D(ori_name + "_MaxPool3D")
                             .set_input_x(data0)
                             .set_attr_ksize(tbe_attr.ksize)
                             .set_attr_strides(tbe_attr.strides)
                             .set_attr_padding(tbe_attr.padding_mode)
                             .set_attr_pads(tbe_attr.pads)
                             .set_attr_dilation(tbe_attr.dilation)
                             .set_attr_ceil_mode(tbe_attr.ceil_mode)
                             .set_attr_data_format("NCDHW");
        if (UpdateFormat(maxpool3d, ge::FORMAT_NCDHW) != SUCCESS) {
            return FAILED;
        }

        outputs.emplace_back(maxpool3d, std::vector<std::size_t>{0});
    }

    graph.SetInputs(inputs).SetOutputs(outputs);
    return SUCCESS;
}

REGISTER_CUSTOM_OP("PartitionedCall")
    .FrameworkType(ONNX)
    .OriginOpType({"ai.onnx::8::MaxPool", "ai.onnx::9::MaxPool", "ai.onnx::10::MaxPool", "ai.onnx::11::MaxPool",
                   "ai.onnx::12::MaxPool", "ai.onnx::13::MaxPool", "ai.onnx::14::MaxPool", "ai.onnx::15::MaxPool",
                   "ai.onnx::16::MaxPool", "ai.onnx::17::MaxPool", "ai.onnx::18::MaxPool"})
    .ParseParamsFn(ParseParamsMaxPool)
    .ParseOpToGraphFn(ParseOpToGraphMaxPool)
    .ImplyType(ImplyType::TVM);
} // namespace domi