/*
 * 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 <math.h>
#include <stdio.h>

namespace nvinfer1
{
namespace plugin
{
using std::max;
using std::min;

// BBD2P KERNEL
template <typename T_DELTAS,
          DLayout_t L_DELTAS,
          typename TV_PROPOSALS,
          DLayout_t L_PROPOSALS,
          typename T_FGSCORES,
          DLayout_t L_FGSCORES>
__global__ void bboxDeltas2Proposals_kernel(
    int N,
    int A,
    int H,
    int W,
    const float* __restrict__ anchors,
    const float* __restrict__ imInfo,
    int featureStride,
    float minSize,
    const T_DELTAS* __restrict__ deltas,
    TV_PROPOSALS* __restrict__ proposals,
    T_FGSCORES* __restrict__ scores)
{
    int tid = threadIdx.x + blockIdx.x * blockDim.x;

    if (tid < N * A * H * W)
    { // TODO this can be a loop.
        // Find out the index of bounding box for the output
        int cnt = tid;
        // width index
        int w = cnt % W;
        cnt = cnt / W;
        // height index
        int h = cnt % H;
        cnt = cnt / H;
        // anchor box index
        int a = cnt % A;
        cnt = cnt / A;
        // batch index
        int n = cnt;
        int hw = h * W + w;

        // Get the height and width of the original input image
        float imHeight = imInfo[3 * n];
        float imWidth = imInfo[3 * n + 1];

        // Point to the right anchor box
        float4 anchor = ((float4*) anchors)[a];
        // Get anchor box coordinates
        float a_ctr_x = anchor.x;
        float a_ctr_y = anchor.y;
        float a_w = anchor.z;
        float a_h = anchor.w;

        // NCHW format
        // Find out the starting position of the bounding box in the input (predicted bounding box offsets)
        int id = ((tid - hw) * 4) + hw;
        T_DELTAS dx;
        T_DELTAS dy;
        T_DELTAS dw;
        T_DELTAS dh;
        if (L_DELTAS == NCHW)
        {
            // The offsets between adjacent coordinates on linear memory is H * W
            dx = deltas[id];
            dy = deltas[id + 1 * H * W];
            dw = deltas[id + 2 * H * W];
            dh = deltas[id + 3 * H * W];
        }
        // NC4HW format
        else if (L_DELTAS == NC4HW)
        {
            dx = deltas[tid * 4 + 0];
            dy = deltas[tid * 4 + 1];
            dw = deltas[tid * 4 + 2];
            dh = deltas[tid * 4 + 3];
        }
        /*
         * Calculate the coordinates of decoded bounding box on the original input image scale
         * Only works if param.minBoxSize == param.featureStride
         */
        float ctr_x = a_ctr_x + w * featureStride;
        float ctr_y = a_ctr_y + h * featureStride;
        // float ctr_x = (w + 0.5) * featureStride;
        // float ctr_y = (h + 0.5) * featureStride;

        /*
         * Decode the predicted bounding box
         * The decoded bounding boxes has coordinates of [x_topleft, y_topleft, x_bottomright, y_bottomright]
         * The units are in pixels
         */
        ctr_x = ctr_x + dx * a_w;
        ctr_y = ctr_y + dy * a_h;
        float b_w = __expf(dw) * a_w;
        float b_h = __expf(dh) * a_h;
        float bx = ctr_x - (b_w / 2);
        float by = ctr_y - (b_h / 2);
        float bz = ctr_x + (b_w / 2);
        float bw = ctr_y + (b_h / 2);

        TV_PROPOSALS bbox;
        // Make sure that the decoded bouding box go outside of the original input image
        bbox.x = fminf(fmaxf(bx, 0.0f), imWidth - 1.0f);
        bbox.y = fminf(fmaxf(by, 0.0f), imHeight - 1.0f);
        bbox.z = fminf(fmaxf(bz, 0.0f), imWidth - 1.0f);
        bbox.w = fminf(fmaxf(bw, 0.0f), imHeight - 1.0f);

        // Put the decoded bounding box information to the outputs
        if (L_PROPOSALS == NC4HW)
        {
            proposals[tid] = bbox;
        }

        int ininf = 0xff800000;
        float ninf = *(float*) &ininf;
        // minBoxSize at the original input image scale
        float scaledMinSize = minSize * imInfo[3 * n + 2];
        // Set the objectness score to -inf if the predicted bounding box has edgth length less than the minimal box size expected.
        if (bbox.z - bbox.x + 1 < scaledMinSize || bbox.w - bbox.y + 1 < scaledMinSize)
        {
            if (L_FGSCORES == NCHW)
                scores[tid] = ninf;
        }
    }
}

// BBD2P KERNEL LAUNCHER
template <typename T_DELTAS,
          DLayout_t L_DELTAS,
          typename TV_PROPOSALS,
          DLayout_t L_PROPOSALS,
          typename T_FGSCORES,
          DLayout_t L_FGSCORES>
