/**************************************************************************************************
*******
**************************************************************************************************/



#include "bcomdef.h"
#include "OSAL.h"
#include "OSAL_PwrMgr.h"
#include "OSAL_bufmgr.h"
#include "gatt.h"
#include "ll.h"
#include "ll_common.h"
#include "hci.h"
#include "gapgattserver.h"
#include "gattservapp.h"
#include "central.h"
#include "gapbondmgr.h"
#include "simpleGATTprofile_ota.h"
#include "ota_mesh_master.h"
#include "timer.h"
#include "log.h"
#include "ll_def.h"
#include "global_config.h"
#include "flash.h"
#include "rflib.h"
#include "otam_cmd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "otam_protocol.h"
#include "ota_mesh.h"
#include "ota_app_service.h"
#include "ota_protocol.h"
#include "ota_flash.h"
#include "ota_flash_mesh.h"
//#include "otam_1clk_ota.h"
#include "crc16.h"
#include "error.h"

#define OTAF_BASE_ADDR          0x11000000
#define OTAF_END_ADDR           0x1107ffff

enum
{
    OTAM_ST_DISCONNECT = 0,
    OTAM_ST_CONNECTED,  //connected, idle
    OTAM_ST_WAIT_STARTED,
    OTAM_ST_PARAM,
    OTAM_ST_WAIT_PARTITION_INFO,
    OTAM_ST_DATA,
    OTAM_ST_COMPLETE,
    OTAM_ST_ERROR,
    OTAM_ST_CANCELING,  //wait stop
};





typedef struct
{
    ota_fw_t         fw;

    //data cache for a block transmit
    uint32_t          cache_size;
    uint32_t          cache_offset;
    uint8_t           cache_retry;
    uint8_t*          cache;
} otam_fw_t;


typedef struct
{
    uint8_t             state;
    uint8_t             run_mode;
    bool                reset_mode;
    uint16_t            mtu_a;
    uint16_t            burst_size;
    uint8_t             opcode;
    otam_fw_t           fw;
    otam_proto_meth_t   method;
} otam_proto_ctx_t;


static otam_proto_ctx_t s_otap_ctx_t;

static void print_hex (const uint8* data, uint16 len)
{
    //return;
    #if(DEBUG_INFO > 1)
    uint16 i;
    char strdata[5];

    for (i = 0; i < len - 1; i++)
    {
        //if(i %16 ==  0 && i >0)
        //  LOG("\n");
        sprintf(strdata, "%.2x", data[i]);
        AT_LOG("%s ",strdata);
    }

    sprintf(strdata, "%.2x", data[i]);
    AT_LOG("%s\n",strdata);
    #endif
}




static int otam_proto_ctx_reset(uint8_t st)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_proto_meth_t* method = &(pctx->method);
    pctx->state = st;
    pctx->opcode = 0;
    memset(&(pctx->fw), 0, sizeof(otam_fw_t));
    method->clear();
    return PPlus_SUCCESS;
}

static void otam_proto_disconnect(void* param)
{
    otam_proto_ctx_reset(OTAM_ST_DISCONNECT);
}
static void otam_proto_connect(void* param)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_proto_conn_param_t* pconn =  (otam_proto_conn_param_t*)param;
    pctx->state = OTAM_ST_CONNECTED;
    pctx->mtu_a = pconn->mtu -3;
    pctx->opcode = 0xff;

    if(pconn->run_mode == OTAC_RUNMODE_APP)
    {
        pctx->run_mode = OTAC_RUNMODE_APP;
        pctx->reset_mode = 0;
    }
    else if(pconn->run_mode == OTAC_RUNMODE_OTA)
    {
        pctx->run_mode = OTAC_RUNMODE_OTA;

        if(pctx->reset_mode == OTAC_RUNMODE_OTARES)
            pctx->run_mode = OTAC_RUNMODE_OTARES;

        pctx->reset_mode = 0;
    }
    else
    {
        pctx->run_mode = 0;
        pctx->reset_mode = 0;
    }
}

