#include <stdlib.h>
#include "app_ota.h"
#include "httpclient.h"
#include "makeimage.h"
#include "crc32.h"

#include "hlog.h"
#define TAG "TAG_APPOTA"

struct RangeUpgardeParam
{
    int32_t offset;
    int32_t totallen; //ota file length
    int32_t progress;
    int32_t retry;
};

enum upgradeStage
{
    STAGE_HEADER = 0,
    STAGE_AW831,
    STAGE_SOUND,
    STAGE_MT7682,
    STAGE_DONE,
};

enum upgradeRet
{
    RET_FW_NOT_ENOUGH_DATA = -3,
    RET_FW_MAGIC_ERROR = -2,
    RET_FW_CRC_ERROR = -1,
    RET_FW_OK,
    RET_FW_NEED_MORE_DATA,
    RET_FW_NO_NEED_UPGRADE,
};

typedef struct {
    char *url;
    onDownloadImageProgress listener;
}Upgrade_config;

//format: version,length,crc
//setenv mt7682_ota_info 1,552996,1652930298
#define DEV_OTA_PARTITION "/dev/OTA"
#define DOWNLOAD_TRY_TIMES 5

static enum upgradeStage sUpgradeStage = STAGE_HEADER;
static iobuffer sDataBuffer = {NULL, 0, 0};
static struct FWPackHeader sFWHeader;
static int32_t sUpgrading;
//EKEN format

typedef enum {
    PAYLOAD_TYPE_SET_WOW = 1,  
	PAYLOAD_TYPE_KEEPALIVE,	
    PAYLOAD_TYPE_GET_WIFI_DATA,	 
    PAYLOAD_TYPE_CHANGE_WIFI_CONNECT,//4
    PAYLOAD_TYPE_SET_PIR,
    PAYLOAD_TYPE_REBOOT_T31,
    PAYLOAD_TYPE_GET_SOME_DATA,
    PAYLOAD_TYPE_OTA_START,//8
    PAYLOAD_TYPE_OTA_ERASE,
    PAYLOAD_TYPE_OTA_ERASE_RET,
    PAYLOAD_TYPE_OTA_WRITE,
    PAYLOAD_TYPE_OTA_CRC_RET,//12
    PAYLOAD_TYPE_OTA_STOP,
    PAYLOAD_TYPE_SET_DATETIME_TS,
    PAYLOAD_TYPE_SET_AP_SCAN,
    PAYLOAD_TYPE_GET_AP_SCAN_LIST,//16
    PAYLOAD_TYPE_SET_AP_MODE,
    PAYLOAD_TYPE_SET_REBOOT_SYS,
    PAYLOAD_TYPE_SET_LED_STATE,
	PAYLOAD_TYPE_DHCP_OK,//20
    PAYLOAD_TYPE_RING_KEY,
    PAYLOAD_TYPE_LOG_DATA,
    PAYLOAD_TYPE_SET_REG_DATA,
    PAYLOAD_TYPE_GET_REG_DATA,
    PAYLOAD_TYPE_TEST = 999,
}_SPI_PAYLOAD_TYPE; 

typedef struct {
	uint32_t payload_type;
	uint32_t payload_len;
} _SPI_DATA_HEADER;


int32_t startOTAFromiobuffer(onDownloadImageProgress listener);

static int32_t writeDatato7682(int32_t type, uint8_t *payload, uint32_t payloadsize)
{
    _SPI_DATA_HEADER header = {type, payloadsize};
    char *send_buf = (char *)hv_malloc(sizeof(header) + payloadsize);

    memcpy(send_buf, (unsigned char *)&header, sizeof(header));
    if (payload)
    {
        memcpy(send_buf + sizeof(header), payload, payloadsize);
    }
    int32_t length = sizeof(header) + payloadsize;
    /*if (t31_device_transfer_mt7682(send_buf, length, NULL, 0) != 0)
    {
        hv_free(send_buf);
        hlogd("%s: t31_device_transfer_mt7682 failed", __FUNCTION__);
        return -1;
    }*/

    hv_free(send_buf);
    return 0;
}

