/***************************************************************************
*
*Copyright 2015-2019 BES.
*All rights reserved. All unpublished rights reserved.
*
*No part of this work may be used or reproduced in any form or by any
*means, or stored in a database or retrieval system, without prior written
*permission of BES.
*
*Use of this work is governed by a license granted by BES.
*This work contains confidential and proprietary information of
*BES. which is protected by copyright, trade secret,
*trademark and other intellectual property rights.
*
****************************************************************************/

/*****************************header include********************************/
#include "cmsis.h"
#include "nv_section_dbg.h"
#include "nvrecord_gsound.h"
#include "factory_section.h"

#ifdef BISTO_ENABLED
#include "gsound_target_ota.h"

#ifdef GSOUND_HOTWORD_ENABLED
#include "gsound_custom_hotword_common.h"
#endif
#endif

/************************private macro defination***************************/
#define HOTWORD_MODEL_MAX_SIZE (80 * 1024) //!< should adjust this value if
//!< hotword model size changed and should adjust the flash section size at
//!< the sametime., @see HOTWORD_SECTION_SIZE in common.mk

#ifndef OTA_FLASH_LOGIC_ADDR
#define OTA_FLASH_LOGIC_ADDR        (FLASH_NC_BASE)
#endif

/************************private type defination****************************/

/************************extern function declearation***********************/
#ifdef GSOUND_HOTWORD_ENABLED
extern uint32_t __hotword_model_start[];
extern uint32_t __hotword_model_end[];
#endif

/**********************private function declearation************************/

/************************private variable defination************************/
#ifdef GSOUND_HOTWORD_ENABLED
#ifdef MODEL_FILE_EMBEDED
const uint8_t EMBEDED_MODEL_FILE [] = {
#include "res/gs_hw/en_all.txt" //!< must correspond to specific model ID, @see DEFAULT_HOTWORD_MODEL_ID
};

/// NOTE: Add more embeded model file info here

#define DEFAULT_MODEL_START_ADDR EMBEDED_MODEL_FILE //!< map to specific flash address within the bin file
#else /// #ifndef MODEL_FILE_EMBEDED
#define DEFAULT_MODEL_START_ADDR __hotword_model_start //!< map to specific flash address out of the bin file
#endif /// #ifdef MODEL_FILE_EMBEDED

HOTWORD_MODEL_INFO_T defaultModel[DEFAULT_MODEL_NUM] = {
    {DEFAULT_HOTWORD_MODEL_ID, (uint32_t)DEFAULT_MODEL_START_ADDR, HOTWORD_MODEL_MAX_SIZE},
    /// NOTE: Add other embeded model info here
};

char supportedModels[GSOUND_MAX_SUPPORTED_HOTWORD_MODELS_BYTES] = {};
#endif /// #ifdef GSOUND_HOTWORD_ENABLED

static NV_GSOUND_INFO_T *nvrecord_gsound_p = NULL;

/****************************function defination****************************/
void nv_record_gsound_rec_init(void)
{
    if (NULL == nvrecord_gsound_p)
    {
        nvrecord_gsound_p = &(nvrecord_extension_p->gsound_info);
    }

#ifdef GSOUND_HOTWORD_ENABLED
    /// init default supported model file(s)
    /// add to supported model file set(if it is not in this set)
    LOG_I("supportedModelCnt:%d", nvrecord_gsound_p->supportedModelCnt);
    if (0 == nvrecord_gsound_p->supportedModelCnt) //!< load default info at first time bootup
    {
        for (uint8_t i = 0; i < DEFAULT_MODEL_NUM; i++)
        {
            LOG_I("hotword_model_start:0x%x",
                  defaultModel[i].startAddr);

            nv_record_gsound_rec_add_new_model((void *)&defaultModel[i]);
        }
    }
#endif
}

void nv_record_gsound_rec_get_ptr(void **ptr)
{
    *ptr = nvrecord_gsound_p;
}

