/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 */

#define pr_fmt(fmt) "barebox-ratpfs: " fmt

#include <common.h>
#include <command.h>
#include <init.h>
#include <malloc.h>
#include <fs.h>
#include <errno.h>
#include <linux/stat.h>
#include <asm/unaligned.h>
#include <ratp_bb.h>

#define RATPFS_TYPE_MOUNT_CALL       1
#define RATPFS_TYPE_MOUNT_RETURN     2
#define RATPFS_TYPE_READDIR_CALL     3
#define RATPFS_TYPE_READDIR_RETURN   4
#define RATPFS_TYPE_STAT_CALL        5
#define RATPFS_TYPE_STAT_RETURN      6
#define RATPFS_TYPE_OPEN_CALL        7
#define RATPFS_TYPE_OPEN_RETURN      8
#define RATPFS_TYPE_READ_CALL        9
#define RATPFS_TYPE_READ_RETURN     10
#define RATPFS_TYPE_WRITE_CALL      11
#define RATPFS_TYPE_WRITE_RETURN    12
#define RATPFS_TYPE_CLOSE_CALL      13
#define RATPFS_TYPE_CLOSE_RETURN    14
#define RATPFS_TYPE_TRUNCATE_CALL   15
#define RATPFS_TYPE_TRUNCATE_RETURN 16

struct ratpfs_file {
	uint32_t handle;
};

struct ratpfs_dir {
	char *entries;
	int len, off;
	DIR dir;
};

static int ratpfs_create(struct device_d __always_unused *dev,
			const char __always_unused *pathname,
			mode_t __always_unused mode)
{
	pr_debug("%s\n", __func__);

	return 0;
}

static int ratpfs_mkdir(struct device_d __always_unused *dev,
		       const char __always_unused *pathname)
{
	pr_debug("%s\n", __func__);

	return -ENOSYS;
}

static int ratpfs_rm(struct device_d __always_unused *dev,
		    const char *pathname)
{
	pr_debug("%s\n", __func__);

	/* Get rid of leading '/' */
	pathname = &pathname[1];

	return 0;
}

static int ratpfs_truncate(struct device_d __always_unused *dev,
			  FILE *f, loff_t size)
{
	int len_tx = 1 /* type */
		+ 4 /* handle */
		+ 4 /* size */;
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx)+len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	struct ratpfs_file *rfile = f->priv;
	int ret;

	pr_debug("%s: len_tx=%i handle=%i size=%i\n", __func__,
		 len_tx, rfile->handle, (int)size);

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_TRUNCATE_CALL;
	put_unaligned_be32(rfile->handle, &pkt_tx->data[1]);
	put_unaligned_be32(size, &pkt_tx->data[5]);

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (ret) {
		ret = -EIO;
		goto out;
	}

	pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len);

	if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_TRUNCATE_RETURN) {
		pr_err("invalid truncate response\n");
		ret = -EIO;
		goto out;
	}

out:
	free(pkt_rx);
	return ret;
}

static int ratpfs_open(struct device_d __always_unused *dev,
		      FILE *file, const char *filename)
{
	int len_name = strlen(filename);
	int len_tx = 1 /* type */
		+ 4 /* flags */
		+ len_name /* path */;
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	struct ratpfs_file *rfile = xzalloc(sizeof(*rfile));
	int ret;

	pr_debug("%s: len_tx=%i filename='%s'\n", __func__, len_tx, filename);

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_OPEN_CALL;
	put_unaligned_be32(file->flags, &pkt_tx->data[1]);
	memcpy(&pkt_tx->data[5], filename, len_name);

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (ret) {
		ret = -EIO;
		goto err;
	}

	pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len);
	if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_OPEN_RETURN) {
		pr_err("invalid open response\n");
		ret = -EIO;
		goto err;
	}
	rfile->handle = get_unaligned_be32(&pkt_rx->data[1]);
	if (rfile->handle == 0) {
		ret = -get_unaligned_be32(&pkt_rx->data[5]); /* errno */
		goto err;
	}
	file->priv = rfile;
	file->size = get_unaligned_be32(&pkt_rx->data[5]);

	goto out;

