/*
 * Copyright (c) 2022-2026 Huawei Device Co., Ltd.
 * 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/qrcodegen/qrcode_rscode.h"
#include "common/qrcodegen/qrcode_generator.h"
#include "common/qrcodegen/qrcode_inner.h"
#include "securec.h"

#define MM (rsCode->mm)
#define NN (rsCode->nn)
#define ALPHA_TO (rsCode->alphaTo)
#define INDEX_OF (rsCode->indexOf)
#define GENPOLY (rsCode->genpoly)
#define NROOTS (rsCode->nroots)
#define FCR (rsCode->fcr)
#define PRIM (rsCode->prim)
#define IPRIM (rsCode->iprim)
#define PAD (rsCode->pad)
#define A0 (NN)
#define SYM_SIZE_MAX 8

static ReedSolomonCode *g_rsList = nullptr;

static int32_t modnn(ReedSolomonCode *rs, int32_t x)
{
    if (rs == nullptr) {
        return -1;
    }
    while (x >= rs->nn) {
        x -= rs->nn;
        x = (((uint32_t)x) >> ((uint32_t)rs->mm)) + (((uint32_t)x) & ((uint32_t)rs->nn));
    }
    return x;
}

static bool InitRsCharCheck(ReedSolomonCodeData codeData)
{
    if (codeData.symsize < 0 || codeData.symsize > (int32_t)(SYM_SIZE_MAX * sizeof(uint8_t))) {
        return false;
    }
    if (codeData.fcr < 0 || codeData.fcr >= (1 << ((uint32_t)codeData.symsize))) {
        return false;
    }
    if (codeData.prim <= 0 || codeData.prim >= (1 << ((uint32_t)codeData.symsize))) {
        return false;
    }
    if (codeData.nroots < 0 || codeData.nroots >= (1 << ((uint32_t)codeData.symsize))) {
        return false;
    }
    if (codeData.pad < 0 || codeData.pad >= ((1 << ((uint32_t)codeData.symsize)) - 1 - codeData.nroots)) {
        return false;
    }

    return true;
}

static bool InitRsCharInit(ReedSolomonCode *rsCode, ReedSolomonCodeData codeData)
{
    if (rsCode == nullptr) {
        return false;
    }
    if (memset_s(rsCode, sizeof(ReedSolomonCode), 0, sizeof(ReedSolomonCode)) != 0) {
        QrcodeFree(rsCode);
        return false;
    }
    if ((codeData.symsize <= 0) || (codeData.symsize > (int32_t)(SYM_SIZE_MAX * sizeof(uint8_t)))) {
        QrcodeFree(rsCode);
        return false;
    }
    rsCode->mm = codeData.symsize;
    rsCode->nn = (1 << ((uint32_t)codeData.symsize)) - 1;
    rsCode->pad = codeData.pad;

    rsCode->alphaTo = (uint8_t *)QrcodeMalloc(sizeof(uint8_t) * (rsCode->nn + 1));
    if (rsCode->alphaTo == nullptr) {
        QrcodeFree(rsCode);
        return false;
    }
    rsCode->indexOf = (uint8_t *)QrcodeMalloc(sizeof(uint8_t) * (rsCode->nn + 1));
    if (rsCode->indexOf == nullptr) {
        QrcodeFree(rsCode->alphaTo);
        QrcodeFree(rsCode);
        return false;
    }
    rsCode->indexOf[0] = A0;
    rsCode->alphaTo[A0] = 0;

    return true;
}

static void InitRsCharGenpoly(ReedSolomonCode *rsCode, ReedSolomonCodeData codeData)
{
    int32_t iprim = 0;
    int32_t i = 0;
    if (rsCode == nullptr) {
        return;
    }
    for (iprim = 1; (iprim % codeData.prim) != 0; iprim += rsCode->nn)
        ;

    rsCode->iprim = iprim / codeData.prim;
    rsCode->genpoly[0] = 1;
    int32_t root = 0;
    for (i = 0, root = (codeData.fcr * codeData.prim); i < codeData.nroots; i++, root += codeData.prim) {
        rsCode->genpoly[i + 1] = 1;
        for (int32_t j = i; j > 0; j--) {
            if (rsCode->genpoly[j] != 0) {
                rsCode->genpoly[j] = rsCode->genpoly[j - 1] ^
                                     rsCode->alphaTo[modnn(rsCode, rsCode->indexOf[rsCode->genpoly[j]] + root)];
            } else {
                rsCode->genpoly[j] = rsCode->genpoly[j - 1];
            }
        }
        rsCode->genpoly[0] = rsCode->alphaTo[modnn(rsCode, rsCode->indexOf[rsCode->genpoly[0]] + root)];
    }
    for (i = 0; i <= codeData.nroots; i++) {
        rsCode->genpoly[i] = rsCode->indexOf[rsCode->genpoly[i]];
    }
}

static ReedSolomonCode *InitRsChar(ReedSolomonCodeData codeData)
{
    if (InitRsCharCheck(codeData) == false) {
        return nullptr;
    }

    ReedSolomonCode *rsCode = (ReedSolomonCode *)QrcodeMalloc(sizeof(ReedSolomonCode));
    if (InitRsCharInit(rsCode, codeData) == false) {
        QrcodeFree(rsCode);
        return nullptr;
    }

    uint32_t sr = 1;
    for (int32_t i = 0; i < rsCode->nn; i++) {
        rsCode->indexOf[sr] = i;
        rsCode->alphaTo[i] = sr;
        sr <<= 1U;
        if (sr & (1U << ((uint32_t)codeData.symsize))) {
            sr ^= (uint32_t)codeData.gfpoly;
            sr &= (uint32_t)rsCode->nn;
        }
    }
    if (sr != 1) {
        QrcodeFree(rsCode->alphaTo);
        QrcodeFree(rsCode->indexOf);
        QrcodeFree(rsCode);
        return nullptr;
    }

    rsCode->genpoly = (uint8_t *)QrcodeMalloc(sizeof(uint8_t) * (codeData.nroots + 1));
    if (rsCode->genpoly == nullptr) {
        QrcodeFree(rsCode->alphaTo);
        QrcodeFree(rsCode->indexOf);
        QrcodeFree(rsCode);
        return nullptr;
    }
    rsCode->fcr = codeData.fcr;
    rsCode->prim = codeData.prim;
    rsCode->nroots = codeData.nroots;
    rsCode->gfpoly = codeData.gfpoly;
    InitRsCharGenpoly(rsCode, codeData);

    return rsCode;
}

ReedSolomonCode *ReedSolomonCodeInit(ReedSolomonCodeData codeData)
{
    ReedSolomonCode *rsCode = nullptr;
    for (rsCode = g_rsList; rsCode != nullptr; rsCode = rsCode->next) {
        if ((rsCode->pad != codeData.pad) || (rsCode->nroots != codeData.nroots) || (rsCode->mm != codeData.symsize) ||
            (rsCode->gfpoly != codeData.gfpoly) || (rsCode->fcr != codeData.fcr) || (rsCode->prim != codeData.prim)) {
            continue;
        }
        goto DONE;
    }

    rsCode = InitRsChar(codeData);
    if (rsCode == nullptr) {
        goto DONE;
    }
    rsCode->next = g_rsList;
    g_rsList = rsCode;
DONE:

    return rsCode;
}

void ReedSolomonCodeFree(ReedSolomonCode *rs)
{
    QrcodeFree(rs->alphaTo);
    QrcodeFree(rs->indexOf);
    QrcodeFree(rs->genpoly);
    QrcodeFree(rs);
}

void ReedSolomonCodeFreeCache(void)
{
    ReedSolomonCode *rs = g_rsList;
    ReedSolomonCode *next = nullptr;

    while (rs != nullptr) {
        next = rs->next;
        ReedSolomonCodeFree(rs);
        rs = next;
    }
    g_rsList = nullptr;
}

void ReedSolomonCodeEncode(ReedSolomonCode *rsCode, const uint8_t *data, uint8_t *parity)
{
    if ((rsCode == nullptr) || (data == nullptr) || (parity == nullptr)) {
        return;
    }
    if (memset_s(parity, NROOTS * sizeof(uint8_t), 0, NROOTS * sizeof(uint8_t)) != 0) {
        return;
    }
    for (int32_t i = 0; i < NN - NROOTS - PAD; i++) {
        uint8_t feedback = INDEX_OF[data[i] ^ parity[0]];
        if (feedback != A0) {
            for (int32_t j = 1; j < NROOTS; j++) {
                parity[j] ^= ALPHA_TO[modnn(rsCode, feedback + GENPOLY[NROOTS - j])];
            }
        }
        if (memmove_s(&parity[0], sizeof(uint8_t) * (NROOTS - 1), &parity[1],
            sizeof(uint8_t) * (NROOTS - 1)) != 0) {
            return;
        }

        parity[NROOTS - 1] = (feedback != A0) ? ALPHA_TO[modnn(rsCode, feedback + GENPOLY[0])] : 0;
    }
}