/*
 * SPDX-FileCopyrightText: Copyright (c) 1993-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

/*
 **************************************************************************
 * Modified from mmcv (https://github.com/open-mmlab/mmcv/tree/master/mmcv)
 * Copyright (c) OpenMMLab. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 [see LICENSE for details]
 * https://github.com/open-mmlab/mmcv/blob/master/LICENSE
 **************************************************************************
 */

#include "modulatedDeformConvPlugin.h"
#include <algorithm>
#include <memory>

using namespace nvinfer1;
using namespace nvinfer1::plugin;
using nvinfer1::plugin::ModulatedDeformableConvPluginDynamic;
using nvinfer1::plugin::ModulatedDeformableConvPluginDynamicCreator;

void ModulatedDeformConvForwardCUDAKernelLauncherFloat(float const* input, float const* weight, float const* bias,
    float const* offset, float const* mask, float* output, void* workspace, int32_t batch, int32_t channels,
    int32_t height, int32_t width, int32_t channelsOut, int32_t kernelW, int32_t kernelH, int32_t strideW,
    int32_t strideH, int32_t padW, int32_t padH, int32_t dilationW, int32_t dilationH, int32_t group,
    int32_t deformableGroup, int32_t im2colStep, nvinfer1::pluginInternal::cublasHandle_t cublasHandle,
    cudaStream_t stream);

void ModulatedDeformConvForwardCUDAKernelLauncherHalf(half const* input, half const* weight, half const* bias,
    half const* offset, half const* mask, half* output, void* workspace, int32_t batch, int32_t channels,
    int32_t height, int32_t width, int32_t channelsOut, int32_t kernelW, int32_t kernelH, int32_t strideW,
    int32_t strideH, int32_t padW, int32_t padH, int32_t dilationW, int32_t dilationH, int32_t group,
    int32_t deformableGroup, int32_t im2colStep, nvinfer1::pluginInternal::cublasHandle_t cublasHandle,
    cudaStream_t stream);

namespace
{
static char const* PLUGIN_VERSION{"2"};
static char const* PLUGIN_NAME{"ModulatedDeformConv2d"};
} // namespace

ModulatedDeformableConvPluginDynamic::ModulatedDeformableConvPluginDynamic(std::string const& name,
    nvinfer1::Dims const stride, nvinfer1::Dims const padding, nvinfer1::Dims const dilation,
    int32_t const deformableGroup, int32_t const group)
    : mLayerName(name)
    , mStride(stride)
    , mPadding(padding)
    , mDilation(dilation)
    , mDeformableGroup(deformableGroup)
    , mGroup(group)
    , mWithBias(0)
{
}

ModulatedDeformableConvPluginDynamic::~ModulatedDeformableConvPluginDynamic() {}

