/*
 * File      : rtthread.h
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006-2012, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE.
 *
 * Change Logs:
 * Date           Author       Notes
 * 2012-11-27     prife        the first version
 * 2013-03-03     aozima       add dfs_win32_stat st_mtime support.
 * 2017-10-20     urey         support rt-thread 3.0
 */
#include <rtthread.h>
#include <rtlibc.h>

#include <dfs_fs.h>
#include <dfs_file.h>
#include <rtdevice.h>

#include <io.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <WinError.h>
#include <windows.h>

#if defined(__MINGW32__) && defined(_NO_OLDNAMES)
#define O_RDONLY    _O_RDONLY
#define O_WRONLY    _O_WRONLY
#define O_RDWR      _O_RDWR
#define O_ACCMODE   _O_ACCMODE
#define O_APPEND    _O_APPEND
#define O_CREAT     _O_CREAT
#define O_TRUNC     _O_TRUNC
#define O_EXCL      _O_EXCL
#define O_TEXT      _O_TEXT
#define O_BINARY    _O_BINARY
#define O_TEMPORARY _O_TEMPORARY
#define O_NOINHERIT _O_NOINHERIT
#define O_SEQUENTIAL   _O_SEQUENTIAL
#define O_RANDOM    _O_RANDOM
#endif
/*
 * RT-Thread DFS Interface for win-directory as an disk device
 */
#define FILE_PATH_MAX           256  /* the longest file path */

#define WIN32_DIRDISK_ROOT  "./disk"

typedef struct {
    HANDLE handle;
    char *start;
    char *end;
    char *curr;
    struct _finddata_t finddata;
} WINDIR;

/* There are so many error codes in windows, you'd better google for details.
 * google  "System Error Codes (Windows)"
 * http://msdn.microsoft.com/ZH-CN/library/windows/desktop/ms681381(v=vs.85).aspx
 */

struct _errcode_map
{
    rt_uint16_t dfserr;
    rt_uint16_t win32err;
};

static const struct _errcode_map errcode_table[] =
{
    {ENOENT, ERROR_FILE_NOT_FOUND },
    {ENOENT, ERROR_PATH_NOT_FOUND },
    {EEXIST, ERROR_FILE_EXISTS },
    {EEXIST, ERROR_ALREADY_EXISTS },
    {ENOTEMPTY, ERROR_DIR_NOT_EMPTY },
    {EBUSY, ERROR_PATH_BUSY },
    {EINVAL, ERROR_ACCESS_DENIED },

#if 0 /* TODO: MORE NEED BE ADDED */
    {DFS_STATUS_EISDIR, ERROR_FILE_EXISTS },
    {DFS_STATUS_ENOTDIR, ERROR_FILE_EXISTS },
    {DFS_STATUS_EBADF, ERROR_FILE_EXISTS },
    {DFS_STATUS_EBUSY, ERROR_FILE_EXISTS },
    {DFS_STATUS_ENOMEM, ERROR_FILE_EXISTS },
    {DFS_STATUS_ENOSPC, ERROR_FILE_EXISTS },
#endif
};
static int win32_result_to_dfs(DWORD res)
{
    int i;
    int err = 0;
    for (i = 0; i < sizeof(errcode_table) / sizeof(struct _errcode_map); i++)
    {
        if (errcode_table[i].win32err == (res & 0xFFFF))
        {
            err = -errcode_table[i].dfserr;
            return err;
        }
    }

    /* unknown error */
    rt_kprintf("dfs win32 error not supported yet: %d\n", res);
    return -1;
}

static int dfs_win32_mount(
    struct dfs_filesystem *fs,
    unsigned long rwflag,
    const void *data)
{
    return 0;
}

static int dfs_win32_unmount(struct dfs_filesystem *fs)
{
    return 0;
}

static int dfs_win32_mkfs(rt_device_t devid)
{
    return -ENOSYS;
}

static int dfs_win32_statfs(struct dfs_filesystem *fs,
                            struct statfs *buf)
{
    return -ENOSYS;
}

static char *winpath_dirdup(char *des, const char *src)
{
    char *path;
    int i = 0;

    path = rt_malloc(FILE_PATH_MAX);
    if (path == RT_NULL)
        return RT_NULL;

    strcpy(path, des);
    strcat(path, src);

    while (1)
    {
        if (path[i] == 0)
            break;

        if (path[i] == '/')
            path[i] = '\\';

        i++;
    }

    return path;
}