void nv_record_gsound_rec_updata_enable_state(bool enable)
{
    LOG_I("gsound enable state update:%d->%d", nvrecord_gsound_p->isGsoundEnabled, enable);

    if (nvrecord_gsound_p->isGsoundEnabled != enable)
    {
        /// disable the MPU protection for write operation
        uint32_t lock = nv_record_pre_write_operation();

        /// update the enable state
        nvrecord_gsound_p->isGsoundEnabled = enable;
        nv_record_extension_update();

        /// enable the MPU protection after the write operation
        nv_record_post_write_operation(lock);
    }
}

#ifdef GSOUND_HOTWORD_ENABLED
bool nv_record_gsound_rec_is_model_insert_allowed(const char *model)
{
    bool ret = true;
    uint8_t i;

    if ((nvrecord_gsound_p->supportedModelCnt >= HOTWORD_MODLE_MAX_NUM) &&
        (0xFF != nvrecord_gsound_p->supportedModelCnt))
    {
        for (i = 0; i < HOTWORD_MODLE_MAX_NUM; i++)
        {
            /// hotword model exist already
            if (!memcmp(&nvrecord_gsound_p->modelInfo[i].modelId, model, strlen(model)))
            {
                break;
            }
        }

        if (HOTWORD_MODLE_MAX_NUM == i)
        {
            ret = false;
        }
    }

    return ret;
}

bool nv_record_gsound_rec_add_new_model(void *pInfo)
{
    bool ret = true;
    bool found = false;
    uint32_t lock = 0;

    HOTWORD_MODEL_INFO_T *info = (HOTWORD_MODEL_INFO_T *)pInfo;

    if (INVALID_MODEL_NUM == nvrecord_gsound_p->supportedModelCnt)
    {
        lock = nv_record_pre_write_operation();
        nvrecord_gsound_p->supportedModelCnt = 0;
        nv_record_post_write_operation(lock);
    }

    if (info)
    {
        lock = nv_record_pre_write_operation();
        info->startAddr |= OTA_FLASH_LOGIC_ADDR;

        for (uint8_t i = 0; i < nvrecord_gsound_p->supportedModelCnt; i++)
        {
            /// hotword model exist already
            if (!memcmp(&nvrecord_gsound_p->modelInfo[i].modelId, info->modelId, GSOUND_HOTWORD_MODEL_ID_BYTES))
            {
                LOG_I("info->startAddr:%x", info->startAddr);

                ///.update the start address
                if (info->startAddr)
                {
                    nvrecord_gsound_p->modelInfo[i].startAddr = info->startAddr;
                    nvrecord_gsound_p->modelInfo[i].len = info->len;
                }

                LOG_I("model already exist in user section, model_id:%s, model_addr:%x",
                      nvrecord_gsound_p->modelInfo[i].modelId,
                      nvrecord_gsound_p->modelInfo[i].startAddr);
                found = true;
                break;
            }
        }

        LOG_I("current supportedModelCnt:%d", nvrecord_gsound_p->supportedModelCnt);

        /// brand new model insert
        if (!found)
        {
            memcpy(&nvrecord_gsound_p->modelInfo[nvrecord_gsound_p->supportedModelCnt],
                   info,
                   sizeof(HOTWORD_MODEL_INFO_T));

            LOG_I("new added modelId:%s, startAddr:%x, modelFileLen:%x", info->modelId, info->startAddr, info->len);
            nvrecord_gsound_p->supportedModelCnt++;
        }
        nv_record_post_write_operation(lock);

        /// new model file incoming, update flash info
        if (!found)
        {
            /// flush the hotword info to flash
            nv_record_update_runtime_userdata();
            nv_record_flash_flush();
        }

        LOG_I("current supportedModelCnt:%d", nvrecord_gsound_p->supportedModelCnt);
    }
    else
    {
        LOG_W("NULL pointer received in %s", __func__);
        ret = false;
    }

    return ret;
}

