// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (C) 2026, BigVision LLC, all rights reserved.
// Third party copyrights are property of their respective owners.

#include "../precomp.hpp"
#include "layers_common.hpp"
#include "../net_impl.hpp"

#if defined(__x86_64__) || defined(_M_X64)
#include <immintrin.h>
#endif

namespace cv
{
namespace dnn
{

/*
    DequantizeLinear layer, as defined in ONNX specification:
    https://onnx.ai/onnx/operators/onnx__DequantizeLinear.html

    Opset's 10 to 25 are covered.
*/

#if defined(__x86_64__) || defined(_M_X64)
#if defined(__GNUC__) || defined(__clang__)
__attribute__((target("avx2")))
#endif
static void dequantizeLinearChunk_u8_f32_avx2(const uint8_t* src, float* dst,
                                               float scale, int zp, int64_t len)
{
    __m256 vscale = _mm256_set1_ps(scale);
    __m256i vzp = _mm256_set1_epi32(zp);
    int64_t j = 0;
    for (; j <= len - 8; j += 8) {
        __m128i raw = _mm_loadl_epi64((__m128i*)(src + j));
        __m256i vi = _mm256_cvtepu8_epi32(raw);
        vi = _mm256_sub_epi32(vi, vzp);
        __m256 vf = _mm256_cvtepi32_ps(vi);
        vf = _mm256_mul_ps(vf, vscale);
        _mm256_storeu_ps(dst + j, vf);
    }
    for (; j < len; j++)
        dst[j] = (float)(src[j] - zp) * scale;
}

static void dequantizeLinearFast_u8_f32_avx2(const uint8_t* inp, float* out,
                                              float scale, int zp,
                                              int64_t total)
{
    const int64_t block = 1024;
    int64_t nblocks = (total + block - 1) / block;

    parallel_for_(Range(0, (int)nblocks), [&](const Range& r) {
        for (int i = r.start; i < r.end; i++) {
            int64_t ofs = i * block;
            int64_t len = std::min(block, total - ofs);
            dequantizeLinearChunk_u8_f32_avx2(inp + ofs, out + ofs, scale, zp, len);
        }
    });
}
#endif

template <typename _InpTp, typename _ScaleTp, typename _OutTp>
static void dequantizeLinear(const _InpTp* inp_, const _ScaleTp* scale_,
                             const _InpTp* zp_, _OutTp* out_,
                             int64_t nslices, int sz_a_,
                             int64_t slice_size_, int block_size_)
{
    int bsz_ = std::max(block_size_, 1);
    int nblocks_per_axis = (sz_a_ + bsz_ - 1) / bsz_;
    int64_t nmacro_blocks = nslices * nblocks_per_axis;
    CV_Assert(nmacro_blocks <= (int64_t)INT_MAX);

    parallel_for_(Range(0, (int)nmacro_blocks), [&](const Range& r) {
        int sz_a = sz_a_;
        int64_t slice_size = slice_size_;
        int block_size = block_size_;
        int delta = 0;
        int64_t scale_step = block_size > 0 ? slice_size : 1;
        int64_t zp_step = zp_ ? scale_step : 0;

        for (int i = r.start; i < r.end; i += delta) {
            int slice_idx = i / nblocks_per_axis;
            int block_idx = i - slice_idx * nblocks_per_axis;
            int64_t block_ofs, scale_ofs;
            if (block_size > 0) {
                delta = std::min(nblocks_per_axis - block_idx, r.end - i);
                block_ofs = (slice_idx*sz_a + block_idx*block_size)*slice_size;
                scale_ofs = (slice_idx*nblocks_per_axis + block_idx)*slice_size;
            } else {
                delta = std::min(sz_a - block_idx, r.end - i);
                block_ofs = (slice_idx*sz_a + block_idx)*slice_size;
                scale_ofs = block_idx;
            }
            const _InpTp* inp = inp_ + block_ofs;
            const _InpTp* zp = zp_ ? zp_ + scale_ofs : nullptr;
            const _ScaleTp* sc = scale_ + scale_ofs;
            _OutTp* out = out_ + block_ofs;

            if (slice_size > 1 && block_size > 0) {
                // Blocked mode: each of `delta` blocks has `block_size` rows along the
                // axis (clipped at sz_a), and within each row the scale/zp vary across
                // the `slice_size` trailing positions. The same `slice_size` scales/zps
                // are reused for every row inside one block.
                for (int k = 0; k < delta; k++) {
                    int rows = std::min(block_size, sz_a - (block_idx + k)*block_size);
                    for (int b = 0; b < rows; b++) {
                        for (int64_t j = 0; j < slice_size; j++) {
                            float scval = (float)sc[j];
                            _InpTp zpval = zp ? zp[j] : (_InpTp)0;
                            out[j] = _OutTp((inp[j] - zpval)*scval);
                        }
                        inp += slice_size;
                        out += slice_size;
                    }
                    sc += scale_step;
                    if (zp) zp += zp_step;
                }
            } else if (slice_size > 1) {
                for (int k = 0; k < delta; k++, inp += slice_size, out += slice_size,
                                                sc += scale_step, zp += zp_step) {
                    float scval = (float)*sc;
                    _InpTp zpval = zp ? *zp : (_InpTp)0;

                    for (int64_t j = 0; j < slice_size; j++)
                        out[j] = _OutTp((inp[j] - zpval)*scval);
                }
            } else if (block_size > 0 ) {
                int bsz = block_size;
                for (int k = 0; k < delta; k++, inp += bsz, out += bsz) {
                    bsz = std::min(bsz, sz_a - (block_idx + k)*block_size);
                    float scval = (float)sc[k];
                    _InpTp zpval = zp ? zp[k] : (_InpTp)0;

                    for (int j = 0; j < bsz; j++)
                        out[j] = _OutTp((inp[j] - zpval)*scval);
                }
                sc += delta;
                zp += zp ? delta : 0;
            } else {
                if (zp) {
                    for (int j = 0; j < delta; j++) {
                        float scval = (float)sc[j];
                        _InpTp zpval = zp[j];
                        out[j] = _OutTp((inp[j] - zpval)*scval);
                    }
                } else {
                    for (int j = 0; j < delta; j++) {
                        float scval = (float)sc[j];
                        out[j] = _OutTp(inp[j]*scval);
                    }
                }
                inp += delta;
                out += delta;
            }
        }
    });
}

// Dequantize INT8/UINT8 to FP32/FP16; out must be preallocated
static void dequantizeLinear(const Mat& inp, const Mat& scale_, const Mat& zp,
                             int axis, int block_size, Mat& out)
{
    Mat scale = scale_;
    CV_Assert(inp.isContinuous());
    CV_Assert(scale.isContinuous());
    CV_Assert(out.isContinuous());

    int inptype = inp.type();
    int outtype = out.type();
    int sctype = scale.type();
    int zptype = zp.type();
    MatShape inpshape = inp.shape();
    MatShape scshape = scale.shape();
    MatShape zpshape = zp.shape();
    int i, ndims = inpshape.dims;
    int64_t nslices = 1, slice_size = 1;

    CV_Assert(inptype == CV_8U || inptype == CV_8S || inptype == CV_32S);
    CV_Assert(sctype == CV_32F || sctype == CV_16F);
    CV_Assert(outtype == CV_32F || outtype == CV_16F);

    if (!zp.empty()) {
        CV_Assert(zp.isContinuous());
        CV_Assert(zptype == inptype);
        CV_Assert(zpshape == scshape);
    }

    if (ndims > 0)
        axis = normalize_axis(axis, ndims);
    else
        axis = 0;
    for (i = 0; i < axis; i++)
        nslices *= inpshape[i];
    for (i = axis+1; i < ndims; i++)
        slice_size *= inpshape[i];
    int sz_a = ndims > 0 ? inpshape[axis] : 1;

    if (block_size == 0) {
        size_t sc_total = scshape.total();
        CV_Assert(scale.dims <= 1);
        CV_Assert(sc_total == 1 || sc_total == (size_t)sz_a);

        // unroll the innermost loop if the scale's/zp's are the same
        if (sc_total == 1) {
            slice_size *= sz_a;
            sz_a = 1;
        }

        // avoid FP16 => FP32 conversion for scale inside the innermost loop
        if (sctype == CV_16F && slice_size == 1 && nslices > 1) {
            Mat temp;
            scale_.convertTo(temp, CV_32F);
            scale = temp;
            sctype = CV_32F;
        }
    } else {
        CV_Assert(block_size > 0);
        CV_Assert(scale.dims == ndims);
        for (int i = 0; i < ndims; i++) {
            int inp_i = inpshape[i];
            int sc_i = scshape[i];
            if (i == axis) {
                CV_Assert((inp_i + block_size - 1)/block_size == sc_i);
            } else {
                CV_Assert(sc_i == inp_i);
            }
        }
    }

    // Fast path: per-tensor dequantization uint8→float with AVX2 + proper parallelism
#if defined(__x86_64__) || defined(_M_X64)
    if (block_size == 0 && sz_a == 1 && inptype == CV_8U && outtype == CV_32F && sctype == CV_32F
        && checkHardwareSupport(CV_CPU_AVX2)) {
        float sc = reinterpret_cast<const float*>(scale.data)[0];
        int zpval = zp.empty() ? 0 : (int)reinterpret_cast<const uint8_t*>(zp.data)[0];
        int64_t total = nslices * slice_size;
        dequantizeLinearFast_u8_f32_avx2(reinterpret_cast<const uint8_t*>(inp.data),
                                          reinterpret_cast<float*>(out.data),
                                          sc, zpval, total);
        return;
    }
#endif

    if (inptype == CV_8U && sctype == CV_32F && outtype == CV_32F)
        dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
                         reinterpret_cast<const float*>(scale.data),
                         reinterpret_cast<const uint8_t*>(zp.data),
                         reinterpret_cast<float*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_8U && sctype == CV_16F && outtype == CV_32F)
        dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
                         reinterpret_cast<const hfloat*>(scale.data),
                         reinterpret_cast<const uint8_t*>(zp.data),
                         reinterpret_cast<float*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_8U && sctype == CV_32F && outtype == CV_16F)
        dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
                         reinterpret_cast<const float*>(scale.data),
                         reinterpret_cast<const uint8_t*>(zp.data),
                         reinterpret_cast<hfloat*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_8U && sctype == CV_16F && outtype == CV_16F)
        dequantizeLinear(reinterpret_cast<const uint8_t*>(inp.data),
                         reinterpret_cast<const hfloat*>(scale.data),
                         reinterpret_cast<const uint8_t*>(zp.data),
                         reinterpret_cast<hfloat*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_8S && sctype == CV_32F && outtype == CV_32F)
        dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
                         reinterpret_cast<const float*>(scale.data),
                         reinterpret_cast<const int8_t*>(zp.data),
                         reinterpret_cast<float*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_8S && sctype == CV_16F && outtype == CV_32F)
        dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
                         reinterpret_cast<const hfloat*>(scale.data),
                         reinterpret_cast<const int8_t*>(zp.data),
                         reinterpret_cast<float*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_8S && sctype == CV_32F && outtype == CV_16F)
        dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
                         reinterpret_cast<const float*>(scale.data),
                         reinterpret_cast<const int8_t*>(zp.data),
                         reinterpret_cast<hfloat*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_8S && sctype == CV_16F && outtype == CV_16F)
        dequantizeLinear(reinterpret_cast<const int8_t*>(inp.data),
                         reinterpret_cast<const hfloat*>(scale.data),
                         reinterpret_cast<const int8_t*>(zp.data),
                         reinterpret_cast<hfloat*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_32S && sctype == CV_32F && outtype == CV_32F)
        dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
                         reinterpret_cast<const float*>(scale.data),
                         reinterpret_cast<const int32_t*>(zp.data),
                         reinterpret_cast<float*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_32S && sctype == CV_16F && outtype == CV_32F)
        dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
                         reinterpret_cast<const hfloat*>(scale.data),
                         reinterpret_cast<const int32_t*>(zp.data),
                         reinterpret_cast<float*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_32S && sctype == CV_32F && outtype == CV_16F)
        dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
                         reinterpret_cast<const float*>(scale.data),
                         reinterpret_cast<const int32_t*>(zp.data),
                         reinterpret_cast<hfloat*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else if (inptype == CV_32S && sctype == CV_16F && outtype == CV_16F)
        dequantizeLinear(reinterpret_cast<const int32_t*>(inp.data),
                         reinterpret_cast<const hfloat*>(scale.data),
                         reinterpret_cast<const int32_t*>(zp.data),
                         reinterpret_cast<hfloat*>(out.data),
                         nslices, sz_a, slice_size, block_size);
    else {
        CV_Error_(Error::StsNotImplemented,
                  ("the following combination of types is not supported in "
                   "DequantizeLinear: inp=%s, scale=%s, out=%s",
                   typeToString(inptype).c_str(),
                   typeToString(sctype).c_str(),
                   typeToString(outtype).c_str()));
    }
}