/* This function can convert the path in rt-thread/dfs to the path in windows */
char * dfs_win32_dirdup(char * path)
{
    char * file_path;
    file_path = winpath_dirdup(WIN32_DIRDISK_ROOT, path);
    return file_path;
}

static int dfs_win32_open(struct dfs_fd *file)
{
    int fd;
    uint32_t oflag, mode;
    char *file_path;
    int res;

    oflag = file->flags;
    if (oflag & O_DIRECTORY)   /* operations about dir */
    {
        WINDIR *wdirp;
        HANDLE handle;
        int len;

        file_path = winpath_dirdup(WIN32_DIRDISK_ROOT, file->path);

        if (oflag & O_CREAT)   /* create a dir*/
        {
            res = CreateDirectory(file_path, NULL);
            if (res == 0)
            {
                rt_free(file_path);
                return win32_result_to_dfs(GetLastError());
            }
        }

        len = strlen(file_path);
        if (file_path[len - 1] != '\\')
        {
            file_path[len] = '\\';
            file_path[len + 1] = 0;
        }

        strcat(file_path, "*.*");
        /* _findfirst will get '.' */
        /* save this pointer,will used by  dfs_win32_getdents*/
        wdirp = rt_malloc(sizeof(WINDIR));
        RT_ASSERT(wdirp != NULL);
        if ((handle = _findfirst(file_path, &wdirp->finddata)) == -1)
        {
            rt_free(wdirp);
            rt_free(file_path);
            goto __err;
        }
        len = strlen(wdirp->finddata.name) + 1;
        wdirp->handle = handle;
        //wdirp->nfiles = 1;
        wdirp->start = malloc(len); //not rt_malloc!
        wdirp->end = wdirp->curr = wdirp->start;
        wdirp->end += len;
        strncpy(wdirp->curr, wdirp->finddata.name, len);

        file->data = (void *)wdirp;
        rt_free(file_path);
        return 0;
    }
    /* regular file operations */
    mode = O_BINARY;
    if (oflag & O_RDONLY) mode |= O_RDONLY;
    if (oflag & O_WRONLY) mode |= O_WRONLY;
    if (oflag & O_RDWR)   mode |= O_RDWR;
    /* Opens the file, if it is existing. If not, a new file is created. */
    if (oflag & O_CREAT) mode |= O_CREAT;
    /* Creates a new file. If the file is existing, it is truncated and overwritten. */
    if (oflag & O_TRUNC) mode |= O_TRUNC;
    /* Creates a new file. The function fails if the file is already existing. */
    if (oflag & O_EXCL) mode |= O_EXCL;

    file_path = winpath_dirdup(WIN32_DIRDISK_ROOT, file->path);
    fd = _open(file_path, mode, 0x0100 | 0x0080); /* _S_IREAD | _S_IWRITE */
    rt_free(file_path);

    if (fd < 0)
        goto __err;

    /* save this pointer, it will be used when calling read(), write(),
     * flush(), seek(), and will be free when calling close()*/
    file->data = (void *)fd;
    file->pos  = 0;
    file->size = _lseek(fd, 0, SEEK_END);

    if (oflag & O_APPEND)
    {
        file->pos = file->size;
    }
    else
        _lseek(fd, 0, SEEK_SET);

    return 0;

__err:
    res = GetLastError();
    return win32_result_to_dfs(res);
}

static int dfs_win32_close(struct dfs_fd *file)
{
    if (file->flags & O_DIRECTORY)
    {
        WINDIR *wdirp = (WINDIR*)(file->data);
        RT_ASSERT(wdirp != RT_NULL);
        if (_findclose((intptr_t)wdirp->handle) == 0) {
            free(wdirp->start); //NOTE: here we don't use rt_free!
            rt_free(wdirp);
            return 0;
        }
    }
    else /* regular file operations */
    {
        if (_close((int)(file->data)) == 0)
            return 0;
    }

    return win32_result_to_dfs(GetLastError());
}

static int dfs_win32_ioctl(struct dfs_fd *file, int cmd, void *args)
{
    return -ENOSYS;
}

