/*
 * orig_fs.c
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * オリジナルファイルシステム
 *
 * ブロックサイズはページサイズ、inodeはデバイスのセクターサイズ、ディレクトリはブロックサイズにする。
 */


#include"config.h"
#include"types.h"
#include"lib.h"
#include"errno.h"
#include"mm.h"
#include"fs.h"
#include"device.h"
#include"orig_fs.h"


enum{
	PT_TYPE_ORIG=0x9a,		/* Partition type number（仮） */
};


static SUPER_BLOCK *super_block[MAX_DEVICE_OPEN];
static EMPTY *empty_blk[MAX_DEVICE_OPEN];
static uchar block_sect[MAX_DEVICE_OPEN];

static void *mount(int);
static int umount(int);
static void *open(const char*,void*,int);
static int read(void*,void*,size_t,size_t);
static int write(void*,void*,size_t,size_t);
static int ioctr(void*,int,void*);
static int rename(void*,const char*);
static int creat(const char*,void*,int);
static void *opendir(const char*,void*,int);
static int mkdir(const char*,void*,int);
static int close(void*);


static FS orig_fs={
	"orig_fs",mount,umount,open,read,write,ioctr,rename,creat,opendir,mkdir,close
};


/************************************************************************************
 *
 * Misc functions
 *
 ************************************************************************************/


/*
 * cmpare path strings
 * parameters : Destination string(end='\0'),Sorce string(end='/' or '\0')
 * return : 一致=0 or 大=1 or 小=-1;
 */
extern inline int cmppath(const char *path1,const char *path2)
{
	while(*path1++==*path2++)
		if(*(path1-1)=='\0')return 0;

	if((*--path1=='\0')&&(*--path2=='/'))return 0;
	if(*path1<*path2)return 1;

	return -1;
}


/*
 * パス文字列で次のパス名のポインタを返す。
 * parameters : path
 * return : next path or error=NULL
 */
extern inline const char *get_next_path(const char *path)
{
	while(*path++!='/');

	return path;
}


/*
 * 最後のパス文字列かどうかを判定する。
 * parameters : path
 * return : 最後 1,最後でない 0
 */
extern inline int is_last_path(const char *path)
{
	if(*path=='\0')return 1;

	while(*path++!='/')
		if(*path=='\0')return 1;

	return 0;
}


/*
 * Get empty block
 * parameters : device inode number
 * return : block number or error=0
 */
uint get_block(int din)
{
	uint block;


	/* 空きブロックインデックスがない場合 */
	if(empty_blk[din]->current==0)
	{
		if(empty_blk[din]->next==0)return 0;
		block=empty_blk[din]->block[0];
		if(dinode[din].dev->read(empty_blk[din],block_sect[din],empty_blk[din]->next)!=block_sect[din])
			return 0;
		return block;
	}
	else
		return empty_blk[din]->block[empty_blk[din]->current--];
}


/************************************************************************************
 *
 * directory functions
 *
 ************************************************************************************/

/*---------------------------- 二分木操作関数 ---------------------------------------*/

/*
 * search paretnt entry
 * parameters : value,root entyr addoress pointer,directory buffer addres
 * return : parent entry or NULL
 */
DIR_ENT *search_parent(const char *path,ushort *root,uint buf_add)
{
	int r;
	DIR_ENT *p,*q;


	for(q=(DIR_ENT*)(*root+buf_add);q!=(DIR_ENT*)buf_add;)
	{
		p=splay(q,root);

		r=cmppath(p->name,path);
		if(r<0)
		{
			q=(DIR_ENT*)(p->low+buf_add);
			++p->low_num;
		}
		else if(r>0)
		{
			q=(DIR_ENT*)(p->high+buf_add);
			++p->high_num;
		}
		else return NULL;
	};

	return p;
}


/*
 * search same name entry
 * parameters : path,root entry addoress pointer,directory buffer addres
 * return : same name entry or NULL
 */
DIR_ENT *search_same(const char *path,DIR_ENT *p,uint buf_add)
{
	int r;


	while(p!=(DIR_ENT*)buf_add)
	{
		r=cmppath(p->name,path);
		if(r<0)p=(DIR_ENT*)(p->low+buf_add);
		else if(r>0)p=(DIR_ENT*)(p->high+buf_add);
		else return p;
	}

	return NULL;
}


/*
 * round entry
 * parameters : top entry,root entry address pointer,directory buffer addres
 * return : new top entry
 */
