/*
 * Copyright (c) 2018-2019 Jan Van Winkel <jan.van_winkel@dxplore.eu>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <lvgl.h>
#include <zephyr/kernel.h>
#include <zephyr/fs/fs.h>
#include "lvgl_fs.h"
#include "lv_conf.h"
#include LV_MEM_CUSTOM_INCLUDE

static bool lvgl_fs_ready(struct _lv_fs_drv_t *drv)
{
	return true;
}

static lv_fs_res_t errno_to_lv_fs_res(int err)
{
	switch (err) {
	case 0:
		return LV_FS_RES_OK;
	case -EIO:
		/*Low level hardware error*/
		return LV_FS_RES_HW_ERR;
	case -EBADF:
		/*Error in the file system structure */
		return LV_FS_RES_FS_ERR;
	case -ENOENT:
		/*Driver, file or directory is not exists*/
		return LV_FS_RES_NOT_EX;
	case -EFBIG:
		/*Disk full*/
		return LV_FS_RES_FULL;
	case -EACCES:
		/*Access denied. Check 'fs_open' modes and write protect*/
		return LV_FS_RES_DENIED;
	case -EBUSY:
		/*The file system now can't handle it, try later*/
		return LV_FS_RES_BUSY;
	case -ENOMEM:
		/*Not enough memory for an internal operation*/
		return LV_FS_RES_OUT_OF_MEM;
	case -EINVAL:
		/*Invalid parameter among arguments*/
		return LV_FS_RES_INV_PARAM;
	case -ENOTSUP:
		/*Not supported by the filesystem*/
		return LV_FS_RES_NOT_IMP;
	default:
		return LV_FS_RES_UNKNOWN;
	}
}

static void *lvgl_fs_open(struct _lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode)
{
	int err;
	int zmode = FS_O_CREATE;
	void *file;

	/* LVGL is passing absolute paths without the root slash add it back
	 * by decrementing the path pointer.
	 */
	path--;

	zmode |= (mode & LV_FS_MODE_WR) ? FS_O_WRITE : 0;
	zmode |= (mode & LV_FS_MODE_RD) ? FS_O_READ : 0;

	file = LV_MEM_CUSTOM_ALLOC(sizeof(struct fs_file_t));
	if (!file) {
		return NULL;
	}

	fs_file_t_init((struct fs_file_t *)file);

	err = fs_open((struct fs_file_t *)file, path, zmode);
	if (err) {
		LV_MEM_CUSTOM_FREE(file);
		return NULL;
	}

	return file;
}

static lv_fs_res_t lvgl_fs_close(struct _lv_fs_drv_t *drv, void *file)
{
	int err;

	err = fs_close((struct fs_file_t *)file);
	return errno_to_lv_fs_res(err);
}

static lv_fs_res_t lvgl_fs_read(struct _lv_fs_drv_t *drv, void *file, void *buf, uint32_t btr,
				uint32_t *br)
{
	int err;

	err = fs_read((struct fs_file_t *)file, buf, btr);
	if (err > 0) {
		if (br != NULL) {
			*br = err;
		}
		err = 0;
	} else if (br != NULL) {
		*br = 0U;
	}
	return errno_to_lv_fs_res(err);
}

static lv_fs_res_t lvgl_fs_write(struct _lv_fs_drv_t *drv, void *file, const void *buf,
				 uint32_t btw, uint32_t *bw)
{
	int err;

	err = fs_write((struct fs_file_t *)file, buf, btw);
	if (err == btw) {
		if (bw != NULL) {
			*bw = btw;
		}
		err = 0;
	} else if (err < 0) {
		if (bw != NULL) {
			*bw = 0U;
		}
	} else {
		if (bw != NULL) {
			*bw = err;
		}
		err = -EFBIG;
	}
	return errno_to_lv_fs_res(err);
}

static lv_fs_res_t lvgl_fs_seek(struct _lv_fs_drv_t *drv, void *file, uint32_t pos,
				lv_fs_whence_t whence)
{
	int err, fs_whence;

	switch (whence) {
	case LV_FS_SEEK_END:
		fs_whence = FS_SEEK_END;
		break;
	case LV_FS_SEEK_CUR:
		fs_whence = FS_SEEK_CUR;
		break;
	case LV_FS_SEEK_SET:
	default:
		fs_whence = FS_SEEK_SET;
		break;
	}

	err = fs_seek((struct fs_file_t *)file, pos, fs_whence);
	return errno_to_lv_fs_res(err);
}

static lv_fs_res_t lvgl_fs_tell(struct _lv_fs_drv_t *drv, void *file, uint32_t *pos_p)
{
	off_t pos;

	pos = fs_tell((struct fs_file_t *)file);
	if (pos < 0) {
		return errno_to_lv_fs_res(pos);
	}

	*pos_p = pos;
	return LV_FS_RES_OK;
}

static void *lvgl_fs_dir_open(struct _lv_fs_drv_t *drv, const char *path)
{
	void *dir;
	int err;

	/* LVGL is passing absolute paths without the root slash add it back
	 * by decrementing the path pointer.
	 */
	path--;

	dir = LV_MEM_CUSTOM_ALLOC(sizeof(struct fs_dir_t));
	if (!dir) {
		return NULL;
	}

	fs_dir_t_init((struct fs_dir_t *)dir);
	err = fs_opendir((struct fs_dir_t *)dir, path);
	if (err) {
		LV_MEM_CUSTOM_FREE(dir);
		return NULL;
	}

	return dir;
}

static lv_fs_res_t lvgl_fs_dir_read(struct _lv_fs_drv_t *drv, void *dir, char *fn)
{
	/* LVGL expects a string as return parameter but the format of the
	 * string is not documented.
	 */
	return LV_FS_RES_NOT_IMP;
}

static lv_fs_res_t lvgl_fs_dir_close(struct _lv_fs_drv_t *drv, void *dir)
{
	int err;

	err = fs_closedir((struct fs_dir_t *)dir);
	LV_MEM_CUSTOM_FREE(dir);
	return errno_to_lv_fs_res(err);
}

static lv_fs_drv_t fs_drv;

void lvgl_fs_init(void)
{
	lv_fs_drv_init(&fs_drv);

	/* LVGL uses letter based mount points, just pass the root slash as a
	 * letter. Note that LVGL will remove the drive letter, or in this case
	 * the root slash, from the path passed via the FS callbacks.
	 * Zephyr FS API assumes this slash is present so we will need to add
	 * it back.
	 */
	fs_drv.letter = '/';
	fs_drv.ready_cb = lvgl_fs_ready;

	fs_drv.open_cb = lvgl_fs_open;
	fs_drv.close_cb = lvgl_fs_close;
	fs_drv.read_cb = lvgl_fs_read;
	fs_drv.write_cb = lvgl_fs_write;
	fs_drv.seek_cb = lvgl_fs_seek;
	fs_drv.tell_cb = lvgl_fs_tell;

	fs_drv.dir_open_cb = lvgl_fs_dir_open;
	fs_drv.dir_read_cb = lvgl_fs_dir_read;
	fs_drv.dir_close_cb = lvgl_fs_dir_close;

	lv_fs_drv_register(&fs_drv);
}