static int dfs_win32_read(struct dfs_fd *file, void *buf, size_t len)
{
    int fd;
    int char_read;

    fd = (int)(file->data);
    char_read = _read(fd, buf, len);
    if (char_read < 0)
        return win32_result_to_dfs(GetLastError());

    /* update position */
    file->pos = _lseek(fd, 0, SEEK_CUR);
    return char_read;
}

static int dfs_win32_write(struct dfs_fd *file, const void *buf, size_t len)
{
    int fd;
    int char_write;

    fd = (int)(file->data);

    char_write = _write(fd, buf, len);
    if (char_write < 0)
        return win32_result_to_dfs(GetLastError());

    /* update position */
    file->pos = _lseek(fd, 0, SEEK_CUR);
    return char_write;
}

static int dfs_win32_flush(struct dfs_fd *file)
{
    return 0;
}

static int dfs_win32_seek(struct dfs_fd *file,
                          rt_off_t offset)
{
    int result;

    /* set offset as current offset */
    if (file->type == FT_DIRECTORY)
    {
        WINDIR* wdirp = (WINDIR*)(file->data);
        RT_ASSERT(wdirp != RT_NULL);
        wdirp->curr = wdirp->start + offset;
        return offset;
    }
    else //file->type == FT_REGULAR)
    {
        result = _lseek((int)(file->data), offset, SEEK_SET);
        if (result >= 0)
            return offset;
        else
            return win32_result_to_dfs(GetLastError());
    }
}

/* return the size of struct dirent*/
static int dfs_win32_getdents(struct dfs_fd *file, struct dirent *dirp, rt_uint32_t count)
{
    WINDIR *wdirp;
    struct dirent *d = dirp;
    int result;

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

    wdirp = (WINDIR*)(file->data);
    RT_ASSERT(wdirp != RT_NULL);
    if (wdirp->curr == NULL) //no more entries in this directory
        return 0;

    /* get the current entry */
    if (wdirp->finddata.attrib & _A_SUBDIR)
        d->d_type = DT_DIR;
    else
        d->d_type = DT_REG;
    d->d_namlen = strlen(wdirp->curr);
    strncpy(d->d_name, wdirp->curr, DFS_PATH_MAX);
    d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
    wdirp->curr += (strlen(wdirp->curr) + 1);
    file->pos = wdirp->curr - wdirp->start + sizeof(struct dirent);//NOTE!

    /* now set up for the next call to readdir */
    if (wdirp->curr >= wdirp->end)
    {
        if (_findnext(wdirp->handle, &wdirp->finddata) == 0)
        {
            char* old_start = wdirp->start;
            long name_len = strlen(wdirp->finddata.name) + 1;
            wdirp->start = realloc(wdirp->start, wdirp->end - wdirp->start + name_len);
            wdirp->curr = wdirp->start + (wdirp->curr - old_start);
            wdirp->end = wdirp->curr + name_len;
            strcpy(wdirp->curr, wdirp->finddata.name);
        }
        else
        {
            if ((result = GetLastError()) == ERROR_NO_MORE_FILES)
                wdirp->curr = NULL;
            else
                return win32_result_to_dfs(result);
        }
    }

    return sizeof(struct dirent);
}

static int dfs_win32_unlink(struct dfs_filesystem *fs, const char *path)
{
    int result;
    char *fp;
    fp = winpath_dirdup(WIN32_DIRDISK_ROOT, path);
    if (fp == RT_NULL)
    {
        rt_kprintf("out of memory.\n");
        return -ENOMEM;
    }

    result = GetFileAttributes(fp);
    if (result == INVALID_FILE_ATTRIBUTES)
        goto __err;

    if (result & FILE_ATTRIBUTE_DIRECTORY)//winnt.h
    {
        if (RemoveDirectory(fp) == RT_FALSE)
            goto __err;
    }
    else //(result & FILE_ATTRIBUTE_NORMAL)
    {
        if (_unlink(fp) < 0)
            goto __err;
    }

    rt_free(fp);
    return 0;
__err:
    rt_free(fp);
    return win32_result_to_dfs(GetLastError());
}

