#include <stdint.h>

#include "em_lite.h"
#include "em_common.h"

static int em_lite_get_type_of_ess_command(unsigned char *command)
{
	if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_DELETE_OFF, strlen(EM_MAGIC_ESS_PREFIX_HEADER_DELETE_OFF)) ==
	    0) {
		return EM_TYPE_ESS_OFFLINE_DELETE_TOKEN;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_GET_INFO, strlen(EM_MAGIC_ESS_PREFIX_HEADER_GET_INFO)) ==
		   0) {
		return EM_TYPE_ESS_GET_INFO;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_REQ_TOK, strlen(EM_MAGIC_ESS_PREFIX_HEADER_REQ_TOK)) ==
		   0) {
		return EM_TYPE_ESS_MAKE_TOKEN;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_INSTALL_TOK,
			  strlen(EM_MAGIC_ESS_PREFIX_HEADER_INSTALL_TOK)) == 0) {
		return EM_TYPE_ESS_INSTALL_TOK;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_DEL_TOK, strlen(EM_MAGIC_ESS_PREFIX_HEADER_DEL_TOK)) ==
		   0) {
		return EM_TYPE_ESS_DELETE_TOKEN;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_REQ_RECOVERY,
			  strlen(EM_MAGIC_ESS_PREFIX_HEADER_REQ_RECOVERY)) == 0) {
		return EM_TYPE_ESS_MAKE_RECOVERY_MSG;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_RECOVERY, strlen(EM_MAGIC_ESS_PREFIX_HEADER_RECOVERY)) ==
		   0) {
		return EM_TYPE_ESS_RECOVERY_DATA;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_HERE_I_AM,
			  strlen(EM_MAGIC_ESS_PREFIX_HEADER_HERE_I_AM)) == 0) {
		return EM_TYPE_ESS_CHECKING_SUPPORT;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_GET_MODES,
			  strlen(EM_MAGIC_ESS_PREFIX_HEADER_GET_MODES)) == 0) {
		return EM_TYPE_ESS_GET_MODES;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_SET_PRIORITY,
			  strlen(EM_MAGIC_ESS_PREFIX_HEADER_SET_PRIORITY)) == 0) {
		return EM_TYPE_ESS_SET_PRIORITY;
	} else if (memcmp(command, EM_MAGIC_ESS_PREFIX_HEADER_GET_PRIORITY,
			  strlen(EM_MAGIC_ESS_PREFIX_HEADER_GET_PRIORITY)) == 0) {
		return EM_TYPE_ESS_GET_PRIORITY;
	} else {
		return EM_TYPE_ESS_UNKNOWN;
	}
}