err:
	file->priv = NULL;
	free(rfile);
out:
	free(pkt_rx);
	return ret;
}

static int ratpfs_close(struct device_d __always_unused *dev,
			FILE *f)
{
	int len_tx = 1 /* type */
		+ 4 /* handle */;
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	struct ratpfs_file *rfile = f->priv;
	int ret;

	pr_debug("%s: len_tx=%i handle=%i\n", __func__,
		 len_tx, rfile->handle);

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_CLOSE_CALL;
	put_unaligned_be32(rfile->handle, &pkt_tx->data[1]);

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (ret) {
		ret = -EIO;
		goto out;
	}

	pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len);

	if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_CLOSE_RETURN) {
		pr_err("invalid close response\n");
		goto out;
	}

out:
	free(pkt_rx);
	return ret;
}

static int ratpfs_write(struct device_d __always_unused *dev,
			FILE *f, const void *buf, size_t orig_size)
{
	int size = min((int)orig_size, 4096);
	int len_tx = 1 /* type */
		+ 4 /* handle */
		+ 4 /* pos */
		+ size /* data */;
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	struct ratpfs_file *rfile = f->priv;
	int ret;

	pr_debug("%s: len_tx=%i handle=%i pos=%i size=%i\n", __func__,
		 len_tx, rfile->handle, (int)f->pos, size);

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_WRITE_CALL;
	put_unaligned_be32(rfile->handle, &pkt_tx->data[1]);
	put_unaligned_be32(f->pos, &pkt_tx->data[5]);
	memcpy(&pkt_tx->data[9], buf, size);

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (ret) {
		ret = -EIO;
		goto out;
	}

	pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len);

	if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_WRITE_RETURN) {
		pr_err("invalid write response\n");
		ret = -EIO;
		goto out;
	}

	ret = size;
out:
	free(pkt_rx);

	return ret;
}

static int ratpfs_read(struct device_d __always_unused *dev,
		       FILE *f, void *buf, size_t orig_size)
{
	int size = min((int)orig_size, 4096);
	int len_tx = 1 /* type */
		+ 4 /* handle */
		+ 4 /* pos */
		+ 4 /* size */;
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	struct ratpfs_file *rfile = f->priv;
	int ret;

	pr_debug("%s: len_tx=%i handle=%i pos=%i size=%i\n", __func__,
		 len_tx, rfile->handle, (int)f->pos, size);

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_READ_CALL;
	put_unaligned_be32(rfile->handle, &pkt_tx->data[1]);
	put_unaligned_be32(f->pos, &pkt_tx->data[5]);
	put_unaligned_be32(size, &pkt_tx->data[9]);

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (ret) {
		ret = -EIO;
		goto out;
	}

	pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len);
	if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_READ_RETURN) {
		pr_err("invalid read response\n");
		ret = -EIO;
		goto out;
	}
	size = pkt_rx->len - 1;
	memcpy(buf, &pkt_rx->data[1], size);
	ret = size;

out:
	free(pkt_rx);
	return ret;
}

static DIR* ratpfs_opendir(struct device_d __always_unused *dev,
			  const char *pathname)
{
	int len_name = strlen(pathname);
	int len_tx = 1 /* type */
		+ len_name /* path */;
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx)+len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	struct ratpfs_dir *rdir = xzalloc(sizeof(*rdir));
	int ret;

	pr_debug("%s: len_tx=%i pathname='%s'\n", __func__, len_tx, pathname);

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_READDIR_CALL;
	memcpy(&pkt_tx->data[1], pathname, len_name);

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (!ret) {
		pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len);
		if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_READDIR_RETURN) {
			pr_err("invalid readdir response\n");
			free(pkt_rx);
			return NULL;
		}
		rdir->len = pkt_rx->len - 1;
		rdir->entries = xmemdup(&pkt_rx->data[1], rdir->len);
		free(pkt_rx);
		return &rdir->dir;
	} else {
		return NULL;
	}
}

