/*---------------------------------------------------------------------------*
  Project:  NitroSDK - FS - libraries
  File:     fs_command.c

  Copyright 2003,2004 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: fs_command.c,v $
  Revision 1.9  2004/10/22 13:06:52  yosizaki
  Fixed thread safe bugs of list operation

  Revision 1.8  2004/09/22 08:55:59  yosizaki
  fix FSi_SeekDirDirect().

  Revision 1.7  2004/09/03 06:54:20  yosizaki
  small fix.

  Revision 1.6  2004/09/02 11:03:00  yosizaki
  fix FS include directives.

  Revision 1.5  2004/08/05 03:26:28  yosizaki
  fix FS_COMMAND_READDIR (numbering of file-ID)

  Revision 1.4  2004/07/23 08:30:06  yosizaki
  fix command operation.

  Revision 1.3  2004/07/19 13:18:03  yosizaki
  change system (sync & async)

  Revision 1.2  2004/07/12 13:04:00  yosizaki
  small fix (FS_COMMAND_GETPATH)

  Revision 1.1  2004/07/08 13:41:00  yosizaki
  (none)

  $NoKeywords: $
 *---------------------------------------------------------------------------*/


#include <nitro/mi.h>
#include <nitro/os.h>

#include <nitro/fs.h>

#include "../include/util.h"
#include "../include/command.h"


/* Archive does not include ARM7 in principle */
#if	defined(FS_IMPLEMENT)


/********************************************************************/
/* Functions */

static FSResult	FSi_ReadFileCommand(FSFile *p_file);
static FSResult	FSi_WriteFileCommand(FSFile *p_file);
static FSResult	FSi_SeekDirCommand(FSFile *p_file);
static FSResult	FSi_ReadDirCommand(FSFile *p_file);
static FSResult	FSi_FindPathCommand(FSFile *p_file);
static FSResult	FSi_GetPathCommand(FSFile *p_file);
static FSResult	FSi_OpenFileFastCommand(FSFile *p_file);
static FSResult	FSi_OpenFileDirectCommand(FSFile *p_file);
static FSResult	FSi_CloseFileCommand(FSFile *p_file);


/* Complete file and wake waiting thread */
void	FSi_ReleaseCommand(FSFile *p_file, FSResult ret)
{
	SDK_ASSERT(FS_IsBusy(p_file));
	{
		OSIntrMode    bak_psr = OS_DisableInterrupts();
		FS_CutFromList(p_file);
		p_file->stat &= ~(FS_FILE_STATUS_CANCEL | FS_FILE_STATUS_BUSY |
			FS_FILE_STATUS_SYNC | FS_FILE_STATUS_ASYNC);
		p_file->error = ret;
		OS_WakeupThread(p_file->queue);
		(void)OS_RestoreInterrupts(bak_psr);
	}
}

/*
 * Call user procedure or default process and return result. 
 * If a synchronous command returns an asynchronous response, wait for completion internally.
 * If an asynchronous command returns an asynchronous response, return it as is.
 */
FSResult	FSi_TranslateCommand(FSFile *p_file, FSCommandType command)
{
	FSResult	ret;

	FSArchive	* const p_arc = p_file->arc;
	const int	bit = (1 << command);

	/* Set the synchronous/asynchronous process flag */
	if(FS_IsFileSyncMode(p_file))
		p_arc->flag |= FS_ARCHIVE_FLAG_IS_SYNC;
	else
		p_arc->flag |= FS_ARCHIVE_FLAG_IS_ASYNC;

	/* If the procedure responds, call it and confirm the response */
	if((p_arc->proc_flag & bit) != 0) {
		switch(ret = (*p_arc->proc)(p_file, command)) {
		case FS_RESULT_SUCCESS:
		case FS_RESULT_FAILURE:
		case FS_RESULT_UNSUPPORTED:
			/* Complete processing of command on the procedure side */
			p_file->error = ret;
			break;
		case FS_RESULT_PROC_ASYNC:
			/* Asynchronous process will be closely controlled later  */
			break;
		case FS_RESULT_PROC_UNKNOWN:
			/* Command is unknown; switch to default from now on. */
			ret = FS_RESULT_PROC_DEFAULT;
			p_arc->proc_flag &= ~bit;
			break;
		}
	} else {
		ret = FS_RESULT_PROC_DEFAULT;
	}
	/* If necessary, call default processing */
	if(ret == FS_RESULT_PROC_DEFAULT) {
		static FSResult	(* const (def_command[]))(FSFile*) = {
			FSi_ReadFileCommand,
			FSi_WriteFileCommand,
			FSi_SeekDirCommand,
			FSi_ReadDirCommand,
			FSi_FindPathCommand,
			FSi_GetPathCommand,
			FSi_OpenFileFastCommand,
			FSi_OpenFileDirectCommand,
			FSi_CloseFileCommand,
		} ;
		ret = (*def_command[command])(p_file);
	}
	/* If asynchronous processing starts but mode is synchronous, use blocking */
	if(ret == FS_RESULT_PROC_ASYNC) {
		if(FS_IsFileSyncMode(p_file)) {
			OSIntrMode    bak_psr = OS_DisableInterrupts();
			while(FSi_IsArchiveSync(p_arc))
				OS_SleepThread(&p_arc->sync_q);
			ret = p_file->error;
			(void)OS_RestoreInterrupts(bak_psr);
		}
	}
	/* If mode is asynchronous, but completion is synchronous, release here */
	else if(!FS_IsFileSyncMode(p_file)) {
		p_arc->flag &= ~FS_ARCHIVE_FLAG_IS_ASYNC;
		FSi_ReleaseCommand(p_file, ret);
	}
	/* If synchronous command completes synchronously, store the result */
	else {
		p_arc->flag &= ~FS_ARCHIVE_FLAG_IS_SYNC;
		p_file->error = ret;
	}
	/* Return the result. (FS_RESULT_PROC_ASYNC is used only for asynchronous processing of asynchronous commands) */
	return	ret;
}


