/*
 * File      : dfs_ramfs.c
 * This file is part of Device File System in RT-Thread RTOS
 * COPYRIGHT (C) 2004-2013, RT-Thread Development Team
 *
 *  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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Change Logs:
 * Date           Author       Notes
 * 2013-04-15     Bernard      the first version
 * 2013-05-05     Bernard      remove CRC for ramfs persistence
 * 2013-05-22     Bernard      fix the no entry issue.
 */

#include <rtthread.h>
#include <dfs.h>
#include <dfs_fs.h>
#include <dfs_file.h>

#include "dfs_ramfs.h"

int dfs_ramfs_mount(struct dfs_filesystem *fs,
                    unsigned long          rwflag,
                    const void            *data)
{
    struct dfs_ramfs* ramfs;

    if (data == NULL)
        return -EIO;

    ramfs = (struct dfs_ramfs *)data;
    fs->data = ramfs;
    
    return RT_EOK;
}

int dfs_ramfs_unmount(struct dfs_filesystem *fs)
{
    fs->data = NULL;

    return RT_EOK;
}

int dfs_ramfs_statfs(struct dfs_filesystem *fs, struct statfs *buf)
{
    struct dfs_ramfs *ramfs;

    ramfs = (struct dfs_ramfs *)fs->data;
    RT_ASSERT(ramfs != NULL);
    RT_ASSERT(buf != NULL);

    buf->f_bsize  = 512;
    buf->f_blocks = ramfs->memheap.pool_size/512;
    buf->f_bfree  = ramfs->memheap.available_size/512;

    return RT_EOK;
}

int dfs_ramfs_ioctl(struct dfs_fd *file, int cmd, void *args)
{
    return -EIO;
}

struct ramfs_dirent *dfs_ramfs_lookup(struct dfs_ramfs *ramfs,
                                      const char       *path,
                                      rt_size_t        *size)
{
    const char *subpath;
    struct ramfs_dirent *dirent;

    subpath = path;
    while (*subpath == '/' && *subpath)
        subpath ++;
    if (! *subpath) /* is root directory */
    {
        *size = 0;

        return &(ramfs->root);
    }

    for (dirent = rt_list_entry(ramfs->root.list.next, struct ramfs_dirent, list);
         dirent != &(ramfs->root);
         dirent = rt_list_entry(dirent->list.next, struct ramfs_dirent, list))
    {
        if (rt_strcmp(dirent->name, subpath) == 0)
        {
            *size = dirent->size;

            return dirent;
        }
    }

    /* not found */
    return NULL;
}

int dfs_ramfs_read(struct dfs_fd *file, void *buf, size_t count)
{
    rt_size_t length;
    struct ramfs_dirent *dirent;

    dirent = (struct ramfs_dirent *)file->data;
    RT_ASSERT(dirent != NULL);

    if (count < file->size - file->pos)
        length = count;
    else
        length = file->size - file->pos;

    if (length > 0)
        memcpy(buf, &(dirent->data[file->pos]), length);

    /* update file current position */
    file->pos += length;

    return length;
}

int dfs_ramfs_write(struct dfs_fd *fd, const void *buf, size_t count)
{
    struct ramfs_dirent *dirent;
    struct dfs_ramfs *ramfs;

    dirent = (struct ramfs_dirent*)fd->data;
    RT_ASSERT(dirent != NULL);

    ramfs = dirent->fs;
    RT_ASSERT(ramfs != NULL);

    if (count + fd->pos > fd->size)
    {
        rt_uint8_t *ptr;
        ptr = rt_memheap_realloc(&(ramfs->memheap), dirent->data, fd->pos + count);
        if (ptr == NULL)
        {
            rt_set_errno(-ENOMEM);

            return 0;
        }

        /* update dirent and file size */
        dirent->data = ptr;
        dirent->size = fd->pos + count;
        fd->size = dirent->size;
    }

    if (count > 0)
        memcpy(dirent->data + fd->pos, buf, count);

    /* update file current position */
    fd->pos += count;

    return count;
}