//VIA format
static int32_t writeRAWSDIODatato7672(const char* data, int32_t len)
{
    #if 0
    SdioPacket* packet = PacketCreate();
    putCmd(packet, UPGRADE_REQ);
    putData(packet, data, len);
    int32_t ret = sdio_writePacket(packet);
    hv_free(packet);
    return ret;
    #endif
    return 0;
}

void requestMT7682RebootandUpgrade()
{
    hlogd("request MT7682 reboot to upgrade");
    for (int32_t i = 0; i < 10; i++)
    {
        int32_t magic = 0x20207682;
        if (writeDatato7682(PAYLOAD_TYPE_OTA_START, (uint8_t*)&magic, sizeof(magic)) != 0)
        {
            sleep(1);
        }
        else
        {
            break;
        }
    }

    hlogd("FIXME: should not be here, 7682 should already reboot us");
}

void dump(struct chipFWInfo *chipInfo)
{
    hlogd("dump chipinfo: magic 0x%x chipid %d version %d offset %d length %d crc 0x%x",
          chipInfo->magic, chipInfo->chipID, chipInfo->version, chipInfo->offset,
          chipInfo->length, chipInfo->crc);
}

enum upgradeRet verifyFirmware(int32_t id)
{
    struct chipFWInfo *chipInfo = &sFWHeader.FWInfo[id];
    int32_t fwLen = chipInfo->length;

    if (chipInfo->magic == 0 && fwLen == 0)
    {
        hlogd("id %d no need upgrade", id);
        return RET_FW_NO_NEED_UPGRADE;
    }

    if (sDataBuffer.size < fwLen)
    {
        return RET_FW_NOT_ENOUGH_DATA;
        // return RET_FW_NEED_MORE_DATA;
    }

    if (chipInfo->magic != IMG_MAGIC || chipInfo->chipID != id)
    {
        dump(chipInfo);
        hlogd("verifyChipInfo: check id %d failed", id);
        return RET_FW_MAGIC_ERROR;
    }

    uint32_t crc = crc32(0, (const void *)sDataBuffer.iodata, fwLen);
    if (crc != chipInfo->crc)
    {
        int32_t *data = (int32_t *)sDataBuffer.iodata;
        hlogd("id %d fw crc error: 0x%x VS 0x%x, fwLen %d, fw data 0x%x 0x%x 0x%x 0x%x",
              id, crc, chipInfo->crc, fwLen, *(data), *(data + 1), *(data + 2), *(data + 3));
        return RET_FW_CRC_ERROR;
    }
    hlogd("verifyChipInfo: id:%d check crc:0x%x", id, crc);
    return RET_FW_OK;
}

static int32_t parseHeader()
{
    if (sDataBuffer.size < sizeof(struct FWPackHeader))
    {
        return -1;
    }
    iobuffer_read(&sDataBuffer, &sFWHeader, sizeof(struct FWPackHeader));

    sUpgradeStage++;
    hlogd("parse OTA package header OK");
    for (int32_t i = 0; i < CHIP_MAX; i++)
    {
        struct chipFWInfo *info = &sFWHeader.FWInfo[i];
        if (info->magic == IMG_MAGIC)
        {
            hlogd("FW id: %d version %d length %d offset %d crc 0x%x",
                  i, info->version, info->length, info->offset, info->crc);
        }
    }

    return 0;
}

static int32_t upgradeModule(int32_t id, onDownloadImageProgress listener)
{
    enum upgradeRet ret = verifyFirmware(id);
    if (ret < 0)
        return -2;
    if (ret == RET_FW_NO_NEED_UPGRADE)
        goto end;

    int32_t length = sFWHeader.FWInfo[id].length;
    if (id == MAIN)
    {
        hlogd("AW831 Firmware verify OK, start upgrade");
        #if 0
        if (aw_upgrade_slice("rtos", sDataBuffer.iodata, 0, length, 1))
        {
            hloge("upgradeAW831 aw_upgrade_slice failed");
            return -1;
        }

        if (aw_upgrade_end(0))
        {
            hloge("upgradeAW831 aw_upgrade_end failed");
            return -1;
        }
        #endif
        hlogd("upgradeModule V831 OK");
    }
    else if (id == SOUND)
    {
        hlogd("SOUND Firmware verify OK, start upgrade");
        #if 0
        if (aw_upgrade_slice("/dev/SOUND", sDataBuffer.iodata, 0, length, 1))
        {
            hloge("aw_upgrade_slice SOUND failed");
            return -1;
        }
        #endif
        hlogd("SOUND Firmware upgrade done");
    }
    else if (id == WIFI)
    {
        hlogd("MT7682 Firmware verify OK, start upgrade");
        //hexdump -n 1024 /dev/OTA
        #if 0
        if (aw_upgrade_slice(DEV_OTA_PARTITION, sDataBuffer.iodata, 0, length, 1))
        {
            hloge("store7682Image aw_upgrade_slice failed");
            return -1;
        }
        #endif

        hlogd("upgradeModule store MT7682 firmware OK");
    }

    iobuffer_erase(&sDataBuffer, 0, length);
end:
    sUpgradeStage++;
    return 0;
}