class DequantizeLinearLayerImpl CV_FINAL : public DequantizeLinearLayer
{
public:
    DequantizeLinearLayerImpl(const LayerParams& params)
    {
        setParamsFrom(params);

        axis = params.get<int>("axis", 1);
        block_size = params.get<int>("block_size", 0);
        CV_Assert(block_size >= 0);
    }

    virtual bool supportBackend(int backendId) CV_OVERRIDE
    {
        return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH;
    }

    bool getMemoryShapes(const std::vector<MatShape> &inputs,
                         const int requiredOutputs,
                         std::vector<MatShape> &outputs,
                         std::vector<MatShape> &internals) const CV_OVERRIDE
    {
        size_t ninputs = inputs.size();
        CV_Assert(2 <= ninputs && ninputs <= 3);
        CV_Assert(requiredOutputs == 1);
        outputs.assign(1, inputs[0]);
        return true;
    }

    int getOutType() const
    {
        Net::Impl* netimpl_ = getNetImpl(this);
        return netimpl_->enableFP16 ? CV_16F : CV_32F;
    }

    void getTypes(const std::vector<MatType>& inputs,
        const int requiredOutputs,
        const int requiredInternals,
        std::vector<MatType>& outputs,
        std::vector<MatType>& internals) const CV_OVERRIDE
    {
        size_t ninputs = inputs.size();
        CV_Assert(2 <= ninputs && ninputs <= 3);
        if (ninputs == 3) {
            CV_Assert(inputs[0] == inputs[2]);
        }
        outputs.assign(1, getOutType());
    }