/** Internal ************************************************************/


/* Blocking read structure for synchronous commands */
typedef	struct
{
	FSArchive	*arc;	/* Target archive */
	u32	pos;	/* Start position */
} FSiSyncReadParam;


/* Execute blocking read inside synchronous command */
static void	FSi_ReadTable(FSiSyncReadParam *p, void *dst, u32 len)
{
	FSArchive	* const p_arc = p->arc;

	/*Start synchronous processing of command */
	p_arc->flag |= FS_ARCHIVE_FLAG_IS_SYNC;
	switch((*p_arc->table_func)(p_arc, dst, p->pos, len)) {
	case FS_RESULT_SUCCESS:
	case FS_RESULT_FAILURE:
		p_arc->flag &= ~FS_ARCHIVE_FLAG_IS_SYNC;
		break;
	case FS_RESULT_PROC_ASYNC:
#if	!defined(SDK_NO_THREAD)
		{	/* If asynchronous, sleep */
			OSIntrMode    bak_psr = OS_DisableInterrupts();
			while(FSi_IsArchiveSync(p_arc))
				OS_SleepThread(&p_arc->sync_q);
			(void)OS_RestoreInterrupts(bak_psr);
		}
#else
		while(FSi_IsArchiveSync(p_arc))
			;
#endif
		break;
	}
	p->pos += len;
}

/* Internal function. Move directly to start of specified directory ID. */
static void	FSi_SeekDirDirect(FSFile *p_dir, u32 id)
{
	p_dir->stat |= FS_FILE_STATUS_SYNC;
	p_dir->arg.seekdir.pos.arc = p_dir->arc;
	p_dir->arg.seekdir.pos.pos = 0;
	p_dir->arg.seekdir.pos.index = 0;
	p_dir->arg.seekdir.pos.own_id = (u16)id;
	(void)FSi_TranslateCommand(p_dir, FS_COMMAND_SEEKDIR);
}

/* Internal function. Compare text strings without case sensitivity. */
static int	my_strnicmp(const char *s, const char *t, int n)
{
	int	i;
	for( i = 0 ; i < n ; ++i ) {
		u32	c = (u32)(MI_ReadByte(s + i) - 'A');
		u32	d = (u32)(MI_ReadByte(t + i) - 'A');
		if(c <= 'Z' - 'A')	c += 'a' - 'A';
		if(d <= 'Z' - 'A')	d += 'a' - 'A';
		if(c != d)	return	(int)(c - d);
	}
	return	0;
}

/* Default command implementation ***************************************/

static FSResult	FSi_ReadFileCommand(FSFile *p_file)
{
	FSArchive	* const p_arc = p_file->arc;
	const u32	pos = p_file->prop.file.pos;
	const u32	len = p_file->arg.readfile.len;
	void	* const dst = p_file->arg.readfile.dst;
	p_file->prop.file.pos += len;
	return	(*p_arc->read_func)(p_arc, dst, pos, len);
}

static FSResult	FSi_WriteFileCommand(FSFile *p_file)
{
	FSArchive	* const p_arc = p_file->arc;
	const u32	pos = p_file->prop.file.pos;
	const u32	len = p_file->arg.writefile.len;
	const void	* const src = p_file->arg.writefile.src;
	p_file->prop.file.pos += len;
	return	(*p_arc->write_func)(p_arc, src, pos, len);
}