static int send_patition_info(void)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_fw_t* pfw = &(pctx->fw);
    otam_proto_meth_t* method = &(pctx->method);
    uint8_t data[20];

    if(pctx->state != OTAM_ST_WAIT_STARTED && pctx->state != OTAM_ST_DATA)
    {
        return PPlus_ERR_INVALID_STATE;
    }

    //partition cmd: 02 ID FA FA FA FA RA RA RA RA SZ SZ SZ SZ CS CS
    //            ID: index
    //            FA: flash address
    //            RA: run address
    //            SZ: partition size
    //            CS: checksum
    uint32_t val;
    uint16_t offset = 0;
    uint32_t flash_addr = 0;
    uint32_t run_addr = pfw->fw.part[pfw->fw.part_current].run_addr;

    if(run_addr > OTAF_BASE_ADDR && run_addr < OTAF_END_ADDR )
    {
        flash_addr = run_addr;
    }
    else
    {
        //calculate store address in flash
        for(int i = 0; i<pfw->fw.part_current; i++ )
        {
            if(pfw->fw.part[i].run_addr > OTAF_BASE_ADDR && pfw->fw.part[i].run_addr < OTAF_END_ADDR)
                continue;

            val = pfw->fw.part[i].size +3;
            val = val - (val%4);
            flash_addr += val;
        }
    }

    data[offset ++] = OTA_CMD_PARTITION_INFO;
    data[offset ++] = pfw->fw.part_current;
    val = flash_addr;
    data[offset ++] = (uint8_t)(val&0xff);
    data[offset ++] = (uint8_t)((val>>8)&0xff);
    data[offset ++] = (uint8_t)((val>>16)&0xff);
    data[offset ++] = (uint8_t)((val>>24)&0xff);
    val = run_addr;
    data[offset ++] = (uint8_t)(val&0xff);
    data[offset ++] = (uint8_t)((val>>8)&0xff);
    data[offset ++] = (uint8_t)((val>>16)&0xff);
    data[offset ++] = (uint8_t)((val>>24)&0xff);
    val = pfw->fw.part[pfw->fw.part_current].size;
    data[offset ++] = (uint8_t)(val&0xff);
    data[offset ++] = (uint8_t)((val>>8)&0xff);
    data[offset ++] = (uint8_t)((val>>16)&0xff);
    data[offset ++] = (uint8_t)((val>>24)&0xff);
    val = (uint32_t)(pfw->fw.part[pfw->fw.part_current].checksum);
    data[offset ++] = (uint8_t)(val&0xff);
    data[offset ++] = (uint8_t)((val>>8)&0xff);
    pctx->state = OTAM_ST_WAIT_PARTITION_INFO;
    pfw->cache_offset = 0;
    pfw->cache_size = 0;
    pfw->cache_retry = 0;
    pfw->fw.offset = 0;
    return method->write_cmd(data, offset, 1000);
}


static int load_data_cache(void)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_fw_t* pfw = &(pctx->fw);
    ota_fw_part_t* ppart = &(pfw->fw.part[pfw->fw.part_current]);
    uint16_t mtu_a = pctx->mtu_a;
    uint32_t size = mtu_a * pctx->burst_size;

    if(pfw->fw.offset + size > ppart->size)
    {
        size = ppart->size - pfw->fw.offset;
    }

    //memset(pfw->cache, 0, (ATT_MTU_SIZE-3));
    pfw->cache = (uint8_t*)(ppart->flash_addr + pfw->fw.offset+OTAFM_FW_OTA_DATA_ADDR);
    pfw->cache_size = size;
    pfw->cache_offset = 0;
    pfw->cache_retry = 0;
    pfw->fw.offset += size;
    return PPlus_SUCCESS;
}

static int send_data(void)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_fw_t* pfw = &(pctx->fw);
    otam_proto_meth_t* method = &(pctx->method);

    if(!(method->write_data))
        return PPlus_ERR_NOT_REGISTED;

    if(pctx->state != OTAM_ST_DATA)
        return PPlus_ERR_INVALID_STATE;

    int ret = PPlus_SUCCESS;
    uint16_t size = 0;
    uint16_t mtu_a = pctx->mtu_a;

    while(pfw->cache_size - pfw->cache_offset)
    {
        size = mtu_a;

        if((pfw->cache_size - pfw->cache_offset) < mtu_a)
            size = pfw->cache_size - pfw->cache_offset;

        ret = method->write_data(pfw->cache + pfw->cache_offset, size);

        if(ret != PPlus_SUCCESS)
        {
            method->write_data_delay(2);
            return PPlus_SUCCESS;
        }

        pfw->cache_offset += size;
    }

    return PPlus_SUCCESS;
}

void print_version(uint8_t* vinfo)
{
}