static int dfs_win32_rename(
    struct dfs_filesystem *fs,
    const char *oldpath,
    const char *newpath)
{
    int result;
    char *op, *np;
    op = winpath_dirdup(WIN32_DIRDISK_ROOT, oldpath);
    np = winpath_dirdup(WIN32_DIRDISK_ROOT, newpath);
    if (op == RT_NULL || np == RT_NULL)
    {
        rt_kprintf("out of memory.\n");
        return -ENOMEM;
    }

    /* If the function fails, the return value is zero. */
    result = MoveFile(op, np);

    rt_free(op);
    rt_free(np);

    if (result == 0)
        return win32_result_to_dfs(GetLastError());

    return 0;
}

static int dfs_win32_stat(struct dfs_filesystem *fs, const char *path, struct stat *st)
{
    WIN32_FIND_DATA fileInfo;
    HANDLE hFind;
    char *fp;
    fp = winpath_dirdup(WIN32_DIRDISK_ROOT, path);
    if (fp == RT_NULL)
    {
        rt_kprintf("out of memory.\n");
        return -ENOMEM;
    }

    hFind = FindFirstFile(fp, &fileInfo);
    rt_free(fp);

    if (hFind == INVALID_HANDLE_VALUE)
        goto __err;

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

    /* convert file info to dfs stat structure */
    if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
        st->st_mode &= ~S_IFREG;
        st->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
    }

    st->st_dev  = 0;
    st->st_size = fileInfo.nFileSizeLow;

    /* get st_mtime. */
    {
        LARGE_INTEGER time_tmp;
        time_tmp.LowPart = fileInfo.ftLastWriteTime.dwLowDateTime;
        time_tmp.HighPart = fileInfo.ftLastWriteTime.dwHighDateTime;

        /* removes the diff between 1970 and 1601. */
        time_tmp.QuadPart -= 11644473600000 * 10000;
        /* converts back from 100-nanoseconds to seconds. */
        time_tmp.QuadPart /= 10UL * 1000 * 1000;

        st->st_mtime = time_tmp.QuadPart;
    }

    FindClose(hFind);

    return 0;

__err:
    return win32_result_to_dfs(GetLastError());
}

static const struct dfs_file_ops dfs_win32_file_ops =
{
    dfs_win32_open,
    dfs_win32_close,
    dfs_win32_ioctl,
    dfs_win32_read,
    dfs_win32_write,
    dfs_win32_flush,
    dfs_win32_seek,
    dfs_win32_getdents,
};

static const struct dfs_filesystem_ops dfs_win32_ops =
{
    "wdir", /* file system type: dir */
    DFS_FS_FLAG_DEFAULT,
    &dfs_win32_file_ops,
    dfs_win32_mount,
    dfs_win32_unmount,
    dfs_win32_mkfs,
    dfs_win32_statfs,
    dfs_win32_unlink,
    dfs_win32_stat,
    dfs_win32_rename,
};

int dfs_win32_init(void)
{
    /* register uffs file system */
    dfs_register(&dfs_win32_ops);

    return 0;
}

static rt_err_t nop_init(rt_device_t dev)
{
    return RT_EOK;
}

static rt_err_t nop_open(rt_device_t dev, rt_uint16_t oflag)
{
    return RT_EOK;
}

static rt_err_t nop_close(rt_device_t dev)
{
    return RT_EOK;
}

static rt_size_t nop_read(rt_device_t dev,
                          rt_off_t    pos,
                          void       *buffer,
                          rt_size_t   size)
{
    return size;
}

static rt_size_t nop_write(rt_device_t dev,
                           rt_off_t    pos,
                           const void *buffer,
                           rt_size_t   size)
{
    return size;
}

static rt_err_t nop_control(rt_device_t dev, int cmd, void *args)
{
    return RT_EOK;
}

static struct rt_device win_sharedir_dev;
rt_err_t rt_win_sharedir_init(const char *name)
{
    rt_device_t dev;

    dev = &win_sharedir_dev;
    RT_ASSERT(dev != RT_NULL);

    /* set device class and generic device interface */
    dev->type        = RT_Device_Class_Block;
    dev->init        = nop_init;
    dev->open        = nop_open;
    dev->read        = nop_read;
    dev->write       = nop_write;
    dev->close       = nop_close;
    dev->control     = nop_control;

    dev->rx_indicate = RT_NULL;
    dev->tx_complete = RT_NULL;

    /* register to RT-Thread device system */
    return rt_device_register(dev, name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);
}