/**
 *!
 * \file        b_srv_ota.c
 * \version     v0.0.1
 * \date        2023/08/27
 * \author      Bean(notrynohigh@outlook.com)
 *******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 Bean
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *******************************************************************************
 */

/*Includes ----------------------------------------------*/
#include "services/inc/b_srv_ota.h"

#if (defined(_OTA_SERVICE_ENABLE) && (_OTA_SERVICE_ENABLE == 1))

#include "algorithm/inc/algo_crc.h"
#include "core/inc/b_sem.h"
#include "core/inc/b_task.h"
#include "hal/inc/b_hal.h"

/**
 * \addtogroup BABYOS
 * \{
 */

/**
 * \addtogroup SERVICES
 * \{
 */

/**
 * \addtogroup OTA
 * \{
 */

/**
 * \defgroup OTA_Private_TypesDefinitions
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup OTA_Private_Defines
 * \{
 */
#ifndef OTA_TIMEOUT_S
#define OTA_TIMEOUT_S (30)
#endif
/**
 * \}
 */

/**
 * \defgroup OTA_Private_Macros
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup OTA_Private_Variables
 * \{
 */

static bProtSrvId_t      bProtocolId    = NULL;
static bOtaSrvSendData_t bProtocolSend  = NULL;
static uint32_t          bOtaFileOffset = 0;

B_TASK_CREATE_ATTR(bOtaTaskAttr);
B_SEM_CREATE_ATTR(bOtaSemAttr);
static bTaskId_t bOtaTaskId = NULL;
static bSemId_t  bOtaSemId  = NULL;

static bProtSrvSubscribe_t bSubInfo = {
    .number = 0,
};

const static bProtoCmd_t bCmdTable[] = {B_PROTO_OTA_FILE_INFO, B_PROTO_FILE_DATA};

/**
 * \}
 */

/**
 * \defgroup OTA_Private_FunctionPrototypes
 * \{
 */

/**
 * \}
 */

/**
 * \defgroup OTA_Private_Functions
 * \{
 */

static int _bOtaSendResult(uint8_t result)
{
    uint8_t  buf[128];
    uint16_t olen = 0;
    buf[0]        = result;
    olen          = bProtSrvPackage(bProtocolId, B_PROTO_TRANS_FILE_RESULT, buf, sizeof(buf));
    if (olen > 0 && bProtocolSend != NULL)
    {
        bProtocolSend(buf, olen);
    }
    return 0;
}

PT_THREAD(bOtaTaskFunc)(struct pt *pt, void *arg)
{
    uint8_t              buf[128];
    uint16_t             len     = 0;
    bProtoReqFileData_t *p_req   = (bProtoReqFileData_t *)buf;
    static uint32_t      timeout = 0;
    B_TASK_INIT_BEGIN();
    timeout = bHalGetSysTick();
    B_TASK_INIT_END();
    PT_BEGIN(pt);
    while (1)
    {
        bTaskDelayMs(pt, 200);
        p_req->offset = bOtaFileOffset;
        p_req->size   = 512;
        len           = bProtSrvPackage(bProtocolId, B_PROTO_REQ_FILE_DATA, buf, sizeof(buf));
        bProtocolSend(buf, len);
        bSemAcquireBlock(pt, bOtaSemId, 3000);
        if (bOtaTaskId != NULL && (bIapGetStatus() != B_IAP_STA_START))
        {
            bTaskRemove(bOtaTaskId);
            bOtaTaskId = NULL;
        }
        if (PT_WAIT_IS_TIMEOUT(pt))
        {
            if (TICK_DIFF_BIT32(timeout, bHalGetSysTick()) >= (MS2TICKS(OTA_TIMEOUT_S * 1000)))
            {
                _bOtaSendResult(4);
                bTaskDelayMs(pt, 1000);
                if (bIapIsInBoot())
                {
                    bIapJump2App();
                    // 没有成功跳入应用程序,继续等待数据
                    timeout = bHalGetSysTick();
                    bHalIntEnable();
                }
                else
                {
                    // 应用程序中,则停止请求数据
                    if (bOtaTaskId != NULL)
                    {
                        bTaskRemove(bOtaTaskId);
                        bOtaTaskId = NULL;
                    }
                }
            }
        }
        else
        {
            timeout = bHalGetSysTick();
        }
    }
    PT_END(pt);
}