DIR_ENT *round_low(DIR_ENT *top,ushort *root,uint buf_add)
{
	DIR_ENT *p;


	if((p=(DIR_ENT*)(top->high+buf_add))==(DIR_ENT*)buf_add)return top;
	if((top->high=p->low)!=0)p->low->parent=(uint)top-buf_add;
	top->high_num=p->low_num;
	p->low=(uint)top-buf_add;
	p->low_num+=top->low_num+1;
	if((p->parent=top->parent)!=0)
	{
		if(p->parent->low==(uint)top-buf_add)p->parent->low=(uint)p-buf_add;
		else p->parent->high=(uint)p-buf_add;
	}
	else *root=(uint)p-buf_add;
	top->parent=(uint)p-buf_add;

	return p;
}

DIR_ENT *round_high(DIR_ENT *top,ushort *root,uint buf_add)
{
	DIR_ENT *p;


	if((p=(DIR_ENT*)(top->low+buf_add))==(DIR_ENT*)buf_add)return top;
	if((top->low=p->high)!=0)p->high->parent=(uint)top-buf_add;
	top->low_num=p->high_num;
	p->high=(uint)top-buf_add;
	p->high_num+=top->high_num+1;
	if((p->parent=top->parent)!=0)
	{
		if(p->parent->low==(uint)top-buf_add)p->parent->low=(uint)p-buf_add;
		else p->parent->high=(uint)p-buf_add;
	}
	else *root=(uint)p-buf_add;
	top->parent=(uint)p-buf_add;

	return p;
}


/*
 * 二分木扁平化関数
 * 片側のエントリー数が反対側のエントリー数の2倍より多く、かつ
 * その片側の内側のエントリー数が片側のエントリー数の1/2より少ない場合、反対側へ回転させる。
 * parameters : top entry,root entry address pointer,directory buffer address
 * return : new top entyr or error=NULL
 */
DIR_ENT *splay(DIR_ENT *top,ushort *root,uint buf_add)
{
	if((top->high_num>top->low_num*2)&&(top->high->low_num<top->high_num/2))
		return round_low(top,root,buf_add);
	else if((top->low_num>top->high_num*2)&&(top->low->high_num<top->low_num/2))
		return round_high(top,root,buf_add);

	return top;
}

/*
 * エントリー数の少ない方へ回転させ、二分木内でエントリーを下降させて、リンクから削除する。
 * parameters : delete path,root entry address pointer,directory buffer address
 * return : delete entry or error=NULL
 */
DIR_ENT *down_and_del(const char *path,ushort *root,uint buf_add)
{
	DIR_ENT *p,*q;


	if((p=search_same(path,root,buf_add))==NULL)return NULL;

	/* rootへ遡りながらエントリー数を1つ減らしていく。 */
	for(q=p;q->parent!=0;q=(DIR_ENT*)(q->parent+buf_add))
	{
		if(q->parent->low==(uint)q-buf_add)--((DIR_ENT*)(q->parent+buf_add))->low_num;
		else --((DIR_ENT*)(q->parent+buf_add))->high_num;
	}

	/* 下降させる。 */
	while(p->low_num*p->high_num)
	{
		if(p->low_num<p->high_num)--round_low(p,root)->low_num;
		else --round_high(p,root)->high_num;
	}

	/* 削除 */
	if(p->parent==0)
	{
		*root=(uint)p->low+(uint)p->high;
		if(*root!=0)((DIR_ENT*)(*root+buf_add))->parent=0;
	}
	else
	{
		if(((DIR_ENT*)(p->parent+buf_add))->low==(uint)p-buf_add)
		{
			((DIR_ENT*)(p->parent+buf_add))->low=(uint)p->low+(uint)p->high;
			((DIR_ENT*)(p->parent+buf_add))->low_num=p->low_num+p->high_num;
			if(((DIR_ENT*)(p->parent+buf_add))->low!=0)
				((DIR_ENT*)(((DIR_ENT*)(p->parent+buf_add))->low+buf_add))->parent=p->parent;
		}
		else
		{
			((DIR_ENT*)(p->parent+buf_add))->high=(uint)p->low+(uint)p->high;
			((DIR_ENT*)(p->parent+buf_add))->high_num=p->low_num+p->high_num;
			if(((DIR_ENT*)(p->parent+buf_add))->high!=NULL)
				((DIR_ENT*)(((DIR_ENT*)(p->parent+buf_add))->low+buf_add))->parent=p->parent;
		}
	}

	return p;
}


/*---------------------------- 検索関数 ---------------------------------------*/