    virtual void finalize(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr) CV_OVERRIDE
    {
    }

    void forward(InputArrayOfArrays inputs_arr,
                 OutputArrayOfArrays outputs_arr,
                 OutputArrayOfArrays) CV_OVERRIDE
    {
        CV_TRACE_FUNCTION();
        CV_TRACE_ARG_VALUE(name, "name", name.c_str());

        int ninputs = inputs_arr.size(-1).area();
        CV_Assert(2 <= ninputs && ninputs <= 3);

        Mat inp = inputs_arr.getMat(0);
        Mat scale = inputs_arr.getMat(1);
        Mat zeropoint;
        int outtype = getOutType();
        MatShape inpshape = inp.shape();

        if (ninputs >= 3) {
            zeropoint = inputs_arr.getMat(2);
        }

        auto kind = outputs_arr.kind();

        if (kind == _InputArray::STD_VECTOR_MAT) {
            std::vector<Mat>& outs = outputs_arr.getMatVecRef();
            outs.resize(1);
            outs[0].fit(inpshape, outtype);
            dequantizeLinear(inp, scale, zeropoint, axis, block_size, outs[0]);
        } else if (kind == _InputArray::STD_VECTOR_UMAT) {
            std::vector<UMat>& outs = outputs_arr.getUMatVecRef();
            outs.resize(1);
            outs[0].fit(inpshape, outtype);
            Mat temp(inpshape, outtype);
            dequantizeLinear(inp, scale, zeropoint, axis, block_size, temp);
            temp.copyTo(outs[0]);
        } else {
            CV_Error(Error::StsNotImplemented, "");
        }
    }
};

Ptr<DequantizeLinearLayer> DequantizeLinearLayer::create(const LayerParams& params)
{
    return Ptr<DequantizeLinearLayer>(new DequantizeLinearLayerImpl(params));
}

}}
