
#include <string.h>

#include "ta_logger.h"
#include "tzWrappers/TzwMemory.h"
#include "ifaa_tee_common.h"

#include "ifaa_fingerprint_id_table.h"
/*#include "ifaa_log_utils.h"*/
/*#include "tee_internal_api.h"*/

extern const char* CHIP_NAME;

static FingerprintIdStruct fid_structs[FID_TABLE_MAX+1];
uint32_t get_max_flag();
uint32_t convert_ifaa_fid(uint32_t fid_index);

#define MAX_SYSTEM_TABLE_LEN 512

IFAA_Result load_system_fid_table(SystemFidStruct *pEnrolledFps, uint8_t *cnt) {

    if(pEnrolledFps == NULL || cnt == NULL) {
        LOG_E("read system table with bad param");
        return IFAA_ERR_BAD_PARAM;
    }
    uint8_t buf[MAX_SYSTEM_TABLE_LEN] = {0};
    uint32_t total_len = MAX_SYSTEM_TABLE_LEN;

    IFAA_Result ret = IFAA_ReadFile(SYSTEM_FID_TABLE_PATH, SYSTEM_FID_TABLE_PATH_LEN, buf, &total_len);
    if (ret != IFAA_ERR_SUCCESS) {
        LOG_E("system table read faild");
        return IFAA_ERR_READ;
    }

    *cnt = buf[0];
    memcpy(pEnrolledFps, &buf[1], total_len-1);

    LOG_D("load finger cnt = %d", *cnt);

#if __DEV_DEBUG__
    for(int i = 0; i<*cnt; i++) {
    
        LOG_D("index = %u", pEnrolledFps[i].realFpIndex);
        LOG_D("realFid = %s", pEnrolledFps[i].realFid);
    }
#endif
    return ret;
}

IFAA_Result save_system_fid_table(const SystemFidStruct *pEnrolledFps, const uint8_t cnt) {
    
    if(pEnrolledFps == NULL || cnt == 0) {
    
        LOG_E("save system fid with bad param cnt = %u", cnt);
        return IFAA_ERR_BAD_PARAM;
    }
    uint32_t idlistLen = cnt*sizeof(SystemFidStruct);
    uint32_t dataLen = sizeof(uint8_t) + idlistLen; 
    uint8_t *buffer = tzwMalloc(dataLen);

    if(buffer == NULL) {

        LOG_E("save system fid, out of memory.");
        return IFAA_ERR_OUT_OF_MEM;
    }

    uint8_t *pBuf = buffer;

    pBuf[0] = cnt;
    memcpy(pBuf+1, (void *)pEnrolledFps, idlistLen);
    
    IFAA_Result result = IFAA_WriteFile(SYSTEM_FID_TABLE_PATH, SYSTEM_FID_TABLE_PATH_LEN, buffer, dataLen);
    return result;
}

IFAA_Result load_fid_table(const char *path, uint32_t path_len)
{
    if(path == NULL || path_len == 0) {    
        LOG_E("load fid table parameter error.");
        return IFAA_ERR_BAD_PARAM;
    }

    for(uint8_t i=0; i<=FID_TABLE_MAX; ++i) {
        fid_structs[i].ifaa_fid = 0;
        if(fid_structs[i].fingerprint_id != 0)
            tzwFree(fid_structs[i].fingerprint_id);
        fid_structs[i].fingerprint_id = 0;
    }

    uint32_t table_len = (sizeof(uint8_t) + sizeof(uint32_t) + FID_LEN) * FID_TABLE_MAX + 1;
    uint8_t table_data[ (8+32+FID_LEN) * FID_TABLE_MAX + 1 ] = {0};

    IFAA_Result ret = IFAA_ReadFile(path, path_len, table_data, &table_len);
    if(IFAA_ERR_NO_FILE == ret) {
        LOG_D("there is no this file");
        return IFAA_ERR_SUCCESS;
    } else if(IFAA_ERR_SUCCESS != ret) {
        LOG_E("load fid table read file error: %x", ret);
        return IFAA_ERR_READ;
    }

    for(uint32_t index = 0; index < table_len; ) {
        uint8_t fid_index = table_data[index++];

        if(index+4 > table_len) {
            LOG_E("load fid table content error.");
            return IFAA_ERR_READ; // check this return value again
        }
        fid_structs[fid_index].ifaa_fid = (table_data[index]<<24) | (table_data[index+1]<<16) | (table_data[index+2]<<8) | table_data[index+3];
        index += 4;

        if(index+FID_LEN > table_len) {
            LOG_E("load fid table content error.");
            return IFAA_ERR_READ; // check this return value again
        }
        fid_structs[fid_index].fingerprint_id = tzwMalloc(FID_LEN);
        if(fid_structs[fid_index].fingerprint_id == NULL) {
            LOG_E("load fid table memory not enough.");
            return IFAA_ERR_MALLOC_FAILED;
        }
        memcpy(fid_structs[fid_index].fingerprint_id, &table_data[index], FID_LEN);
        index += FID_LEN;
    }
    LOG_I("load fid table success.");

    return IFAA_ERR_SUCCESS;
}