static void handle_app_notify_event(void* param, uint8_t len)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;

    switch(pctx->opcode)
    {
    case OTAAPP_CMD_START_OTA:
        break;

    case OTAAPP_CMD_VER:
        print_version(param);
        break;

    default:
        break;
    }
}

static void handle_ota_notify_event(void* param, uint8_t len)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_fw_t* pfw = &(pctx->fw);
    uint8_t* pnotify = (uint8_t*)param;
    otam_proto_meth_t* method = &(pctx->method);
    int retval = pnotify[0];

    if(len == 1)  //fatal error
    {
        otam_proto_ctx_reset(OTAM_ST_CONNECTED);
        return;
    }

    LOG("OTA Notif %x, %x\n",pnotify[0],pnotify[1]);
    print_hex(pnotify, len);

    switch(pnotify[1]) //response type
    {
    case OTA_RSP_START_OTA:
    {
        if(retval == PPlus_SUCCESS && pctx->state == OTAM_ST_WAIT_STARTED)
        {
            send_patition_info();
        }
        else
        {
            pctx->state = OTAM_ST_ERROR;
        }

        break;
    }

    case OTA_RSP_PARAM:
    case OTA_RSP_BLOCK_INFO:
    case OTA_RSP_BLOCK_COMPLETE:
    {
        pctx->state = OTAM_ST_ERROR;
        break;
    }

    case OTA_RSP_OTA_COMPLETE:
    {
        uint8_t data[20];

        if(method->write_cmd)
        {
            data[0] = OTA_CMD_REBOOT;
            data[1] = 1;
            pctx->state = OTAM_ST_COMPLETE;
            LOG("OTA completed!!\n");
            method->write_cmd(data, 2, 1000);
        }

        break;
    }

    case OTA_RSP_PARTITION_INFO:
    {
        pctx->state = OTAM_ST_DATA;

        if(pfw->fw.part_current)
        {
            LOG(" ");
        }

        load_data_cache();
        send_data();
        break;
    }

    case OTA_RSP_PARTITION_COMPLETE:
    {
        pfw->fw.total_offset += pfw->cache_size;
        pfw->fw.part_current ++;
        send_patition_info();
        break;
    }

    case OTA_RSP_BLOCK_BURST:
    {
        if(pctx->state != OTAM_ST_DATA)
        {
            pctx->state = OTAM_ST_ERROR;
            break;
        }

        if(retval == PPlus_SUCCESS)
        {
            load_data_cache();
            send_data();
        }
        else if(retval == PPlus_ERR_OTA_BAD_DATA)
        {
            //case block data is not completed, retry block data
            pfw->cache_retry++;
            pfw->cache_offset = 0;

            if(pfw->cache_retry > 3)
            {
                pctx->state = OTAM_ST_ERROR;
                break;
            }

            send_data();
        }
        else
        {
            pctx->state = OTAM_ST_ERROR;
        }

        break;
    }

    case OTA_RSP_REBOOT:
    {
        LOG("[OTA_RSP_REBOOT]GAPCentralRole_TerminateLink\n");
        GAPCentralRole_TerminateLink(0);
    }

    case OTA_RSP_ERASE:
    case OTA_RSP_ERROR:
    default:
    {
        pctx->state = OTAM_ST_ERROR;
        break;
    }
    }
}


void otamProtocol_event(otap_evt_t* pev)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;

    switch(pev->ev)
    {
    case OTAP_EVT_DISCONNECTED:
        otam_proto_disconnect(pev->data);
        break;

    case OTAP_EVT_CONNECTED:
        otam_proto_connect(pev->data);
        break;

    case OTAP_EVT_NOTIFY:
    {
        if(pctx->run_mode == OTAC_RUNMODE_APP)
            handle_app_notify_event(pev->data, pev->len);
        else
            handle_ota_notify_event(pev->data, pev->len);

        break;
    }

    case OTAP_EVT_DATA_WR_DELAY:
    {
        send_data();
        break;
    }

    case OTAP_EVT_BLE_TIMEOUT:
    default:
    {
        //pctx->state = OTAM_ST_ERROR;
        break;
    }
    }
}
#define OTAM_FW_DATA_ADDR   0x11040000
#define OTAM_FW_DATA_ADDR1  0x11060000

/*
    word      | desc:
    0         | flag: "OTAF"
    1         | partition number
    i*2 + 2   | run address
    i*2 + 3   | size
    N*2 +2    | data area

*/

