/*
 * \file        optimus_img_decoder.c 
 * \brief       
 *
 * \version     1.0.0
 * \date        2013-7-8
 * \author      Sam.Wu <yihui.wu@amlogic.com>
 *
 * Copyright (c) 2013 Amlogic. All Rights Reserved.
 *
 */
#include "../v2_burning_i.h"

//FIMXE:
COMPILE_TYPE_CHK(128 == sizeof(ItemInfo_V1), _op_a);
COMPILE_TYPE_CHK(576 == sizeof(ItemInfo_V2), __op_a2);
COMPILE_TYPE_CHK(64  == sizeof(AmlFirmwareImg_t), __op_b);

typedef struct _ImgSrcIf{
        unsigned        devIf;             //mmc/usb/store
        unsigned        devNo;          //0/1/2
        unsigned        devAlignSz;     //64K for store
        unsigned        reserv2Align64;
        uint64_t        itemCurSeekOffsetInImg;//fread will auto seek the @readSz, but for STORE we must do it

        char            partName[28];       //partIndex <= 28 (+4 if partIndex not used)
        unsigned        partIndex;      //partIndex and part
        unsigned char   resrv[512 - 32 - 24];
}ImgSrcIf_t;

COMPILE_TYPE_CHK(512  == sizeof(ImgSrcIf_t), bb);
#define MAX_ITEM_NUM 48

typedef struct _ImgInfo_s
{
        ImgSrcIf_t          imgSrcIf;
        AmlFirmwareImg_t    imgHead;//Must begin align 512, or store read wiill exception
        union               ItemInfo_u{
                            ItemInfo_V1 v1[MAX_ITEM_NUM];
                            ItemInfo_V2 v2[MAX_ITEM_NUM];
        }itemInfo;

}ImgInfo_t;

typedef struct _AmlFirmwareItem0_s
{
    __u32           itemId;
    __u32           fileType;           //image file type, sparse and normal
    __u64           curoffsetInItem;    //current offset in the item
    __u64           offsetInImage;      //item offset in the image
    __u64           itemSz;             //item size in the image
    const char*     itemMainType;
    const char*     itemSubType;
}ItemInfo;

static int _hFile = -1;

//open a Amlogic firmware image
//return value is a handle
HIMAGE image_open(const char* interface, const char* device, const char* part, const char* imgPath)
{
    const int HeadSz = sizeof(ImgInfo_t) - sizeof(ImgSrcIf_t);
    ImgInfo_t* hImg = (ImgInfo_t*)OPTIMUS_BURN_PKG_HEAD_BUF_ADDR;
    int ret = 0;
    ImgSrcIf_t* pImgSrcIf = NULL;
    unsigned imgVer = 0;

    pImgSrcIf = &hImg->imgSrcIf;
    memset(pImgSrcIf, 0, sizeof(ImgSrcIf_t));

    if(!strcmp("store", interface))
    {
            DWN_DBG("imgHead=0x%x, hImg=%p\n", (unsigned)&hImg->imgHead, hImg);
            ret = store_read_ops((u8*)part, (u8*)&hImg->imgHead, IMG_OFFSET_IN_PART, HeadSz);
            if(ret){
                    DWN_ERR("Fail to read image header.\n");
                    ret = __LINE__; goto _err;
            }

            pImgSrcIf->devIf = IMAGE_IF_TYPE_STORE;
            pImgSrcIf->devAlignSz = 4*1024;//512;//OPTIMUS_DOWNLOAD_SLOT_SZ;
            strcpy(pImgSrcIf->partName, part); 
    }
    else
    {
            int pFile = do_fat_fopen(imgPath);
            if(pFile < 0){
                    DWN_ERR("Fail to open file %s\n", imgPath);
                    goto _err;
            }
            _hFile = pFile;

            ret = do_fat_fread(pFile, (u8*)&hImg->imgHead, HeadSz);
            if(ret != HeadSz){
                    DWN_ERR("want to read %d, but %d\n", HeadSz, ret);
                    goto _err;
            }

            pImgSrcIf->devAlignSz = do_fat_get_bytesperclust(pFile);
    }

    if(IMAGE_MAGIC != hImg->imgHead.magic){
        DWN_ERR("error magic 0x%x\n", hImg->imgHead.magic);
        goto _err;
    }
    imgVer = hImg->imgHead.version;
    if(AML_FRMWRM_VER_V1 !=  imgVer && AML_FRMWRM_VER_V2 != imgVer){
        DWN_ERR("error verison 0x%x\n", hImg->imgHead.version);
        goto _err; 
    }
    DWN_MSG("image version [0x%8x]\n", imgVer);
    if(MAX_ITEM_NUM < hImg->imgHead.itemNum){
            DWN_ERR("max itemNum(%d)<actual itemNum (%d)\n", MAX_ITEM_NUM, hImg->imgHead.itemNum);
            goto _err;
    }

    return hImg;
_err:
    return NULL;
}