uint32_t nv_record_gsound_rec_get_hotword_model_addr(const char *model_id, bool add_new, int32_t newModelLen)
{
    uint32_t addr = 0;
    bool found = false;

    LOG_I("%s revceived model ID: %s", __func__, model_id);
    if (nvrecord_gsound_p)
    {
        for (uint8_t i = 0; i < ARRAY_SIZE(nvrecord_gsound_p->modelInfo); i++)
        {
            if (!memcmp(nvrecord_gsound_p->modelInfo[i].modelId, model_id, 4))
            {
                addr = nvrecord_gsound_p->modelInfo[i].startAddr;
                found = true;
                LOG_I("found saved model info, index:%d", i);
                break;
            }
        }
    }

    do
    {
        if (add_new && !found)
        {
            addr = (uint32_t)__hotword_model_start;
            uint32_t tempAddr;
            uint8_t startIdx = 0;
#ifdef MODEL_FILE_EMBEDED
            startIdx = DEFAULT_MODEL_NUM; //!< set offset to skip the embeded model file info
#endif

            if (INVALID_MODEL_NUM == nvrecord_gsound_p->supportedModelCnt)
            {
                break;
            }

            /// get the largest flash address
            for (uint8_t i = startIdx; i < nvrecord_gsound_p->supportedModelCnt; i++)
            {
                tempAddr = nvrecord_gsound_p->modelInfo[i].startAddr + nvrecord_gsound_p->modelInfo[i].len;
                if ((tempAddr & 0xFFFFFF) > (addr & 0xFFFFFF))
                {
                    addr = tempAddr;
                }
            }

            /// force 4K align the address
            addr = F_4K_ALIGN(addr);
            LOG_I("supportedModelCnt:%d, Largest address:0x%x", nvrecord_gsound_p->supportedModelCnt, addr);

            /// check is overwrite needed
            if ((newModelLen + addr) > (uint32_t)__hotword_model_end ||
                (nvrecord_gsound_p->supportedModelCnt >= HOTWORD_MODLE_MAX_NUM))
            {
                uint8_t cleanIdx = 0;                                                                      //!< model file index to clean
                addr = nvrecord_gsound_p->modelInfo[startIdx].startAddr;                                   //!< init anchor point
                uint32_t cleanSize = F_4K_ALIGN(addr + nvrecord_gsound_p->modelInfo[startIdx].len) - addr; //!< flash size to clean

                uint32_t lock = nv_record_pre_write_operation(); //!< disable the MPU for info update
                uint32_t intLock = int_lock();                   //!< lock the global interrupt

                /// remove the first record
                for (uint8_t i = startIdx; i < (nvrecord_gsound_p->supportedModelCnt - 1); i++)
                {
                    nvrecord_gsound_p->modelInfo[i] = nvrecord_gsound_p->modelInfo[i + 1];
                }

                /// decrease the number of supported model
                nvrecord_gsound_p->supportedModelCnt--;

                while (cleanSize < newModelLen)
                {
                    uint8_t headAdjacentIdx = 0xFF, tailAdjacentIdx = 0xFF;
                    uint32_t headAdjacentOffset = 0xFFFFFFFF, tailAdjacentOffset = 0xFFFFFFFF;

                    /// find the adjacent chunck
                    for (uint8_t i = startIdx; i < nvrecord_gsound_p->supportedModelCnt; i++)
                    {
                        if (nvrecord_gsound_p->modelInfo[i].startAddr > addr)
                        {
                            if (nvrecord_gsound_p->modelInfo[i].startAddr - addr < tailAdjacentOffset)
                            {
                                tailAdjacentOffset = nvrecord_gsound_p->modelInfo[i].startAddr - addr;
                                tailAdjacentIdx = i;
                            }
                        }
                        else if (nvrecord_gsound_p->modelInfo[i].startAddr < addr)
                        {
                            if (addr - nvrecord_gsound_p->modelInfo[i].startAddr < headAdjacentOffset)
                            {
                                headAdjacentOffset = nvrecord_gsound_p->modelInfo[i].startAddr - addr;
                                headAdjacentIdx = i;
                            }
                        }
                    }

                    LOG_I("headAdjacentIdx:%d, tailAdjacentIdx:%d", headAdjacentIdx, tailAdjacentIdx);

                    /// find the smaller index, smaller index means older record
                    if (headAdjacentIdx > tailAdjacentIdx)
                    {
                        cleanSize += F_4K_ALIGN(nvrecord_gsound_p->modelInfo[tailAdjacentIdx].startAddr +
                                                nvrecord_gsound_p->modelInfo[tailAdjacentIdx].len) -
                                     nvrecord_gsound_p->modelInfo[tailAdjacentIdx].startAddr;
                        cleanIdx = tailAdjacentIdx;
                    }
                    else
                    {
                        addr = nvrecord_gsound_p->modelInfo[headAdjacentIdx].startAddr;
                        cleanSize += F_4K_ALIGN(addr + nvrecord_gsound_p->modelInfo[headAdjacentIdx].len) - addr;
                        cleanIdx = headAdjacentIdx;
                    }

                    /// remove the first record
                    for (uint8_t i = cleanIdx; i < (nvrecord_gsound_p->supportedModelCnt - 1); i++)
                    {
                        nvrecord_gsound_p->modelInfo[i] = nvrecord_gsound_p->modelInfo[i + 1];
                    }

                    /// decrease the number of supported model
                    nvrecord_gsound_p->supportedModelCnt--;
                }

                if ((addr < (uint32_t)__hotword_model_start) ||
                    (addr > (uint32_t)__hotword_model_end))
                {
                    LOG_I("invalid addr found:0x%x, clean all model files", addr);
                    nvrecord_gsound_p->supportedModelCnt = startIdx;
                    addr = (uint32_t)__hotword_model_start;
                }

                if (0 == nvrecord_gsound_p->supportedModelCnt)
                {
                    /// invalid number used to identify with fist time bootup
                    nvrecord_gsound_p->supportedModelCnt = INVALID_MODEL_NUM;
                }

                int_unlock(intLock);                  //!< unlock the global interrupt
                nv_record_post_write_operation(lock); //!< enable the MPU

                /// flush the hotword info to flash
                nv_record_update_runtime_userdata();
                nv_record_flash_flush();
            }
        }
    } while (0);

    return addr;
}

