#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_create_ctx
{
    TC_HANDLE        handle;
    TC_ALG           alg_hash;
    uint32_t         primary_index;
    TC_BUFFER       *primary_auth_msg;
    TC_ALG           alg_key;
    TC_BUFFER       *key_auth_msg;
    uint32_t        *key_index;
};

TC_RC tpm2_create_init(struct api_ctx_st *api_ctx, int num, ...)
{
    TC_RC rc = TC_SUCCESS;
    struct tpm2_create_ctx* cctx = (struct tpm2_create_ctx*)malloc(sizeof(struct tpm2_create_ctx));

    va_list ap;
    va_start(ap, num);
    cctx->handle = va_arg(ap, TC_HANDLE);
    cctx->alg_hash = va_arg(ap, TC_ALG);
    cctx->primary_index = va_arg(ap, uint32_t);
    cctx->primary_auth_msg = va_arg(ap, TC_BUFFER*);
    cctx->alg_key = va_arg(ap, TC_ALG);
    cctx->key_auth_msg = va_arg(ap, TC_BUFFER*);
    cctx->key_index = va_arg(ap, uint32_t*);
    va_end(ap);

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

TC_RC tpm2_create_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_create(API_CTX *ctx)
{
    TC_RC rc = TC_SUCCESS;

    struct tpm2_create_ctx* cctx = (struct tpm2_create_ctx*)ctx->data;
    TC_HANDLE_CTX* tc_handle_ctx = (TC_HANDLE_CTX*)(cctx->handle);

    if (tc_handle_ctx->handle.tc_object->count >= 0 
        && tc_handle_ctx->handle.tc_object->count <= MAX_OBJECT_NODE_COUNT) {
        tc_handle_ctx->handle.tc_object->count += 1;
    }else{
        log_error("Object cache capacity exceeded\n");
        return TC_NODE_OVERCAPACITY;
    }

    if (cctx->primary_index > tc_handle_ctx->handle.tc_object->count) {
        log_error("Invalid parent index\n");
        return TC_PARENT_INDEX;
    }

    tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1] = malloc(sizeof(TC_NODE));

    tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->parent_handle = tc_handle_ctx->handle.tc_object->node_info[cctx->primary_index]->obj_handle;

    TPM2B_DATA outsideInfo = TPM2B_EMPTY_INIT;
    TPML_PCR_SELECTION creationPCR = TPML_PCR_SELECTION_EMPTY_INIT;
    TPM2B_CREATION_DATA creationData = TPM2B_EMPTY_INIT;
    TPM2B_DIGEST creationHash = TPM2B_TYPE_INIT(TPM2B_DIGEST, buffer);
    TPMT_TK_CREATION creationTicket = TPMT_TK_CREATION_EMPTY_INIT;
    TSS2L_SYS_AUTH_RESPONSE sessionsDataOut;
    TSS2L_SYS_AUTH_COMMAND  sessionsData = {
        .auths    = {{.sessionHandle = TPM2_RS_PW}},
        .count    = 1
    };
    TPM2B_SENSITIVE_CREATE in_sensitive = TPM2B_SENSITIVE_CREATE_EMPTY_INIT;
    TPM2B_PUBLIC in_public = {
        .publicArea = {
            .objectAttributes = TPMA_OBJECT_DECRYPT|TPMA_OBJECT_SIGN_ENCRYPT
            |TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT
            |TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH
        }
    };

    tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->obj_name = malloc(sizeof(TPM2B_NAME));
    tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->obj_private = malloc(sizeof(TPM2B_PRIVATE));
    tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->obj_public = malloc(sizeof(TPM2B_PUBLIC));

    TPM2B_PRIVATE* private = (TPM2B_PRIVATE*)tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->obj_private;
    private->size = BUFFER_SIZE(TPM2B_PRIVATE, buffer);
    TPM2B_PUBLIC* public = (TPM2B_PUBLIC*)tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->obj_public;
    public->size = 0;

    if (cctx->primary_auth_msg != NULL) {
        if (cctx->primary_auth_msg->size > sizeof(TPMU_HA)) {
            log_error("The primary key authorization authentication password exceeds the limit\n");
            return TC_AUTH_HMAC_OVERSIZE;
        }
        sessionsData.auths->hmac.size = 
            cctx->primary_auth_msg->size;
        memcpy(sessionsData.auths->hmac.buffer,
               cctx->primary_auth_msg->buffer,
               cctx->primary_auth_msg->size);
    }

    if (cctx->key_auth_msg != NULL) {
        if (cctx->key_auth_msg->size > sizeof(TPMU_HA)) {
            log_error("Key authorization authentication password exceeds limit\n");
            return TC_AUTH_HMAC_OVERSIZE;
        }
        in_sensitive.sensitive.userAuth.size =
            cctx->key_auth_msg->size;
        memcpy(in_sensitive.sensitive.userAuth.buffer,
               cctx->key_auth_msg->buffer,
               cctx->key_auth_msg->size);
    }

    switch (cctx->alg_hash)
    {
    case TC_SHA256:
        in_public.publicArea.nameAlg = TPM2_ALG_SHA256;
        break;
    case TC_SM3:
        in_public.publicArea.nameAlg = TPM2_ALG_SM3_256;
        break;
    case TC_SHA1:
        in_public.publicArea.nameAlg = TPM2_ALG_SHA1;
        break;    
    default:
        log_error("unrecogize the tpm2_hash algorithms, %d\n", cctx->alg_hash);
        return TC_UNDEFINE_ALGO;
    }

    switch (cctx->alg_key)
    {
    case TC_RSA:
        in_public.publicArea.type = TPM2_ALG_RSA;
        break;
    case TC_SM2:
        in_public.publicArea.type = TPM2_ALG_SM2;
        break;
    case TC_SYMMETRIC:
        in_public.publicArea.type = TPM2_ALG_SYMCIPHER;
        break;    
    default:
        log_error("unrecogize the crypto algorithms, %d\n", cctx->alg_key);
        return TC_UNDEFINE_ALGO;
    }

    tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->name_hash_alg = in_public.publicArea.nameAlg;
    tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->alg_object = in_public.publicArea.type;    

    if (setup_create_alg(&in_public)) {
        log_error("Failed to setup alg\n");
        return TC_UNDEFINE_ALGO;
    }

    rc = Tss2_Sys_Create((TSS2_SYS_CONTEXT*)tc_handle_ctx->handle.tc_context,
                          tc_handle_ctx->handle.tc_object->node_info[tc_handle_ctx->handle.tc_object->count -1]->parent_handle,
                          &sessionsData,
                          &in_sensitive,
                          &in_public,
                          &outsideInfo,
                          &creationPCR,
                          private,
                          public,
                          &creationData,
                          &creationHash,
                          &creationTicket,
                          &sessionsDataOut); 

end:
    if (rc != TSS2_RC_SUCCESS) {
        log_error("Failed to run api_create:0x%0x\n", rc);
        rc = TC_COMMAND_CREATE;
    }
    return rc;
}