//close a Amlogic firmware image
int image_close(HIMAGE hImg)
{
    DWN_MSG("to close image\n");

    if(_hFile >= 0)do_fat_fclose(_hFile), _hFile = -1;

    if(hImg){
        hImg = NULL;
    }
    return 0;
}

static const ItemInfo* image_item_get_item_info_byid(HIMAGE hImg, const int itemIndex)
{
        ImgInfo_t* imgInfo = (ImgInfo_t*)hImg;
        const unsigned imgVer = imgInfo->imgHead.version;
        static ItemInfo theItem;

        switch(imgVer)
        {
                case AML_FRMWRM_VER_V2:
                        {
                                ItemInfo_V2* pItem = &imgInfo->itemInfo.v2[itemIndex];
                                theItem.itemMainType    = pItem->itemMainType;
                                theItem.itemSubType     = pItem->itemSubType;
                                theItem.itemSz          = pItem->itemSz;
                                theItem.offsetInImage   = pItem->offsetInImage;
                                theItem.curoffsetInItem = pItem->curoffsetInItem;
                                theItem.fileType        = pItem->fileType;
                                theItem.itemId          = pItem->itemId;
                        }
                        break;

                case AML_FRMWRM_VER_V1:
                        {
                                ItemInfo_V1* pItem = &imgInfo->itemInfo.v1[itemIndex];
                                theItem.itemMainType    = pItem->itemMainType;
                                theItem.itemSubType     = pItem->itemSubType;
                                theItem.itemSz          = pItem->itemSz;
                                theItem.offsetInImage   = pItem->offsetInImage;
                                theItem.curoffsetInItem = pItem->curoffsetInItem;
                                theItem.fileType        = pItem->fileType;
                                theItem.itemId          = pItem->itemId;
                        }
                        break;

                default:
                        DWN_ERR("Exception, imgVer=0x%x\n", imgVer);
                        return NULL;
        }

        return &theItem;
}

//open a item in the image
//@hImage: image handle;
//@mainType, @subType: main type and subtype to index the item, such as ["IMAGE", "SYSTEM"]
HIMAGEITEM image_item_open(HIMAGE hImg, const char* mainType, const char* subType)
{
    ImgInfo_t* imgInfo          = (ImgInfo_t*)hImg;
    const int itemNr            = imgInfo->imgHead.itemNum;
    const ItemInfo*      pItem  = NULL;
    int i = 0;

    for (; i < itemNr ;i++)
    {
            pItem = image_item_get_item_info_byid(hImg, i);
            if(!pItem){
                    DWN_ERR("Fail to get item at index %d\n", i);
                    return NULL;
            }

            if (!strcmp(mainType, pItem->itemMainType) && !strcmp(subType, pItem->itemSubType))
            {
                    break;
            }
    }
    if(i >= itemNr){
        DWN_ERR("Can't find item [%s, %s]\n", mainType, subType);
        return NULL;
    }

    if(i != pItem->itemId){
        DWN_ERR("itemid %d err, should %d\n", pItem->itemId, i);
        return NULL;
    }

    if(IMAGE_IF_TYPE_STORE != imgInfo->imgSrcIf.devIf)
    {
            DWN_DBG("Item offset 0x%llx\n", pItem->offsetInImage);
            i = do_fat_fseek(_hFile, pItem->offsetInImage, 0);
            if(i){
                    DWN_ERR("fail to seek, offset is 0x%x\n", (u32)pItem->offsetInImage);
                    return NULL;
            }
    }
    imgInfo->imgSrcIf.itemCurSeekOffsetInImg = pItem->offsetInImage;
    
    return (HIMAGEITEM)pItem;
}

