#include "em_common.h"

static int em_init_make_shared(em_context *ctx, em_core_v20 *core, const uint8_t *key)
{
	int ret;

	uint8_t iin[EM_LEN_IIN] = {0,};
	em_esi_meta meta = {0,};

	ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_INIT_STATE, (const uint8_t *)EM_MAGIC_ESI_V20_SHARED,
				 key, EM_LEN_KEY_CORE_V20);
	if (ret != EM_SUCCESS) {
		LOGE("Failed to update init state(0x%08x)\n", ret);
		goto out;
	}

	LOGI("updated status is %s\n", EM_MAGIC_ESI_V20_SHARED);

	em_esi_read_meta(&meta, ctx->esi);
	memcpy(core->magic, EM_MAGIC_EM_CORE, strlen(EM_MAGIC_EM_CORE));
	memcpy(core->key, key, EM_LEN_KEY_CORE_V20);
	core->esi_ctr = meta.write_counter + 1;
	core->flags |= EM_CORE_FLAG_INIT_FLAG;
	meta.write_counter += 1;

	ret = em_esi_get_item(ctx->esi, EM_TYPE_ESI_ITEM_IIN, iin, EM_LEN_IIN);
	if (ret == EM_SUCCESS) {
		memcpy(core->iin, iin, EM_LEN_IIN);
		ctx->flags[1] |= EM_FLAGS_1_WRITE_CORE;
	}

	ret = em_esi_update(20, ctx->esi, &meta, NULL, key, EM_LEN_KEY_CORE_V20);
	if (ret != EM_SUCCESS) {
		LOGE("Failed to update esi(0x%08x)\n", ret);
		goto out;
	}

	ret = EM_SUCCESS;
out:
	memset(iin, 0, EM_LEN_IIN);
	return ret;
}

