#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>
#include <../source/PAL/fsops.h>
#include <../source/PAL/oshead.h>
#include <../source/vfs/fbase.h>

#define	ARCH_INVALID_FILE		-1
typedef int						arch_file_t;

struct arch_filemap_s
{
	void_p	base_addr;
	void_p	real_addr;
	u32_t	len;
};

arch_file_t	V_CALL
archFileOpen(char *right_slashed_name,filemode_t filemode)
{
	int fd;
	int flags;
	/*
	 * All access for owner and group, noexec, no access for other
	 */
	int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
	switch(filemode)
	{
		case filemode_read:
			flags = O_RDONLY;
			break;
		case filemode_write:
			flags = O_RDWR | O_CREAT | O_TRUNC;
			break;
		case filemode_append:
			flags = O_RDWR | O_CREAT;
		default:
			return ARCH_INVALID_FILE;
			break;
	}
	fd = open(right_slashed_name,flags,mode);
	if(fd == -1)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"archOpenFile('%s'): open() failed (%s).\n",
			right_slashed_name,strerror(errno));
#endif	/* ARCH_VERBOSE */
		return ARCH_INVALID_FILE;
	}
	if(filemode == filemode_append)
		lseek(fd,0,SEEK_END);
	return fd;
}

u32_t	V_CALL
archFileSize(arch_file_t file)
{
	off_t pos, sz;
	pos = lseek(file,0,SEEK_CUR);
	sz = lseek(file,0,SEEK_END);
	lseek(file,pos,SEEK_SET);
	return (sz == (off_t)-1) ? 0 : (u32_t)sz;
}

u32_t	V_CALL
archFileTell(arch_file_t file)
{
	return lseek(file,0,SEEK_CUR);
}

u32_t	V_CALL
archFileSeek(arch_file_t file,u32_t offset,u32_t whence)
{
	return lseek(file,offset,whence);
}

u32_t	V_CALL
archFileRead(arch_file_t file,void *buffer,u32_t len)
{
	ssize_t bytes_read = read(file,buffer,len);
	return (bytes_read == (ssize_t)-1) ? 0 : (u32_t)bytes_read;
}

u32_t	V_CALL
archFileWrite(arch_file_t file,void *buffer,u32_t len)
{
	ssize_t bytes_written = write(file,buffer,len);
	return (bytes_written == (ssize_t)-1) ? 0 : (u32_t)bytes_written;
}

void	V_CALL
archFileClose(arch_file_t file)
{
	close(file);
}

/*
 * Mapping length can be 0; in this case size-start bytes will be mapped.
 */
void_p	V_CALL
archMapRead(arch_file_t file,u32_t start,u32_t len,struct arch_filemap_s *map)
{
	u32_t size, page_size, page_diff;
	size = (u32_t)lseek(file,0,SEEK_CUR);
	if(size == (u32_t)-1 || !size)
	{
#ifdef	ARCH_VERBOSE
		conlPuts(LOG_ERROR,
"archMapRead(): empty file, couldn't mmap().\n");
#endif	/* ARCH_VERBOSE */
		return NULL;
	}
	if(!len)
		len = size - start;
	if(start+len > size)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
"archMapRead(): file too small (%u),"
			" couldn't mmap() %u bytes at 0x%08X.\n",size,len,start);
#endif	/* ARCH_VERBOSE */
		return NULL;
	}
	/*
	 * Determine page-aligned offset
	 */
	page_size = getpagesize();
	page_diff = start % page_size;
	map->len = len + page_diff;
	map->real_addr = mmap(NULL,map->len,PROT_READ,MAP_PRIVATE,file,
		start - page_diff);
	if(!map->real_addr)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"archMapRead(): mmap() failed (%s).\n",
			strerror(errno));
#endif	/* ARCH_VERBOSE */
		return NULL;
	}
	map->base_addr = (u8_t*)map->real_addr + page_diff;
	return map->base_addr;
}

/*
 * Mapping length must be non-0.
 */
void_p	V_CALL
archMapWrite(arch_file_t file,u32_t start,u32_t len,struct arch_filemap_s *map)
{
	u32_t page_size, page_diff;
	ssize_t size;
	if(!len)
		return NULL;
	size = lseek(file,0,SEEK_END);
	if(size == (ssize_t)-1)
		return NULL;
	/*
	 * Grow file, if necessary (otherwise we get SIGBUS).
	 */
	page_size = getpagesize();
	if(size < start+len)
	{
		int i;
		char *buffer = (char*)malloc(page_size);
		size_t pages, rest;
		memZero(buffer,page_size);
		rest = start+len-size;
		pages = rest / page_size;
		rest %= page_size;
		/* At this moment file pointer already points to eof */
		if(rest)
			write(file,buffer,rest);
		for(i=0;i<pages;i++)
			write(file,buffer,page_size);
		free(buffer);
	}
	/*
	 * Determine page-aligned offset
	 */
	page_diff = start % page_size;
	map->len = len + page_diff;
	map->real_addr = mmap(NULL,map->len,PROT_READ | PROT_WRITE,MAP_SHARED,
		file,start - page_diff);
	if(!map->real_addr)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"archMapWrite(): mmap() failed (%s).\n",
			strerror(errno));
#endif	/* ARCH_VERBOSE */
		return NULL;
	}
	map->base_addr = (u8_t*)map->real_addr + page_diff;
	return map->base_addr;
}

void	V_CALL
archUnmap(struct arch_filemap_s *map)
{
	if(map->real_addr)
		munmap(map->real_addr,map->len);
	return;
}

int main()
{
	int i;
	u8_t *base;
	arch_file_t f;
	struct arch_filemap_s map;
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);
	f = archFileOpen("test",filemode_write);
	if(f == ARCH_INVALID_FILE)
		return -1;
	base = (u8_t*)archMapWrite(f,10,20,&map);
	if(!base)
	{
		archFileClose(f);
		return -1;
	}
	for(i=0;i<10;i++)
		base[i] = 'a'+i;
	archUnmap(&map);
	archFileClose(f);
	return 0;
}