//Need this if item offset in the image file is not aligned to bytesPerCluster of FAT
unsigned image_item_get_first_cluster_size(HIMAGE hImg, HIMAGEITEM hItem)
{
    const ImgInfo_t* imgInfo = (ImgInfo_t*)hImg;
    ItemInfo* pItem = (ItemInfo*)hItem;
    unsigned itemSizeNotAligned = 0;
    const unsigned fat_bytesPerCluste = imgInfo->imgSrcIf.devAlignSz;

    itemSizeNotAligned = pItem->offsetInImage & (fat_bytesPerCluste - 1);
    itemSizeNotAligned = fat_bytesPerCluste - itemSizeNotAligned;

    DWN_MSG("itemSizeNotAligned 0x%x\n", itemSizeNotAligned);
    return itemSizeNotAligned;
}

unsigned image_get_cluster_size(HIMAGEITEM hImg)
{
    const ImgInfo_t* imgInfo = (ImgInfo_t*)hImg;
    const unsigned fat_bytesPerCluste = imgInfo->imgSrcIf.devAlignSz;

    return fat_bytesPerCluste;
}

//close a item
int image_item_close(HIMAGEITEM hItem)
{
    return 0;
}

__u64 image_item_get_size(HIMAGEITEM hItem)
{
    ItemInfo* pItem = (ItemInfo*)hItem;

    return pItem->itemSz;
}


//get image item type, current used type is normal or sparse
int image_item_get_type(HIMAGEITEM hItem)
{
    ItemInfo* pItem = (ItemInfo*)hItem;

    return pItem->fileType;
}

//read item data, like standard fread
int image_item_read(HIMAGE hImg, HIMAGEITEM hItem, void* pBuf, const __u32 wantSz)
{
    ImgInfo_t* imgInfo = (ImgInfo_t*)hImg;
    unsigned readSz = 0;

    if(IMAGE_IF_TYPE_STORE == imgInfo->imgSrcIf.devIf)
    {
            unsigned char* part = (unsigned char*)imgInfo->imgSrcIf.partName;
            const uint64_t offsetInPart = imgInfo->imgSrcIf.itemCurSeekOffsetInImg + IMG_OFFSET_IN_PART;
            int rc = 0;
            const unsigned storeBlkSz      = imgInfo->imgSrcIf.devAlignSz;
            const unsigned offsetNotAlign = offsetInPart & (storeBlkSz - 1);
            const unsigned sizeNotAlignInFirstBlk = storeBlkSz - offsetNotAlign;//in the the first block and its offset not aligned

            //Attention: deal with the align issue in "optimus_burn_one_partition", then not need to modify "do_fat_fread"
            if(offsetNotAlign)
            {
                    unsigned char* bufInABlk = NULL;
                    const uint64_t readOffset = offsetInPart - offsetNotAlign;
                    const unsigned bufLen = sizeNotAlignInFirstBlk < wantSz ? sizeNotAlignInFirstBlk : (wantSz);
                    unsigned thisTotalReadSz = wantSz;

                    DWN_MSG("offsetInPart %llx, wantSz=%x\n", offsetInPart, wantSz);
                    bufInABlk = (u8*)malloc(storeBlkSz);
                    rc = store_read_ops(part, bufInABlk, readOffset, storeBlkSz);
                    if(rc){
                            DWN_ERR("Fail to read: readOffset=%llx, storeBlkSz=%x\n", readOffset, storeBlkSz);
                            free(bufInABlk);
                            return __LINE__;
                    }
                    memcpy(pBuf, bufInABlk + offsetNotAlign, bufLen);
                    thisTotalReadSz -= bufLen;
                    pBuf            += bufLen/sizeof(void);
                    free(bufInABlk);

                    if(sizeNotAlignInFirstBlk < wantSz && offsetNotAlign)
                    {
                            rc = store_read_ops(part, (u8*)pBuf, (offsetInPart + sizeNotAlignInFirstBlk), thisTotalReadSz);
                            if(rc){
                                    DWN_ERR("Fail in store_read_ops to read %u at offset %llx.\n", wantSz, 
                                                    offsetInPart + sizeNotAlignInFirstBlk);
                                    return __LINE__;
                            }
                    }
            }
            else
            {
                    rc = store_read_ops(part, (u8*)pBuf, offsetInPart, wantSz);
                    if(rc){
                            DWN_ERR("Fail in store_read_ops to read %u at offset %llx.\n", wantSz, offsetInPart);
                            return __LINE__;
                    }
            }

            imgInfo->imgSrcIf.itemCurSeekOffsetInImg += wantSz;
    }
    else
    {
            readSz = do_fat_fread(_hFile, pBuf, wantSz);
            if(readSz != wantSz){
                    DWN_ERR("want to read 0x%x, but 0x%x\n", wantSz, readSz);
                    return __LINE__;
            }
    }
    
    return 0;
}