int em_init_v20(em_context *ctx)
{
	int ret;

	uint32_t shared_esi_counter = 0;
	uint8_t key[EM_LEN_KEY_CORE_V20] = {0,};
	uint8_t init_state[EM_LEN_ESI_INIT_STATE] = {0,};
	uint8_t type_init_state = EM_TYPE_INIT_STATE_UNKNOWN;
	uint8_t iin[EM_LEN_IIN] = {0,};
	uint8_t esi_did[EM_LEN_DID] = {0,};

	em_core_v20 *core = NULL;
	em_esi_meta meta = {0,};

	EM_CHECK_NULL(__func__, EM_ERR_EM_INIT_V20, ctx);
	core = (em_core_v20 *)&ctx->core_v20;

	
	if (ctx->flags[2] & EM_FLAGS_2_ENABLED_LSEC_TOKEN) {
		// Bootloader API will be available after checking EM_CMD_INIT process
		// So we have to return EM_SUCCESS when lsec token is installed on device
		ret = EM_SUCCESS;
		goto out;
	}
		
	if (!(ctx->flags[0] & EM_FLAGS_0_EXIST_ESI)) {
		LOGE("esi isn't exists\n");
		ret = EM_ERR_EM_INIT_V20_ESI;
		goto out;
	}

	if (em_is_all_zero(ctx->esi, EM_LEN_ESI) == EM_SUCCESS) {
		if (ctx->flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
			if ((core != NULL) && (em_is_all_zero((uint8_t *)core, EM_LEN_CORE_V20) == EM_SUCCESS)) {
				LOGI("BootLoader doesn't keysharing\n");
			} else {
				LOGI("Esi is allzero, need ESI recovery after booting\n");
			}

			ret = EM_SUCCESS;
			goto out;
		}

		if (ctx->is_provision) {
			if (memcmp(core->magic, EM_MAGIC_EM_CORE, strlen(EM_MAGIC_EM_CORE)) == 0) {
				LOGE("esi isn't normal(all_zero)\n");
				ret = EM_ERR_EM_INIT_V20_ESI_ALL_ZERO;
				ctx->flags[1] |= EM_FLAGS_1_NEED_RECOVERY_ESI;
				goto out;
			}
		}

		if (!(ctx->flags[0] & EM_FLAGS_0_EXIST_DID) || em_is_all_zero(ctx->did, EM_LEN_DID) == EM_SUCCESS) {
			LOGE("Please check flags and did\n");
			ret = EM_ERR_EM_INIT_V20_DID;
			goto out;
		}

		ret = em_esi_initialize(20, ctx->esi, NULL, 0);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to make esi(0x%08x)\n", ret);
			goto out;
		}

		ret = em_get_random(key, EM_LEN_KEY_CORE_V20);
		if (ret != EM_LEN_KEY_CORE_V20) {
			LOGE("Failed to generate key(0x%08x)\n", ret);
			ret = EM_ERR_EM_INIT_V20_KEY;
			goto out;
		}

		ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_SHARED_KEY, key, key, EM_LEN_KEY_CORE_V20);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to add key to esi(0x%08x)\n", ret);
			goto out;
		}

		ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_INIT_STATE, (uint8_t *)EM_MAGIC_ESI_V20_SHARING,
					 key, EM_LEN_KEY_CORE_V20);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to add init state to esi(0x%08x)\n", ret);
			goto out;
		}

		ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_DID, ctx->did, key, EM_LEN_KEY_CORE_V20);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to did to esi(0x%08x)\n", ret);
			goto out;
		}

		ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_SHARED_ESI_COUNTER, (uint8_t *)&shared_esi_counter, key, EM_LEN_KEY_CORE_V20);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to add shared esi counter to esi(0x%08x)\n", ret);
			goto out;
		}

		ctx->flags[1] |= EM_FLAGS_1_EXIST_RETURN_ESI;

		LOGI("ESI initialized as sharing");
		goto out;
	}

	ret = em_esi_check_meta(ctx->esi);
	if (ret != EM_SUCCESS) {
		LOGE("Invalid ESI meta\n");
		goto out;
	}

	ret = em_esi_get_item(ctx->esi, EM_TYPE_ESI_ITEM_INIT_STATE, init_state, EM_LEN_ESI_INIT_STATE);
	if (ret != EM_SUCCESS) {
		LOGE("Failed to get esi(0x%08x)\n", ret);
		goto out;
	}

	type_init_state = em_esi_check_init_state(init_state);
	if (type_init_state == (uint8_t)EM_ERR_EM_ESI_CHECK_INIT_STATE_UNKNOWN) {
		LOGE("init state is unknown\n");
		ret = type_init_state;
		goto out;
	}

	if (type_init_state != EM_TYPE_INIT_STATE_COMPLETED) {
		ret = em_esi_get_item(ctx->esi, EM_TYPE_ESI_ITEM_SHARED_KEY, key, EM_LEN_KEY_CORE_V20);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to get key from esi(0x%08x)\n", ret);
			goto out;
		}
	} else {
		if (memcmp(core->magic, EM_MAGIC_EM_CORE, strlen(EM_MAGIC_EM_CORE)) != 0) {
			LOGE("Core is invalid\n");
			ret = EM_ERR_EM_INIT_V20_INVALID_CORE;
			goto out;
		}
	}

	if (type_init_state != EM_TYPE_INIT_STATE_SHARED && type_init_state != EM_TYPE_INIT_STATE_COMPLETED) {
		if (ctx->is_provision) {
			if (memcmp(core->magic, EM_MAGIC_EM_CORE, strlen(EM_MAGIC_EM_CORE)) == 0) {
				LOGE("core isn't normal\n");
				ret = EM_ERR_EM_INIT_V20_ALREADY_SHARED;
				goto out;
			}
		}
	}

	if ((ctx->flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) && (ctx->flags[0] & EM_FLAGS_0_EXIST_DID)) {
		ret = em_esi_get_item(ctx->esi, EM_TYPE_ESI_ITEM_DID, esi_did, EM_LEN_DID);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to get did from esi(0x%08x)\n", ret);
			goto out;
		}
		if (memcmp(esi_did, ctx->did, EM_LEN_DID)) {
			if (!memcmp(esi_did, ctx->did, EM_LEN_DID - 2) || !ctx->is_provision) {
				ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_DID, ctx->did,
							 (type_init_state == EM_TYPE_INIT_STATE_COMPLETED) ? core->key
													   : key,
							 EM_LEN_KEY_CORE_V20);
				if (ret != EM_SUCCESS) {
					LOGE("Failed to add DID(%08x)\n", ret);
					goto out;
				}
				ctx->flags[1] |= EM_FLAGS_1_EXIST_RETURN_ESI;
			}
		}
	}

	LOGI("current status is %s, %02x\n", init_state, type_init_state);
	switch (type_init_state) {
	case EM_TYPE_INIT_STATE_SHARING:
		if (ctx->flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
			if (ctx->is_provision) {
				ret = em_init_make_shared(ctx, core, key);
				if (ret != EM_SUCCESS) {
					LOGE("Failed make shared(0x%08x)\n", ret);
					goto out;
				}
				ctx->flags[1] |= EM_FLAGS_1_WRITE_CORE;
			} else {
				ret =
				    em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_INIT_STATE,
						       (uint8_t *)EM_MAGIC_ESI_V20_PRESHARED, key, EM_LEN_KEY_CORE_V20);
				if (ret != EM_SUCCESS) {
					LOGE("Failed to update init state(0x%08x)\n", ret);
					goto out;
				}
				LOGI("updated status is %s\n", EM_MAGIC_ESI_V20_PRESHARED);
			}
		} else {
			LOGI("Init state is sharing, pass\n");
			ret = EM_SUCCESS;
			goto out;
		}
		break;
	case EM_TYPE_INIT_STATE_SHARED:
		if (ctx->flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
			LOGI("Init state is shared, pass\n");
			ret = EM_SUCCESS;
			goto out;
		} else {
			if (!ctx->is_provision) {
				LOGE("rpmb provision isn't normal\n");
				ret = EM_ERR_EM_INIT_V20_SHARED_BUT_NOT_PROVISION;
				goto out;
			}
			ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_INIT_STATE,
						 (uint8_t *)EM_MAGIC_ESI_V20_COMPLETED, key, EM_LEN_KEY_CORE_V20);
			if (ret != EM_SUCCESS) {
				LOGE("Failed to update init state(0x%08x)\n", ret);
				goto out;
			}

			ret = em_esi_remove_item(20, ctx->esi, EM_TYPE_ESI_ITEM_SHARED_KEY, key, EM_LEN_KEY_CORE_V20);
			if (ret != EM_SUCCESS) {
				LOGE("Failed to remove key from esi(0x%08x)\n", ret);
				goto out;
			}

			ret = em_esi_get_item(ctx->esi, EM_TYPE_ESI_ITEM_IIN, iin, EM_LEN_IIN);
			if (ret == EM_SUCCESS) {
				memcpy(core->iin, iin, EM_LEN_IIN);
				memset(iin, 0, EM_LEN_IIN);
				ret = em_esi_remove_item(20, ctx->esi, EM_TYPE_ESI_ITEM_IIN, key, EM_LEN_KEY_CORE_V20);
				if (ret != EM_SUCCESS) {
					LOGE("Failed to remove iin from esi(0x%08x)\n", ret);
					goto out;
				}
			}

			em_esi_read_meta(&meta, ctx->esi);
			memcpy(core->magic, EM_MAGIC_EM_CORE, strlen(EM_MAGIC_EM_CORE));
			memcpy(core->key, key, EM_LEN_KEY_CORE_V20);
			core->esi_ctr = meta.write_counter + 1;
			core->flags |= EM_CORE_FLAG_INIT_FLAG;
			meta.write_counter += 1;

			ret = em_esi_update(20, ctx->esi, &meta, NULL, key, EM_LEN_KEY_CORE_V20);
			if (ret != EM_SUCCESS) {
				LOGE("Failed to update esi(0x%08x)\n", ret);
				goto out;
			}

			ctx->flags[1] |= EM_FLAGS_1_WRITE_CORE;
		}
		break;
	case EM_TYPE_INIT_STATE_PRESHARED:
		if (ctx->flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
			if (!ctx->is_provision) {
				LOGI("init state is preshared, pass\n");
				ret = EM_SUCCESS;
				goto out;
			}

			ret = em_init_make_shared(ctx, core, key);
			if (ret != EM_SUCCESS) {
				LOGE("Failed to make shared(0x%08x)\n", ret);
				goto out;
			}
			ctx->flags[1] |= EM_FLAGS_1_WRITE_CORE;
		} else {
			ret = em_esi_update_item(20, ctx->esi, EM_TYPE_ESI_ITEM_INIT_STATE,
						 (const uint8_t *)EM_MAGIC_ESI_V20_INTERMEDIATE, key,
						 EM_LEN_KEY_CORE_V20);
			if (ret != EM_SUCCESS) {
				LOGE("Failed to update init state(0x%08x)\n", ret);
				goto out;
			}
		}
		break;
	case EM_TYPE_INIT_STATE_INTERMEDIATE:
		if (!ctx->is_provision) {
			LOGI("init state is intermediate, pass\n");
			ret = EM_SUCCESS;
			goto out;
		} else {
			if (ctx->flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
				ret = em_init_make_shared(ctx, core, key);
				if (ret != EM_SUCCESS) {
					LOGE("Failed to make shared(0x%08x)\n", ret);
					goto out;
				}
				ctx->flags[1] |= EM_FLAGS_1_WRITE_CORE;
			}
		}
		break;
	case EM_TYPE_INIT_STATE_COMPLETED:
		if (ctx->is_provision && !(core->flags & EM_CORE_FLAG_INIT_FLAG)) {
			LOGW("Add core init flag\n");
			core->flags |= EM_CORE_FLAG_INIT_FLAG;
			ctx->flags[1] |= EM_FLAGS_1_WRITE_CORE;
		}
		LOGI("init state is completed, pass\n");
		ret = EM_SUCCESS; // Don't update esi to steady;
		goto out;
	}

	// TODO CHECK
	ctx->flags[1] |= EM_FLAGS_1_EXIST_RETURN_ESI;

	ret = EM_SUCCESS;
out:
	core = NULL;

	memset(key, 0, EM_LEN_KEY_CORE_V20);
	memset(iin, 0, EM_LEN_IIN);

	return ret;
}