static FSResult	FSi_SeekDirCommand(FSFile *p_dir)
{
	FSArchive	* const p_arc = p_dir->arc;
	const FSDirPos	* const arg = &p_dir->arg.seekdir.pos;

	/* Access to specified index of FNT */
	FSArchiveFNT	fnt_entry;
	FSiSyncReadParam	param;
	param.arc = p_arc;
	param.pos = p_arc->fnt + arg->own_id * sizeof(fnt_entry);
	FSi_ReadTable(&param, &fnt_entry, sizeof(fnt_entry));

	p_dir->prop.dir.pos = *arg;
	/* if !index && !pos, seek to first entry */
	if((arg->index == 0) && (arg->pos == 0)) {
		p_dir->prop.dir.pos.index = fnt_entry.index;
		p_dir->prop.dir.pos.pos = p_arc->fnt + fnt_entry.start;
	}
	p_dir->prop.dir.parent = (fnt_entry.parent & BIT_MASK(12));

	return	FS_RESULT_SUCCESS;
}

static FSResult	FSi_ReadDirCommand(FSFile *p_dir)
{
	FSDirEntry	*p_entry = p_dir->arg.readdir.p_entry;

	/* pos directs next entry of directory */
	FSiSyncReadParam	param;
	param.arc = p_dir->arc;
	param.pos = p_dir->prop.dir.pos.pos;

	{	/* read length and property */
		u8	len;
		FSi_ReadTable(&param, &len, sizeof(len));
		p_entry->name_len = (u32)(len & BIT_MASK(7));
		p_entry->is_directory = (u32)((len >> 7) & 1);
	}
	/* if end of entry, return FAILURE */
	if(p_entry->name_len == 0)
		return	FS_RESULT_FAILURE;

	/* read or skip filename section */
	if(!p_dir->arg.readdir.skip_string) {
		FSi_ReadTable(&param, p_entry->name, p_entry->name_len);
		MI_WriteByte((u8*)p_entry->name + p_entry->name_len, (u8)'\0');
	} else {
		param.pos += p_entry->name_len;
	}

	if(p_entry->is_directory) {
		/* if directory information, read index */
		u16	id;
		FSi_ReadTable(&param, &id, sizeof(id));
		p_entry->dir_id.arc = p_dir->arc;
		p_entry->dir_id.own_id = (u16)(id & BIT_MASK(12));
		p_entry->dir_id.index = 0;
		p_entry->dir_id.pos = 0;
	} else {
		/* if file information, use index incrementally */
		p_entry->file_id.arc = p_dir->arc;
		p_entry->file_id.file_id = p_dir->prop.dir.pos.index;
		++p_dir->prop.dir.pos.index;
	}
	/* if successful, increment position to next entry */
	p_dir->prop.dir.pos.pos = param.pos;

	return	FS_RESULT_SUCCESS;
}

static FSResult	FSi_FindPathCommand(FSFile *p_dir)
{
	const char	*path = p_dir->arg.findpath.path;
	const BOOL	is_dir = p_dir->arg.findpath.find_directory;

	/* Search path in order from specified position */
	p_dir->arg.seekdir.pos = p_dir->arg.findpath.pos;
	(void)FSi_TranslateCommand(p_dir, FS_COMMAND_SEEKDIR);
	for( ; MI_ReadByte(path) ; path += (MI_ReadByte(path) ? 1 : 0) ) {
		u32	is_directory;
		int	name_len = 0;
		while((is_directory = MI_ReadByte(path + name_len)),
			(is_directory && !is_slash(is_directory)))
			++name_len;
		if(is_directory || is_dir)	is_directory = 1;

		/* Invalid path such as "//" */
		if(name_len == 0) {
			return	FS_RESULT_FAILURE;
		}
		/* Special directory */
		else if(MI_ReadByte(path) == '.') {
			/* "." means current directory */
			if(name_len == 1) {
				path += 1;
				continue;
			}
			/* ".."  means parent directory */
			else if((name_len == 2) & (MI_ReadByte(path + 1) == '.')) {
				if(p_dir->prop.dir.pos.own_id != 0)
					FSi_SeekDirDirect(p_dir, p_dir->prop.dir.parent);
				path += 2;
				continue;
			}
		}
		/* Filename too long (error of archive) */
		if(name_len > FS_FILE_NAME_MAX) {
			return	FS_RESULT_FAILURE;
		}
		/* Search the coincidence name in a directory in order */
		else {
			FSDirEntry	etr;
			p_dir->arg.readdir.p_entry = &etr;
			p_dir->arg.readdir.skip_string = FALSE;
			for( ; ; ) {
				/* if end of entry or error, not found */
				if(FSi_TranslateCommand(p_dir, FS_COMMAND_READDIR)
					!= FS_RESULT_SUCCESS)
					return	FS_RESULT_FAILURE;
				/* if no match, ignore */
				if((is_directory != etr.is_directory) ||
					(name_len != etr.name_len) ||
					my_strnicmp(path, etr.name, name_len))
					continue;
				/* if match to directory, move to its first position */
				if(is_directory) {
					path += name_len;
					p_dir->arg.seekdir.pos = etr.dir_id;
					(void)FSi_TranslateCommand(p_dir, FS_COMMAND_SEEKDIR);
					break;
				}
				/* if match to file with expecting directory, FAILURE */
				else if(is_dir) {
					return	FS_RESULT_FAILURE;
				}
				/* file is found, return its index */
				else {
					*p_dir->arg.findpath.result.file = etr.file_id;
					return	FS_RESULT_SUCCESS;
				}

			}
		}
	}
	/* if it reaches end of path without matching file, FAILURE */
	if(!is_dir)
		return	FS_RESULT_FAILURE;
	/* directory is found, return its first position */
	*p_dir->arg.findpath.result.dir = p_dir->prop.dir.pos;
	return	FS_RESULT_SUCCESS;
}

