#include <../arch/arch.h>

V_LOCAL	file_type_t
arch_ftype_from_stat(struct stat *st)
{
	mode_t m = st->st_mode & S_IFMT;
	if(m == S_IFREG)
		return file_type_reg;
	if(m == S_IFCHR)
		return file_type_chr_dev;
	if(m == S_IFBLK)
		return file_type_blk_dev;
	return file_type_unknown;
}

vboolean	V_CALL
archFileExists(char *right_slashed_name)
{
	return (access(right_slashed_name,F_OK)==0) ? true : false;
}

file_type_t V_CALL
archGetFileType(arch_file_t file)
{
	struct stat st;
	if(fstat(file,&st) == -1)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"archGetFileType(%d): fstat() failed (%s).\n",
			file,strerror(errno));
#endif	/* ARCH_VERBOSE */
		return file_type_unknown;
	}
	return arch_ftype_from_stat(&st);
}

arch_file_t	V_CALL
archFileOpen(char *right_slashed_name,file_mode_t filemode,file_type_t *ft_ret_opt)
{
	int fd;
	int flags;
	file_type_t ft;
	/*
	 * All access for owner and group, noexec, no access for other.
	 */
	int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
	switch(filemode)
	{
		case file_mode_read:
			flags = O_RDONLY;
			break;
		case file_mode_write:
			flags = O_RDWR | O_CREAT | O_TRUNC;
			break;
		case file_mode_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,"archFileOpen('%s'): open() failed (%s).\n",
			right_slashed_name,strerror(errno));
#endif	/* ARCH_VERBOSE */
		return ARCH_INVALID_FILE;
	}
	if(filemode == file_mode_read)
	{
		ft = archGetFileType(fd);
		if(ft_ret_opt != NULL)
			*ft_ret_opt = ft;
		if(ft == file_type_unknown)
		{
#ifdef	ARCH_VERBOSE
			conlPrintf(LOG_ERROR,"archFileOpen('%s'): unknown file type.\n",
				right_slashed_name);
#endif	/* ARCH_VERBOSE */
			archFileClose(fd);
			return ARCH_INVALID_FILE;
		}
	}
	else
	{
		if(ft_ret_opt != NULL)
			*ft_ret_opt = file_type_reg;
		if(filemode == file_mode_append)
			lseek(fd,0,SEEK_END);
	}
	return fd;
}

arch_file_t	V_CALL
archOpenConsole(file_mode_t filemode)
{
	if(filemode == file_mode_read)
		return fileno(stdin);
	else
		return fileno(stdout);
}

arch_file_off_t	V_CALL
archGetFileSize(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) ? (arch_file_off_t)sz : 0;
}

arch_file_off_t	V_CALL
archGetFilePointer(arch_file_t file)
{
	return (arch_file_off_t)lseek(file,0,SEEK_CUR);
}

vboolean	V_CALL
archSetFilePointer(arch_file_t file,arch_file_off_t offset,u32_t whence)
{
	off_t retval = lseek(file,(off_t)offset,(int)whence);
	return (retval != (off_t)(-1)) ? true : false;
}

vboolean V_CALL
archFlushFileBuffers(arch_file_t file)
{
	return fsync(file) ? false : true;
}

arch_file_off_t	V_CALL
archFileRead(arch_file_t file,void *buffer,arch_file_off_t len)
{
	ssize_t bytes_read = read(file,buffer,len);
	if(bytes_read == (ssize_t)-1)
		return 0;
	else
		return (arch_file_off_t)bytes_read;
}

arch_file_off_t V_CALL
archFileWrite(arch_file_t file,void *buffer,arch_file_off_t len)
{
	ssize_t bytes_written = write(file,buffer,len);
	if(bytes_written == (ssize_t)-1)
		return 0;
	else
		return (arch_file_off_t)bytes_written;
}

void	V_CALL
archFileClose(arch_file_t file)
{
	if(file != ARCH_INVALID_FILE 
		&& file != fileno(stdin) && file !=fileno(stdout))
		close(file);
}

/*
 * Filesystem API (mandatory)
 */
vboolean	V_CALL
archGetCurrentDirectory(char *dir_ret,u32_t dir_ret_size)
{
	V_ASSERT(dir_ret && dir_ret_size);
	return (getcwd(dir_ret,dir_ret_size) != NULL) ? true : false;
}