int dfs_ramfs_lseek(struct dfs_fd *file, off_t offset)
{
    if (offset <= (off_t)file->size)
    {
        file->pos = offset;

        return file->pos;
    }

    return -EIO;
}

int dfs_ramfs_close(struct dfs_fd *file)
{
    file->data = NULL;

    return RT_EOK;
}

int dfs_ramfs_open(struct dfs_fd *file)
{
    rt_size_t size;
    struct dfs_ramfs *ramfs;
    struct ramfs_dirent *dirent;
    struct dfs_filesystem *fs;

    fs = (struct dfs_filesystem *)file->data;

    ramfs = (struct dfs_ramfs *)fs->data;
    RT_ASSERT(ramfs != NULL);

    if (file->flags & O_DIRECTORY)
    {
        if (file->flags & O_CREAT)
        {
            return -ENOSPC;
        }

        /* open directory */
        dirent = dfs_ramfs_lookup(ramfs, file->path, &size);
        if (dirent == NULL)
            return -ENOENT;
        if (dirent == &(ramfs->root)) /* it's root directory */
        {
            if (!(file->flags & O_DIRECTORY))
            {
                return -ENOENT;
            }
        }
    }
    else
    {
        dirent = dfs_ramfs_lookup(ramfs, file->path, &size);
        if (dirent == &(ramfs->root)) /* it's root directory */
        {
            return -ENOENT;
        }

        if (dirent == NULL)
        {
            if (file->flags & O_CREAT || file->flags & O_WRONLY)
            {
                char *name_ptr;

                /* create a file entry */
                dirent = (struct ramfs_dirent *)
                         rt_memheap_alloc(&(ramfs->memheap),
                                          sizeof(struct ramfs_dirent));
                if (dirent == NULL)
                {
                    return -ENOMEM;
                }

                /* remove '/' separator */
                name_ptr = file->path;
                while (*name_ptr == '/' && *name_ptr)
                    name_ptr ++;
                strncpy(dirent->name, name_ptr, RAMFS_NAME_MAX);

                rt_list_init(&(dirent->list));
                dirent->data = NULL;
                dirent->size = 0;
                dirent->fs = ramfs;

                /* add to the root directory */
                rt_list_insert_after(&(ramfs->root.list), &(dirent->list));
            }
            else
                return -ENOENT;
        }

        /* Creates a new file.
         * If the file is existing, it is truncated and overwritten.
         */
        if (file->flags & O_TRUNC)
        {
            dirent->size = 0;
            if (dirent->data != NULL)
            {
                rt_memheap_free(dirent->data);
                dirent->data = NULL;
            }
        }
    }

    file->data = dirent;
    file->size = dirent->size;
    if (file->flags & O_APPEND)
        file->pos = file->size;
    else 
        file->pos = 0;

    return 0;
}

int dfs_ramfs_stat(struct dfs_filesystem *fs,
                   const char            *path,
                   struct stat           *st)
{
    rt_size_t size;
    struct ramfs_dirent *dirent;
    struct dfs_ramfs *ramfs;

    ramfs = (struct dfs_ramfs *)fs->data;
    dirent = dfs_ramfs_lookup(ramfs, path, &size);

    if (dirent == NULL)
        return -ENOENT;

    st->st_dev = 0;
    st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH |
                  S_IWUSR | S_IWGRP | S_IWOTH;

    st->st_size = dirent->size;
    st->st_mtime = 0;

    return RT_EOK;
}

int dfs_ramfs_getdents(struct dfs_fd *file,
                       struct dirent *dirp,
                       uint32_t    count)
{
    rt_size_t index, end;
    struct dirent *d;
    struct ramfs_dirent *dirent;
    struct dfs_ramfs *ramfs;

    dirent = (struct ramfs_dirent *)file->data;

    ramfs  = dirent->fs;
    RT_ASSERT(ramfs != RT_NULL);

    if (dirent != &(ramfs->root))
        return -EINVAL;

    /* make integer count */
    count = (count / sizeof(struct dirent));
    if (count == 0)
        return -EINVAL;

    end = file->pos + count;
    index = 0;
    count = 0;
    for (dirent = rt_list_entry(dirent->list.next, struct ramfs_dirent, list);
         dirent != &(ramfs->root) && index < end;
         dirent = rt_list_entry(dirent->list.next, struct ramfs_dirent, list))
    {
        if (index >= (rt_size_t)file->pos)
        {
            d = dirp + count;
            d->d_type = DT_REG;
            d->d_namlen = RT_NAME_MAX;
            d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
            rt_strncpy(d->d_name, dirent->name, RAMFS_NAME_MAX);

            count += 1;
            file->pos += 1;
        }
        index += 1;
    }

    return count * sizeof(struct dirent);
}