int get_total_itemnr(HIMAGE hImg)
{
    ImgInfo_t* imgInfo = (ImgInfo_t*)hImg;

    return imgInfo->imgHead.itemNum;
}

HIMAGEITEM get_item(HIMAGE hImg, int itemId)
{
    int ret = 0;
    ImgInfo_t* imgInfo = (ImgInfo_t*)hImg;
    const ItemInfo* pItem    = NULL;

    pItem = image_item_get_item_info_byid(hImg, itemId);
    if(!pItem){
            DWN_ERR("Fail to get item at index %d\n", itemId);
            return NULL;
    }
    if(itemId != pItem->itemId){
        DWN_ERR("itemid %d err, should %d\n", pItem->itemId, itemId);
        return NULL;
    }
    DWN_MSG("get item [%s, %s] at %d\n", pItem->itemMainType, pItem->itemSubType, itemId);

    if(IMAGE_IF_TYPE_STORE != imgInfo->imgSrcIf.devIf)
    {
            ret = do_fat_fseek(_hFile, pItem->offsetInImage, 0);
            if(ret){
                    DWN_ERR("fail to seek, offset is 0x%x, ret=%d\n", (u32)pItem->offsetInImage, ret);
                    return NULL;
            }
    }
    imgInfo->imgSrcIf.itemCurSeekOffsetInImg = pItem->offsetInImage;
     
    return (HIMAGEITEM)pItem;
}

int get_item_name(HIMAGE hImg, int itemId, const char** main_type, const char** sub_type)
{
    const ItemInfo* pItem    = NULL;

    pItem = image_item_get_item_info_byid(hImg, itemId);
    if(!pItem){
            DWN_ERR("Fail to get item at index %d\n", itemId);
            return __LINE__;
    }
    if(itemId != pItem->itemId){
        DWN_ERR("itemid %d err, should %d\n", pItem->itemId, itemId);
        return __LINE__;
    }
    DWN_DBG("get item [%s, %s] at %d\n", pItem->itemMainType, pItem->itemSubType, itemId);

    *main_type = pItem->itemMainType;
    *sub_type  = pItem->itemSubType;
     
    return OPT_DOWN_OK;
}

__u64 image_get_item_size_by_index(HIMAGE hImg, const int itemId)
{
    const ItemInfo* pItem    = NULL;

    pItem = image_item_get_item_info_byid(hImg, itemId);
    if(!pItem){
            DWN_ERR("Fail to get item at index %d\n", itemId);
            return 0;
    }
    if(itemId != pItem->itemId){
        DWN_ERR("itemid %d err, should %d\n", pItem->itemId, itemId);
        return __LINE__;
    }
    DWN_DBG("get item [%s, %s] at %d\n", pItem->itemMainType, pItem->itemSubType, itemId);
     
    return pItem->itemSz;
}

u64 optimus_img_decoder_get_data_parts_size(HIMAGE hImg, int* hasBootloader)
{
    int i = 0;
    int ret = 0;
    u64 dataPartsSz = 0;
    const int totalItemNum = get_total_itemnr(hImg);

    *hasBootloader = 0;
    for (i = 0; i < totalItemNum; i++) 
    {
        const char* main_type = NULL;
        const char* sub_type  = NULL;

        ret = get_item_name(hImg, i, &main_type, &sub_type);
        if(ret){
            DWN_ERR("Exception:fail to get item name!\n");
            return __LINE__;
        }

        if(strcmp("PARTITION", main_type)){ continue; }
        if(!strcmp("bootloader", sub_type)){
                *hasBootloader = 1; 
                continue;
        }
        if(!strcmp(AML_SYS_RECOVERY_PART, sub_type))
        {
                if(OPTIMUS_WORK_MODE_SYS_RECOVERY == optimus_work_mode_get())continue;
        }

        dataPartsSz += image_get_item_size_by_index(hImg, i);
    }

    return dataPartsSz;
}