IFAA_Result save_fid_table(const char *path, uint32_t path_len, bool is_free)
{
    if(path==NULL || path_len==0) {
        LOG_E("load fid table parameter error.");
        return IFAA_ERR_BAD_PARAM;
    }

    uint32_t table_len = (sizeof(uint8_t) + sizeof(uint32_t) + FID_LEN) * get_fid_table_count();
    uint8_t table_data[ (8+32+FID_LEN) * FID_TABLE_MAX + 1 ] = {0};

    uint32_t index = 0;
    for(uint8_t i=0; i<=FID_TABLE_MAX; ++i)
        if(fid_structs[i].fingerprint_id != 0) {

            table_data[index++] = i;

            uint32_t flag = fid_structs[i].ifaa_fid;
            for (uint8_t j = 0; j < 4; ++j)
                table_data[index++] = flag >> (24 - j * 8);
            if ((FID_LEN + index - 1) >= ((8 + 32 + FID_LEN) * FID_TABLE_MAX + 1)) {
                LOG_E("Can't use memcpy operation because overflow");
                return IFAA_ERR_BUF_TOO_SHORT;
            }
            memcpy(&table_data[index], fid_structs[i].fingerprint_id, FID_LEN);
            index += FID_LEN;

            if(is_free) {
                fid_structs[i].ifaa_fid = 0;
                tzwFree(fid_structs[i].fingerprint_id);
                fid_structs[i].fingerprint_id = NULL;
            }
        }

    IFAA_Result result = IFAA_WriteFile(path, path_len, table_data, table_len);
    if(IFAA_ERR_SUCCESS == result) {
        LOG_I("save fid table success.");
    }

    return result;
}

uint32_t get_ifaa_fid(const uint8_t *fingerprint_id, uint8_t fid_index)
{
    if(fid_index > FID_TABLE_MAX) {
        LOG_E("fid_index is too large");
        return 0;
    }

    if(fingerprint_id == 0) {
        LOG_E("the input fingerprint id is null.");
        return 0;
    }

    if(fid_structs[fid_index].ifaa_fid==0 || fid_structs[fid_index].fingerprint_id==0 \
            || memcmp(fid_structs[fid_index].fingerprint_id, fingerprint_id, FID_LEN)) {
        fid_structs[fid_index].ifaa_fid = get_max_flag() + 1;
        if(fid_structs[fid_index].ifaa_fid==0) {
            LOG_F("fid flag(uint32_t) overflow.");
            return 0;
        }

        if(fid_structs[fid_index].fingerprint_id == 0)
            fid_structs[fid_index].fingerprint_id = tzwMalloc(FID_LEN);
        LOG_D("the input fid is %s.", fingerprint_id);
        LOG_D("the updated fid is %s.", fid_structs[fid_index].fingerprint_id);

        if(fid_structs[fid_index].fingerprint_id)
            memcpy(fid_structs[fid_index].fingerprint_id, fingerprint_id, FID_LEN);
        LOG_I("update fid table success.");
    }
    LOG_D("fid index is %d, ifaa fid is %d.", fid_index, fid_structs[fid_index].ifaa_fid);

    return convert_ifaa_fid(fid_index);
}

uint32_t auto_get_ifaa_fid(const char *path, uint32_t path_len, const uint8_t *fingerprint_id, uint8_t fid_index)
{
    IFAA_Result ret = load_fid_table(path, path_len);
    if(ret!=IFAA_ERR_SUCCESS) {
        LOG_E("load fid table failed: %x", ret);
        return 0;
    }

    uint32_t ifaa_fid = get_ifaa_fid(fingerprint_id, fid_index);
    if(ifaa_fid == 0)
        LOG_E("get unique flag failed.");

    if(save_fid_table(path, path_len, 1) != IFAA_ERR_SUCCESS)
        LOG_E("save fid table failed.");

    return ifaa_fid;
}