int dfs_ramfs_unlink(struct dfs_filesystem *fs, const char *path)
{
    rt_size_t size;
    struct dfs_ramfs *ramfs;
    struct ramfs_dirent *dirent;

    ramfs = (struct dfs_ramfs *)fs->data;
    RT_ASSERT(ramfs != NULL);

    dirent = dfs_ramfs_lookup(ramfs, path, &size);
    if (dirent == NULL)
        return -ENOENT;

    rt_list_remove(&(dirent->list));
    if (dirent->data != NULL)
        rt_memheap_free(dirent->data);
    rt_memheap_free(dirent);

    return RT_EOK;
}

int dfs_ramfs_rename(struct dfs_filesystem *fs,
                     const char            *oldpath,
                     const char            *newpath)
{
    struct ramfs_dirent *dirent;
    struct dfs_ramfs *ramfs;
    rt_size_t size;

    ramfs = (struct dfs_ramfs *)fs->data;
    RT_ASSERT(ramfs != NULL);

    dirent = dfs_ramfs_lookup(ramfs, newpath, &size);
    if (dirent != NULL)
        return -EEXIST;

    dirent = dfs_ramfs_lookup(ramfs, oldpath, &size);
    if (dirent == NULL)
        return -ENOENT;

    strncpy(dirent->name, newpath, RAMFS_NAME_MAX);

    return RT_EOK;
}

static const struct dfs_file_ops _ram_fops = 
{
    dfs_ramfs_open,
    dfs_ramfs_close,
    dfs_ramfs_ioctl,
    dfs_ramfs_read,
    dfs_ramfs_write,
    NULL, /* flush */
    dfs_ramfs_lseek,
    dfs_ramfs_getdents,
};

static const struct dfs_filesystem_ops _ramfs =
{
    "ram",
    DFS_FS_FLAG_DEFAULT,
    &_ram_fops,

    dfs_ramfs_mount,
    dfs_ramfs_unmount,
    NULL, /* mkfs */
    dfs_ramfs_statfs,

    dfs_ramfs_unlink,
    dfs_ramfs_stat,
    dfs_ramfs_rename,
};

int dfs_ramfs_init(void)
{
    /* register ram file system */
    dfs_register(&_ramfs);

    return 0;
}
INIT_COMPONENT_EXPORT(dfs_ramfs_init);

struct dfs_ramfs* dfs_ramfs_create(rt_uint8_t *pool, rt_size_t size)
{
    struct dfs_ramfs *ramfs;
    rt_uint8_t *data_ptr;
    rt_err_t result;

    size  = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
    ramfs = (struct dfs_ramfs *)pool;

    data_ptr = (rt_uint8_t *)(ramfs + 1);
    size = size - sizeof(struct dfs_ramfs);
    size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);

    result = rt_memheap_init(&ramfs->memheap, "ramfs", data_ptr, size);
    if (result != RT_EOK)
        return NULL;
    /* detach this memheap object from the system */
    rt_object_detach((rt_object_t)&(ramfs->memheap));

    /* initialize ramfs object */
    ramfs->magic = RAMFS_MAGIC;

    /* initialize root directory */
    memset(&(ramfs->root), 0x00, sizeof(ramfs->root));
    rt_list_init(&(ramfs->root.list));
    ramfs->root.size = 0;
    strcpy(ramfs->root.name, ".");
    ramfs->root.fs = ramfs;

    return ramfs;
}