int nv_record_gsound_rec_get_supported_model_id(char *models_out,
                                                 uint8_t *length_out)
{
    int ret=  0;
    const char* terminator = NULL;

    if (INVALID_MODEL_NUM == nvrecord_gsound_p->supportedModelCnt)
    {
        memcpy(models_out, STRING_TERMINATOR_NULL, STRING_TERMINATOR_SIZE);
        *length_out = STRING_TERMINATOR_SIZE;
    }
    else
    {
        for (uint8_t i = 0; i < nvrecord_gsound_p->supportedModelCnt; i++)
        {
            LOG_I("supported model_id:%s, model_addr:%x",
                  nvrecord_gsound_p->modelInfo[i].modelId,
                  nvrecord_gsound_p->modelInfo[i].startAddr);

            memcpy((supportedModels + i * GSOUND_HOTWORD_SUPPORTED_MODEL_ID_BYTES),
                   nvrecord_gsound_p->modelInfo[i].modelId,
                   GSOUND_HOTWORD_MODEL_ID_BYTES);

            if ((i + 1) == nvrecord_gsound_p->supportedModelCnt)
            {
                terminator = STRING_TERMINATOR_NULL;
            }
            else
            {
                terminator = SUPPORTED_HOTWORD_MODEL_DELIM;
            }
            memcpy(&supportedModels[(i * GSOUND_HOTWORD_SUPPORTED_MODEL_ID_BYTES) + GSOUND_HOTWORD_MODEL_ID_BYTES],
                   terminator,
                   STRING_TERMINATOR_SIZE);
        }

        if (nvrecord_gsound_p->supportedModelCnt * GSOUND_HOTWORD_SUPPORTED_MODEL_ID_BYTES <= *length_out)
        {
            strcpy(models_out, supportedModels);
            *length_out = strlen(supportedModels) + STRING_TERMINATOR_SIZE;
            LOG_I("total supported model_id(length is %d):", *length_out);
            DUMP8("0x%02x ", models_out, *length_out);
        }
        else
        {
            ret = -1;
            LOG_W("supported model id length exceeded max length");
        }
    }

    return ret;
}
#endif