static FSResult	FSi_GetPathCommand(FSFile *p_file)
{
	FSArchive	* const p_arc = p_file->arc;

	FSGetPathInfo	*p_info = &p_file->arg.getpath;

	FSDirEntry	entry;
	FSFile	tmp;
	u32	dir_id;
	u32	file_id;
	u32	id;
	u32	len;

	enum { INVALID_ID = 0x10000 } ;

	FS_InitFile(&tmp);
	tmp.arc = p_file->arc;

	if(FS_IsDir(p_file)) {
		/* If directory, no special processing needed */
		dir_id = p_file->prop.dir.pos.own_id;
		file_id = INVALID_ID;
	} else {
		/* If file, directory is unknown */
		file_id = p_file->prop.file.own_id;
		if(p_info->total_len != 0) {
			/* If already searched, no special processing needed */
			dir_id = p_info->dir_id;
		} else {
			/* If unsearched, search directory from FNT */
			u32	pos = 0;
			u32	num_dir = 0;
			dir_id = INVALID_ID;
			do {
				/* Search all directories exhaustively */
				FSi_SeekDirDirect(&tmp, pos);
				if(!pos)	num_dir = tmp.prop.dir.parent;
				/*
				 * Eliminate index-based search conditions so that   
				 * customized low-level commands can be used
				 */
				tmp.arg.readdir.p_entry = &entry;
				tmp.arg.readdir.skip_string = TRUE;
				while(FSi_TranslateCommand(&tmp, FS_COMMAND_READDIR) == FS_RESULT_SUCCESS) {
					if(!entry.is_directory && (entry.file_id.file_id == file_id)) {
						dir_id = tmp.prop.dir.pos.own_id;
						break;
					}
				}
			} while((dir_id == INVALID_ID) && (++pos < num_dir));
		}
	}
	/* If directory is not found, FALSE */
	if(dir_id == INVALID_ID) {
		p_info->total_len = 0;
		return	FS_RESULT_FAILURE;
	}
	/* If path length is not measured, measure once now */
	if(p_info->total_len == 0) {
		len = 0;
		/* First, add  "archive name:/" */
		if(p_arc->name.pack <= 0x000000FF)
			len += 1;
		else if(p_arc->name.pack <= 0x0000FF00)
			len += 2;
		else
			len += 3;
		len += 1 + 1;	/* ":/" */
		/* If necessary, add file name. (It has already been obtained.) */
		if(file_id != INVALID_ID)
			len += entry.name_len;
		/* Next, go back sequentially and add own name */
		id = dir_id;
		if(id != 0) {
			FSi_SeekDirDirect(&tmp, id);
			do {
				/* Move to parent, search for self */
				FSi_SeekDirDirect(&tmp, tmp.prop.dir.parent);
				tmp.arg.readdir.p_entry = &entry;
				tmp.arg.readdir.skip_string = TRUE;
				while(FSi_TranslateCommand(&tmp, FS_COMMAND_READDIR) == FS_RESULT_SUCCESS) {
					if(entry.is_directory && (entry.dir_id.own_id == id)) {
						len += entry.name_len + 1;
						break;
					}
				}
				id = tmp.prop.dir.pos.own_id;
			} while(id != 0);
		}
		/* Save the data after calculation */
		p_info->total_len = (u16)(len + 1);
		p_info->dir_id = (u16)dir_id;
	}
	/* If the path length calculation is correct, attempt is successful */
	if(!p_info->buf)
		return	FS_RESULT_SUCCESS;
	/* If buffer length is insufficient, attempt failed */
	if(p_info->buf_len < p_info->total_len)
		return	FS_RESULT_FAILURE;
	/* Store from end of text string */
	else {
		u8	*dst = p_info->buf;
		u32	total = p_info->total_len;
		u32	pos = 0;
		/* First, add "archive name:/" */
		if(p_arc->name.pack <= 0x000000FF)
			len = 1;
		else if(p_arc->name.pack <= 0x0000FF00)
			len = 2;
		else
			len = 3;
		MI_CpuCopy8(p_arc->name.ptr, dst + pos, len);
		pos += len;
		MI_CpuCopy8(":/", dst + pos, 2);
		pos += 2;
		/* Obtain the base directory  */
		id = dir_id;
		FSi_SeekDirDirect(&tmp, id);
		if(file_id != INVALID_ID) {
			/* If necessary, add file name */
			tmp.arg.readdir.p_entry = &entry;
			tmp.arg.readdir.skip_string = FALSE;
			while(FSi_TranslateCommand(&tmp, FS_COMMAND_READDIR) == FS_RESULT_SUCCESS) {
				if(!entry.is_directory && (entry.file_id.file_id == file_id))
					break;
			}
			len = entry.name_len + 1;
			MI_CpuCopy8(entry.name, dst + total - len, len);
			total -= len;
		} else {
			/* If directory, just add termination */
			MI_WriteByte(dst + total - 1, '\0');
			total -= 1;
		}
		/* Next, go back sequentially and add own name */
		if(id != 0) {
			do {
				/* Move to parent */
				FSi_SeekDirDirect(&tmp, tmp.prop.dir.parent);
				tmp.arg.readdir.p_entry = &entry;
				tmp.arg.readdir.skip_string = FALSE;
				/* Add "/" */
				MI_WriteByte(dst + total - 1, '/');
				total -= 1;
				/* Child of parent, search  (previous self) */
				while(FSi_TranslateCommand(&tmp, FS_COMMAND_READDIR) == FS_RESULT_SUCCESS) {
					if(entry.is_directory && (entry.dir_id.own_id == id)) {
						len = entry.name_len;
						MI_CpuCopy8(entry.name, dst + total - len, len);
						total -= len;
						break;
					}
				}
				id = tmp.prop.dir.pos.own_id;
			} while(id != 0);
		}
		SDK_ASSERT(pos == total);
	}

	return	FS_RESULT_SUCCESS;
}

