/*
 * File      : filerw.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006 - 2009, 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
 * 2009-10-16     Bernard      first version
 */

#include <rtgui/filerw.h>
#include <rtgui/rtgui_system.h>

#ifdef RTGUI_USING_DFS_FILERW
#include <dfs_posix.h>

/* standard file read/write */
struct rtgui_filerw_stdio
{
	/* inherit from rtgui_filerw */
	struct rtgui_filerw parent;

	int fd;
	rt_bool_t eof;
};

static int stdio_seek(struct rtgui_filerw *context, rt_off_t offset, int whence)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;
	int stdio_whence[3] = {SEEK_SET, SEEK_CUR, SEEK_END};

	if (whence < RTGUI_FILE_SEEK_SET || whence > RTGUI_FILE_SEEK_END)
	{
		return -1;
	}

	return lseek(stdio_filerw->fd, offset, stdio_whence[whence]);
}

static int stdio_read(struct rtgui_filerw *context, void *ptr, rt_size_t size, rt_size_t maxnum)
{
	int result;
	
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	/* end of file */
	if (stdio_filerw->eof == RT_TRUE) return -1;

	result = read(stdio_filerw->fd, ptr, size * maxnum);
	if (result == 0) stdio_filerw->eof = RT_TRUE;

	return result;
}

static int stdio_write(struct rtgui_filerw *context, const void *ptr, rt_size_t size, rt_size_t num)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	return write(stdio_filerw->fd, (char*)ptr, size * num);
}

static int stdio_tell(struct rtgui_filerw* context)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	return lseek(stdio_filerw->fd, 0, SEEK_CUR);
}

static int stdio_eof(struct rtgui_filerw* context)
{
	int result;
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	if (stdio_filerw->eof == RT_TRUE) result = 1;
	else result = -1;

	return result;
}

static int stdio_close(struct rtgui_filerw *context)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	if (stdio_filerw)
	{
		close(stdio_filerw->fd);
		rtgui_free(stdio_filerw);

		return 0;
	}

	return -1;
}
#elif defined(RTGUI_USING_STDIO_FILERW)
#include <stdio.h>

/* standard file read/write */
struct rtgui_filerw_stdio
{
	/* inherit from rtgui_filerw */
	struct rtgui_filerw parent;

	FILE* fp;
};

static int stdio_seek(struct rtgui_filerw *context, rt_off_t offset, int whence)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;
	int stdio_whence[3] = {SEEK_SET, SEEK_CUR, SEEK_END};

	if (whence < RTGUI_FILE_SEEK_SET || whence > RTGUI_FILE_SEEK_END)
	{
		return -1;
	}

	if (fseek(stdio_filerw->fp, offset, stdio_whence[whence]) == 0)
	{
		return ftell(stdio_filerw->fp);
	}

	return -1;
}

static int stdio_read(struct rtgui_filerw *context, void *ptr, rt_size_t size, rt_size_t maxnum)
{
	size_t nread;
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	nread = fread(ptr, size, maxnum, stdio_filerw->fp);
	if (nread == 0 && ferror(stdio_filerw->fp))
	{
		return -1;
	}

	return nread;
}

static int stdio_write(struct rtgui_filerw *context, const void *ptr, rt_size_t size, rt_size_t num)
{
	size_t nwrote;
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	nwrote = fwrite(ptr, size, num, stdio_filerw->fp);

	if ( nwrote == 0 && ferror(stdio_filerw->fp) )
	{
		return -1;
	}

	return nwrote;
}

static int stdio_tell(struct rtgui_filerw* context)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	return ftell(stdio_filerw->fp);
}

static int stdio_eof(struct rtgui_filerw* context)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	return feof(stdio_filerw->fp);
}

static int stdio_close(struct rtgui_filerw *context)
{
	struct rtgui_filerw_stdio* stdio_filerw = (struct rtgui_filerw_stdio *)context;

	if (stdio_filerw)
	{
		fclose(stdio_filerw->fp);
		rtgui_free(stdio_filerw);

		return 0;
	}

	return -1;
}
#endif

