#include <stdarg.h>

#include "tc_private/tc_handle.h"
#include "tc_private/tc_private.h"

#include "log/log.h"

#include "tc_tpm2.h"
#include "tpm2_common.h"

#include "tc_type.h"
#include "tc_errcode.h"

struct tpm2_nvread_ctx
{
    TC_HANDLE        handle;
    uint32_t         nv_index;
    uint32_t         hierarchy;
    TC_BUFFER       *hierarchy_auth_msg;
    uint32_t         offset_read;
    uint32_t         size_read;
    TC_BUFFER       *nv_data_read;
};

TC_RC tpm2_nvread_init(struct api_ctx_st *api_ctx, int num, ...)
{
    TC_RC rc = TC_SUCCESS;
    struct tpm2_nvread_ctx* nctx = (struct tpm2_nvread_ctx*)malloc(sizeof(struct tpm2_nvread_ctx));

    va_list ap;
    va_start(ap, num);
    nctx->handle = va_arg(ap, TC_HANDLE);
    nctx->nv_index = va_arg(ap, uint32_t);
    nctx->hierarchy = va_arg(ap, uint32_t);
    nctx->hierarchy_auth_msg = va_arg(ap, TC_BUFFER*);
    nctx->offset_read = va_arg(ap, uint32_t);
    nctx->size_read = va_arg(ap, uint32_t);
    nctx->nv_data_read = va_arg(ap, TC_BUFFER*);
    va_end(ap);

    api_ctx->data = (HANDLE_DATA*)nctx;
    return rc;
}

TC_RC tpm2_nvread_free(struct api_ctx_st *api_ctx)
{
    TC_RC rc = TC_SUCCESS;  
    free(api_ctx->data); 
    api_ctx->data = NULL;
    api_ctx->cmd_code = API_NULL;
    return rc;
}

TC_RC tpm2_nvread(API_CTX *ctx)
{
    TC_RC rc = TC_SUCCESS;

    struct tpm2_nvread_ctx* nctx = (struct tpm2_nvread_ctx*)ctx->data;
    TC_HANDLE_CTX* tc_handle_ctx = (TC_HANDLE_CTX*)(nctx->handle);
    TSS2L_SYS_AUTH_RESPONSE sessionsDataout;
    TSS2L_SYS_AUTH_COMMAND sessionsData = {
        .auths    = {{.sessionHandle = TPM2_RS_PW}},
        .count    = 1
    };
    TPM2B_NV_PUBLIC nv_public = TPM2B_EMPTY_INIT;

    if (nctx->hierarchy_auth_msg != NULL) {
        if (nctx->hierarchy_auth_msg->size > sizeof(TPMU_HA)) {
            log_error("The hierarchy authorization authentication password exceeds the limit\n");
            return TC_AUTH_HMAC_OVERSIZE;
        }
        sessionsData.auths[0].hmac.size = nctx->hierarchy_auth_msg->size;
        memcpy(sessionsData.auths[0].hmac.buffer,
               nctx->hierarchy_auth_msg->buffer,
               nctx->hierarchy_auth_msg->size);
    }

    rc = tpm2_util_nv_read_public((TSS2_SYS_CONTEXT*)tc_handle_ctx->handle.tc_context,
                                   nctx->nv_index,
                                   &nv_public);
    if (rc != TSS2_RC_SUCCESS) {
        log_error("nv read public failed\n");
        return TC_NV_READPUBLIC;
    }

    uint16_t data_size = nv_public.nvPublic.dataSize;

    if (nctx->offset_read > data_size) {
        log_error("Request offset to read from is greater than size\n");
        return TC_NV_OFFSET;
    }

    if (nctx->offset_read + nctx->size_read > data_size) {
        log_error("Request to read more bytes than avaiable from offset\n");
        return TC_NV_SIZE;
    }

    uint32_t max_data_size;
    rc = tpm2_util_nv_max_buffer_size((TSS2_SYS_CONTEXT*)tc_handle_ctx->handle.tc_context,
                                       &max_data_size);
    if (rc != TSS2_RC_SUCCESS) {
        log_error("nv max buffer failed\n");
        return TC_NV_MAXBUFFER;
    }

    if (max_data_size > TPM2_MAX_NV_BUFFER_SIZE) {
        max_data_size = TPM2_MAX_NV_BUFFER_SIZE;
    }else if (max_data_size ==0 ){
        max_data_size = NV_DEFAULT_BUFFER_SIZE;
    }

    nctx->nv_data_read->buffer = malloc(data_size);
    uint16_t data_offset = 0;
    while (nctx->size_read)
    {   
        uint16_t bytes_to_read = nctx->size_read > max_data_size ?
                                 max_data_size : nctx->size_read;

        TPM2B_MAX_NV_BUFFER nv_data = TPM2B_TYPE_INIT(TPM2B_MAX_NV_BUFFER, buffer);

        rc = Tss2_Sys_NV_Read((TSS2_SYS_CONTEXT*)tc_handle_ctx->handle.tc_context,
                               nctx->hierarchy,
                               nctx->nv_index,
                               &sessionsData,
                               bytes_to_read,
                               nctx->offset_read,
                               &nv_data,
                               &sessionsDataout);
        if (rc != TSS2_RC_SUCCESS) {
            return TC_COMMAND_NVREAD;
        }

        nctx->size_read -= nv_data.size;
        nctx->offset_read += nv_data.size;

        memcpy(nctx->nv_data_read->buffer + data_offset,
               nv_data.buffer,
               nv_data.size);
        nctx->nv_data_read->size += nv_data.size;
        data_offset += nv_data.size;
    }
    
    if (rc != TSS2_RC_SUCCESS) {
        log_error("Failed to run api_nvread:0x%0x\n", rc);
        rc = TC_COMMAND_NVREAD;
    }
    return rc;
}