// File: binding.cpp
#include "conv2d_w8a8ch_contract.h"
#include "mat.h"
#include "option.h"

namespace {
constexpr int kernel_h   = 3;
constexpr int kernel_w   = 3;
constexpr int stride_h   = 2;
constexpr int stride_w   = 2;
constexpr int dilation_h = 1;
constexpr int dilation_w = 1;
constexpr int pad_top    = 1;
constexpr int pad_left   = 1;
} // namespace

extern "C" {
int armbench_entry_conv2d(
    void* bottom_v, void* top_v,
    void* weight_v, void* bias_v,
    void* weight_scales_v,
    void* opt_v)
{
    const auto& bottom         = *reinterpret_cast<const ncnn::Mat*>(bottom_v);
    auto&       top            = *reinterpret_cast<ncnn::Mat*>(top_v);
    const auto& weight         = *reinterpret_cast<const ncnn::Mat*>(weight_v);
    const auto& bias           = *reinterpret_cast<const ncnn::Mat*>(bias_v);
    const auto& weight_scales  = *reinterpret_cast<const ncnn::Mat*>(weight_scales_v);
    const auto& opt            = *reinterpret_cast<const ncnn::Option*>(opt_v);

    // C_out varies per workload — derive from the weight tensor instead of baking it.
    const int C_in  = bottom.c;
    const int C_out = static_cast<int>(weight.total()) / (C_in * kernel_h * kernel_w);

    const int ext_kh = dilation_h * (kernel_h - 1) + 1;
    const int ext_kw = dilation_w * (kernel_w - 1) + 1;
    const int H_out  = (bottom.h + 2 * pad_top  - ext_kh) / stride_h + 1;
    const int W_out  = (bottom.w + 2 * pad_left - ext_kw) / stride_w + 1;

    // Dequantized float32 output — same elemsize as the fp32 baseline. w8a8ch
    // only quantizes inputs; the task doesn't require requantizing the result.
    top.create(W_out, H_out, C_out, (size_t)4u, opt.blob_allocator);
    if (top.empty()) return -1;

    return ncnn::convolution2d_w8a8ch_kernel(
        bottom, top, weight, bias, weight_scales,
        kernel_h, kernel_w,
        stride_h, stride_w,
        dilation_h, dilation_w,
        pad_top, pad_left,
        opt);
}
} // extern "C"