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

/*
 * File IO
 */

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)
{
#ifdef	ARCH_VERBOSE
	static char *caller	= "archFileExists";
#endif	/* ARCH_VERBOSE */
	struct stat st;
	if(access(right_slashed_name,F_OK))
		return false;
	if(stat(right_slashed_name,&st) == -1)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"%s(%s): exists but can't be stat()'ed (%s).\n",
			caller,right_slashed_name,strerror(errno));
#endif	/* ARCH_VERBOSE */
		return false;
	}
	if((st.st_mode & S_IFMT) == S_IFDIR)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_WARNING,"%s(%s): is a directory.\n",
			caller,right_slashed_name);
#endif	/* ARCH_VERBOSE */
		return false;
	}
	return true;
}

vboolean	V_CALL
archDeleteFile(char *right_slashed_name)
{
	return remove(right_slashed_name) ? false : true;
}

vboolean	V_CALL
archMoveFile(char *old_path,char *new_path)
{
	return rename(old_path,new_path) ? false : true;
}

file_type_t V_CALL
archGetFileType(arch_file_t file)
{
	ARCH_CALLER_NAME(archGetFileType)
	struct stat st;
	if(fstat(file,&st) == -1)
	{
		SYSCALL_FAILED("fstat");
		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)
{
	ARCH_CALLER_NAME(archFileOpen)
	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)
	{
		SYSCALL_FAILED_1STR("open",right_slashed_name);
		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,"%s('%s'): unknown file type.\n",
				arch_caller_name,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
archDirectoryExists(char *right_slashed_name)
{
	ARCH_CALLER_NAME(archDirectoryExists)
	struct stat st;
	if(access(right_slashed_name,F_OK))
		return false;
	if(stat(right_slashed_name,&st) == -1)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"%s(%s): exists but can't be stat()'ed (%s).\n",
			arch_caller_name,right_slashed_name,strerror(errno));
#endif	/* ARCH_VERBOSE */
		return false;
	}
	if((st.st_mode & S_IFMT) != S_IFDIR)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_WARNING,"%s(%s): exists but isn't a directory.\n",
			arch_caller_name,right_slashed_name);
#endif	/* ARCH_VERBOSE */
		return false;
	}
	return true;
}

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)
{
	ARCH_CALLER_NAME(archSetCurrentDirectory)
	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
	{
		SYSCALL_FAILED_1STR("chdir",new_dir);
		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
archGetTempDirectory(char *dest,u32_t dest_len)
{
	ARCH_CALLER_NAME(archGetTempDirectory)
	char *home;
	char tmp[_MAX_PATH] = {0};
	u32_t tmp_len;
	/* Check wheither or not "~/tmp" exists */
	home = getenv("HOME");
	if(home)
	{
		if(strlen(home) < (_MAX_PATH - 5))
		{
			strCopy(tmp,home,_MAX_PATH);
			strAppendSlash(tmp);
			strCat(tmp,"tmp",_MAX_PATH - strlen(tmp));
			if(!archDirectoryExists(tmp))
			{
#ifdef	ARCH_VERBOSE
				conlPrintf(LOG_WARNING,
					"%s(): no '%s/tmp' directory, using '/tmp'.\n",
					arch_caller_name,home);
#endif	/* ARCH_VERBOSE */
				tmp[0] = 0;
			}
		}
#ifdef	ARCH_VERBOSE
		else
		{
			conlPrintf(LOG_WARNING,
				"%s(): path to home dir '%s' too long.\n",
				arch_caller_name,home);
		}
#endif	/* ARCH_VERBOSE */
	}
	if(!tmp[0])
		strcpy(tmp,"/tmp");
	tmp_len = strlen(tmp) + 1;
	if(dest_len < tmp_len)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"%s(): required %u bytes, got %u.\n",
			arch_caller_name,tmp_len,dest_len);
#endif	/* ARCH_VERBOSE */
		return false;
	}
	strCopy(dest,tmp,dest_len);
	return true;
}

vboolean V_CALL
archFindNext(struct arch_dir_s *dir)
{
	ARCH_CALLER_NAME(archFindNext)
	struct stat st;
	char absolute_name[_MAX_PATH];
	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,
			"%s(): path '%s/%s' too long.\n",
			arch_caller_name,dir->dir_start,dir->ent->d_name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	strCopy(dir->name,dir->ent->d_name,_MAX_PATH);
	strCopy(absolute_name,dir->dir_start,_MAX_PATH);
	strCat(absolute_name,dir->ent->d_name,_MAX_PATH-dir->dir_start_len);
	if(stat(absolute_name,&st)==-1)
	{
		SYSCALL_FAILED_1STR("stat",absolute_name);
		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)
{
	ARCH_CALLER_NAME(archFindFirst)
	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,
			"%s(%s): path too long.\n",arch_caller_name,dir_name);
#endif	/* ARCH_VERBOSE */
		return false;
	}
	dir->dir = opendir(dir_name);
	if(dir->dir == NULL)
	{
		SYSCALL_FAILED_1STR("opendir",dir_name);
		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_CALLER_NAME(archMapRead)
	arch_file_off_t size, page_size, page_diff;
	size = archGetFileSize(file);
	if(!size)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,
			"%s(): empty file, couldn't mmap().\n",arch_caller_name);
#endif	/* ARCH_VERBOSE */
		return NULL;
	}
	if(!len)
		len = size - start;
	if((start + len) > size)
	{
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_ERROR,"%s(): file too small (%u), couldn't mmap()"
			" %u bytes at 0x%08X.\n",arch_caller_name,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)
	{
		SYSCALL_FAILED("mmap");
		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)
{
	ARCH_CALLER_NAME(archMapReadWrite)
	u32_t page_size, page_diff;
	ssize_t size;
	if(!len)
		return NULL;
	size = lseek(file,0,SEEK_END);
	if(size == (ssize_t)-1)
	{
		SYSCALL_FAILED("lseek");
		return NULL;
	}
	/*
	 * Grow file, if necessary (otherwise we get SIGBUS).
	 */
	page_size = getpagesize();
	if(size < start + len)
	{
		int i, ok = 1;
		char *buffer = (char*)malloc(page_size); /* fixme?: alloca() ? */
		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)
			ok = ok && (write(file,buffer,rest) == rest);
		for(i=0;i<pages;i++)
			ok = ok && (write(file,buffer,page_size) == page_size);
		free(buffer);
		if(!ok)
		{
			SYSCALL_FAILED("write");
			return NULL;
		}
	}
	/*
	 * 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))
	{
		SYSCALL_FAILED("mmap");
		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 */