/*
 * 最後のディレクトリを引数のバッファに読み込んで、最後のパス名を返す。
 * parameters : path,directoy address,device inode number
 * return : path or error=NULL
 */
const char *get_last_directory(const char *path,DIR *dir,int din)
{
	DIR_ENT *entry;


	for(;is_last_path(path)==0;path=get_next_path(path))
	{
		/* directory entryを検索する。 */
		if((entry=search_same(path,(DIR_ENT*)(dir->entry_top+buf_add),(uint)dir))==NULL)
			do
			{
				if(dir->next_secter==0)return NULL;
				if(dinode[din].dev->read(dir,block_sect[din],dir->next_blk+dinode[din].begin_blk)!=block_sect[din])
					return NULL;
			}while((entry=search_same(path,(DIR_ENT*)(((DIR_NEXT*)dir)->entry_top+buf_add),(uint)dir))==NULL);
		if(entry->type!=DIRECTORY)return NULL;

		/* Read directory */
		if(dinode[din].dev->read(dir,block_sect[din],entry->block+dinode[din].begin_blk)!=block_sect[din])
			return NULL;
	}

	return path;
}


/*
 * search same name entry
 * parameters : path,directory
 * return : entry point ot false=NULL
 */
DIR_ENT *search_same_entry(const char *path,DIR *dir)
{
	DIR_ENT *entry;
	DIR_NEXT *buf;


	/* directory entryを検索する。 */
	if((entry=search_same(path,(DIR_ENT*)(dir->entry_top+buf_add),(uint)dir))==NULL)
	{
		if(dir->next_secter==0)return NULL;
		else
		{
			if((buf=(DIR_NEXT*)kmalloc(DIR_SIZE))==NULL)return NULL;
			do
			{
				if(dinode[din].dev->read(buf,block_sect[din],dir->next_sect+dinode[din].begin_blk)!=block_sect[din])
					return NULL;
			}while((entry=search_same(path,(DIR_ENT*)(((DIR_NEXT*)dir)->entry_top+buf_add),(uint)dir))==NULL);
			kfree(buf);
		}
	}

	return entry;
}


/*---------------------------- エントリー追加削除関数 ---------------------------------------*/

/*
 * get empty entry
 * parameters : path length,buffer addres
 * return : new entry or error=NULL
 */
DIR_ENT *get_empty_entry(EMPTY_ENT *top,int pathlen,uint buf_add)
{
	enum{MIN_ENT_SIZE=sizeof(DIR_ENT)-(MAX_NAME_SIZE-sizeof(int))};

	int size;


	size=ROUNDUP(sizeof(DIR_ENT)-(MAX_NAME_SIZE-pathlen),sizeof(int));
	p=top;
	do
	{
		if(p->size>=size)
		{
			if(p->size-size>=MIN_ENT_SIZE)
			{
				ushort a=(uint)p-buf_add+size;
				EMPTY_ENT *q=(EMPTY_ENT*)((uint)p+size);

				q->next=p->next;
				q->prev=p->prev;
				q->size=p->size-size;
				((EMPTY_ENT*)(p->prev+buf_add))->next=a;
				((EMPTY_ENT*)(p->next+buf_add))->prev=a;
			}
			else
			{
				((EMPTY_ENT*)(p->prev+buf_add))->next=p->next;
				((EMPTY_ENT*)(p->next+buf_add))->prev=p->prev;
			}

			return p;
		}
	}while((p=(EMPTY_ENT*)(p->next+buf_add))!=top);

	return NULL;
}


/*
 * ディレクトリエントリーを追加する。
 * タイプとセクターナンバーは未入力で返す。
 * parameters : add value,root joint adoress pointer
 * return : directory entry or error=NULL
 */
DIR_ENT *add_entry_to_dir(const char *path,DIR *dir,int din)
{
	if((path=get_last_directory(path,dir,din))==NULL)return NULL;
	if(search_same_entry(path,dir)!=NULL)return NULL;

	/*  */
	if((parent=search_parent(path,root))==NULL)return -1;
	else
	{
		if((current=(DIR_ENT*)kmalloc(sizeof(DIR_ENT)))==NULL)return -1;
		if(comppath(parent->name,path)==-1)parent->low=current;
		else parent->high=current;
	}
	current->value=value;
	current->parent=parent;
	current->low=NULL;
	current->high=NULL;
	current->low_num=0;
	current->high_num=0;
	current->next=NULL;		/* テスト用 */

	return 0;
}


