/*
 * Copyright (c) 2020 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "zcbor_common.h"

_Static_assert((sizeof(size_t) == sizeof(void *)),
	"This code needs size_t to be the same length as pointers.");

_Static_assert((sizeof(zcbor_state_t) >= sizeof(struct zcbor_state_constant)),
	"This code needs zcbor_state_t to be at least as large as zcbor_backups_t.");

bool zcbor_new_backup(zcbor_state_t *state, uint_fast32_t new_elem_count)
{
	ZCBOR_CHECK_ERROR();

	if ((state->constant_state->current_backup)
		>= state->constant_state->num_backups) {
		ZCBOR_ERR(ZCBOR_ERR_NO_BACKUP_MEM);
	}

	state->payload_moved = false;

	(state->constant_state->current_backup)++;

	/* use the backup at current_backup - 1, since otherwise, the 0th
	 * backup would be unused. */
	uint_fast32_t i = (state->constant_state->current_backup) - 1;

	memcpy(&state->constant_state->backup_list[i], state,
		sizeof(zcbor_state_t));

	state->elem_count = new_elem_count;

	return true;
}


bool zcbor_process_backup(zcbor_state_t *state, uint32_t flags,
		uint_fast32_t max_elem_count)
{
	const uint8_t *payload = state->payload;
	const uint_fast32_t elem_count = state->elem_count;

	ZCBOR_CHECK_ERROR();

	if (state->constant_state->current_backup == 0) {
		zcbor_print("No backups available.\r\n");
		ZCBOR_ERR(ZCBOR_ERR_NO_BACKUP_ACTIVE);
	}

	if (flags & ZCBOR_FLAG_RESTORE) {
		/* use the backup at current_backup - 1, since otherwise, the
		 * 0th backup would be unused. */
		uint_fast32_t i = state->constant_state->current_backup - 1;

		if (!(flags & ZCBOR_FLAG_TRANSFER_PAYLOAD)) {
			if (state->constant_state->backup_list[i].payload_moved) {
				zcbor_print("Payload pointer out of date.\r\n");
				ZCBOR_FAIL();
			}
		}
		memcpy(state, &state->constant_state->backup_list[i],
			sizeof(zcbor_state_t));
	}

	if (flags & ZCBOR_FLAG_CONSUME) {
		state->constant_state->current_backup--;
	}

	if (elem_count > max_elem_count) {
		zcbor_print("elem_count: %" PRIuFAST32 " (expected max %" PRIuFAST32 ")\r\n",
			elem_count, max_elem_count);
		ZCBOR_ERR(ZCBOR_ERR_HIGH_ELEM_COUNT);
	}

	if (flags & ZCBOR_FLAG_TRANSFER_PAYLOAD) {
		state->payload = payload;
	}

	return true;
}

static void update_backups(zcbor_state_t *state, uint8_t const *new_payload_end)
{
	if (state->constant_state) {
		for (int i = 0; i < state->constant_state->current_backup; i++) {
			state->constant_state->backup_list[i].payload_end = new_payload_end;
			state->constant_state->backup_list[i].payload_moved = true;
		}
	}
}


bool zcbor_union_start_code(zcbor_state_t *state)
{
	if (!zcbor_new_backup(state, state->elem_count)) {
		ZCBOR_FAIL();
	}
	return true;
}


bool zcbor_union_elem_code(zcbor_state_t *state)
{
	if (!zcbor_process_backup(state, ZCBOR_FLAG_RESTORE, state->elem_count)) {
		ZCBOR_FAIL();
	}
	return true;
}

bool zcbor_union_end_code(zcbor_state_t *state)
{
	if (!zcbor_process_backup(state, ZCBOR_FLAG_CONSUME, state->elem_count)) {
		ZCBOR_FAIL();
	}
	return true;
}

void zcbor_new_state(zcbor_state_t *state_array, uint_fast32_t n_states,
		const uint8_t *payload, size_t payload_len, uint_fast32_t elem_count)
{
	state_array[0].payload = payload;
	state_array[0].payload_end = payload + payload_len;
	state_array[0].elem_count = elem_count;
	state_array[0].indefinite_length_array = false;
	state_array[0].payload_moved = false;
	state_array[0].constant_state = NULL;

	if(n_states < 2) {
		return;
	}

	/* Use the last state as a struct zcbor_state_constant object. */
	state_array[0].constant_state = (struct zcbor_state_constant *)&state_array[n_states - 1];
	state_array[0].constant_state->backup_list = NULL;
	state_array[0].constant_state->num_backups = n_states - 2;
	state_array[0].constant_state->current_backup = 0;
	state_array[0].constant_state->error = ZCBOR_SUCCESS;
#ifdef ZCBOR_STOP_ON_ERROR
	state_array[0].constant_state->stop_on_error = false;
#endif
	if (n_states > 2) {
		state_array[0].constant_state->backup_list = &state_array[1];
	}
}

void zcbor_update_state(zcbor_state_t *state,
		const uint8_t *payload, size_t payload_len)
{
	state->payload = payload;
	state->payload_end = payload + payload_len;

	update_backups(state, state->payload_end);
}


bool zcbor_validate_string_fragments(struct zcbor_string_fragment *fragments,
		uint_fast32_t num_fragments)
{
	size_t total_len = 0;

	if (fragments == NULL) {
		return false;
	}

	for (uint_fast32_t i = 0; i < num_fragments; i++) {
		if (fragments[i].offset != total_len) {
			return false;
		}
		if (fragments[i].fragment.value == NULL) {
			return false;
		}
		if (fragments[i].total_len != fragments[0].total_len) {
			return false;
		}
		total_len += fragments[i].fragment.len;
		if (total_len > fragments[0].total_len) {
			return false;
		}
	}

	if (num_fragments && total_len != fragments[0].total_len) {
		return false;
	}

	if (num_fragments && (fragments[0].total_len == ZCBOR_STRING_FRAGMENT_UNKNOWN_LENGTH)) {
		for (uint_fast32_t i = 0; i < num_fragments; i++) {
			fragments[i].total_len = total_len;
		}
	}

	return true;
}

bool zcbor_splice_string_fragments(struct zcbor_string_fragment *fragments,
		uint_fast32_t num_fragments, uint8_t *result, size_t *result_len)
{
	size_t total_len = 0;

	if (!fragments) {
		return false;
	}

	for (uint_fast32_t i = 0; i < num_fragments; i++) {
		if ((total_len > *result_len)
			|| (fragments[i].fragment.len > (*result_len - total_len))) {
			return false;
		}
		memcpy(&result[total_len],
			fragments[i].fragment.value, fragments[i].fragment.len);
		total_len += fragments[i].fragment.len;
	}

	*result_len = total_len;
	return true;
}
