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

#include <stddef.h>
#include <string.h>

#include <fs/fcb.h>
#include "fcb_priv.h"

static struct flash_sector *
fcb_new_sector(struct fcb *fcb, int cnt)
{
	struct flash_sector *prev;
	struct flash_sector *cur;
	int i;

	prev = NULL;
	i = 0;
	cur = fcb->f_active.fe_sector;
	do {
		cur = fcb_getnext_sector(fcb, cur);
		if (!prev) {
			prev = cur;
		}
		if (cur == fcb->f_oldest) {
			return NULL;
		}
	} while (i++ < cnt);
	return prev;
}

/*
 * Take one of the scratch blocks into use, if at all possible.
 */
int
fcb_append_to_scratch(struct fcb *fcb)
{
	struct flash_sector *sector;
	int rc;

	sector = fcb_new_sector(fcb, 0);
	if (!sector) {
		return -ENOSPC;
	}
	rc = fcb_sector_hdr_init(fcb, sector, fcb->f_active_id + 1);
	if (rc) {
		return rc;
	}
	fcb->f_active.fe_sector = sector;
	fcb->f_active.fe_elem_off = sizeof(struct fcb_disk_area);
	fcb->f_active_id++;
	return 0;
}

int
fcb_append(struct fcb *fcb, uint16_t len, struct fcb_entry *append_loc)
{
	struct flash_sector *sector;
	struct fcb_entry *active;
	int cnt;
	int rc;
	uint8_t tmp_str[8];

	cnt = fcb_put_len(fcb, tmp_str, len);
	if (cnt < 0) {
		return cnt;
	}
	cnt = fcb_len_in_flash(fcb, cnt);
	len = fcb_len_in_flash(fcb, len) + fcb_len_in_flash(fcb, FCB_CRC_SZ);

	__ASSERT_NO_MSG(cnt <= sizeof(tmp_str));

	rc = k_mutex_lock(&fcb->f_mtx, K_FOREVER);
	if (rc) {
		return -EINVAL;
	}
	active = &fcb->f_active;
	if (active->fe_elem_off + len + cnt > active->fe_sector->fs_size) {
		sector = fcb_new_sector(fcb, fcb->f_scratch_cnt);
		if (!sector || (sector->fs_size <
			sizeof(struct fcb_disk_area) + len + cnt)) {
			rc = -ENOSPC;
			goto err;
		}
		rc = fcb_sector_hdr_init(fcb, sector, fcb->f_active_id + 1);
		if (rc) {
			goto err;
		}
		fcb->f_active.fe_sector = sector;
		fcb->f_active.fe_elem_off = sizeof(struct fcb_disk_area);
		fcb->f_active_id++;
	}

	rc = fcb_flash_write(fcb, active->fe_sector, active->fe_elem_off, tmp_str, cnt);
	if (rc) {
		rc = -EIO;
		goto err;
	}
	append_loc->fe_sector = active->fe_sector;
	append_loc->fe_elem_off = active->fe_elem_off;
	append_loc->fe_data_off = active->fe_elem_off + cnt;

	active->fe_elem_off = append_loc->fe_data_off + len;

	k_mutex_unlock(&fcb->f_mtx);

	return 0;
err:
	k_mutex_unlock(&fcb->f_mtx);
	return rc;
}

int
fcb_append_finish(struct fcb *fcb, struct fcb_entry *loc)
{
	int rc;
	uint8_t crc8[fcb->f_align];
	off_t off;

	(void)memset(crc8, 0xFF, sizeof(crc8));

	rc = fcb_elem_crc8(fcb, loc, &crc8[0]);
	if (rc) {
		return rc;
	}
	off = loc->fe_data_off + fcb_len_in_flash(fcb, loc->fe_data_len);

	rc = fcb_flash_write(fcb, loc->fe_sector, off, crc8, fcb->f_align);
	if (rc) {
		return -EIO;
	}
	return 0;
}