int em_lite_ess_command(unsigned char *command, int len_command, unsigned char *model, unsigned char *did,
			unsigned char *out, int *out_len, int (*em_lite_client_io)(int type, unsigned char *buf))
{

	static em_keeping_item keep = {};

	int ret;
	em_req_payload *mReq = NULL;
	em_rsp_payload *mRsp = NULL;
	uint32_t rsp_buf_len = sizeof(em_rsp_payload);
	uint32_t offset = 0;
	char *allZero = NULL, *lastStatus = NULL;
	int clearKeep = 0, readIIN = 0, readToken = 0, readPriority = 0, removeToken = 0, writeLastStatus = 0, type = 0;

	mReq = (em_req_payload *)em_calloc(1, sizeof(em_req_payload));
	mRsp = (em_rsp_payload *)em_calloc(1, sizeof(em_rsp_payload));
	EM_CHECK_NULL(__func__, EM_ERR_EM_LITE_ESS_COMMAND, command, model, did, out, out_len, em_lite_client_io,
		      mReq, mRsp);

	if (len_command > EM_LEN_MESSAGE) {
		LOGE("Please check command length(%u)\n", len_command);
		ret = EM_ERR_EM_LITE_ESS_COMMAND_LENGTH;
		goto out;
	}

	mReq->primary.cmd = EM_CMD_ESS;
	mReq->version = EM_PAYLOAD_VERSION;

	memcpy(mReq->secondary.message, command, len_command);
	mReq->secondary.flags[0] |= EM_FLAGS_0_EXIST_MESSAGE;
	mReq->secondary.len_message = len_command;

	memcpy(mReq->primary.did, did, EM_LEN_DID);
	mReq->primary.flags[0] |= EM_FLAGS_0_EXIST_DID;

	memcpy((char *)mReq->primary.model_name, model, strlen(model));
	mReq->primary.flags[0] |= EM_FLAGS_0_EXIST_MODEL_NAME;

	mReq->primary.flags[0] |= EM_FLAGS_0_EXIST_IS_CALLER_CHECKED;

	type = em_lite_get_type_of_ess_command(command);
	switch (type) {
		case EM_TYPE_ESS_DELETE_TOKEN:
			readIIN = readToken = removeToken = writeLastStatus =  1;
			lastStatus = (char *)EM_MAGIC_LTS_DELETED;
			break;
		case EM_TYPE_ESS_INSTALL_TOK:
			memcpy(&mReq->primary.keep, &keep, sizeof(em_keeping_item));
			mReq->primary.flags[0] |= EM_FLAGS_0_EXIST_KEEPING_ITEM;
			clearKeep = 1;
			break;
		case EM_TYPE_ESS_GET_MODES:
			readToken = 1;
			break;
		case EM_TYPE_ESS_SET_PRIORITY:
		case EM_TYPE_ESS_GET_PRIORITY:
			readToken = readPriority = 1;
			break;
		case EM_TYPE_ESS_OFFLINE_DELETE_TOKEN:
			removeToken = writeLastStatus = 1;
			lastStatus = (char *)EM_MAGIC_LTS_DELETED;
			break;
		case EM_TYPE_ESS_GET_INFO:
			readToken = 1;
			break;
		default:
			LOGI("Unknown command type(0x%08x)\n", type);
	}

	if (readIIN == 1) {
			ret = em_lite_client_io(EM_LITE_READ_TYPE_2, (char *)mReq->primary.iin);
			if (ret != EM_SUCCESS) {
				LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_READ_TYPE_2, ret);
				goto out;
			}
			mReq->primary.flags[0] |= EM_FLAGS_0_EXIST_IIN;
	}

	if (readToken == 1) {
			ret = em_lite_client_io(EM_LITE_READ_TYPE_1, (char *)mReq->secondary.token);
			if (ret != EM_SUCCESS) {
				LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_READ_TYPE_1, ret);
				goto out;
			}
			mReq->secondary.flags[0] |= EM_FLAGS_0_EXIST_TOKEN;
	}

	if (removeToken == 1) {
			allZero = (char *)em_calloc(1, EM_LEN_TOKEN);
			if (allZero) {
				if ((ret = em_lite_client_io(EM_LITE_WRITE_TYPE_1, allZero)) != EM_SUCCESS)
					LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_WRITE_TYPE_1, ret);
			} else {
				LOGE("Failed to allocate buffer\n");
				ret = EM_ERR_EM_LITE_ESS_COMMAND_FAILED_ALLOC_BUFFER;
				goto out;
			}
	}

	if (writeLastStatus == 1) {
		ret = em_lite_client_io(EM_LITE_WRITE_TYPE_4, lastStatus);
		if (ret != EM_SUCCESS)
			LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_WRITE_TYPE_4, ret);
	}

	if (readPriority == 1) {
		ret = em_lite_client_io(EM_LITE_READ_TYPE_3, (char *)mReq->primary.priority);
		if (ret != EM_SUCCESS) {
			LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_READ_TYPE_3, ret);
			goto out;
		}
		mReq->primary.flags[0] |= EM_FLAGS_0_EXIST_PRIORITY_TIME;
	}

	ret = em_lite_entry((uint8_t *)mReq, sizeof(em_req_payload), (uint8_t *)mRsp, &rsp_buf_len);
	if (ret != EM_SUCCESS) {
		LOGE("Failed to process command = 0x%08x\n", ret);
		goto out;
	}

	ret = mRsp->ret;
	if (ret != EM_SUCCESS) {
		LOGE("Failed to get status(0x%08x)\n", ret);
		goto out;
	}

	if (type == EM_TYPE_ESS_INSTALL_TOK) {
		ret = em_lite_client_io(EM_LITE_WRITE_TYPE_2, (char *)keep.iin);
		if (ret != EM_SUCCESS) {
			LOGE("Failed client io(0x%08x, 0x%08x\n", EM_LITE_WRITE_TYPE_2, ret);
			goto out;
		}

		if ((ret = em_lite_client_io(EM_LITE_WRITE_TYPE_4, EM_MAGIC_LTS_INSTALLED)) != EM_SUCCESS)
			LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_WRITE_TYPE_4, ret);
	}

	if (mRsp->primary.flags[1] & EM_FLAGS_1_EXIST_RETURN_PRIORITY_TIME) {
		ret = em_lite_client_io(EM_LITE_WRITE_TYPE_3, (char *)mRsp->primary.priority);
		if (ret != EM_SUCCESS) {
			LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_WRITE_TYPE_3, ret);
			goto out;
		}
	}

	if (mRsp->secondary.flags[1] & EM_FLAGS_1_EXIST_RETURN_TOKEN) {
		ret = em_lite_client_io(EM_LITE_WRITE_TYPE_1, (char *)mRsp->secondary.token);
		if (ret != EM_SUCCESS) {
			LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_WRITE_TYPE_1, ret);
			goto out;
		}
	}

	if (mRsp->primary.flags[1] & EM_FLAGS_1_EXIST_RETURN_KEEPING_ITEM)
		memcpy(&keep, &mRsp->primary.keep, sizeof(em_keeping_item));

	if (mRsp->secondary.flags[1] & EM_FLAGS_1_EXIST_RETURN_MESSAGE) {
		memcpy(out, EM_MAGIC_ESS_PREFIX_COMMAND, strlen(EM_MAGIC_ESS_PREFIX_COMMAND));
		offset += strlen(EM_MAGIC_ESS_PREFIX_COMMAND);
		memcpy(out + offset, command, len_command);
		offset += len_command;
		memcpy(out + offset, EM_MAGIC_ESS_AT_COMMAND_NEW_LINE, strlen(EM_MAGIC_ESS_AT_COMMAND_NEW_LINE));
		offset += strlen(EM_MAGIC_ESS_AT_COMMAND_NEW_LINE);
		memcpy(out + offset, mRsp->secondary.message, mRsp->secondary.len_message);
		offset += mRsp->secondary.len_message;
		out[offset] = '\0';
		*out_len = offset;
		LOGI("Result : %u/%s\n", mRsp->secondary.len_message, mRsp->secondary.message);
	} else {
		LOGE("No return message");
		ret = EM_ERR_EM_LITE_ESS_COMMAND_NO_MESSAGE;
		goto out;
	}

	ret = EM_SUCCESS;