static int32_t rangeUpgrade(Upgrade_config *downcfg, struct RangeUpgardeParam *upgradeParam, onDownloadImageProgress listener){

    httpclient_data_t client_data = {0};
    httpclient_t client = {0};
    static int32_t left_data = 0;
    int32_t ret = UPGRADING;

    sUpgradeStage = STAGE_HEADER;

    int32_t header_size = 1024;
    int32_t body_size = 32001; //one byte more for httpclient

    char *response_header = (char *)hv_malloc(header_size);
    char *response_body = (char *)hv_malloc(body_size);

    hlogd("%s: url %s", __FUNCTION__, downcfg->url);

    HTTPCLIENT_RESULT result = httpclient_connect(&client, downcfg->url);
    if (result != HTTPCLIENT_OK)
    {
        hlogd("httpclient_connect %s failed result %d", downcfg->url, result);
        listener(UPGRADING, 0);
        ret = UPGRADING;
        goto exit;
    }
    else
    {
        hlogd("httpclient_connect %s OK fd %d", downcfg->url, client.socket);
    }

    memset(&client_data, 0, sizeof(client_data));
    client_data.header_buf = response_header;
    client_data.header_buf_len = header_size;
    client_data.response_buf = response_body;
    client_data.response_buf_len = body_size;
    client_data.range_enable = 1;
    client_data.range_begin = sDataBuffer.size;
    client_data.range_end = 0;

    listener(UPGRADING, 0);

    result = httpclient_send_request(&client, downcfg->url, HTTPCLIENT_GET, &client_data);
    if (result != HTTPCLIENT_OK)
    {
        hlogd("httpclient_send_request %s failed result %d", downcfg->url, result);
        listener(UPGRADING, 0);
        ret = UPGRADING;
        goto exit;
    }

    while (result >= HTTPCLIENT_OK)
    {
        result = httpclient_recv_response(&client, &client_data);
        if (result < 0)
        {
            hlogd("httpclient_recv_response %s failed result %d", downcfg->url, result);
			listener(UPGRADING, 0);
            ret = UPGRADING;
            goto exit;
        }
      
        if(upgradeParam->offset == 0)
            upgradeParam->retry = 0;

        if (upgradeParam->totallen == 0)
        {
            left_data = upgradeParam->totallen = client_data.response_content_len;
            hlogd("url %s size %d", downcfg->url, upgradeParam->totallen);
        }

        uint32_t data_len = left_data - client_data.retrieve_len;
        upgradeParam->offset += data_len;
        left_data -= data_len;

        hlogd("upgradeParam totallen %d offset %d leftLen %d",
              upgradeParam->totallen, upgradeParam->offset, client_data.retrieve_len);

        int32_t curProgress = upgradeParam->offset * 100 / upgradeParam->totallen;
        if(curProgress > upgradeParam->progress && curProgress != 100) 
        {
            upgradeParam->progress = curProgress;
            listener(UPGRADING, curProgress);
            ret = UPGRADING;
        }
        
        if (data_len != 0)
        {
            iobuffer_appendData(&sDataBuffer, client_data.response_buf, data_len);
        }

        if (upgradeParam->offset == upgradeParam->totallen)
        {
            hlogd("firmware download done");
            listener(UPGRADING, 100);
            listener(DOWNLOAD_OK, 0);
            ret = DOWNLOAD_OK;
            break;
        }
    }

exit:
    hlogd("httpclient_close fd %d", client.socket);
    httpclient_close(&client);
    
    if (response_header)
        hv_free(response_header);
    if (response_body)
        hv_free(response_body);
/* for compile
    if(ret == UPGRADING) 
        rt_thread_mdelay(2000);*/

    return ret;
}

