/*
 * SPDX-FileCopyrightText: Copyright (c) 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 <cstdlib>
#include <exception>
#include <iostream>
#include <string_view>
#include <vector>

#include "NvInferSafePlugin.h"
#include "NvInferSafeRuntime.h"

#include "maxPoolKernel.h"
#include "maxPoolPlugin.h"
#include "safeCommon.h"

namespace nvinfer1
{
namespace plugin
{

IPluginV3* MaxPoolPlugin::clone() noexcept
{
    try
    {
        return new MaxPoolPlugin{mParams};
    }
    catch (std::exception const& e)
    {
        std::cerr << "Error cloning MaxPoolPlugin: " << e.what() << std::endl;
        return nullptr;
    }
}

IPluginCapability* MaxPoolPlugin::getCapabilityInterface(PluginCapabilityType type) noexcept
{
    switch (type)
    {
    case PluginCapabilityType::kBUILD: return static_cast<IPluginV3OneSafeBuildMSS*>(this);
    case PluginCapabilityType::kRUNTIME: return static_cast<IPluginV3OneSafeRuntime*>(this);
    case PluginCapabilityType::kCORE: return static_cast<IPluginV3OneSafeCore*>(this);
    }
    return nullptr;
}

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

int32_t MaxPoolPlugin::configurePlugin(
    TensorDescriptor const* in, int32_t nbInputs, TensorDescriptor const* out, int32_t nbOutputs) noexcept
{
    SAFE_ASSERT(in && nbInputs == 1);
    SAFE_ASSERT(out && nbOutputs == 1);
    SAFE_ASSERT(in[0].dataType == out[0].dataType);

    mParams.dtype = in[0].dataType;
    mParams.C = in[0].shape.d[1];
    mParams.H = in[0].shape.d[2];
    mParams.W = in[0].shape.d[3];
    mParams.H_out = out[0].shape.d[2];
    mParams.W_out = out[0].shape.d[3];

    switch (mParams.dtype)
    {
    case nvinfer1::DataType::kINT8: mParams.dtypeBytes = 1; break;
    case nvinfer1::DataType::kHALF: mParams.dtypeBytes = 2; break;
    case nvinfer1::DataType::kFLOAT: mParams.dtypeBytes = 4; break;
    default:
    {
        mRecorder->reportError(
            nvinfer1::ErrorCode::kFAILED_EXECUTION, "Failed to execute due to unavailable precision.");
        return 1;
    }
    }
    return 0;
}

bool MaxPoolPlugin::supportsFormatCombination(
    int32_t pos, TensorDescriptor const* inOut, int32_t nbInputs, int32_t nbOutputs) noexcept
{
    // For this method inputs are numbered 0..(nbInputs-1) and outputs are
    // numbered nbInputs..(nbInputs+nbOutputs-1). Using this numbering, pos is
    // an index into InOut, where 0 <= pos < nbInputs+nbOutputs.
    if (!(nbInputs == 1 && nbOutputs == 1 && pos < nbInputs + nbOutputs))
    {
        return false;
    }

    // Check if the data type is supported
    bool const supportedDataType = (inOut[pos].dataType == nvinfer1::DataType::kFLOAT)
        || (inOut[pos].dataType == nvinfer1::DataType::kHALF) || (inOut[pos].dataType == nvinfer1::DataType::kINT8);

    if (!supportedDataType)
    {
        return false;
    }

    // Check if the format is supported (no vectorization)
    bool const supportedFormat = (inOut[pos].vectorizedDim == -1);

    if (!supportedFormat)
    {
        return false;
    }

    // For output tensors, ensure they match the input data type
    if (pos >= nbInputs) // This is an output tensor
    {
        // Output must match input data type
        return inOut[pos].dataType == inOut[0].dataType && inOut[pos].vectorizedDim == inOut[0].vectorizedDim;
    }

    // For input tensors, just check that the type and format are supported
    return true;
}

int32_t MaxPoolPlugin::getOutputDataTypes(
    DataType* outputTypes, int32_t nbOutputs, DataType const* inputTypes, int32_t nbInputs) const noexcept
{
    SAFE_ASSERT(inputTypes && nbInputs == 1);
    outputTypes[0] = inputTypes[0];
    return 0;
}

int32_t MaxPoolPlugin::getOutputShapes(
    Dims const* inputs, int32_t nbInputs, Dims* outputs, int32_t nbOutputs) const noexcept
{
    // Empty, will not be called
    return 0;
}

int32_t MaxPoolPlugin::getSymbolicOutputShapes(DimsExprs const* inputs, int32_t nbInputs, DimsExprs* outputs,
    int32_t nbOutputs, IExprBuilder& exprBuilder) const noexcept
{
    SAFE_ASSERT(inputs && nbInputs == 1 && inputs[0U].nbDims == 4);
    SAFE_ASSERT(outputs && nbOutputs == 1);
    outputs[0U].nbDims = inputs[0U].nbDims;
    outputs[0U].d[0U] = inputs[0U].d[0U];
    outputs[0U].d[1U] = inputs[0U].d[1U];

    // Calculate height: (input_height + pad_y*2 - kernel_y) / stride_y + 1
    auto const* padY2
        = exprBuilder.operation(DimensionOperation::kPROD, *exprBuilder.constant(mParams.Py), *exprBuilder.constant(2));
    SAFE_ASSERT(padY2);
    SAFE_ASSERT(inputs[0U].d[2U]);
    auto const* inputPlusPadY = exprBuilder.operation(DimensionOperation::kSUM, *inputs[0U].d[2U], *padY2);
    SAFE_ASSERT(inputPlusPadY);
    auto const* heightMinusKernel
        = exprBuilder.operation(DimensionOperation::kSUB, *inputPlusPadY, *exprBuilder.constant(mParams.Ky));
    SAFE_ASSERT(heightMinusKernel);
    auto const* heightDivStride
        = exprBuilder.operation(DimensionOperation::kFLOOR_DIV, *heightMinusKernel, *exprBuilder.constant(mParams.Sy));
    SAFE_ASSERT(heightDivStride);
    outputs[0U].d[2U] = exprBuilder.operation(DimensionOperation::kSUM, *heightDivStride, *exprBuilder.constant(1));
    SAFE_ASSERT(outputs[0U].d[2U]);

    // Calculate width: (input_width + pad_x*2 - kernel_x) / stride_x + 1
    auto const* padX2
        = exprBuilder.operation(DimensionOperation::kPROD, *exprBuilder.constant(mParams.Px), *exprBuilder.constant(2));
    SAFE_ASSERT(padX2);
    SAFE_ASSERT(inputs[0U].d[3U]);
    auto const* inputPlusPadX = exprBuilder.operation(DimensionOperation::kSUM, *inputs[0U].d[3U], *padX2);
    SAFE_ASSERT(inputPlusPadX);
    auto const* widthMinusKernel
        = exprBuilder.operation(DimensionOperation::kSUB, *inputPlusPadX, *exprBuilder.constant(mParams.Kx));
    SAFE_ASSERT(widthMinusKernel);
    auto const* widthDivStride
        = exprBuilder.operation(DimensionOperation::kFLOOR_DIV, *widthMinusKernel, *exprBuilder.constant(mParams.Sx));
    SAFE_ASSERT(widthDivStride);
    outputs[0U].d[3U] = exprBuilder.operation(DimensionOperation::kSUM, *widthDivStride, *exprBuilder.constant(1));
    SAFE_ASSERT(outputs);

    return 0;
}

size_t MaxPoolPlugin::getWorkspaceSize(
    TensorDescriptor const* inputs, int32_t nbInputs, TensorDescriptor const* outputs, int32_t nbOutputs) const noexcept
{
    // MaxPool doesn't need any workspace memory
    return 0;
}

} // namespace plugin
} // namespace nvinfer1

extern "C" nvinfer2::safe::consistency::IPluginChecker* getPluginChecker(char const* name)
{
    using namespace std::string_view_literals;
    if (name != nullptr && name == "MaxPoolPlugin1"sv)
    {
        auto checker = std::make_unique<nvinfer2::safe::consistency::MaxPoolPluginChecker>();
        return checker.release();
    }
    return nullptr;
}