out:
	if (clearKeep == 1)
		memset(&keep, 0, sizeof(em_keeping_item));

	if (mReq) {
		memset(mReq, 0, sizeof(em_req_payload));
		em_free(mReq);
		mReq = NULL;
	}

	if (mRsp) {
		memset(mRsp, 0, sizeof(em_rsp_payload));
		em_free(mRsp);
		mRsp = NULL;
	}

	if (ret != EM_SUCCESS && out != NULL) {
		offset = 0;
		memcpy(out, EM_MAGIC_ESS_PREFIX_COMMAND, strlen(EM_MAGIC_ESS_PREFIX_COMMAND));
		offset += strlen(EM_MAGIC_ESS_PREFIX_COMMAND);
		memcpy(out + offset, command, len_command);
		offset += len_command;
		memcpy(out + offset, EM_MAGIC_ESS_AT_COMMAND_NEW_LINE, strlen(EM_MAGIC_ESS_AT_COMMAND_NEW_LINE));
		offset += strlen(EM_MAGIC_ESS_AT_COMMAND_NEW_LINE);
		memcpy(out + offset, EM_MAGIC_ESS_PREFIX_RETURN_COMMAND, strlen(EM_MAGIC_ESS_PREFIX_RETURN_COMMAND));
		offset += strlen(EM_MAGIC_ESS_PREFIX_RETURN_COMMAND);
		if (command)
			out[offset] = command[0];
		else
			out[offset] = 'U';
		offset++;
		memcpy(out + offset, EM_MAGIC_ESS_AT_COMMAND_DELIM, strlen(EM_MAGIC_ESS_AT_COMMAND_DELIM));
		offset += strlen(EM_MAGIC_ESS_AT_COMMAND_DELIM);
		memcpy(out + offset, EM_MAGIC_ESS_AT_COMMAND_NG, strlen(EM_MAGIC_ESS_AT_COMMAND_NG));
		offset += strlen(EM_MAGIC_ESS_AT_COMMAND_NG);
		memcpy(out + offset, EM_MAGIC_ESS_AT_COMMAND_DELIM, strlen(EM_MAGIC_ESS_AT_COMMAND_DELIM));
		offset += strlen(EM_MAGIC_ESS_AT_COMMAND_DELIM);
		em_snprintf(out + offset, 8 + 1, "%08X", ret);
		offset += 8;
		memcpy(out + offset, EM_MAGIC_ESS_AT_COMMAND_END_LINE, strlen(EM_MAGIC_ESS_AT_COMMAND_END_LINE));
		offset += strlen(EM_MAGIC_ESS_AT_COMMAND_END_LINE);
		out[offset] = '\0';
		*out_len = offset;

		ret = EM_SUCCESS;
	}

	return ret;
}

