/*
 * 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.
 */
#include "nvFasterRCNNPlugin.h"
#include <cstdio>
#include <iostream>
#include <memory>
#include <string_view>

namespace nvinfer1::plugin
{
namespace
{
char const* const kRPROI_PLUGIN_VERSION{"1"};
char const* const kRPROI_PLUGIN_NAME{"RPROI_TRT"};
} // namespace

RPROIPlugin::RPROIPlugin(RPROIParams params, float const* anchorsRatios, float const* anchorsScales)
    : params(params)
{
    /*
     * It only supports the scenario where params.featureStride == params.minBoxSize
     * assert(params.featureStride == params.minBoxSize);
     */
    PLUGIN_VALIDATE(params.anchorsRatioCount > 0 && params.anchorsScaleCount > 0);
    anchorsRatiosHost = copyToHost(anchorsRatios, params.anchorsRatioCount);
    anchorsScalesHost = copyToHost(anchorsScales, params.anchorsScaleCount);

    PLUGIN_CHECK(
        cudaMalloc((void**) &anchorsDev, 4 * params.anchorsRatioCount * params.anchorsScaleCount * sizeof(float)));
    pluginStatus_t status = generateAnchors(0, params.anchorsRatioCount, anchorsRatiosHost, params.anchorsScaleCount,
        anchorsScalesHost, params.featureStride, anchorsDev);
    PLUGIN_VALIDATE(status == STATUS_SUCCESS);

    deviceSmemSize = getSmemSize();
}

// Constructor for cloning one plugin instance to another
RPROIPlugin::RPROIPlugin(RPROIParams params, float const* anchorsRatios, float const* anchorsScales, int32_t A,
    int32_t C, int32_t H, int32_t W, float const* _anchorsDev, size_t deviceSmemSize, DataType inFeatureType,
    DataType outFeatureType, DLayout_t inFeatureLayout)
    : deviceSmemSize(deviceSmemSize)
    , params(params)
    , A(A)
    , C(C)
    , H(H)
    , W(W)
    , inFeatureType(inFeatureType)
    , outFeatureType(outFeatureType)
    , inFeatureLayout(inFeatureLayout)
{
    PLUGIN_VALIDATE(params.anchorsRatioCount > 0 && params.anchorsScaleCount > 0);
    anchorsRatiosHost = copyToHost(anchorsRatios, params.anchorsRatioCount);
    anchorsScalesHost = copyToHost(anchorsScales, params.anchorsScaleCount);

    PLUGIN_CHECK(
        cudaMalloc((void**) &anchorsDev, 4 * params.anchorsRatioCount * params.anchorsScaleCount * sizeof(float)));
    // Perform deep copy
    if (_anchorsDev != nullptr)
    {
        PLUGIN_CHECK(cudaMemcpy(anchorsDev, _anchorsDev,
            4 * params.anchorsRatioCount * params.anchorsScaleCount * sizeof(float), cudaMemcpyDeviceToDevice));
    }
}

RPROIPlugin::RPROIPlugin(void const* data, size_t length)
{
    deserialize(static_cast<int8_t const*>(data), length);
}

void RPROIPlugin::deserialize(int8_t const* data, size_t length)
{
    auto const* d{data};
    params = *reinterpret_cast<RPROIParams const*>(d);
    d += sizeof(RPROIParams);
    A = read<int32_t>(d);
    C = read<int32_t>(d);
    H = read<int32_t>(d);
    W = read<int32_t>(d);
    inFeatureType = read<DataType>(d);
    outFeatureType = read<DataType>(d);
    inFeatureLayout = read<DLayout_t>(d);
    anchorsRatiosHost = copyToHost(d, params.anchorsRatioCount);
    d += params.anchorsRatioCount * sizeof(float);
    anchorsScalesHost = copyToHost(d, params.anchorsScaleCount);
    d += params.anchorsScaleCount * sizeof(float);
    PLUGIN_VALIDATE(d == data + length);

    PLUGIN_CHECK(
        cudaMalloc((void**) &anchorsDev, 4 * params.anchorsRatioCount * params.anchorsScaleCount * sizeof(float)));
    pluginStatus_t status = generateAnchors(0, params.anchorsRatioCount, anchorsRatiosHost, params.anchorsScaleCount,
        anchorsScalesHost, params.featureStride, anchorsDev);
    PLUGIN_VALIDATE(status == STATUS_SUCCESS);

    deviceSmemSize = getSmemSize();
}

RPROIPlugin::~RPROIPlugin()
{
    if (anchorsDev != nullptr)
    {
        PLUGIN_CHECK(cudaFree(anchorsDev));
        anchorsDev = nullptr;
    }
    if (anchorsRatiosHost != nullptr)
    {
        PLUGIN_CHECK(cudaFreeHost(anchorsRatiosHost));
        anchorsRatiosHost = nullptr;
    }
    if (anchorsScalesHost != nullptr)
    {
        PLUGIN_CHECK(cudaFreeHost(anchorsScalesHost));
        anchorsScalesHost = nullptr;
    }
}

int32_t RPROIPlugin::initialize() noexcept
{
    return STATUS_SUCCESS;
}

size_t RPROIPlugin::getSmemSize() const noexcept
{
    int32_t devId{-1};
    PLUGIN_CHECK(cudaGetDevice(&devId));
    cudaDeviceProp prop{};
    PLUGIN_CHECK(cudaGetDeviceProperties(&prop, devId));
    return prop.sharedMemPerBlockOptin;
}

int32_t RPROIPlugin::getNbOutputs() const noexcept
{
    return 2;
}

Dims RPROIPlugin::getOutputDimensions(int32_t index, Dims const* inputs, int32_t nbInputDims) noexcept
{
    PLUGIN_ASSERT(index >= 0 && index < 2);
    PLUGIN_ASSERT(nbInputDims == 4);
    PLUGIN_ASSERT(inputs[0].nbDims == 3 && inputs[1].nbDims == 3 && inputs[2].nbDims == 3 && inputs[3].nbDims == 3);
    if (index == 0) // rois
    {
        return Dims3(1, params.nmsMaxOut, 4);
    }
    // Feature map of each ROI after ROI Pooling
    // pool5
    return Dims4(params.nmsMaxOut, inputs[2].d[0], params.poolingH, params.poolingW);
}

size_t RPROIPlugin::getWorkspaceSize(int32_t maxBatchSize) const noexcept
{
    return RPROIInferenceFusedWorkspaceSize(maxBatchSize, A, H, W, params.nmsMaxOut);
}

int32_t RPROIPlugin::enqueue(
    int32_t batchSize, void const* const* inputs, void* const* outputs, void* workspace, cudaStream_t stream) noexcept
{
    // Bounding box (region proposal) objectness scores.
    void const* const scores = inputs[0];
    // Predicted bounding box offsets.
    void const* const deltas = inputs[1];
    // Feature map using for bounding box regression and classification.
    void const* const fmap = inputs[2];
    // Original image input information.
    void const* const iinfo = inputs[3];

    // Coordinates of region of interest (ROI) bounding boxes on the original input image.
    void* rois = outputs[0];
    // ROI pooled feature map corresponding to the region of interest (ROI).
    void* pfmap = outputs[1];

    pluginStatus_t status = RPROIInferenceFused(stream, batchSize, A, C, H, W, params.poolingH, params.poolingW,
        params.featureStride, params.preNmsTop, params.nmsMaxOut, params.iouThreshold, params.minBoxSize,
        params.spatialScale, (float const*) iinfo, this->anchorsDev, nvinfer1::DataType::kFLOAT, NCHW, scores,
        nvinfer1::DataType::kFLOAT, NCHW, deltas, inFeatureType, inFeatureLayout, fmap, workspace,
        nvinfer1::DataType::kFLOAT, rois, outFeatureType, NCHW, pfmap, deviceSmemSize);
    return status;
}

size_t RPROIPlugin::getSerializationSize() const noexcept
{
    size_t paramSize = sizeof(RPROIParams);
    size_t intSize = sizeof(int32_t) * 4;
    size_t ratiosSize = sizeof(float) * params.anchorsRatioCount;
    size_t scalesSize = sizeof(float) * params.anchorsScaleCount;
    size_t typeSize = sizeof(DataType) * 2;
    size_t layoutSize = sizeof(DLayout_t);
    return paramSize + intSize + ratiosSize + scalesSize + typeSize + layoutSize;
}

void RPROIPlugin::serialize(void* buffer) const noexcept
{
    char *d = reinterpret_cast<char*>(buffer), *a = d;
    *reinterpret_cast<RPROIParams*>(d) = params;
    d += sizeof(RPROIParams);
    *reinterpret_cast<int32_t*>(d) = A;
    d += sizeof(int32_t);
    *reinterpret_cast<int32_t*>(d) = C;
    d += sizeof(int32_t);
    *reinterpret_cast<int32_t*>(d) = H;
    d += sizeof(int32_t);
    *reinterpret_cast<int32_t*>(d) = W;
    d += sizeof(int32_t);
    *reinterpret_cast<DataType*>(d) = inFeatureType;
    d += sizeof(DataType);
    *reinterpret_cast<DataType*>(d) = outFeatureType;
    d += sizeof(DataType);
    *reinterpret_cast<DLayout_t*>(d) = inFeatureLayout;
    d += sizeof(DLayout_t);
    d += copyFromHost(d, anchorsRatiosHost, params.anchorsRatioCount);
    d += copyFromHost(d, anchorsScalesHost, params.anchorsScaleCount);
    PLUGIN_ASSERT(d == a + getSerializationSize());
}

float* RPROIPlugin::copyToHost(void const* srcHostData, int32_t count) noexcept
{
    float* dstHostPtr = nullptr;
    PLUGIN_CHECK(cudaMallocHost(&dstHostPtr, count * sizeof(float)));
    PLUGIN_CHECK(cudaMemcpy(dstHostPtr, srcHostData, count * sizeof(float), cudaMemcpyHostToHost));
    return dstHostPtr;
}

int32_t RPROIPlugin::copyFromHost(char* dstHostBuffer, void const* source, int32_t count) const noexcept
{
    PLUGIN_CHECK(cudaMemcpy(dstHostBuffer, source, count * sizeof(float), cudaMemcpyHostToHost));
    return count * sizeof(float);
}

bool RPROIPlugin::supportsFormatCombination(
    int32_t pos, PluginTensorDesc const* inOut, int32_t nbInputs, int32_t nbOutputs) const noexcept
{
    PLUGIN_ASSERT(nbInputs == PluginNbInputs && nbOutputs == PluginNbOutputs && pos < nbInputs + nbOutputs);
    bool isValidCombination = false;

    // input:  bbox confindence, bbox offset, image info and output: rois
    if (pos == 0 || pos == 1 || pos == 3 || pos == 4)
    {
        isValidCombination |= (inOut[pos].format == TensorFormat::kLINEAR && inOut[pos].type == DataType::kFLOAT);
    }
    // input:  feature map
    else if (pos == 2)
    {
        isValidCombination |= (inOut[pos].format == TensorFormat::kLINEAR && inOut[pos].type == DataType::kINT8);
        isValidCombination |= (inOut[pos].format == TensorFormat::kLINEAR && inOut[pos].type == DataType::kFLOAT);
        isValidCombination |= (inOut[pos].format == TensorFormat::kCHW4 && inOut[pos].type == DataType::kINT8);
        isValidCombination |= (inOut[pos].format == TensorFormat::kCHW32 && inOut[pos].type == DataType::kINT8);
    }
    // output: pooled feature map (data type should be the same with input feature map)
    else if (pos == 5)
    {
        isValidCombination |= (inOut[pos].format == TensorFormat::kLINEAR && inOut[pos].type == DataType::kINT8);
        isValidCombination |= (inOut[pos].format == TensorFormat::kLINEAR && inOut[pos].type == DataType::kFLOAT);
        isValidCombination &= inOut[pos].type == inOut[2].type;
    }
    return isValidCombination;
}

char const* RPROIPlugin::getPluginType() const noexcept
{
    return kRPROI_PLUGIN_NAME;
}

char const* RPROIPlugin::getPluginVersion() const noexcept
{
    return kRPROI_PLUGIN_VERSION;
}

void RPROIPlugin::terminate() noexcept {}

void RPROIPlugin::destroy() noexcept
{
    delete this;
}

IPluginV2Ext* RPROIPlugin::clone() const noexcept
{
    try
    {
        auto plugin = std::make_unique<RPROIPlugin>(params, anchorsRatiosHost, anchorsScalesHost, A, C, H, W,
            anchorsDev, deviceSmemSize, inFeatureType, outFeatureType, inFeatureLayout);
        plugin->setPluginNamespace(mPluginNamespace.c_str());
        return plugin.release();
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

// Set plugin namespace
void RPROIPlugin::setPluginNamespace(char const* pluginNamespace) noexcept
{
    mPluginNamespace = pluginNamespace;
}

char const* RPROIPlugin::getPluginNamespace() const noexcept
{
    return mPluginNamespace.c_str();
}

// Return the DataType of the plugin output at the requested index.
DataType RPROIPlugin::getOutputDataType(
    int32_t index, nvinfer1::DataType const* inputTypes, int32_t nbInputs) const noexcept
{
    // Two outputs
    PLUGIN_ASSERT(index == 0 || index == 1);
    return DataType::kFLOAT;
}

DLayout_t RPROIPlugin::convertTensorFormat(TensorFormat const& srcFormat) const noexcept
{
    PLUGIN_ASSERT(
        srcFormat == TensorFormat::kLINEAR || srcFormat == TensorFormat::kCHW4 || srcFormat == TensorFormat::kCHW32);
    switch (srcFormat)
    {
    case nvinfer1::TensorFormat::kLINEAR: return DLayout_t::NCHW;
    case nvinfer1::TensorFormat::kCHW4: return DLayout_t::NC4HW;
    case nvinfer1::TensorFormat::kCHW32: return DLayout_t::NC32HW;
    default: return DLayout_t::NCHW;
    }
}

void RPROIPlugin::configurePlugin(
    PluginTensorDesc const* in, int32_t nbInput, PluginTensorDesc const* out, int32_t nbOutput) noexcept
{
    PLUGIN_ASSERT(nbInput == PluginNbInputs);
    PLUGIN_ASSERT(nbOutput == PluginNbOutputs);

    A = params.anchorsRatioCount * params.anchorsScaleCount;
    C = in[2].dims.d[0];
    H = in[2].dims.d[1];
    W = in[2].dims.d[2];
    inFeatureType = in[2].type;
    outFeatureType = out[1].type;
    inFeatureLayout = convertTensorFormat(in[2].format);

    PLUGIN_ASSERT(in[0].dims.d[0] == (2 * A) && in[1].dims.d[0] == (4 * A));
    PLUGIN_ASSERT(in[0].dims.d[1] == in[1].dims.d[1] && in[0].dims.d[1] == in[2].dims.d[1]);
    PLUGIN_ASSERT(in[0].dims.d[2] == in[1].dims.d[2] && in[0].dims.d[2] == in[2].dims.d[2]);
    PLUGIN_ASSERT(out[0].dims.nbDims == 3 // rois
        && out[1].dims.nbDims == 4);      // pooled feature map
    PLUGIN_ASSERT(out[0].dims.d[0] == 1 && out[0].dims.d[1] == params.nmsMaxOut && out[0].dims.d[2] == 4);
    PLUGIN_ASSERT(out[1].dims.d[0] == params.nmsMaxOut && out[1].dims.d[1] == C && out[1].dims.d[2] == params.poolingH
        && out[1].dims.d[3] == params.poolingW);
}

// Attach the plugin object to an execution context and grant the plugin the access to some context resource.
void RPROIPlugin::attachToContext(
    cudnnContext* cudnnContext, cublasContext* cublasContext, IGpuAllocator* gpuAllocator) noexcept
{
}

// Detach the plugin object from its execution context.
void RPROIPlugin::detachFromContext() noexcept {}

RPROIPluginCreator::RPROIPluginCreator()
{
    mPluginAttributes.clear();
    mPluginAttributes.emplace_back(PluginField("poolingH", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("poolingW", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("featureStride", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("preNmsTop", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("nmsMaxOut", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("anchorsRatioCount", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("anchorsScaleCount", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("iouThreshold", nullptr, PluginFieldType::kFLOAT32, 1));
    mPluginAttributes.emplace_back(PluginField("minBoxSize", nullptr, PluginFieldType::kFLOAT32, 1));
    mPluginAttributes.emplace_back(PluginField("spatialScale", nullptr, PluginFieldType::kFLOAT32, 1));

    // TODO Do we need to pass the size attribute here for float arrarys, we
    // dont have that information at this point.
    mPluginAttributes.emplace_back(PluginField("anchorsRatios", nullptr, PluginFieldType::kFLOAT32, 1));
    mPluginAttributes.emplace_back(PluginField("anchorsScales", nullptr, PluginFieldType::kFLOAT32, 1));

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

RPROIPluginCreator::~RPROIPluginCreator()
{
    // Free allocated memory (if any) here
}

char const* RPROIPluginCreator::getPluginName() const noexcept
{
    return kRPROI_PLUGIN_NAME;
}

char const* RPROIPluginCreator::getPluginVersion() const noexcept
{
    return kRPROI_PLUGIN_VERSION;
}

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

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
IPluginV2Ext* RPROIPluginCreator::createPlugin(char const* name, PluginFieldCollection const* fc) noexcept
{
    try
    {
        using namespace std::string_view_literals;
        PluginField const* fields = fc->fields;
        int32_t nbFields = fc->nbFields;

        for (int32_t i = 0; i < nbFields; ++i)
        {
            std::string_view const attrName = fields[i].name;
            if (attrName == "poolingH"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
                params.poolingH = *(static_cast<int32_t const*>(fields[i].data));
            }
            if (attrName == "poolingW"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
                params.poolingW = *(static_cast<int32_t const*>(fields[i].data));
            }
            if (attrName == "featureStride"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
                params.featureStride = *(static_cast<int32_t const*>(fields[i].data));
            }
            if (attrName == "preNmsTop"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
                params.preNmsTop = *(static_cast<int32_t const*>(fields[i].data));
            }
            if (attrName == "nmsMaxOut"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
                params.nmsMaxOut = *(static_cast<int32_t const*>(fields[i].data));
            }
            if (attrName == "anchorsRatioCount"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
                params.anchorsRatioCount = *(static_cast<int32_t const*>(fields[i].data));
            }
            if (attrName == "anchorsScaleCount"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kINT32);
                params.anchorsScaleCount = *(static_cast<int32_t const*>(fields[i].data));
            }
            if (attrName == "iouThreshold"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kFLOAT32);
                params.iouThreshold = static_cast<float>(*(static_cast<float const*>(fields[i].data)));
            }
            if (attrName == "minBoxSize"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kFLOAT32);
                params.minBoxSize = static_cast<float>(*(static_cast<float const*>(fields[i].data)));
            }
            if (attrName == "spatialScale"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kFLOAT32);
                params.spatialScale = static_cast<float>(*(static_cast<float const*>(fields[i].data)));
            }
            if (attrName == "anchorsRatios"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kFLOAT32);
                anchorsRatios.reserve(params.anchorsRatioCount);
                float const* ratios = static_cast<float const*>(fields[i].data);
                for (int32_t j = 0; j < params.anchorsRatioCount; ++j)
                {
                    anchorsRatios.push_back(*ratios);
                    ratios++;
                }
            }
            if (attrName == "anchorsScales"sv)
            {
                PLUGIN_VALIDATE(fields[i].type == PluginFieldType::kFLOAT32);
                anchorsScales.reserve(params.anchorsScaleCount);
                float const* scales = static_cast<float const*>(fields[i].data);
                for (int32_t j = 0; j < params.anchorsScaleCount; ++j)
                {
                    anchorsScales.push_back(*scales);
                    scales++;
                }
            }
        }

        // This object will be deleted when the network is destroyed, which will
        // call RPROIPlugin::terminate()
        auto plugin = std::make_unique<RPROIPlugin>(params, anchorsRatios.data(), anchorsScales.data());
        plugin->setPluginNamespace(mNamespace.c_str());
        return plugin.release();
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

IPluginV2Ext* RPROIPluginCreator::deserializePlugin(
    char const* name, void const* serialData, size_t serialLength) noexcept
{
    try
    {
        // This object will be deleted when the network is destroyed, which will
        // call RPROIPlugin::terminate()
        auto plugin = std::make_unique<RPROIPlugin>(serialData, serialLength);
        plugin->setPluginNamespace(mNamespace.c_str());
        return plugin.release();
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}
} // namespace nvinfer1::plugin