int load_fw(uint8_t fw_id)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_fw_t* pfw = &(pctx->fw);
    uint32_t faddr = fw_id == 0 ? OTAM_FW_DATA_ADDR : OTAM_FW_DATA_ADDR1;
    uint8_t* pdata = (uint8_t*) (faddr);
    uint32_t* pdata32 = (uint32_t*) pdata;
    uint32_t offset = 0;
    LOG("load fw %x\n", faddr);
    memset((void*)pfw, 0,sizeof(otam_fw_t));

    if(!((char)(pdata[0]) == 'O' && (char)(pdata[1]) == 'T' &&(char)(pdata[2]) == 'A'&&(char)(pdata[3]) == 'F'))
    {
        return PPlus_ERR_INVALID_DATA;
    }

    pfw->fw.part_num = (uint8_t)(pdata32[1]);
    offset = 2 * 4 + 2 * 4 * pfw->fw.part_num;
    pfw->fw.total_size = 0;

    for (uint8_t i = 0; i < pfw->fw.part_num; i++)
    {
        pfw->fw.part[i].run_addr = pdata32[i*2+2];
        pfw->fw.part[i].size = pdata32[i*2+3];
        pfw->fw.part[i].flash_addr = faddr + offset;
        pfw->fw.part[i].checksum = crc16(0, (const volatile void* )(pfw->fw.part[i].flash_addr), pfw->fw.part[i].size);
        offset += pfw->fw.part[i].size;
        pfw->fw.total_size += pfw->fw.part[i].size;
    }

    pfw->fw.total_size += 0;
    return PPlus_SUCCESS;
}

int load_res(void)
{
    return PPlus_ERR_NOT_IMPLEMENTED;
}


int otamProtocol_start_ota(ota_fw_t* pffw)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_fw_t* pfw = &(pctx->fw);
    otam_proto_meth_t* method = &(pctx->method);
    uint8_t data[20];
    memcpy(&(pfw->fw), pffw, sizeof(ota_fw_t));
    pfw->fw.offset = 0;
    pfw->fw.part_current = 0;
    pfw->fw.total_offset = 0;

    if(pctx->state < OTAM_ST_CONNECTED)
        return PPlus_ERR_BLE_NOT_READY;

    if(pctx->run_mode != OTAC_RUNMODE_OTA)
    {
        return PPlus_ERR_INVALID_STATE;
    }

    if(method->write_cmd)
    {
        pctx->state = OTAM_ST_WAIT_STARTED;
        pfw->fw.part_current = 0;

        if(pctx->mtu_a == 20)
        {
            pctx->burst_size = OTA_BURST_SIZE_DEFAULT;
            data[0] = OTA_CMD_START_OTA;
            data[1] = pfw->fw.part_num;
            data[2] = 0;
        }
        else
        {
            pctx->burst_size = 0xffff;
            data[0] = OTA_CMD_START_OTA;
            data[1] = pfw->fw.part_num;
            data[2] = OTA_BURST_SIZE_HISPEED;
        }

        return method->write_cmd(data, 3, 1000);
    }

    return PPlus_ERR_NOT_REGISTED;
}


int otamProtocol_stop_ota(void)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_proto_meth_t* method = &(pctx->method);
    uint8_t data[20];

    if(method->write_cmd)
    {
        data[0] = OTA_CMD_START_OTA;
        data[1] = 0xff;
        data[2] = 0;
        pctx->state = OTAM_ST_CANCELING;
        return method->write_cmd(data, 3, 1000);
    }

    return PPlus_ERR_NOT_REGISTED;
}

int otamProtocol_app_start_ota(uint8_t mode)
{
    otam_proto_ctx_t* pctx = &s_otap_ctx_t;
    otam_proto_meth_t* method = &(pctx->method);
    uint8_t data[20];

    if(pctx->run_mode != OTAC_RUNMODE_APP)
        return PPlus_ERR_INVALID_STATE;

    if(mode > OTA_MODE_RESOURCE)
        return PPlus_ERR_INVALID_PARAM;

    if(method->write_cmd)
    {
        data[0] = OTAAPP_CMD_START_OTA;
        data[1] = mode;
        data[2] = 1;
        return method->write_cmd(data, 3, 0);
    }

    return PPlus_ERR_NOT_REGISTED;
}

int otamProtocol_init(otam_proto_meth_t* method)
{
    memset(&s_otap_ctx_t, 0, sizeof(s_otap_ctx_t));
    s_otap_ctx_t.method = *method;
    return PPlus_SUCCESS;
}