IFAA_Result update_ifaa_fid(SystemFidStruct  *enrolledFps, uint32_t *ifaaIds, uint8_t realCnt) {

    if(enrolledFps == NULL || ifaaIds == NULL || realCnt == 0) {
    
        LOG_E("update ifaa fid with bad param!");
        return IFAA_ERR_BAD_PARAM;
    }

    IFAA_Result ret = load_fid_table(FID_TABLE_PATH, FID_TABLE_PATH_LEN);
    if(ret!=IFAA_ERR_SUCCESS) {
        LOG_E("load fid table failed: %x", ret);
        return ret;
    }

    uint32_t maxFid = get_max_flag();
    bool fidUpdated = false;

    for(uint8_t i = 0; i<realCnt; i++) {
        SystemFidStruct  *pEnrolledFp = &enrolledFps[i];
        uint32_t fid_index = pEnrolledFp->realFpIndex;

        if(fid_structs[fid_index].ifaa_fid==0 || fid_structs[fid_index].fingerprint_id==0 \
            || memcmp(fid_structs[fid_index].fingerprint_id, pEnrolledFp->realFid, FID_LEN)) {
            fid_structs[fid_index].ifaa_fid = maxFid + 1;
            if(fid_structs[fid_index].ifaa_fid == 0) {
                LOG_F("fid flag(uint32_t) overflow.");
                return IFAA_ERR_OUT_OF_MEM;
            }
            maxFid = fid_structs[fid_index].ifaa_fid;
            if(fid_structs[fid_index].fingerprint_id == 0)
                fid_structs[fid_index].fingerprint_id = tzwMalloc(FID_LEN);
             
            LOG_I("enrolled real fid is %s.", pEnrolledFp->realFid);
            memcpy(fid_structs[fid_index].fingerprint_id, pEnrolledFp->realFid, FID_LEN);
            ifaaIds[i] = convert_ifaa_fid(fid_index);
            LOG_I("ifaa_id[%d] = %u", pEnrolledFp->realFpIndex, ifaaIds[i]);
            fidUpdated = true;   
        }
        else {
            ifaaIds[i] = convert_ifaa_fid(fid_index);
            LOG_D("fid[index-%d] = %s already registered, fid = %u ", fid_index, pEnrolledFp->realFid,ifaaIds[i]);
        }
    }
    if(fidUpdated) {
        ret = save_fid_table(FID_TABLE_PATH,FID_TABLE_PATH_LEN,1);
        if(ret!=IFAA_ERR_SUCCESS) {
            LOG_E("save fid table failed: %x", ret);
        }
    } else {
        LOG_D("no id changes");
        for(uint8_t i=0; i<=FID_TABLE_MAX; ++i) {
            if(fid_structs[i].fingerprint_id != NULL)
                tzwFree(fid_structs[i].fingerprint_id);
            fid_structs[i].fingerprint_id = NULL;
            fid_structs[i].ifaa_fid = 0;
        }
    }
    return ret;
}

uint8_t get_fid_table_count()
{
    uint8_t result = 0;

    for(uint8_t i=0; i<=FID_TABLE_MAX; ++i)
        if(fid_structs[i].fingerprint_id != 0)
            ++result;

    return result;
}

uint32_t get_max_flag()
{
    uint32_t result = 0;

    for(uint8_t i=0; i<=FID_TABLE_MAX; ++i)
        if(result < fid_structs[i].ifaa_fid)
            result = fid_structs[i].ifaa_fid;

    return result;
}

uint32_t need_convert_fid() {
    const char *remained_fid_chip[] = {"SDM845", "MSM8998", "SDM660", "SDM450", "\0"};
    for(int i=0; 0!=strcmp(remained_fid_chip[i],"\0"); i++) {
        if(strcmp(remained_fid_chip[i],CHIP_NAME)==0) return 0;
    }
    return 1;
}

uint32_t get_xor_fid() {

    if(strcmp(__IFBIO_TA_VERSION_NAME__, "v1") == 0) {
        return XOR_FID_FOR_SERVER_V1;
    } else {
        return XOR_FID_FOR_SERVER;
    }
}
uint32_t convert_ifaa_fid(uint32_t fid_index)
{
    uint32_t rfid = fid_structs[fid_index].ifaa_fid;
    if(need_convert_fid()) {
        do {
            int rot = ROTATE_FID_FOR_SERVER % (sizeof(uint32_t)*8);
            uint32_t xor_fid = fid_structs[fid_index].ifaa_fid ^ get_xor_fid();
            rfid = (xor_fid<<rot) | (xor_fid>>(sizeof(uint32_t)*8-rot));
            if(rfid == 0) {
                fid_structs[fid_index].ifaa_fid = get_max_flag() + 1;
                if(fid_structs[fid_index].ifaa_fid == 0) {
                    LOG_F("fid flag(uint32_t) overflow.");
                    return 0;
                }
            }
        } while(rfid==0);
    }

    return rfid;
}
