/*
 * SPDX-FileCopyrightText: Copyright (c) 1993-2024 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 "common/kernels/kernel.h"
#include <algorithm>
#include <array>
#include <assert.h>
#include <cfloat>
#include <cstdio>
#include <math.h>
#include <stdio.h>
namespace nvinfer1
{
namespace plugin
{
// This macro is to control shared memory usage. If set to 1, kernel loads the whole feature map
// into shared memory for reuse; If set to 0, kernel loads data from global memory directly.
// Roi pooling performance is data dependent. You can test which value is better to your data.
// If all bboxes are very small, 0 is recommended, otherwise, shared memory will load many unused
// data; If bboxes have many overlaps, 1 is recommended to avoid duplicate loads.
// 1 requires larger shared memory size. It may fail if it is larger than CUDA allowed per-block
// shared memory size upper bound. Then you have to use 0.
#define ROIPOOLING_FEATURE_MAP_USE_SHMEM 1

template <typename T>
__device__ T getMax();

template <>
__device__ __forceinline__ int8_t getMax<int8_t>()
{
    return INT8_MAX;
}

template <>
__device__ __forceinline__ float getMax<float>()
{
    return FLT_MAX;
}

// ROI POOLING FORWARD KERNEL
template <typename DATA_T, typename ROI_T, bool INFER_ONLY, bool FM_IN_SMEM>
__global__ void ROIPoolingForwardKernelAligned(int32_t ROICount, const ROI_T* rois,
    int32_t N, // feature map size
    int32_t C, // feature map size
    int32_t H, // feature map size
    int32_t W, // feature map size
    const DATA_T* featureMap, const int32_t poolingH, const int32_t poolingW, const float spatialScale, DATA_T* top,
    int32_t* maxIds, int32_t fmapStep)
{
    extern __shared__ float smem[];
    DATA_T* feature_shr = (DATA_T*) &smem[0];
    int* rois_shr = nullptr;
    if (FM_IN_SMEM)
    {
        rois_shr = (int*) &feature_shr[H * W];
    }
    else
    {
        rois_shr = (int*) &feature_shr[0];
        feature_shr = nullptr;
    }

    const int batch = blockIdx.x / C;
    const int channel = blockIdx.x % C;

    // load ROIs to shared memory
    for (int j = threadIdx.x; j < ROICount; j += blockDim.x)
    {
        int offset = j << 2;
        float4 roi = reinterpret_cast<float4*>(const_cast<float*>(rois))[batch * ROICount + j];
        // spatialScale = 1.0 / featureStride
        // Convert the coordinates to feature map scale
        rois_shr[offset] = round(roi.x * spatialScale);     //roi_start_w
        rois_shr[offset + 1] = round(roi.y * spatialScale); //roi_start_h
        rois_shr[offset + 2] = round(roi.z * spatialScale) - round(roi.x * spatialScale); //roi_length_w
        rois_shr[offset + 3] = round(roi.w * spatialScale) - round(roi.y * spatialScale); // roi_length_h
    }

    // NC/xHW
    int fmapOffset = blockIdx.x / fmapStep * H * W * fmapStep + blockIdx.x % fmapStep;

    // Assumes #CTAs is just enough to cover all channels of all blocks
    const DATA_T* bottom_data_offset = featureMap + fmapOffset;
    if (FM_IN_SMEM)
    {
        // load the current channel to the shared memory
        for (int j = threadIdx.x; j < H * W; j += blockDim.x)
        {
            feature_shr[j] = bottom_data_offset[j * fmapStep];
        }
    }
    __syncthreads();

    for (int j = threadIdx.x; j < ROICount; j += blockDim.x)
    {
        const int offset = j << 2;
        // Force malformed ROIs to be 1x1
        int roi_start_w = rois_shr[offset];
        int roi_start_h = rois_shr[offset + 1];
        int roi_width = max(rois_shr[offset + 2] + 1, 1);
        int roi_height = max(rois_shr[offset + 3] + 1, 1);
        float bin_size_h = static_cast<float>(roi_height) / static_cast<float>(poolingH);
        float bin_size_w = static_cast<float>(roi_width) / static_cast<float>(poolingW);

        for (int ph = 0; ph < poolingH; ++ph)
        {
            for (int pw = 0; pw < poolingW; ++pw)
            {
                int hstart = static_cast<int>(floor(static_cast<float>(ph) * bin_size_h));
                int wstart = static_cast<int>(floor(static_cast<float>(pw) * bin_size_w));
                int hend = static_cast<int>(ceil(static_cast<float>(ph + 1) * bin_size_h));
                int wend = static_cast<int>(ceil(static_cast<float>(pw + 1) * bin_size_w));

                // Add roi offsets and clip to input boundaries
                // In fact, clipping should be done in the RPN, but just in case...
                hstart = min(max(hstart + roi_start_h, 0), H);
                hend = min(max(hend + roi_start_h, 0), H);
                wstart = min(max(wstart + roi_start_w, 0), W);
                wend = min(max(wend + roi_start_w, 0), W);
                bool is_empty = (hend <= hstart) || (wend <= wstart);

                // Define an empty pooling region to be zero
                DATA_T maxval = is_empty ? 0 : -getMax<DATA_T>();
                int maxId = -1;
                DATA_T data = 0;
                for (int h = hstart; h < hend; ++h)
                {
                    for (int w = wstart; w < wend; ++w)
                    {
                        int index = h * W + w;
                        if (FM_IN_SMEM)
                        {
                            data = feature_shr[index];
                        }
                        else
                        {
                            data = bottom_data_offset[index * fmapStep];
                        }
                        if (data > maxval)
                        {
                            maxval = data;
                            maxId = index;
                        }
                    }
                }
                top[(((batch * ROICount + j) * C + channel) * poolingH + ph) * poolingW + pw] = maxval;
                if (!INFER_ONLY)
                {
                    maxIds[(((batch * ROICount + j) * C + channel) * poolingH + ph) * poolingW + pw] = maxId;
                }
            } //for:pw
        }     //for:ph
    }         // for:j
}

template <typename DATA_T, DLayout_t DATA_L, typename ROI_T, bool INFER_ONLY>
pluginStatus_t ROIPoolingForwardKernelAlignedLauncher(cudaStream_t stream,
    const int R,        // TOTAL number of rois -> ~nmsMaxOut * N
    const int N,        // Batch size
    const int C,        // Channels
    const int H,        // Input feature map H
    const int W,        // Input feature map W
    const int poolingH, // Output feature map H
    const int poolingW, // Output feature map W
    const float spatialScale, const void* rois, const void* featureMap, void* top, int* maxIds, size_t deviceSmemSize)
{
    size_t roiShmemSize = (R / N) * 4 * sizeof(ROI_T);

#if ROIPOOLING_FEATURE_MAP_USE_SHMEM
    size_t shmemSize = H * W * sizeof(DATA_T) + roiShmemSize;
    const bool fmap_in_shmem = true;
#else
    size_t shmemSize = roiShmemSize;
    const bool fmap_in_shmem = false;
#endif

    if (shmemSize > deviceSmemSize)
    {
        return STATUS_BAD_PARAM;
    }

    // in the aligned version of ROI Pooling R should always be a multiple of N
    PLUGIN_ASSERT(R % N == 0);

    // NC/xHW
    int32_t fmapStep = 1;
    switch(DATA_L)
    {
    case NCHW: fmapStep = 1; break;
    case NC4HW:
        fmapStep = 4;
        PLUGIN_ASSERT((N * C) % 4 == 0);
        break;
    case NC32HW:
        fmapStep = 32;
        PLUGIN_ASSERT((N * C) % 32 == 0);
        break;
    default: PLUGIN_ASSERT(false);
    }

    if (shmemSize > 48 * 1024)
    {
        PLUGIN_CHECK(cudaFuncSetAttribute(&ROIPoolingForwardKernelAligned<DATA_T, ROI_T, INFER_ONLY, true>,
            cudaFuncAttributeMaxDynamicSharedMemorySize, shmemSize));
    }
    ROIPoolingForwardKernelAligned<DATA_T, ROI_T, INFER_ONLY, fmap_in_shmem><<<N * C, 256, shmemSize, stream>>>(R / N,
        (const ROI_T*) rois,
        N, // feature map size
        C, // feature map size
        H, // feature map size
        W, // feature map size
        (const DATA_T*) featureMap, poolingH, poolingW, spatialScale, (DATA_T*) top, maxIds, fmapStep);

    CSC(cudaGetLastError(), STATUS_FAILURE);
    return STATUS_SUCCESS;
}

// ROI POOLING LAUNCH CONFIG

typedef pluginStatus_t (*roiFwd)(cudaStream_t,
                                const int,   //R, // TOTAL number of rois -> ~nmsMaxOut * N
                                const int,   //N, // Batch size
                                const int,   //C, // Channels
                                const int,   //H, // Input feature map H
                                const int,   //W, // Input feature map W
                                const int,   //poolingH, // Output feature map H
                                const int,   //poolingW, // Output feature map W
                                const float, //spatialScale,
                                const void*, //rois,
                                const void*, //featureMap,
                                void*,       //top
                                int*,        //maxIds
                                size_t);     //device shmem size

// struct
struct roiFwdLaunchConfig
{
    DataType t_rois;
    DataType t_featureMap;
    DLayout_t l_featureMap;
    DataType t_top;
    DLayout_t l_top;
    bool inferOnly;
    roiFwd function;

    roiFwdLaunchConfig(
        DataType t_rois, DataType t_featureMap, DLayout_t l_featureMap, DataType t_top, DLayout_t l_top, bool inferOnly)
        : t_rois(t_rois)
        , t_featureMap(t_featureMap)
        , l_featureMap(l_featureMap)
        , t_top(t_top)
        , l_top(l_top)
        , inferOnly(inferOnly)
        , function(nullptr)
    {
    }

    roiFwdLaunchConfig(DataType t_rois,
                       DataType t_featureMap,
                       DLayout_t l_featureMap,
                       DataType t_top,
                       DLayout_t l_top,
                       bool inferOnly,
                       roiFwd function)
        : t_rois(t_rois)
        , t_featureMap(t_featureMap)
        , l_featureMap(l_featureMap)
        , t_top(t_top)
        , l_top(l_top)
        , inferOnly(inferOnly)
        , function(function)
    {
    }

    bool operator==(roiFwdLaunchConfig const& other) const
    {
        return (t_rois == other.t_rois)
            && (t_featureMap == other.t_featureMap)
            && (l_featureMap == other.l_featureMap)
            && (t_top == other.t_top)
            && (l_top == other.l_top)
            && (inferOnly == other.inferOnly);
    }
};

#define FLOAT32 nvinfer1::DataType::kFLOAT
#define INT8 nvinfer1::DataType::kINT8
static std::array<roiFwdLaunchConfig, 6> roiFwdLCOptions = {
    roiFwdLaunchConfig(FLOAT32, FLOAT32, NCHW, FLOAT32, NCHW, true, ROIPoolingForwardKernelAlignedLauncher<float, NCHW, float, true>),
    roiFwdLaunchConfig(FLOAT32, FLOAT32, NCHW, FLOAT32, NCHW, false, ROIPoolingForwardKernelAlignedLauncher<float, NCHW, float, false>),
    roiFwdLaunchConfig(FLOAT32, INT8, NCHW, INT8, NCHW, true, ROIPoolingForwardKernelAlignedLauncher<int8_t, NCHW, float, true>),
    roiFwdLaunchConfig(FLOAT32, INT8, NC4HW, INT8, NCHW, true, ROIPoolingForwardKernelAlignedLauncher<int8_t, NC4HW, float, true>),
    roiFwdLaunchConfig(FLOAT32, INT8, NC32HW, INT8, NCHW, true, ROIPoolingForwardKernelAlignedLauncher<int8_t, NC32HW, float, true>),
    roiFwdLaunchConfig(FLOAT32, FLOAT32, NC4HW, FLOAT32, NCHW, true, ROIPoolingForwardKernelAlignedLauncher<float, NC4HW, float, true>)};

// ROI INFERENCE
pluginStatus_t roiInference(cudaStream_t stream,
                           const int R,        // TOTAL number of rois -> ~nmsMaxOut * N
                           const int N,        // Batch size
                           const int C,        // Channels
                           const int H,        // Input feature map H
                           const int W,        // Input feature map W
                           const int poolingH, // Output feature map H
                           const int poolingW, // Output feature map W
                           const float spatialScale,
                           const nvinfer1::DataType t_rois,
                           const void* rois,
                           const nvinfer1::DataType t_featureMap,
                           const DLayout_t l_featureMap,
                           const void* featureMap,
                           const nvinfer1::DataType t_top,
                           const DLayout_t l_top,
                           void* top,
                           size_t deviceSmemSize)
{
    if (featureMap == NULL || rois == NULL || top == NULL)
    {
        return STATUS_BAD_PARAM;
    }

    DEBUG_PRINTF("&&&& ROIS %u\n", hash(rois, R * 4 * sizeof(float)));
    DEBUG_PRINTF("&&&& FMAP %u\n", hash(featureMap, N * C * H * W * sizeof(float)));

    roiFwdLaunchConfig rflc = roiFwdLaunchConfig(t_rois, t_featureMap, l_featureMap, t_top, l_top, true);
    ASSERT_PARAM(R > 0);

    for (unsigned i = 0; i < roiFwdLCOptions.size(); i++)
    {
        if (rflc == roiFwdLCOptions[i])
        {
            DEBUG_PRINTF("$$$$ ROI KERNEL %d\n", i);
            return roiFwdLCOptions[i].function(stream,
                                                R, N, C, H, W, poolingH, poolingW,
                                                spatialScale, rois, featureMap, top, NULL, deviceSmemSize);
        }
    }
    return STATUS_BAD_PARAM;
}
} // namespace plugin
} // namespace nvinfer1