static void upgradeTask(void *arg)
{
	sUpgrading = 1;
    Upgrade_config *downcfg =(Upgrade_config *)arg;
    onDownloadImageProgress listener = downcfg->listener;
    //RT_ASSERT(listener);
    struct RangeUpgardeParam upgradeParam;
    memset(&upgradeParam, 0, sizeof(upgradeParam));
    int32_t upgradeStatus = UPGRADING;
	//add_suspend(SUSPEND_UPGRADE, 8*60);

    iobuffer_init(&sDataBuffer);
    iobuffer_reserve(&sDataBuffer, 4*1024*1024);
    listener(READY, 0);

    while(strlen(downcfg->url) > 0){
        hlogd("retried %d times rangeUpgrade", upgradeParam.retry);
        upgradeStatus = rangeUpgrade(downcfg, &upgradeParam, listener);
		if(upgradeStatus == DOWNLOAD_OK){
			hlogd("rangeUpgrade download ok");
			break;
		}else if(++upgradeParam.retry > DOWNLOAD_TRY_TIMES){
            hlogd("retried %d times and still download fail !!!", upgradeParam.retry);
            upgradeStatus = DOWNLOAD_FAIL;
            listener(DOWNLOAD_FAIL, 0);
            break;
        }
    }

    if(upgradeStatus == DOWNLOAD_OK)
        startOTAFromiobuffer(listener);
 
exit:
	sUpgrading = 0;
    if (STAGE_DONE != sUpgradeStage)
    {
        hlogd("upgradeTask error, current stage %d", sUpgradeStage);
        #if 0
		goWow(__func__);
        #endif
    }
    else
    {
        hlogd("upgradeTask OK");
    }

    hv_free(downcfg->url);
    hv_free(downcfg);
    return;
}

iobuffer* getOTAiobuffer()
{
    return &sDataBuffer;
}

int32_t startOTAFromiobuffer(onDownloadImageProgress listener)
{
	int32_t ret;
	int32_t upgraded7682 = 0;
    if(sDataBuffer.size == 0) {
        hlogd("sDataBuffer size is 0");
        return -1;
    }
	listener(INSTALL_ING, 0);
	ret = parseHeader();
	if(ret < 0)
		goto end;
    listener(UPGRADING, 10);
	ret = upgradeModule(MAIN, listener);
	if(ret < 0)
		goto end;
    listener(UPGRADING, 50);
	ret = upgradeModule(SOUND, listener);
	if(ret < 0)
		goto end;
    listener(UPGRADING, 80);
	ret = upgradeModule(WIFI, listener);
	if(ret < 0)
		goto end;
	else if(ret == RET_FW_OK){
		upgraded7682 = 1;
	}
    listener(UPGRADING, 100);
end:
	switch(ret){
		case RET_FW_NOT_ENOUGH_DATA:
		case RET_FW_MAGIC_ERROR:
			listener(INSTALL_FAIL, 0);
			break;
		case RET_FW_CRC_ERROR:
			listener(CRC_FAIL, 0);
			break;
		default:// RET_FW_OK or RET_FW_NO_NEED_UPGRADE
			listener(UPGRADE_KERNEL, 0);
	        listener(INSTALL_OK, 0);
			listener(UPGRADE_ALL, 0);
			break;
	}
	/*env_mutex_lock();
	if(upgraded7682){
		char info[32] = {0};
		snprint32_tf(info, sizeof(info), "%d,%d,%d", sFWHeader.FWInfo[WIFI].version, sFWHeader.FWInfo[WIFI].length, sFWHeader.FWInfo[WIFI].crc);
		env_set_string(env_ota_info, info);
	}
	if(!is_adb_online()){//do not report version when update throught adb
		env_set_int32_t(env_need_report_version, 1);
	}
	env_save();
	env_mutex_unlock();*/
	if(upgraded7682){
        requestMT7682RebootandUpgrade();
	}
    return 0;
}