/*
 * Add new directory entry
 * paramters : path,directory,device inode number
 * return : derectory entry or error=NULL
 */
DIR_ENT *add_entry_to_dir(const char *path,DIR *dir,int din)
{
	int path_size;
	int hash;
	uint top_blk,current_blk;
	DIR *buf;
	DIR_ENT *dir_ent;
	char *p;


	/* 名前のサイズの確認 */
	path_size=strlen(path)+1;
	if(path_size>MAX_NAME_SIZE)return NULL;

	/* 同じ名前が存在するか確認 */
	if(search_dir(path,dir)!=NULL)return NULL;

	/* ディレクトリエントリー空きスペースがあるか確認する。 */
	top_blk=current_blk=dir->my_block;
	while(BLOCK_SIZE-(sizeof(DIR_ENT)-MAX_NAME_SIZE)-dir->empty<path_size)
	{
		/* 新しいリンクディレクトリを割り当てる。 */
		if(dir->next_blk==0)
		{
			/*
			 * 新しいブロックを割り当てて前のディレクトリを書き戻し、
			 * トップディレクトリのサイズメンバーを更新する。
			 */
			if((dir->next_blk=get_block(din))==0)return NULL;
			if(dir->my_block==top_blk)
			{
				dir->size+=BLOCK_SIZE;
				if(dinode[din].dev->write(dir,block_sect[din],current_blk+dinode[din].begin_blk)!=block_sect[din])return NULL;
			}
			else
			{
				if(dinode[din].dev->write(dir,block_sect[din],current_blk+dinode[din].begin_blk)!=block_sect[din])return NULL;
				if(dinode[din].dev->read(dir,1,top_blk+dinode[din].begin_blk)!=1)return NULL;
				dir->size+=BLOCK_SIZE;
				if(dinode[din].dev->write(dir,1,top_blk+dinode[din].begin_blk)!=1)return NULL;
			}

			/* 新しいリンクディレクトリの初期化 */
			current_blk=dir->next_blk;
			memset(dir,0,sizeof(DIR));
			dir->my_block=current_blk;
			dir->empty=sizeof(DIR);

			break;
		}
		else
		{
			/* リンクディレクトリを読む。 */
			if(dinode[din].dev->read(dir,block_sect[din],dir->next_blk+dinode[din].begin_blk)!=block_sect[din])return NULL;

			/* 同じ名前が存在するか確認 */
			if(search_dir(path,dir)!=NULL)return NULL;
		}
	}

	/* 他のリンクディレクトリに同じ名前があるか確認 */
	if(dir->next_blk!=0)
	{
		if((buf=(DIR*)kmalloc(BLOCK_SIZE))==NULL)return NULL;

		do
		{
			/* リンクディレクトリを読む。 */
			if(dinode[din].dev->read(buf,block_sect[din],dir->next_blk+dinode[din].begin_blk)!=block_sect[din])goto ERR;

			/* 同じ名前が存在するか確認 */
			if(search_dir(path,dir)!=NULL)goto ERR;
		}while(buf->next_blk!=0);

		kfree(buf);
	}

	/* ディレクトリエントリーの設定 */
	dir_ent=(DIR_ENT*)((uint)dir+dir->empty);
	dir_ent->next=0;
	strncpy(dir_ent->name,path,path_size);

	/* ハッシュテーブルの設定 */
	hash=calc_hash(path);
	if(dir->hash[hash]==0)dir->hash[hash]=dir->empty;
	else
	{
		for(p=(char*)dir+dir->hash[hash];((DIR_ENT*)p)->next!=0;p=(char*)dir+((DIR_ENT*)p)->next);
		((DIR_ENT*)p)->next=dir->empty;
	}
	dir->empty+=sizeof(DIR_ENT)-MAX_NAME_SIZE+ROUNDUP(path_size,sizeof(int));

	return dir_ent;

ERR:
	kfree(buf);
	return NULL;
}


int del_entry()
{

}


/************************************************************************************
 *
 * Init functions
 *
 ************************************************************************************/


int init_orig_fs()
{
	int i;


	for(i=0;i<MAX_DEVICE_OPEN;++i)
	{
		super_block[i]=NULL;
		empty_blk[i]=NULL;
	}

	return regist_fs(&orig_fs);
}


/************************************************************************************
 *
 * System call interface
 *
 ************************************************************************************/


/*
 * return : 0 or error nuber
 */