pluginStatus_t bboxDeltas2Proposals_gpu(cudaStream_t stream,
                                       int N,
                                       int A,
                                       int H,
                                       int W,
                                       const float* imInfo,
                                       int featureStride,
                                       float minBoxSize,
                                       const float* anchors,
                                       const void* deltas,
                                       void* propos,
                                       void* scores)
{
    const int BS = 32;
    const int GS = ((N * A * H * W) + BS - 1) / BS;

    bboxDeltas2Proposals_kernel<T_DELTAS, L_DELTAS, TV_PROPOSALS, L_PROPOSALS, T_FGSCORES, L_FGSCORES><<<GS, BS, 0, stream>>>(N, A, H, W,
                                                                                                                              anchors,
                                                                                                                              imInfo,
                                                                                                                              featureStride,
                                                                                                                              minBoxSize,
                                                                                                                              (T_DELTAS*) deltas,
                                                                                                                              (TV_PROPOSALS*) propos,
                                                                                                                              (T_FGSCORES*) scores);

    DEBUG_PRINTF("&&&& [bboxD2P] POST LAUNCH\n");
    DEBUG_PRINTF("&&&& [bboxD2P] PROPOS %u\n", hash(propos, N * A * H * W * 4 * sizeof(float)));

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

// BBD2P LAUNCH CONFIG {{{
typedef pluginStatus_t (*bd2pFun)(cudaStream_t,
                                 int,
                                 int,
                                 int,
                                 int,
                                 const float*,
                                 int,
                                 float,
                                 const float*,
                                 const void*,
                                 void*,
                                 void*);

struct bd2pLaunchConfig
{
    DataType t_deltas;
    DLayout_t l_deltas;
    DataType t_proposals;
    DLayout_t l_proposals;
    DataType t_scores;
    DLayout_t l_scores;
    bd2pFun function;

    bd2pLaunchConfig(DataType t_deltas, DLayout_t l_deltas, DataType t_proposals, DLayout_t l_proposals,
        DataType t_scores, DLayout_t l_scores)
        : t_deltas(t_deltas)
        , l_deltas(l_deltas)
        , t_proposals(t_proposals)
        , l_proposals(l_proposals)
        , t_scores(t_scores)
        , l_scores(l_scores)
        , function(nullptr)
    {
    }

    bd2pLaunchConfig(DataType t_deltas, DLayout_t l_deltas, DataType t_proposals, DLayout_t l_proposals, DataType t_scores, DLayout_t l_scores, bd2pFun function)
        : t_deltas(t_deltas)
        , l_deltas(l_deltas)
        , t_proposals(t_proposals)
        , l_proposals(l_proposals)
        , t_scores(t_scores)
        , l_scores(l_scores)
        , function(function)
    {
    }

    bool operator==(bd2pLaunchConfig const& other) const
    {
        return t_deltas == other.t_deltas && l_deltas == other.l_deltas && t_proposals == other.t_proposals && l_proposals == other.l_proposals && t_scores == other.t_scores && l_scores == other.l_scores;
    }
};

#define FLOAT32 nvinfer1::DataType::kFLOAT
static std::array<bd2pLaunchConfig, 2> bd2pLCOptions = {
    bd2pLaunchConfig(FLOAT32, NC4HW, FLOAT32, NC4HW, FLOAT32, NCHW, bboxDeltas2Proposals_gpu<float, NC4HW, float4, NC4HW, float, NCHW>),
    bd2pLaunchConfig(FLOAT32, NCHW, FLOAT32, NC4HW, FLOAT32, NCHW, bboxDeltas2Proposals_gpu<float, NCHW, float4, NC4HW, float, NCHW>)};

// BBD2P
pluginStatus_t bboxDeltas2Proposals(cudaStream_t stream,
                                   const int N,
                                   const int A,
                                   const int H,
                                   const int W,
                                   const int featureStride,
                                   const float minBoxSize,
                                   const float* imInfo,
                                   const float* anchors,
                                   const DataType t_deltas,
                                   const DLayout_t l_deltas,
                                   const void* deltas,
                                   const DataType t_proposals,
                                   const DLayout_t l_proposals,
                                   void* proposals,
                                   const DataType t_scores,
                                   const DLayout_t l_scores,
                                   void* scores)
{
    bd2pLaunchConfig lc = bd2pLaunchConfig(t_deltas, l_deltas, t_proposals, l_proposals, t_scores, l_scores);
    for (unsigned i = 0; i < bd2pLCOptions.size(); i++)
    {
        if (lc == bd2pLCOptions[i])
        {
            DEBUG_PRINTF("BBD2P kernel %d\n", i);
            return bd2pLCOptions[i].function(stream,
                                          N, A, H, W,
                                          imInfo,
                                          featureStride,
                                          minBoxSize,
                                          anchors,
                                          deltas,
                                          proposals,
                                          scores);
        }
    }
    return STATUS_BAD_PARAM;
}
} // namespace plugin
} // namespace nvinfer1