vboolean	V_CALL
archSetCurrentDirectory(char *new_dir,vboolean *dir_exist_ret_opt)
{
	vboolean retval;
	V_ASSERT(new_dir);
	retval = (chdir(new_dir) == 0) ? true : false;
	if(retval)
	{
		if(dir_exist_ret_opt)
			*dir_exist_ret_opt = true;
		return true;
	}
	if(errno == ENOENT)
	{
		if(dir_exist_ret_opt)
			*dir_exist_ret_opt = false;
	}
	else
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"archSetCurrentDirectory('%s'): chdir() failed (%s).\n",
			new_dir,strerror(errno));
#endif	/* ARCH_VERBOSE */
		if(dir_exist_ret_opt)
			*dir_exist_ret_opt = true;
	}
	return false;
}

vboolean	V_CALL
archMakeDirectory(char *new_dir)
{
	V_ASSERT(new_dir);
	return (mkdir(new_dir,0755) == 0) ? true : false;
}

vboolean V_CALL
archFindNext(struct arch_dir_s *dir)
{
	struct stat st;
	V_ASSERT(dir && dir->dir);
	dir->ent = readdir(dir->dir);
	if(dir->ent == NULL)
		return false;
	if(dir->dir_start_len+strlen(dir->ent->d_name) >= _MAX_PATH)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_WARNING,
			"archFindNext(): path '%s/%s' too long.\n",
			dir->dir_start,dir->ent->d_name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	strCopy(dir->name,dir->dir_start,_MAX_PATH);
	strCat(dir->name,dir->ent->d_name,_MAX_PATH-dir->dir_start_len);
	if(stat(dir->name,&st)==-1)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_WARNING,
			"archFindNext(): stat('%s') failed (%s).\n",
			dir->name,strerror(errno));
#endif	/* CHECKED_BUILD */
		dir->is_dir = false;
		dir->ftype = file_type_unknown;
	}
	else
	{
		if((st.st_mode & S_IFMT) == S_IFDIR)
		{
			dir->is_dir = true;
			dir->ftype = file_type_unknown;
		}
		else
		{
			dir->is_dir = false;
			dir->ftype = arch_ftype_from_stat(&st);
		}
	}
	return true;
}

vboolean V_CALL
archFindFirst(char *dir_name,struct arch_dir_s *dir)
{
	V_ASSERT(dir_name && dir);
	dir->dir_start_len = strlen(dir_name)+1;
	if(dir->dir_start_len >= _MAX_PATH)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"archFindFirst(%s): path too long.\n",dir_name);
#endif	/* ARCH_VERBOSE */
		return false;
	}
	dir->dir = opendir(dir_name);
	if(dir->dir == NULL)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"archFindFirst(): opendir(%s) failed(%s).\n",
			dir_name,strerror(errno));
#endif	/* ARCH_VERBOSE */
		return false;
	}
	strCopy(dir->dir_start,dir_name,_MAX_PATH);
	strAppendSlash(dir->dir_start);
	return archFindNext(dir);
}

void	V_CALL
archFindClose(struct arch_dir_s *dir)
{
	V_ASSERT(dir);
	if(dir->dir != NULL)
		closedir(dir->dir);
}

/*
 * Memory - mapped I/O (optional)
 */

#ifdef	ARCH_MAPPED_IO_SUPPORTED

/*
 * Mapping length can be 0; in this case 'size'-'start' bytes will be mapped.
 */
void_p	V_CALL
archMapRead(arch_file_t file,
	arch_file_off_t start,arch_file_off_t len,struct arch_filemap_s *map)
{
	arch_file_off_t size, page_size, page_diff;
	size = archGetFileSize(file);
	if(!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 || map->real_addr == (void_p)-1)
	{
#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 NOT be 0.
 */
void_p	V_CALL
archMapReadWrite(arch_file_t file,
	arch_file_off_t start,arch_file_off_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)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"archMapReadWrite(): lseek() failed (%s).\n",
			strerror(errno));
#endif	/* ARCH_VERBOSE */
		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 || map->real_addr == (void_p)(-1))
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"archMapReadWrite(): 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 && map->real_addr != (void_p)(-1))
		munmap(map->real_addr,map->len);
	return;
}

#endif	/* ARCH_MAPPED_IO_SUPPORTED */
