#include <assert.h>
#include <string.h>
#include <stdio.h>

#include "MobiCoreDriverApi.h"
#include "mcUuid.h"
#include "CommLayerData.h"
#include "TLV.h"
#include "log.h"

#include "TAStartupCode.h"

static const mcUuid_t UUID = MC_UUID_MLDAP;
static const uint32_t DEVICE_ID = MC_DEVICE_ID_DEFAULT;
static mcSessionHandle_t sessionHandle = {};
static uint8_t *mcTci = NULL;

int32_t kmLoadTA(void)
{
	int32_t ret = PLATFORM_INTERNAL_ERROR;

	if (mcTci != NULL) {
		LOGE("Trustlet is already loaded\n");
		return PLATFORM_INTERNAL_ERROR;
	}

	ret = mcOpenDevice(DEVICE_ID);
	if (ret != MC_DRV_OK) {
		LOGE("Running trustlet failed with ret = 0x%x", ret);
		return PLATFORM_INTERNAL_ERROR;
	}

	ret = mcMallocWsm(DEVICE_ID, 0, sizeof(cmd_req_t), &mcTci, 0);
	if (ret != MC_DRV_OK) {
		LOGE("Shared buffer allocation failed with ret = 0x%x", ret);
		mcCloseDevice(DEVICE_ID);
		return PLATFORM_INTERNAL_ERROR;
	}

	memset(&sessionHandle, 0, sizeof(sessionHandle));
	sessionHandle.deviceId = DEVICE_ID;
	ret = mcOpenSession(&sessionHandle, &UUID, mcTci, MAX_TRANSFER_SIZE);
	if (ret != MC_DRV_OK) {
		LOGE("Open session failed with ret = 0x%x", ret);
		mcCloseDevice(DEVICE_ID);
		mcTci = NULL;
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

int32_t kmUnloadTA(void)
{
	int32_t ret = PLATFORM_INTERNAL_ERROR;

	ret = mcCloseSession(&sessionHandle);
	if (ret != MC_DRV_OK) {
		LOGE("Close session failed with ret = 0x%x", ret);
	}

	ret = mcCloseDevice(DEVICE_ID);
	if (ret != MC_DRV_OK) {
		LOGE("Close device failed with ret = 0x%x", ret);
		ret = PLATFORM_INTERNAL_ERROR;
	}

	mcTci = NULL;
	return ret;
}

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)
{
	int32_t ret = 0;
	int32_t lastErr = 0;
	cmd_req_t* cmd = (cmd_req_t*)mcTci;
	cmd_rsp_t* rsp = (cmd_rsp_t*)mcTci;

	if (mcTci == NULL) {
		LOGE("Trustlet isn't loaded");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (inLen > MAX_TRANSFER_SIZE) {
		LOGE("Too long command %d, buffer overflow", inLen);
		return PLATFORM_INTERNAL_ERROR;
	}

	LOGI("sending command 0x%x, len: %d", cmdId, inLen);

	cmd->cmd_id = cmdId;
	cmd->dataLen = inLen;

	if (inData != NULL) {
		memcpy(cmd->data, inData, inLen);
	}

	ret = mcNotify(&sessionHandle);
	if (ret != MC_DRV_OK) {
		LOGE("mcNotify() failed! Error code: 0x%x", ret);
		return PLATFORM_INTERNAL_ERROR;
	}

	/* TODO: set timeout */
	ret = mcWaitNotification(&sessionHandle, MC_INFINITE_TIMEOUT);
	if (ret != MC_DRV_OK) {
		LOGE("mcWaitNotification() failed! Error code: 0x%x", ret);
		if (ret == MC_DRV_INFO_NOTIFICATION) {
			ret = mcGetSessionErrorCode(&sessionHandle, &lastErr);
			LOGE("problem with the session was encountered, lastErr: 0x%x", lastErr);
			// lastErr: 0xfffffffd
			// task terminated due to invalid operation
			// ERR_INVALID_OPERATION   = -3 (0xfffffffd)
		}

		return PLATFORM_INTERNAL_ERROR;
	}

	if (rsp->status != NO_ERROR) {
		LOGE( "Application TA reported error code: 0x%x", rsp->status );
		return rsp->status;
	}

	LOGI("received %d bytes", rsp->dataLen);
	if (outData && outLen) {
		if (rsp->dataLen > *outLen) {
			LOGE("not enough room for outData");
			return WRONG_DATA;
		}

		*outLen = rsp->dataLen;
		memcpy(outData, rsp->data, *outLen);
	}

	return NO_ERROR;
}