void createUpgradeTask(char *otaURL, onDownloadImageProgress listener)
{
	if(sUpgrading){
		hlogd("%s sUpgrading return",__func__);
		return;
	}
    hlogd("createUpgradeTask : %s", otaURL);
    Upgrade_config *p = (Upgrade_config*)hv_malloc(sizeof(Upgrade_config));
    //RT_ASSERT(p);
    p->url = strdup(otaURL);
    p->listener = listener;
/*
    rt_thread_t task_t = rt_thread_create("upgradeTask", upgradeTask, p, 10*1024, CONFIG_FINSH_THREAD_PRIORITY, 20);
    if(task_t == RT_NULL){
         listener(READY_FAIL, 0);
    }else{
        rt_thread_startup(task_t);
    }*/
}

typedef struct
{
    uint32_t m_magic;
    /* version of app */
    uint32_t m_ver;
    /* app len*/
    uint32_t m_body_len;
    uint32_t m_checkcrc;
    uint32_t m_bin_start_addr;
    uint32_t reserve[3];

} bl_sota_update_info_t; //32bytes

int32_t isUpgrading(void){
    return sUpgrading;
}
int32_t upgradeMT7682(void)
{
    hlogd("%s start", __FUNCTION__);
	sUpgrading = 1;
    char *info = NULL;//env_get_string(env_ota_info, NULL);
    if (info == NULL)
    {
        hlogd("%s: can't find env  env_ota_info", __FUNCTION__);
        return -1;
    }

    int32_t ret = -1;
    int32_t max_block_size = 1024;
    int32_t block_size = max_block_size;
    int32_t fwVersion = -1, fwSize = 0, fwCrc = 0;
    if (3 != sscanf(info, "%d,%d,%d", &fwVersion, &fwSize, &fwCrc))
    {
        hlogd("%s: firmware info error: %s", __FUNCTION__, info);
        return -1;
    }

    int32_t otaFD = open(DEV_OTA_PARTITION, O_RDONLY);
    if (otaFD < 0)
    {
        hlogd("%s: open %s failed", __FUNCTION__, DEV_OTA_PARTITION);
        return -1;
    }

    char *buf = hv_malloc(block_size);
    if (!buf)
    {
        hlogd("%s: hv_malloc upgrade buffer %d error", __FUNCTION__, block_size);
        goto __ota_exit;
    }

    {
        //先发送一包数据表示固件的相关信息
        bl_sota_update_info_t sota_header;
        sota_header.m_magic = 0x08317682;
        sota_header.m_ver = fwVersion;
        sota_header.m_body_len = fwSize;
        sota_header.m_checkcrc = fwCrc;
        hlogd("%s: start upgrade version %d length %d crc 0x%x",
              __FUNCTION__, sota_header.m_ver, sota_header.m_body_len, sota_header.m_checkcrc);

        if (writeRAWSDIODatato7672((const char *)&sota_header, sizeof(sota_header)))
        {
            hlogd("%s: write sota_header failed", __FUNCTION__);
            return -1;
        }
        hlogd("%s: write sota_header ok", __FUNCTION__);
    }

    int32_t leftSize = fwSize;
    do
    {
        block_size = leftSize > max_block_size ? max_block_size : leftSize;
        int32_t avail = read(otaFD, buf, block_size);
        if (avail == -1)
        {
            hlogd("read source failed");
            goto __ota_exit;
        }

        leftSize -= avail;
        if (writeRAWSDIODatato7672((const char *)buf, avail) != 0)
        {
            hlogd("%s: write sota data failed", __FUNCTION__);
            goto __ota_exit;
        }
        hlogd("%s: write data %d ok", __FUNCTION__, avail);

    } while (leftSize);

    ret = 0;
__ota_exit:
    if (buf)
    {
        hv_free(buf);
    }
    if (otaFD)
    {
        close(otaFD);
    }

    if (ret == 0)
    {
        hlogd("%s: done", __FUNCTION__);
    }
    else
    {
        hlogd("%s: failed", __FUNCTION__);
    }
    return ret;
}