nvinfer1::IPluginV3* ModulatedDeformableConvPluginDynamic::clone() noexcept
{
    try
    {
        auto plugin = std::make_unique<ModulatedDeformableConvPluginDynamic>(
            mLayerName, mStride, mPadding, mDilation, mDeformableGroup, mGroup);
        plugin->setPluginNamespace(getPluginNamespace());
        return plugin.release();
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

IPluginCapability* ModulatedDeformableConvPluginDynamic::getCapabilityInterface(PluginCapabilityType type) noexcept
{
    try
    {
        if (type == PluginCapabilityType::kBUILD)
        {
            return static_cast<IPluginV3OneBuild*>(this);
        }
        if (type == PluginCapabilityType::kRUNTIME)
        {
            return static_cast<IPluginV3OneRuntime*>(this);
        }
        PLUGIN_ASSERT(type == PluginCapabilityType::kCORE);
        return static_cast<IPluginV3OneCore*>(this);
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

int32_t ModulatedDeformableConvPluginDynamic::getOutputShapes(nvinfer1::DimsExprs const* inputs, int32_t nbInputs,
    nvinfer1::DimsExprs const* shapeInputs, int32_t nbShapeInputs, nvinfer1::DimsExprs* outputs, int32_t nbOutputs,
    nvinfer1::IExprBuilder& exprBuilder) noexcept
{
    try
    {
        PLUGIN_VALIDATE(inputs != nullptr && outputs != nullptr);
        PLUGIN_VALIDATE(nbOutputs == 1);
        PLUGIN_VALIDATE(nbInputs == 4 || nbInputs == 5); // nbInputs depends on bias

        // Output shape is (N, C_out, H_out, W_out)
        // N = N_in (inputs[0].d[0])
        // C_out = C_weight (inputs[3].d[0])
        // H_out = H_offset (inputs[1].d[2])
        // W_out = W_offset (inputs[1].d[3])
        outputs[0].nbDims = 4;
        outputs[0].d[0] = inputs[0].d[0]; // Batch size
        outputs[0].d[1] = inputs[3].d[0]; // Output channels from weight tensor
        outputs[0].d[2] = inputs[1].d[2]; // Output height from offset tensor
        outputs[0].d[3] = inputs[1].d[3]; // Output width from offset tensor
        return STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return STATUS_FAILURE;
}

bool ModulatedDeformableConvPluginDynamic::supportsFormatCombination(
    int32_t pos, nvinfer1::DynamicPluginTensorDesc const* inOut, int32_t nbInputs, int32_t nbOutputs) noexcept
{
    try
    {
        if (pos == 0)
        {
            // Input tensor must be FP32 or FP16 and linear format
            return ((inOut[pos].desc.type == nvinfer1::DataType::kFLOAT
                        || inOut[pos].desc.type == nvinfer1::DataType::kHALF)
                && inOut[pos].desc.format == nvinfer1::TensorFormat::kLINEAR);
        }
        // All other tensors must have the same type and format as the input tensor
        return inOut[pos].desc.type == inOut[0].desc.type && inOut[pos].desc.format == inOut[0].desc.format;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return false;
}

int32_t ModulatedDeformableConvPluginDynamic::configurePlugin(nvinfer1::DynamicPluginTensorDesc const* /* in */,
    int32_t /* nbInputs */, nvinfer1::DynamicPluginTensorDesc const* /* out */, int32_t /* nbOutputs */) noexcept
{
    // Bias presence (mWithBias) is determined dynamically in onShapeChange based on nbInputs.
    // No other configuration needed here.
    return STATUS_SUCCESS;
}

int32_t ModulatedDeformableConvPluginDynamic::onShapeChange(nvinfer1::PluginTensorDesc const* /* inputs */,
    int32_t nbInputs, nvinfer1::PluginTensorDesc const* /* outputs */, int32_t /* nbOutputs */) noexcept
{
    try
    {
        // Determine if bias is present based on the number of inputs.
        mWithBias = (nbInputs == 5);
        // No specific shape-dependent updates needed for this plugin's internal state.
        return STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return STATUS_FAILURE;
}

size_t ModulatedDeformableConvPluginDynamic::getWorkspaceSize(nvinfer1::DynamicPluginTensorDesc const* inputs,
    int32_t /* nbInputs */, nvinfer1::DynamicPluginTensorDesc const* outputs, int32_t /* nbOutputs */) const noexcept
{
    // Calculate workspace size needed for the im2col buffer.
    int32_t const sizeOfDtype = nvinfer1::plugin::bert::getElementSize(outputs[0].desc.type);

    int32_t const nInputPlane = inputs[0].desc.dims.d[1]; // Input channels
    int32_t const outputHeight = outputs[0].desc.dims.d[2];
    int32_t const outputWidth = outputs[0].desc.dims.d[3];
    int32_t const kernelH = inputs[3].desc.dims.d[2]; // Weight kernel height
    int32_t const kernelW = inputs[3].desc.dims.d[3]; // Weight kernel width

    // Calculate size needed for the intermediate 'columns' buffer used in im2col + GEMM approach.
    int64_t const colSize
        = divUp(static_cast<int64_t>(nInputPlane) * kernelW * kernelH * outputHeight * outputWidth * sizeOfDtype, 16)
        * 16; // Align to 16 bytes

    return static_cast<size_t>(colSize);
}

int32_t ModulatedDeformableConvPluginDynamic::enqueue(nvinfer1::PluginTensorDesc const* inputDescs,
    nvinfer1::PluginTensorDesc const* outputDescs, void const* const* inputs, void* const* outputs, void* workspace,
    cudaStream_t stream) noexcept
{
    try
    {
        PLUGIN_VALIDATE(inputDescs != nullptr && outputDescs != nullptr && inputs != nullptr && outputs != nullptr
            && workspace != nullptr);

        // Extract dimensions
        int32_t const batch = inputDescs[0].dims.d[0];
        int32_t const channels = inputDescs[0].dims.d[1];
        int32_t const height = inputDescs[0].dims.d[2];
        int32_t const width = inputDescs[0].dims.d[3];
        int32_t const channelsOut = outputDescs[0].dims.d[1];
        int32_t const kernelH = inputDescs[3].dims.d[2]; // Weight kernel height
        int32_t const kernelW = inputDescs[3].dims.d[3]; // Weight kernel width

        // Get input/output pointers
        void const* inputTensor = inputs[0];
        void const* offsetTensor = inputs[1];
        void const* maskTensor = inputs[2];
        void const* weightTensor = inputs[3];
        void const* biasTensor = mWithBias ? inputs[4] : nullptr;
        void* outputTensor = outputs[0];

        // Determine im2col step size
        int32_t const im2colStep = std::min(batch, 32);

        DataType const dataType = inputDescs[0].type;
        switch (dataType)
        {
        case nvinfer1::DataType::kFLOAT:
            ModulatedDeformConvForwardCUDAKernelLauncherFloat(static_cast<float const*>(inputTensor),
                static_cast<float const*>(weightTensor), static_cast<float const*>(biasTensor),
                static_cast<float const*>(offsetTensor), static_cast<float const*>(maskTensor),
                static_cast<float*>(outputTensor), workspace, batch, channels, height, width, channelsOut, kernelW,
                kernelH, mStride.d[0], mStride.d[1], mPadding.d[0], mPadding.d[1], mDilation.d[0], mDilation.d[1],
                mGroup, mDeformableGroup, im2colStep, mCublasHandle, stream);
            break;
        case nvinfer1::DataType::kHALF:
            ModulatedDeformConvForwardCUDAKernelLauncherHalf(static_cast<half const*>(inputTensor),
                static_cast<half const*>(weightTensor), static_cast<half const*>(biasTensor),
                static_cast<half const*>(offsetTensor), static_cast<half const*>(maskTensor),
                static_cast<half*>(outputTensor), workspace, batch, channels, height, width, channelsOut, kernelW,
                kernelH, mStride.d[0], mStride.d[1], mPadding.d[0], mPadding.d[1], mDilation.d[0], mDilation.d[1],
                mGroup, mDeformableGroup, im2colStep, mCublasHandle, stream);
            break;
        default:
            // Unsupported data type
            return STATUS_FAILURE;
        }
        return STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return STATUS_FAILURE;
}

IPluginV3* ModulatedDeformableConvPluginDynamic::attachToContext(nvinfer1::IPluginResourceContext* context) noexcept
{
    try
    {
        auto* p = static_cast<ModulatedDeformableConvPluginDynamic*>(clone());
        // The clone has shared ownership of the underlying cublasWrapper instance
        // that is mapped to the current context.
        p->setCublasResources(nvinfer1::pluginInternal::createPluginCublasWrapper(context));
        return p;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

void ModulatedDeformableConvPluginDynamic::setCublasResources(
    std::shared_ptr<nvinfer1::pluginInternal::CublasWrapper> cublasWrapper)
{
    mCublasWrapper = cublasWrapper;
    if (mCublasWrapper)
    {
        // The shared cublasWrapper resource owns the handle.
        // `this` instance has a non-owning pointer to the handle.
        // The cublasWrapper initializes the handle and checks for nullptr.
        mCublasHandle = mCublasWrapper->getCublasHandle();
    }
    // else: mCublasHandle remains nullptr, handle potential errors in enqueue
}

int32_t ModulatedDeformableConvPluginDynamic::getOutputDataTypes(nvinfer1::DataType* outputTypes, int32_t nbOutputs,
    nvinfer1::DataType const* inputTypes, int32_t nbInputs) const noexcept
{
    try
    {
        PLUGIN_VALIDATE(outputTypes != nullptr && inputTypes != nullptr);
        PLUGIN_VALIDATE(nbOutputs == 1);
        PLUGIN_VALIDATE(nbInputs == 4 || nbInputs == 5); // Depends on bias

        // Output type must match the input type
        outputTypes[0] = inputTypes[0];
        return STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return STATUS_FAILURE;
}

char const* ModulatedDeformableConvPluginDynamic::getPluginName() const noexcept
{
    return PLUGIN_NAME;
}

char const* ModulatedDeformableConvPluginDynamic::getPluginVersion() const noexcept
{
    return PLUGIN_VERSION;
}

void ModulatedDeformableConvPluginDynamic::setPluginNamespace(char const* pluginNamespace) noexcept
{
    try
    {
        mNamespace = (pluginNamespace == nullptr) ? "" : pluginNamespace;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
}

char const* ModulatedDeformableConvPluginDynamic::getPluginNamespace() const noexcept
{
    return mNamespace.c_str();
}

int32_t ModulatedDeformableConvPluginDynamic::getNbOutputs() const noexcept
{
    return 1;
}

nvinfer1::PluginFieldCollection const* ModulatedDeformableConvPluginDynamic::getFieldsToSerialize() noexcept
{
    try
    {
        mDataToSerialize.clear();
        // stride, padding, dilation are stored natively as int64 in memory
        // even though the plugin exposes them as int32.
        // Therefore, during build time, we upcast them to int64.
        // During runtime, we serialize/deserialize them as int64.
        // See ModulatedDeformableConvPluginDynamicCreator::createPlugin() on how we handle this.
        mDataToSerialize.emplace_back("stride", mStride.d, PluginFieldType::kINT64, 2);
        mDataToSerialize.emplace_back("padding", mPadding.d, PluginFieldType::kINT64, 2);
        mDataToSerialize.emplace_back("dilation", mDilation.d, PluginFieldType::kINT64, 2);
        mDataToSerialize.emplace_back("group", &mGroup, PluginFieldType::kINT32, 1);
        mDataToSerialize.emplace_back("deformable_group", &mDeformableGroup, PluginFieldType::kINT32, 1);

        mFCToSerialize.nbFields = mDataToSerialize.size();
        mFCToSerialize.fields = mDataToSerialize.data();
        return &mFCToSerialize;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

////////////////////// creator /////////////////////////////

ModulatedDeformableConvPluginDynamicCreator::ModulatedDeformableConvPluginDynamicCreator()
{
    mPluginAttributes.clear();
    mPluginAttributes.emplace_back(PluginField("stride", nullptr, PluginFieldType::kINT32, 2));
    mPluginAttributes.emplace_back(PluginField("padding", nullptr, PluginFieldType::kINT32, 2));
    mPluginAttributes.emplace_back(PluginField("dilation", nullptr, PluginFieldType::kINT32, 2));
    mPluginAttributes.emplace_back(PluginField("group", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("deformable_group", nullptr, PluginFieldType::kINT32, 1));

    mFC.nbFields = mPluginAttributes.size();
    mFC.fields = mPluginAttributes.data();
}

char const* ModulatedDeformableConvPluginDynamicCreator::getPluginName() const noexcept
{
    return PLUGIN_NAME;
}

char const* ModulatedDeformableConvPluginDynamicCreator::getPluginVersion() const noexcept
{
    return PLUGIN_VERSION;
}

nvinfer1::PluginFieldCollection const* ModulatedDeformableConvPluginDynamicCreator::getFieldNames() noexcept
{
    return &mFC;
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
nvinfer1::IPluginV3* ModulatedDeformableConvPluginDynamicCreator::createPlugin(
    char const* name, nvinfer1::PluginFieldCollection const* fc, nvinfer1::TensorRTPhase phase) noexcept
{
    try
    {
        PLUGIN_VALIDATE(fc != nullptr);
        PLUGIN_VALIDATE(fc->fields != nullptr || fc->nbFields == 0);

        nvinfer1::Dims stride{2, {1, 1}};
        nvinfer1::Dims padding{2, {0, 0}};
        nvinfer1::Dims dilation{2, {1, 1}};
        int32_t deformableGroup = 1;
        int32_t group = 1;

        plugin::validateRequiredAttributesExist({"deformable_group", "group", "stride", "padding", "dilation"}, fc);

        bool const isBuildPhase = (phase == nvinfer1::TensorRTPhase::kBUILD);

        for (int32_t i = 0; i < fc->nbFields; ++i)
        {
            PluginField const& field = fc->fields[i];
            // Skip fields with null data pointer
            if (field.data == nullptr)
            {
                continue;
            }

            std::string const fieldName(field.name);

            if (fieldName == "deformable_group")
            {
                PLUGIN_VALIDATE(field.type == PluginFieldType::kINT32);
                PLUGIN_VALIDATE(field.length == 1);
                deformableGroup = *static_cast<int32_t const*>(field.data);
                PLUGIN_VALIDATE(deformableGroup > 0);
            }
            else if (fieldName == "group")
            {
                PLUGIN_VALIDATE(field.type == PluginFieldType::kINT32);
                PLUGIN_VALIDATE(field.length == 1);
                group = *static_cast<int32_t const*>(field.data);
                PLUGIN_VALIDATE(group > 0);
            }
            else if (bert::elem(fieldName, {"stride", "padding", "dilation"}))
            {
                nvinfer1::Dims* dimsPtr
                    = (fieldName == "stride") ? &stride : ((fieldName == "padding") ? &padding : &dilation);

                PluginFieldType const expectedFieldType
                    = isBuildPhase ? PluginFieldType::kINT32 : PluginFieldType::kINT64;
                PLUGIN_VALIDATE(field.type == expectedFieldType);
                PLUGIN_VALIDATE(field.length == 2);
                dimsPtr->nbDims = 2;

                // To stay consistent with this plugin's IO, we expose int32 stride, padding, dilation
                // during build but store and serialize/deserialize as int64.
                if (isBuildPhase)
                {
                    // During build time, data is INT32, upcast to int64 for internal storage (Dims uses int64_t).
                    auto const* dataPtr = static_cast<int32_t const*>(field.data);
                    dimsPtr->d[0] = dataPtr[0];
                    dimsPtr->d[1] = dataPtr[1];
                }
                else // Runtime phase
                {
                    // During runtime, data is deserialized as INT64.
                    PLUGIN_VALIDATE(phase == nvinfer1::TensorRTPhase::kRUNTIME);
                    auto const* dataPtr = static_cast<int64_t const*>(field.data);
                    dimsPtr->d[0] = dataPtr[0];
                    dimsPtr->d[1] = dataPtr[1];
                }

                // Validate values
                if (fieldName == "padding")
                {
                    PLUGIN_VALIDATE(dimsPtr->d[0] >= 0 && dimsPtr->d[1] >= 0);
                }
                else // stride or dilation
                {
                    // Stride and dilation must be positive
                    PLUGIN_VALIDATE(dimsPtr->d[0] > 0 && dimsPtr->d[1] > 0);
                }
            }
        }

        auto plugin = std::make_unique<ModulatedDeformableConvPluginDynamic>(
            name, stride, padding, dilation, deformableGroup, group);
        plugin->setPluginNamespace(mNamespace.c_str());
        return plugin.release();
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

void ModulatedDeformableConvPluginDynamicCreator::setPluginNamespace(char const* libNamespace) noexcept
{
    try
    {
        mNamespace = (libNamespace == nullptr) ? "" : libNamespace;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
}

char const* ModulatedDeformableConvPluginDynamicCreator::getPluginNamespace() const noexcept
{
    return mNamespace.c_str();
}