static FSResult	FSi_OpenFileFastCommand(FSFile *p_file)
{
	FSArchive	* const p_arc = p_file->arc;
	const FSFileID	*p_id = &p_file->arg.openfilefast.id;
	const u32	index = p_id->file_id;

	{
		/* Determine validity of FAT access area */
		u32	pos = (u32)(index * sizeof(FSArchiveFAT));
		if(pos >= p_arc->fat_size)
			return	FS_RESULT_FAILURE;
		else {
			// Obtain the image area of the specified file from FAT.
			FSArchiveFAT	fat;
			FSiSyncReadParam	param;
			param.arc = p_arc;
			param.pos = p_arc->fat + pos;
			FSi_ReadTable(&param, &fat, sizeof(fat));
			/* Open directly */
			p_file->arg.openfiledirect.top = fat.top;
			p_file->arg.openfiledirect.bottom = fat.bottom;
			p_file->arg.openfiledirect.index = index;
			return	FSi_TranslateCommand(p_file, FS_COMMAND_OPENFILEDIRECT);
		}
	}
	return	FS_RESULT_SUCCESS;
}

static FSResult	FSi_OpenFileDirectCommand(FSFile *p_file)
{
	/* Initialize file data */
	p_file->prop.file.top = p_file->arg.openfiledirect.top;
	p_file->prop.file.pos = p_file->arg.openfiledirect.top;
	p_file->prop.file.bottom = p_file->arg.openfiledirect.bottom;
	p_file->prop.file.own_id = p_file->arg.openfiledirect.index;

	return	FS_RESULT_SUCCESS;
}

static FSResult	FSi_CloseFileCommand(FSFile *p_file)
{
	(void)p_file;
	return	FS_RESULT_SUCCESS;
}


#endif	/* FS_IMPLEMENT */
