// File: kernel.cpp
#include "conv2d_w8a8ch_contract.h"
#include "convolution_arm.h"
#include "mat.h"
#include "option.h"
#include <cstring>

namespace {
// input_scale is constant across every workload for this definition (checked
// at generation time), so it's baked here rather than plumbed through the ABI.
constexpr float input_scale = 0.03331;
} // namespace

int ncnn::convolution2d_w8a8ch_kernel(
    const Mat& bottom_blob, Mat& top_blob,
    const Mat& weight_data, const Mat& bias_data,
    const Mat& weight_scales,
    int kernel_h, int kernel_w,
    int stride_h, int stride_w,
    int dilation_h, int dilation_w,
    int pad_top, int pad_left,
    const Option& opt)
{
    const int num_output = top_blob.c;  // pre-set by binding.cpp

    // ncnn's int8 scale fields are *quantization* multipliers (int8 =
    // round(float * scale)); this baseline's weight_scales/input_scale are
    // *dequantization* multipliers (real = int8 * scale) per the reference —
    // invert them, or the output is silently wrong (not a crash).
    Mat weight_int8_scales;
    weight_int8_scales.create(num_output, (size_t)4u);
    if (weight_int8_scales.empty()) return -1;
    const float* ws = (const float*)weight_scales.data;
    for (int p = 0; p < num_output; ++p)
        weight_int8_scales[p] = 1.0f / ws[p];

    Mat bottom_int8_scales;
    bottom_int8_scales.create(1, (size_t)4u);
    if (bottom_int8_scales.empty()) return -1;
    bottom_int8_scales[0] = 1.0f / input_scale;

    Convolution_arm conv;
    conv.num_output              = num_output;
    conv.kernel_h                = kernel_h;    conv.kernel_w   = kernel_w;
    conv.stride_h                = stride_h;    conv.stride_w   = stride_w;
    conv.dilation_h              = dilation_h;  conv.dilation_w = dilation_w;
    conv.pad_top                 = pad_top;     conv.pad_bottom = pad_top;
    conv.pad_left                = pad_left;    conv.pad_right  = pad_left;
    conv.pad_value                = 0.f;
    conv.bias_term                = 1;
    conv.weight_data_size          = static_cast<int>(weight_data.total());
    // Must stay truthy to enter the int8 input path, but <=100 so ncnn takes
    // the plain-dequantize branch (dequantize_from_int32: out = acc*scale+bias,
    // no rounding/clip) instead of requantizing to int8 — w8a8ch only quantizes
    // inputs, the task doesn't require a quantized output.
    conv.int8_scale_term          = 2;
    conv.activation_type          = 0;
    conv.activation_params        = Mat();
    conv.dynamic_weight            = 0;
    conv.weight_data               = const_cast<Mat&>(weight_data);
    conv.bias_data                 = const_cast<Mat&>(bias_data);
    conv.weight_data_int8_scales    = weight_int8_scales;
    conv.bottom_blob_int8_scales    = bottom_int8_scales;

    if (conv.create_pipeline(opt) != 0) return -1;

    Mat local_top;
    if (conv.forward(bottom_blob, local_top, opt) != 0) return -1;

    // Copy from local_top to pre-allocated top_blob, channel by channel (never
    // bulk-memcpy — channels can have cstep alignment padding).
    for (int c = 0; c < num_output; ++c)
        std::memcpy((float*)top_blob.channel(c), (const float*)local_top.channel(c),
                    top_blob.h * top_blob.w * sizeof(float));
    return 0;
}