int em_lite_get_status(int mode, unsigned char *did, int (*em_lite_client_io)(int type, unsigned char *buf))
{
	int ret;
	em_req_payload *req = NULL;
	em_rsp_payload *rsp = NULL;
	uint32_t rsp_buf_len = sizeof(em_rsp_payload);

	req = (em_req_payload *)em_calloc(1, sizeof(em_req_payload));
	rsp = (em_rsp_payload *)em_calloc(1, sizeof(em_rsp_payload));
	EM_CHECK_NULL(__func__, EM_ERR_EM_LITE_GET_STATUS, em_lite_client_io, req, rsp);

	req->primary.cmd = EM_CMD_GET_STATUS;
	req->version = EM_PAYLOAD_VERSION;

	ret = em_lite_client_io(EM_LITE_READ_TYPE_1, (unsigned char *)req->secondary.token);
	if (ret != EM_SUCCESS) {
		LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_READ_TYPE_1, ret);
		goto out;
	}
	req->secondary.flags[0] |= EM_FLAGS_0_EXIST_TOKEN;

	memcpy(req->primary.did, did, EM_LEN_DID);
	req->primary.flags[0] |= EM_FLAGS_0_EXIST_DID;

	req->primary.cnt_mode = 1;
	req->primary.modes[0] = mode;

	req->primary.flags[0] |= EM_FLAGS_0_EXIST_IS_CALLER_CHECKED;

	ret = em_lite_entry((uint8_t *)req, sizeof(em_req_payload), (uint8_t *)rsp, &rsp_buf_len);
	if (ret != EM_SUCCESS) {
		LOGE("Failed to process command = 0x%08x\n", ret);
		goto out;
	}

	ret = rsp->ret;
	if (ret != EM_SUCCESS) {
		LOGE("Failed to get status(0x%08x)\n", ret);
		goto out;
	}

	ret = EM_SUCCESS;