#define MYDBG 0
#if MYDBG
int test_item(HIMAGE hImg, const char* main_type, const char* sub_type, char* pBuf, const int sz)
{
    HIMAGEITEM hItem = NULL;
    int ret = 0;

    hItem = image_item_open(hImg, main_type, sub_type);
    if(!hItem){
        DWN_ERR("fail to open %s, %s\n", main_type, sub_type);
        return __LINE__;
    }

    ret = image_item_read(hImg, hItem, pBuf, sz);
    if(ret){
        DWN_ERR("fail to read\n");
        goto _err;
    }
    if(64 >= sz)DWN_MSG("%s\n", pBuf);

_err:
    image_item_close(hItem);
    return ret;
}

int test_pack(const char* interface, const char* device, const char* part, const char* imgPath)
{
    const int ImagBufLen = OPTIMUS_DOWNLOAD_SLOT_SZ;
    char* pBuf = (char*)OPTIMUS_DOWNLOAD_TRANSFER_BUF_ADDR + ImagBufLen;
    int ret = 0;
    int i = 0;
    HIMAGEITEM hItem = NULL;

    if(!strcmp("store", interface))
    {
            ret = run_command("store init 1", 0);
            if(ret){
                    DWN_ERR("Fail in init mmc, Does sdcard not plugged in?\n");
                    return __LINE__;
            }
    }
    else
    {
            s64 fileSz = 0;

            ret = run_command("mmcinfo", 0);
            if(ret){
                    DWN_ERR("Fail in init mmc, Does sdcard not plugged in?\n");
                    return __LINE__;
            }

            fileSz = do_fat_get_fileSz(imgPath);
            if(!fileSz){
                    DWN_ERR("file %s not exist\n", imgPath);
                    return __LINE__;
            }
    }
    
    HIMAGE hImg = image_open(interface, device, part, imgPath);
    if(!hImg){
        DWN_ERR("Fail to open image\n");
        return __LINE__;
    }

    const int itemNr = get_total_itemnr(hImg);
    for (i = 0; i < itemNr ; i++)
    {
        __u64 itemSz = 0;
        int fileType = 0;

        hItem = get_item(hImg, i);
        if(!hItem){
            DWN_ERR("Fail to open item at id %d\n", i);
            break;
        }

        itemSz = image_item_get_size(hItem);
        DWN_MSG("Item Sz is 0x%llx\n", itemSz);

        unsigned wantSz = ImagBufLen > itemSz ? (unsigned)itemSz : ImagBufLen;
        unsigned itemSizeNotAligned = 0;
        char* readBuf = pBuf;
        unsigned readSz = wantSz;

        itemSizeNotAligned = image_item_get_first_cluster_size(hImg, hItem);
        if(itemSizeNotAligned)
        {
                ret = image_item_read(hImg, hItem, readBuf, itemSizeNotAligned);
                readSz = (wantSz > itemSizeNotAligned) ? (wantSz - itemSizeNotAligned) : 0;
        }

        if(readSz)
        {
                ret = image_item_read(hImg, hItem, readBuf + itemSizeNotAligned, readSz);
                if(ret){
                        DWN_ERR("fail to read item data\n");
                        break;
                }
        }
        
        fileType = image_item_get_type(hItem);
        if(IMAGE_ITEM_TYPE_SPARSE == fileType)
        {
                DWN_MSG("sparse packet\n");
                ret = optimus_simg_probe((const u8*)pBuf, wantSz);
                if(!ret){
                        DWN_ERR("item data error, should sparse, but no\n");
                        break;
                }
        }
    }

#if 0
    test_item(hImg, "PARTITION", "logo", pBuf, ImagBufLen);
    test_item(hImg, "VERIFY", "logo", pBuf, 50);
#endif

    image_close(hImg);
    return 0;
}


int do_unpack(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    int rcode = 0;
    const char* imgPath = "a";

#if 1
    if(2 > argc)imgPath = "dt.img";
#else
    if(2 > argc){
        cmd_usage(cmdtp);
        return -1;
    }
#endif//#if MYDBG
    DWN_MSG("argc %d, %s, %s\n", argc, argv[0], argv[1]);

    rcode = test_pack("store", "0", "aml_sysrecovery", imgPath);

    DWN_MSG("rcode %d\n", rcode);
    return rcode;
}

U_BOOT_CMD(
   unpack,      //command name
   5,               //maxargs
   1,               //repeatable
   do_unpack,   //command function
   "unpack the image in sdmmc ",           //description
   "Usage: unpack imagPath\n"   //usage
);
#endif//#if MYDBG