/* memory file read/write */
struct rtgui_filerw_mem
{
	/* inherit from rtgui_filerw */
	struct rtgui_filerw parent;

	const rt_uint8_t *mem_base, *mem_position, *mem_end;
};

static int mem_seek(struct rtgui_filerw *context, rt_off_t offset, int whence)
{
	const rt_uint8_t* newpos;
	struct rtgui_filerw_mem* mem = (struct rtgui_filerw_mem*)context;

	RT_ASSERT(mem != RT_NULL);

	switch (whence) {
	case RTGUI_FILE_SEEK_SET:
		newpos = mem->mem_base + offset;
		break;

	case RTGUI_FILE_SEEK_CUR:
		newpos = mem->mem_position + offset;
		break;

	case RTGUI_FILE_SEEK_END:
		newpos = mem->mem_end + offset;
		break;

	default:
		return -1;
	}

	if ( newpos < mem->mem_base )
		newpos = mem->mem_base;

	if ( newpos > mem->mem_end )
		newpos = mem->mem_end;

	mem->mem_position = newpos;
	return mem->mem_position- mem->mem_base;
}

static int mem_read(struct rtgui_filerw *context, void *ptr, rt_size_t size, rt_size_t maxnum)
{
	int total_bytes;
	int mem_available;
	struct rtgui_filerw_mem* mem = (struct rtgui_filerw_mem*)context;

	total_bytes = (maxnum * size);
	if ( (maxnum <= 0) || (size <= 0) || ((total_bytes / maxnum) != size) )
	{
		return -1;
	}

	mem_available = mem->mem_end - mem->mem_position;
	if (total_bytes > mem_available)
		total_bytes = mem_available;

	rt_memcpy(ptr, mem->mem_position, total_bytes);
	mem->mem_position += total_bytes;

	return (total_bytes / size);
}

static int mem_write(struct rtgui_filerw *context, const void *ptr, rt_size_t size, rt_size_t num)
{
#if 0
	struct rtgui_filerw_mem* mem = (struct rtgui_filerw_mem*)context;

	if ((mem->mem_position + (num * size)) > mem->mem_end)
	{
		num = (mem->mem_end - mem->mem_position)/size;
	}

	rt_memcpy(mem->mem_position, ptr, num*size);
	mem->mem_position += num*size;

	return num;
#else
	return 0; /* not support memory write */
#endif
}

static int mem_tell(struct rtgui_filerw* context)
{
	struct rtgui_filerw_mem* mem = (struct rtgui_filerw_mem*)context;

	return mem->mem_position - mem->mem_base;
}

static int mem_eof(struct rtgui_filerw* context)
{
	struct rtgui_filerw_mem* mem = (struct rtgui_filerw_mem*)context;

	return mem->mem_position >= mem->mem_end;
}

static int mem_close(struct rtgui_filerw *context)
{
	struct rtgui_filerw_mem* mem = (struct rtgui_filerw_mem*)context;

	if (mem != RT_NULL)
	{
		rtgui_free(mem);
		return 0;
	}

	return -1;
}

const rt_uint8_t* rtgui_filerw_mem_getdata(struct rtgui_filerw* context)
{
	struct rtgui_filerw_mem* mem = (struct rtgui_filerw_mem*)context;

	/* check whether it's a memory filerw */
	if (mem->parent.read != mem_read) return RT_NULL;

	return mem->mem_base;
}

/* file read/write public interface */
#ifdef RTGUI_USING_DFS_FILERW
static int parse_mode(const char *mode)
{
  int f=0;
 
  for (;;)
  {
    switch (*mode)
	{
    case 0: return f;
    case 'b': break;
    case 'r': f=O_RDONLY; break;
    case 'w': f=O_WRONLY|O_CREAT|O_TRUNC; break;
    case 'a': f=O_WRONLY|O_CREAT|O_APPEND; break;
    case '+': f=(f&(~O_WRONLY))|O_RDWR; break;
    }

    ++mode;
  }
}

