#include "em_common.h"

static int em_init_make_shared(uint8_t *esi_v20, 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, esi_v20, 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;
	}

	memcpy(&meta, esi_v20 + EM_LEN_ESI_DIGEST, sizeof(em_esi_meta));
	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;
	meta.write_counter += 1;

	ret = em_esi_get_item(esi_v20, 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, esi_v20, 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;
		}
	}

	ret = em_esi_update(20, esi_v20, &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_write_core((uint8_t *)core, sizeof(em_core_v20));
	if (ret != EM_SUCCESS) {
		LOGE("Failed to write core(0x%08x)\n", ret);
		goto out;
	}

	ret = EM_SUCCESS;
out:
	memset(iin, 0, EM_LEN_IIN);
	return ret;
}

int em_init_v20(uint8_t *esi_v20, uint64_t *flags, const uint8_t *did, const uint32_t is_provision) {
	int ret;

	uint8_t em_version = 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,};

	em_core_v20 *core = NULL;
	em_esi_meta meta = {0,};

	EM_CHECK_NULL(__func__, EM_ERR_EM_INIT_V20, esi_v20, did);

	if (!(flags[0] & EM_FLAGS_0_EXIST_ESI)) {
		LOGE("esi isn't exists\n");
		ret = EM_ERR_EM_INIT_V20_ESI;
		goto out;
	}

	if (is_provision) {
		core = (em_core_v20 *)em_calloc(sizeof(em_core_v20), 1);
		if (core == NULL) {
			LOGE("Failed to allocate core v20\n");
			ret = EM_ERR_EM_INIT_V20_CORE;
			goto out;
		}

		ret = em_read_core((uint8_t *)core, sizeof(em_core_v20));
		if (ret != EM_SUCCESS) {
			if ((uint32_t)ret == EM_ERR_EM_READ_CORE_ALL_ZERO) {
				LOGI("Core is all zero(0x%08x)\n", ret);
			} else {
				LOGE("Failed to read core(0x%08x)\n", ret);
				goto out;
			}
		}
	}

	if (em_is_all_zero(esi_v20, EM_LEN_ESI) == EM_SUCCESS) {
		if (core != NULL) {
			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;
				flags[1] |= EM_FLAGS_1_NEED_RECOVERY_ESI;
				goto out;
			}
		}

		ret = em_esi_initialize(20, esi_v20, 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, esi_v20, 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, esi_v20, 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, esi_v20, EM_TYPE_ESI_ITEM_DID, did, key, EM_LEN_KEY_CORE_V20);
		if (ret != EM_SUCCESS) {
			LOGE("Failed to did to esi(0x%08x)\n", ret);
			goto out;
		}

		flags[1] |= EM_FLAGS_1_EXIST_RETURN_ESI;

		goto out;
	}

	// TODO Check validation
	ret = em_esi_get_item(esi_v20, 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(esi_v20, 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;
		}
	}

	if (type_init_state != EM_TYPE_INIT_STATE_SHARED && type_init_state != EM_TYPE_INIT_STATE_COMPLETED) {
		if (is_provision) {
			if (memcmp(core->magic, EM_MAGIC_EM_CORE, strlen(EM_MAGIC_EM_CORE)) == 0) {
				LOGE("esi isn't normal\n");
				ret = EM_ERR_EM_INIT_V20_ALREADY_SHARED;
				goto out;
			}
		}
	}

	switch (type_init_state) {
	case EM_TYPE_INIT_STATE_SHARING:
		if (flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
			if (is_provision) {
				ret = em_init_make_shared(esi_v20, core, key);
				if (ret != EM_SUCCESS) {
					LOGE("Failed make shared(0x%08x)\n", ret);
					goto out;
				}
			} else {
				ret =
				    em_esi_update_item(20, esi_v20, 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;
				}
			}
		} else {
			LOGI("Init state is sharing, pass\n");
		}
		break;
	case EM_TYPE_INIT_STATE_SHARED:
		if (flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
			LOGI("Init state is shared, pass\n");
		} else {
			if (!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, esi_v20, 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, esi_v20, 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(esi_v20, 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, esi_v20, 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;
				}
			}

			memcpy(&meta, esi_v20 + EM_LEN_ESI_DIGEST, sizeof(em_esi_meta));
			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;
			meta.write_counter += 1;

			ret = em_esi_update(20, esi_v20, &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_write_core((uint8_t *)core, sizeof(em_core_v20));
			if (ret != EM_SUCCESS) {
				LOGE("Failed to write core(0x%08x)\n", ret);
				goto out;
			}
		}
		break;
	case EM_TYPE_INIT_STATE_PRESHARED:
		if (flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
			if (!is_provision) {
				LOGI("init state is preshared, pass\n");
				break;
			}

			ret = em_init_make_shared(esi_v20, core, key);
			if (ret != EM_SUCCESS) {
				LOGE("Failed to make shared(0x%08x)\n", ret);
				goto out;
			}
		} else {
			ret = em_esi_update_item(20, esi_v20, 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 (!is_provision) {
			LOGI("init state is intermediate, pass\n");
		} else {
			if (flags[0] & EM_FLAGS_0_EXIST_BOOTLOADER) {
				ret = em_init_make_shared(esi_v20, core, key);
				if (ret != EM_SUCCESS) {
					LOGE("Failed to make shared(0x%08x)\n", ret);
					goto out;
				}
			}
		}
		break;
	case EM_TYPE_INIT_STATE_COMPLETED:
		LOGI("init state is completed, pass\n");
		ret = EM_SUCCESS; // Don't update esi to steady;
		goto out;
	}

	// TODO CHECK
	flags[1] |= EM_FLAGS_1_EXIST_RETURN_ESI;

	ret = EM_SUCCESS;
out:
	memset(key, 0, EM_LEN_KEY_CORE_V20);
	memset(iin, 0, EM_LEN_IIN);

	if (core) {
		memset(core, 0, sizeof(em_core_v20));
		em_free(core);
		core = NULL;
	}

	return ret;
}