static struct dirent *ratpfs_readdir(struct device_d *dev, DIR *dir)
{
	struct ratpfs_dir *rdir = container_of(dir, struct ratpfs_dir, dir);
	int i;

	pr_debug("%s\n", __func__);

	if (rdir->len <= rdir->off)
		return NULL;

	for (i = 0; rdir->off < rdir->len; rdir->off++, i++) {
		dir->d.d_name[i] = rdir->entries[rdir->off];
		if (dir->d.d_name[i] == 0)
			break;
	}
	rdir->off++;

	return &dir->d;
}

static int ratpfs_closedir(struct device_d *dev, DIR *dir)
{
	struct ratpfs_dir *rdir = container_of(dir, struct ratpfs_dir, dir);

	pr_debug("%s\n", __func__);

	free(rdir->entries);
	free(rdir);

	return 0;
}

static int ratpfs_stat(struct device_d __always_unused *dev,
		      const char *filename, struct stat *s)
{
	int len_name = strlen(filename);
	int len_tx = 1 /* type */
		+ len_name; /* path */
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	int ret;

	pr_debug("%s: len_tx=%i filename='%s'\n", __func__, len_tx, filename);

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_STAT_CALL;
	memcpy(&pkt_tx->data[1], filename, len_name);

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (ret) {
		ret = -EIO;
		goto out;
	}

	pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len);
	if (pkt_rx->len < 6 || pkt_rx->data[0] != RATPFS_TYPE_STAT_RETURN) {
		pr_err("invalid stat response\n");
		goto out;
	}
	switch (pkt_rx->data[1]) {
	case 0:
		ret = -ENOENT;
		break;
	case 1:
		s->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
		break;
	case 2:
		s->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
		break;
	}
	s->st_size = get_unaligned_be32(&pkt_rx->data[2]);

out:
	free(pkt_rx);
	return ret;
}

static int ratpfs_probe(struct device_d *dev)
{
	int len_tx = 1; /* type */
	struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx);
	struct ratp_bb_pkt *pkt_rx = NULL;
	int ret;
	struct fs_device_d *fsdev = dev_to_fs_device(dev);

	pr_debug("%s\n", __func__);

	ret = barebox_ratp_fs_mount(fsdev->path);
	if (ret)
		return ret;

	pkt_tx->len = len_tx;
	pkt_tx->data[0] = RATPFS_TYPE_MOUNT_CALL;

	ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx);
	if (ret)
		goto out;

	if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_MOUNT_RETURN) {
		pr_err("invalid mount response\n");
		ret = -EINVAL;
		goto out;
	}

out:
	free(pkt_rx);

	if (ret)
		barebox_ratp_fs_mount(NULL);

	return ret;
}

static void ratpfs_remove(struct device_d __always_unused *dev)
{
	pr_debug("%s\n", __func__);

	barebox_ratp_fs_mount(NULL);
}

static struct fs_driver_d ratpfs_driver = {
	.open      = ratpfs_open,
	.close     = ratpfs_close,
	.read      = ratpfs_read,
	.opendir   = ratpfs_opendir,
	.readdir   = ratpfs_readdir,
	.closedir  = ratpfs_closedir,
	.stat      = ratpfs_stat,
	.create    = ratpfs_create,
	.unlink    = ratpfs_rm,
	.mkdir     = ratpfs_mkdir,
	.rmdir     = ratpfs_rm,
	.write     = ratpfs_write,
	.truncate  = ratpfs_truncate,
	.flags     = FS_DRIVER_NO_DEV,
	.drv = {
		.probe  = ratpfs_probe,
		.remove = ratpfs_remove,
		.name = "ratpfs",
	}
};

static int ratpfs_init(void)
{
	return register_fs_driver(&ratpfs_driver);
}
coredevice_initcall(ratpfs_init);