struct rtgui_filerw* rtgui_filerw_create_file(const char* filename, const char* mode)
{
	int fd;
	struct rtgui_filerw_stdio *rw;

	RT_ASSERT(filename != RT_NULL);

	rw = RT_NULL;
	fd = open(filename, parse_mode(mode), 0);

	if ( fd >= 0 )
	{
		rw = (struct rtgui_filerw_stdio*) rtgui_malloc(sizeof(struct rtgui_filerw_stdio));
		if (rw != RT_NULL)
		{
			rw->parent.seek  = stdio_seek;
			rw->parent.read  = stdio_read;
			rw->parent.write = stdio_write;
			rw->parent.tell  = stdio_tell;
			rw->parent.close = stdio_close;
			rw->parent.eof	 = stdio_eof;

			rw->fd  = fd;
			rw->eof = RT_FALSE;
		}
	}

	return &(rw->parent);
}
#elif defined(RTGUI_USING_STDIO_FILERW)
struct rtgui_filerw* rtgui_filerw_create_file(const char* filename, const char* mode)
{
	FILE *fp;
	struct rtgui_filerw_stdio *rw;

	RT_ASSERT(filename != RT_NULL);

	rw = RT_NULL;
	fp = fopen(filename, mode);

	if ( fp != NULL )
	{
		rw = (struct rtgui_filerw_stdio*) rtgui_malloc(sizeof(struct rtgui_filerw_stdio));
		if (rw != RT_NULL)
		{
			rw->parent.seek  = stdio_seek;
			rw->parent.read  = stdio_read;
			rw->parent.write = stdio_write;
			rw->parent.tell  = stdio_tell;
			rw->parent.close = stdio_close;
			rw->parent.eof	 = stdio_eof;

			rw->fp = fp;
		}
	}

	return &(rw->parent);
}
#endif

struct rtgui_filerw* rtgui_filerw_create_mem(const rt_uint8_t* mem, rt_size_t size)
{
	struct rtgui_filerw_mem* rw;
	RT_ASSERT(mem != RT_NULL);

	rw = (struct rtgui_filerw_mem*) rtgui_malloc(sizeof(struct rtgui_filerw_mem));
	if (rw != RT_NULL)
	{
		rw->parent.seek  = mem_seek;
		rw->parent.read  = mem_read;
		rw->parent.write = mem_write;
		rw->parent.tell  = mem_tell;
		rw->parent.eof	 = mem_eof;
		rw->parent.close = mem_close;

		rw->mem_base = mem;
		rw->mem_position = mem;
		rw->mem_end = mem + size;
	}

	return &(rw->parent);
}

int rtgui_filerw_seek(struct rtgui_filerw* context, rt_off_t offset, int whence)
{
	RT_ASSERT(context != RT_NULL);

	return context->seek(context, offset, whence);
}

int rtgui_filerw_read(struct rtgui_filerw* context, void* buffer, rt_size_t size, rt_size_t count)
{
	RT_ASSERT(context != RT_NULL);

	return context->read(context, buffer, size, count);
}

int rtgui_filerw_write(struct rtgui_filerw* context, const void* buffer, rt_size_t size, rt_size_t count)
{
	RT_ASSERT(context != RT_NULL);

	return context->write(context, buffer, size, count);
}

int rtgui_filerw_eof	(struct rtgui_filerw* context)
{
	RT_ASSERT(context != RT_NULL);

	return context->eof(context);
}

int rtgui_filerw_tell(struct rtgui_filerw* context)
{
	RT_ASSERT(context != RT_NULL);

	return context->tell(context);
}

int rtgui_filerw_close(struct rtgui_filerw* context)
{
	int result;

	RT_ASSERT(context != RT_NULL);

	/* close context */
	result = context->close(context);
	if (result != 0)
	{
		/* close file failed */
		return -1;
	}

	return 0;
}