static void _OtaProtStart()
{
    if (bOtaTaskId != NULL)
    {
        bTaskRemove(bOtaTaskId);
        bOtaTaskId = NULL;
    }
    bOtaTaskId = bTaskCreate("ota", bOtaTaskFunc, NULL, &bOtaTaskAttr);
    if (bOtaSemId == NULL)
    {
        bOtaSemId = bSemCreate(1, 0, &bOtaSemAttr);
    }
    bSemAcquireNonblock(bOtaSemId);
    bOtaFileOffset = 0;
}

static int _OtaProtCallback(bProtoCmd_t cmd, void *param)
{
    if (cmd == B_PROTO_OTA_FILE_INFO)
    {
        bProtoFileInfo_t *file_info = (bProtoFileInfo_t *)param;
        bIapFwInfo_t      fwinfo;
        fwinfo.crc      = file_info->fcrc32;
        fwinfo.crc_type = ALGO_CRC32;
        fwinfo.len      = file_info->size;
        memcpy(fwinfo.name, file_info->name, sizeof(fwinfo.name));
        if (0 == bIapEventHandler(B_IAP_EVENT_START, &fwinfo))
        {
            _OtaProtStart();
        }
    }
    else if ((cmd == B_PROTO_FILE_DATA) && (bIapGetStatus() == B_IAP_STA_START))
    {
        bProtoFileData_t *pFileData = (bProtoFileData_t *)param;
        if (pFileData->size > 0 && pFileData->offset == bOtaFileOffset)
        {
            bIapFwData_t fwdata;
            fwdata.pbuf    = pFileData->dat;
            fwdata.len     = pFileData->size;
            fwdata.release = NULL;
            int ret        = bIapEventHandler(B_IAP_EVENT_DATA, &fwdata);
            if (ret >= 0)
            {
                bOtaFileOffset = ret;
            }
        }
        if ((bIapGetStatus() == B_IAP_STA_READY) && (bIapIsInBoot() == 0))
        {
            bIapJump2Boot();
        }
        else if ((bIapGetStatus() == B_IAP_STA_FINISHED) && (bIapIsInBoot()))
        {
            bIapJump2App();
        }
        else if (bIapGetStatus() == B_IAP_STA_START)
        {
            bSemRelease(bOtaSemId);
        }
    }
    return 0;
}

/**
 * \}
 */

/**
 * \addtogroup OTA_Exported_Functions
 * \{
 */

int bOtaSrvInit(bProtSrvId_t protocol_id, bOtaSrvSendData_t send, uint32_t cache_dev_no,
                uint32_t backup_dev_no, uint32_t backup_time_s)
{
    int ret = 0;
    if (protocol_id == NULL || send == NULL)
    {
        return -1;
    }
    bProtocolId         = protocol_id;
    bProtocolSend       = send;
    ret                 = bIapInit(cache_dev_no, backup_dev_no, backup_time_s);
    bOtaFileOffset      = 0;
    bSubInfo.number     = sizeof(bCmdTable) / sizeof(bProtoCmd_t);
    bSubInfo.pcmd_table = &bCmdTable[0];
    bSubInfo.callback   = _OtaProtCallback;
    bProtSrvSubscribe(protocol_id, &bSubInfo);
    if ((bIapIsInBoot()) && (bIapGetStatus() == B_IAP_STA_START))
    {
        _OtaProtStart();
    }
    if ((bIapIsInBoot() == 0) && (ret == 1))
    {
        _bOtaSendResult(0);
    }
    return 0;
}

/**
 * \}
 */

/**
 * \}
 */

/**
 * \}
 */

/**
 * \}
 */
#endif

/************************ Copyright (c) 2023 Bean *****END OF FILE****/