void *mount(int din)
{
	void *buf;
	DEV_INFO *dev;


	dev=dinode[din].dev;

	/* Check partition table */
	if((dinode[din].pt_prn==1)&&(dinode[din].pt_type!=PT_TYPE_ORIG))return NULL;

	/* Read super block */
	if(super_block[din]!=NULL)return NULL;				/* すでにマウントされているか確認 */
	if((buf=kmalloc(BLOCK_SIZE))==NULL)return NULL;
	if(dev->read(buf,1,SBLOCK_SECTER+dev->begin_blk)!=1);
	if((super_block[din]=(SUPER_BLOCK*)kmalloc(sizeof(SUPER_BLOCK)))==NULL)goto ERR;
	memcpy(super_block[din],buf,sizeof(SUPER_BLOCK));

	/* Calc block/secter */
	block_sect[din]=BLOCK_SIZE/dev->secter_size;

	/* Init empty block */
	if(dev->read(buf,block_sect[din],empty_blk[din]->block[empty_blk[din]->current])!=block_sect[din])goto ERR;
	empty_blk[din]=(EMPTY*)buf;

	/* read root directory */
	if((buf=kmalloc(BLOCK_SIZE))==NULL)return NULL;
	if(dev->read(buf,block_sect[din],dev->begin_blk+block_sect[din])!=block_sect[din])goto ERR;

	return buf;

ERR:
	kfree(empty_blk[din]);
	kfree(buf);
	kfree(super_block[din]);

	return NULL;
}


/*
 *
 */
int umount(int din)
{
	return 0;
}


/*
 * inodeまたはディレクトリをメモリーに読み込んでポインタを返す。
 * parameters : path,current directory,Device inode number
 * return : inode or directory pointer
 */
void *open(const char *path,void *dir,int din)
{
	DIR_ENT *dir_ent;
	DIR *dir_buf=NULL;
	INODE *inode=NULL;


	/* Alloc directory buffer */
	if((dir_buf=(DIR*)kmalloc(BLOCK_SIZE))==NULL)return NULL;
	memcpy(dir_buf,dir,((DIR*)dir)->empty);

	/* Get last directory */
	if((path=get_last_directory(path,dir_buf,din))==NULL)goto ERR;

	if((dir_ent=search_dir(path,dir_buf))==NULL)goto ERR;

	/* Get inode */
	if(dir_ent->type==NORMAL_FILE)
	{
		if((inode=(INODE*)kmalloc(dinode[din].dev->secter_size))==NULL)goto ERR;
		if(dinode[din].dev->read(inode,1,dir_ent->block+dinode[din].begin_blk)!=1)goto ERR;
		kfree(dir_buf);

		return inode;
	}
	/* Get directory */
	else if(dir_ent->type==DIRECTORY)
	{
		if(dinode[din].dev->read(dir_buf,block_sect[din],dir_ent->block+dinode[din].begin_blk)!=block_sect[din])
			goto ERR;

		return dir_buf;
	}

ERR:
	kfree(dir_buf);
	kfree(inode);

	return NULL;
}


/*
 *
 */
int read(void *inode,void *buf,size_t size,size_t begin)
{
	return 0;
}


/*
 *
 */
int write(void *inode,void *buf,size_t size,size_t begin)
{
	return 0;
}


/*
 *
 */
int ioctr(void *inode,int command,void *param)
{
	return 0;
}


/*
 *
 */
int rename(void *inode,const char *name)
{
	return 0;
}


/*
 *
 */
int creat(const char *path,void *dir,int din)
{
	return 0;
}


/*
 *
 */
void *opendir(const char *path,void *dir,int din)
{
	return NULL;
}


/*
 * parameters : path,current directory,Device inode number
 * return : 0 or error=-1
 */
int mkdir(const char *path,void *dir,int din)
{
	uint new_blk;
	DIR_ENT *dir_ent;
	DIR *dir_buf=NULL;


	/* Alloc directory buffer */
	if((dir_buf=(DIR*)kmalloc(BLOCK_SIZE))==NULL)return -1;
	memcpy(dir_buf,dir,((DIR*)dir)->empty);

	/* Get last directory */
	if((path=get_last_directory(path,dir_buf,din))==NULL)goto ERR;

	/* Get directoy entry */
	if((dir_ent=add_to_dir_entry(path,dir,din))==NULL)goto ERR;

	/* Set new directory */
	if((new_blk=get_block(din))==0)

	return 0;

ERR:
	kfree(dir_buf);
	return -1;
}


/*
 *
 */
int close(void* inode)
{
	return 0;
}
