#include "CommLayerData.h"
#include "log.h"
#include <tee_client_api.h>
#include <string.h>

#include "TAStartupCode.h"

TEEC_Context context = {0};
TEEC_Session session;
TEEC_SharedMemory sharedMem;

TEEC_UUID uuid = {
	.timeLow = 0x0,
	.timeMid = 0x0,
	.timeHiAndVersion = 0x0,
	.clockSeqAndNode = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46},
};

TEEC_Operation operation = {
	.paramTypes = TEEC_PARAM_TYPES(
	TEEC_MEMREF_PARTIAL_INOUT,
	TEEC_NONE,
	TEEC_NONE,
	TEEC_NONE)
};

uint8_t* sharedBuffer()
{
	return sharedMem.buffer;
}

uint32_t sharedBufferLen()
{
	return operation.params[0].memref.size;
}

void setSharedBufferLen(uint32_t len)
{
	operation.params[0].memref.size = len;
}

void clearSharedBuffer()
{
	memset(sharedBuffer(), 0, MAX_TRANSFER_SIZE);
}

int32_t kmLoadTA(void)
{
	sharedMem.size  = MAX_TRANSFER_SIZE;
	sharedMem.flags = TEEC_MEM_INPUT | TEEC_MEM_OUTPUT;
	TEEC_Result res = TEEC_SUCCESS;
	uint32_t returnOrigin = PLATFORM_INTERNAL_ERROR;
	TEEC_Operation    ga_operation     = {0}; // operation to be sent in invoke command

	LOGD("TEEC_InitializeContext");
	res = TEEC_InitializeContext(NULL, &context);
	if (res != TEEC_SUCCESS) {
		LOGE("InitializeContext failed, error %d", res);
		return PLATFORM_INTERNAL_ERROR;
	}
	ga_operation.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE,TEEC_NONE, TEEC_NONE, TEEC_NONE);
	res = TEEC_OpenSession(&context, &session, &uuid, TEEC_LOGIN_PUBLIC, NULL, &ga_operation, &returnOrigin);
	if (res != TEEC_SUCCESS) {
		LOGE("TEEC_OpenSession failed, error 0x%x, returnOrigin 0x%x", res, returnOrigin);
		return PLATFORM_INTERNAL_ERROR;
	}

	LOGD("TEEC_AllocateSharedMemory");
	res = TEEC_AllocateSharedMemory(&context, &sharedMem);
	if (res != TEEC_SUCCESS) {
		LOGE("TEEC_AllocateSharedMemory failed, error %d", res);
		return PLATFORM_INTERNAL_ERROR;
	}

	operation.params[0].memref.parent = &sharedMem;
	operation.params[0].memref.offset = 0x0;
	operation.params[0].memref.size = sharedMem.size;

	LOGD("TEEC_OpenSession");

	LOGD("kmLoadTA Exited");
	return NO_ERROR;
}

int32_t kmUnloadTA(void)
{
	TEEC_CloseSession(&session);
	TEEC_ReleaseSharedMemory(&sharedMem);
	TEEC_FinalizeContext(&context);
	return NO_ERROR;
}

int32_t kmGetTid(uint8_t* tid, uint32_t* tidLen)
{
	if (!tid || !tidLen) {
		LOGE("Some argument is NULL");
		return WRONG_DATA;
	}

	if (*tidLen < sizeof(uuid)) {
		LOGE("kmGetTid: *tidLen < sizeof(uuid)");
		return WRONG_DATA;
	}

	memcpy(tid, &uuid, sizeof(uuid));
	*tidLen = sizeof(uuid);

	return NO_ERROR;
}

int32_t kmSendCmdInOut(uint32_t cmdId, uint8_t* inData, uint32_t inLen, uint8_t* outData, uint32_t* outLen)
{
	int ret = PLATFORM_INTERNAL_ERROR;
	uint32_t returnOrigin = TEEC_ORIGIN_TRUSTED_APP;
	if (inLen > MAX_TRANSFER_SIZE) {
		LOGE("Too long command %d, buffer overflow", inLen);
		return PLATFORM_INTERNAL_ERROR;
	}

	if (inData != NULL && inLen != 0) {
		memcpy(sharedBuffer(), inData, inLen);
		setSharedBufferLen(inLen);
	} else {
		/*setting buffer length to 20, since setting it 0 will result in crash in blowfish.
		This dummy legth is handled in swd side by populating 0 to response buffer when inDataLen is 20.*/

		setSharedBufferLen(SHA1_SIZE);
	}

	LOGD("Sending %d bytes, cmdId 0x%x", sharedBufferLen(), cmdId);
	if ((ret = TEEC_InvokeCommand(&session, cmdId, &operation, &returnOrigin) )!= TEEC_SUCCESS) {
		LOGE("TEEC_InvokeCommand failed with ret %d", ret);
		return PLATFORM_INTERNAL_ERROR;
	} else {
		LOGD("TEEC_InvokeCommand success for cmd = 0x%x",cmdId);
	}
	LOGD("Received %d bytes", sharedBufferLen());
	if (outData && outLen) {
		if (sharedBufferLen() > *outLen) {
			LOGE("not enough room for outData");
			return WRONG_DATA;
		}

		*outLen = sharedBufferLen();
		memcpy(outData, sharedBuffer(), *outLen);
	}

	return NO_ERROR;
}