out:
	if (req) {
		memset(req, 0, sizeof(em_req_payload));
		em_free(req);
		req = NULL;
	}

	if (rsp) {
		memset(rsp, 0, sizeof(em_rsp_payload));
		em_free(rsp);
		rsp = NULL;
	}

	return ret;
}

int em_lite_install_token(unsigned char *token, int len_token, unsigned char *did,
			  int (*em_lite_client_io)(int type, unsigned char *buf))
{
	int ret;
	em_req_payload *req = NULL;
	em_rsp_payload *rsp = NULL;
	uint32_t rsp_buf_len = sizeof(em_rsp_payload);

	req = (em_req_payload *)em_calloc(1, sizeof(em_req_payload));
	rsp = (em_rsp_payload *)em_calloc(1, sizeof(em_rsp_payload));
	EM_CHECK_NULL(__func__, EM_ERR_EM_LITE_INSTALL_TOKEN, token, did, em_lite_client_io, req, rsp);

	req->primary.cmd = EM_CMD_INSTALL_TOKEN;
	req->version = EM_PAYLOAD_VERSION;

	memcpy(req->secondary.token, token, len_token);
	req->secondary.flags[0] |= EM_FLAGS_0_EXIST_TOKEN;

	memcpy(req->primary.did, did, EM_LEN_DID);
	req->primary.flags[0] |= EM_FLAGS_0_EXIST_DID;

	req->primary.flags[0] |= EM_FLAGS_0_EXIST_IS_CALLER_CHECKED;

	req->primary.flags[0] |= EM_FLAGS_0_EXIST_BOOTLOADER;

	ret = em_lite_entry((uint8_t *)req, sizeof(em_req_payload), (uint8_t *)rsp, &rsp_buf_len);
	if (ret != EM_SUCCESS) {
		LOGE("Failed to process command = 0x%08x\n", ret);
		goto out;
	}

	ret = rsp->ret;
	if (ret != EM_SUCCESS) {
		LOGE("Failed to get status(0x%08x)\n", ret);
		goto out;
	}

	ret = em_lite_client_io(EM_LITE_WRITE_TYPE_1, (char *)req->secondary.token);
	if (ret != EM_SUCCESS) {
		LOGE("Failed client io(0x%08x, 0x%08x)\n", EM_LITE_WRITE_TYPE_1, ret);
		goto out;
	}

	ret = EM_SUCCESS;
out:
	if (req) {
		memset(req, 0, sizeof(em_req_payload));
		em_free(req);
		req = NULL;
	}

	if (rsp) {
		memset(rsp, 0, sizeof(em_rsp_payload));
		em_free(rsp);
		rsp = NULL;
	}

	return ret;
}

int em_lite_entry(unsigned char *req_buf, int req_buf_len, unsigned char *rsp_buf, uint32_t *rsp_buf_len)
{
	int ret;

	LOGI("EngineeringMode Lite(%s) Here\n", EM_MODULE_VERSION);

	em_context *ctx = (em_context *)em_calloc(1, sizeof(em_context));

	EM_CHECK_NULL(__func__, EM_ERR_EM_LITE_ENTRY, ctx);

	ret = em_context_make_request(req_buf, ctx);
	if (ret != EM_SUCCESS) {
		LOGE("Failed make context(0x%08x)\n", ret);
		goto out;
	}

	ret = em_cmd_handler(ctx);
	if (ret != EM_SUCCESS) {
		LOGE("Failed cmd processing(0x%08x)\n", ret);
		goto make_msg;
	}

	LOGI("command result %d\n", ret);
make_msg:
	ret = em_context_make_response(rsp_buf, ctx, ret);
	if (ret != EM_SUCCESS) {
		LOGE("Failed make context_response(0x%08x)\n", ret);
		goto out;
	}

	ret = EM_SUCCESS;
out:
	if (ctx) {
		memset(ctx, 0, sizeof(em_context));
		em_free(ctx);
		ctx = NULL;
	}

	LOGI("EngineeringMode Lite Bye\n");

	return ret;
}
