#include "filesystem.h"
#include "screen.h"
#include "debug.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <boost/scoped_array.hpp>

BYTE FileSystem::buf[sizeof(FileSystem)];

void FileSystem::Init(BOOTINFO *binfo){
	new ((void *)buf) FileSystem(binfo);
}

FileSystem::FileSystem(BOOTINFO *binfo):binfo(binfo){
	//m_drivenum = binfo->bootdrive & ~0x80;
	m_drivenum = 0;
	
	//ClearTrace();
	pio_set_iobase_addr(0x1f0, 0x3f0 , 0); // primary
	//ShowAll();
	int rc = 0;
	//ClearTrace();
	int numDev = reg_config();
	//ShowAll();
	debugprint("Found %d devices\n", numDev);

	//---------- Try some commands in "polling" mode

	//debugprint("Polling mode...\n" );

	// do an ATA soft reset (SRST) and return the command block
	// regs for device 0 in struct reg_cmd_info

	//debugprint("Soft Reset...\n" );
	ClearTrace();
	rc = reg_reset(0, m_drivenum);
	//debugprint("result:%d\n", rc);
	//ShowAll();
	if(rc){
		debugprint("error! reg_reset failed\n");
		ShowAll();
		hung();
	}
	//パーティションテーブルを読む
	if(ReadSector(m_drivenum, 1, 0, (BYTE *)&mbr, sizeof(mbr), 1)){
		debugprint("error! ReadSector failed\n");
		ShowAll();
		hung();
	}
	/*for(int i = 0; i < 32; i++){
		for(int j = 0; j < 16; j++){
			debugprint("%02X ", ((BYTE *)&mbr)[i * 16 + j]);
		}
		debugprint("\n");
	}*/
	
	//debugprint("beginlba:0x%X\n", mbr.pt[pat_index].begin_lba);
	if(ReadSector(m_drivenum, 1, mbr.pt[pat_index].begin_lba, readbuf, READBUFSIZE, 1)){
		debugprint("error! ReadSector failed\n");
		ShowAll();
		hung();
	}
	memcpy(&bootsect, readbuf, sizeof(bootsect));

	if(getclustersize() > READBUFSIZE){
		debugprint("error! getclustersize()(%d) > READBUFSIZE(%d)\n", getclustersize(), READBUFSIZE);
		ShowAll();
		hung();
	}

	FileList(bootsect.BPB_RootClus);
}

//----------------------class FileSystem::File--------------------------//
FileSystem::File::File(FileSystem *fs, const wchar_t *filename, DIRENT *ent, DWORD pos):fs(fs){
	int len = wcslen(filename);
	this->filename = new wchar_t [len + 1];
	memcpy(this->filename, filename, (len + 1) * sizeof(wchar_t));
	size = ent->DIR_FileSize;
	curpos = pos;
	startclus = ent->getfirstclus();
	curclus = INVLID_CLUS;
	//debugprint("ad:%d %p\n", fs->bootsect.BPB_BytsPerSec, &fs->bootsect.BPB_BytsPerSec);

}

int FileSystem::File::Seek(DWORD offset, SEEK_ORG seekorg){
	DWORD newoffset = 0;


	if(seekorg == SEEK_ORG::SET){
		newoffset = offset;
	}else if(seekorg == SEEK_ORG::CUR){
		newoffset = curpos + offset;
	}else if(seekorg == SEEK_ORG::END){
		newoffset = size + offset;
	}else{
		debugprint("can not recognize seekorg:%d in %s\n", seekorg, __FUNCTION__);
		return 1;
	}

	DWORD virtual_clus = fs->getclusindexbysize(newoffset);
	DWORD virtual_cur_clus = fs->getclusindexbysize(curpos);
	if(virtual_clus != virtual_cur_clus){
		curclus = -1;
	}
	curpos = newoffset;
	return 0;
}

int FileSystem::File::Read(BYTE *buf, int len){
	if(size < (len + curpos)){
		len = size - curpos;
	}
	//debugprint("c:%d %d %d\n", curpos, curclus, fs->getclustersize());

	BYTE *oldbuf = buf;
	reload_curclus();
	DWORD readclus = ((size + len - 1) / fs->getclustersize()) - (size / fs->getclustersize()) + 1;
	//debugprint("a:%d %d\n", curpos, curclus);


	//最初のクラスタを読む
	DWORD firstremaind = fs->getclustersize() - curpos % fs->getclustersize();
	DWORD firstreadbyte = firstremaind > len ? len : firstremaind;
	fs->ReadCluster(curclus);
	memcpy(buf, fs->readbuf + curpos % fs->getclustersize(), firstreadbyte);
	buf += firstreadbyte;
	curpos += firstreadbyte;
	//debugprint("firstreadbyte:%d\n", firstreadbyte);
	if(firstremaind <= len){
		curclus = fs->GetNextCluster(curclus);
	}

	//debugprint("2:%d\n", (len - firstreadbyte) / fs->getclustersize());
	//hung();

	//真ん中辺のクラスタを読む
	for(int i = 0; i < (len - firstreadbyte) / fs->getclustersize(); i++){
		fs->ReadCluster(curclus);
		memcpy(buf, fs->readbuf, fs->getclustersize());
		buf += fs->getclustersize();
		curclus = fs->GetNextCluster(curclus);
		curpos += fs->getclustersize();
	}

	//最後のクラスタを読む
	int remaind = (oldbuf + len) - buf;
	//debugprint("3:%d\n", remaind);

	if(remaind){
		fs->ReadCluster(curclus);
		memcpy(buf, fs->readbuf, remaind);
		curpos += remaind;
	}
	return len;
}

void FileSystem::File::reload_curclus(){
	if(curclus == INVLID_CLUS){
		curclus = startclus;

		for(int i = 0; i < curpos / fs->getclustersize(); i++){
			curclus = fs->GetNextCluster(curclus);
		}
	}
}


void FileSystem::ClearTrace( void ){
	// clear the command history and low level traces
	trc_cht_dump0();     // zero the command history
	trc_llt_dump0();     // zero the low level trace
}

void FileSystem::pause(){}

void FileSystem::ShowAll( void ){
	int lc = 0;
	char * cp;

	debugprint( "ERROR !\n" );

	// display the command error information
	trc_err_dump1();           // start
	while ( 1 ){
		lc++;
		cp = trc_err_dump2();   // get and display a line
		if ( cp == NULL )
		break;
		Screen::Inst().puts("1 ");
		Screen::Inst().puts(cp);
		Screen::Inst().puts("\n");
		}
	pause();
	debugprint("1 END\n");
//	for(;;);

	// display the command history
	trc_cht_dump1();           // start
	while ( 1 )
	{
		cp = trc_cht_dump2();   // get and display a line
		if ( cp == NULL )
		break;
		Screen::Inst().puts("2 ");
		Screen::Inst().puts(cp);
		Screen::Inst().puts("\n");
		lc ++ ;
		if ( ! ( lc & 0x000f ) )
		pause();
	}
	debugprint("2 END\n");

	// display the low level trace
	trc_llt_dump1();           // start
	while ( 1 )
	{
		cp = trc_llt_dump2();   // get and display a line
		if ( cp == NULL )
		break;
		Screen::Inst().puts("3 ");
		Screen::Inst().puts(cp);
		Screen::Inst().puts("\n");
		lc ++ ;
		if ( ! ( lc & 0x000f ) )
		pause();
	}
	debugprint("3 END\n");
	if ( lc & 0x000f )
	pause();
}


int FileSystem::ReadSector(int dev, int sectorcount, QWORD lba, BYTE *buf, int bufsize, int numsect){
	reg_buffer_size = bufsize;

	memset(buf, 0, bufsize);
	ClearTrace();
	int rc = reg_pio_data_in_lba48(dev, CMD_READ_SECTORS_EXT, 0, sectorcount,
	(DWORD)((lba & 0xFFFF00000000LLU) >> 32), (DWORD)(lba & 0xFFFFFFFF),
	0, (int)buf, numsect, 0
	);
	return rc;
	//ShowAll();
}

DWORD FileSystem::getclustersize()const{
	//debugprint("getclustersize %d * %d", bootsect.BPB_BytsPerSec, bootsect.BPB_SecPerClus);
	return bootsect.BPB_BytsPerSec * bootsect.BPB_SecPerClus;
}

DWORD FileSystem::firstdatasector()const{
	//FirstDataSector = BPB_ResvdSecCnt + (BPB_NumFATs * FATSz) + RootDirSectors;
	//RootDirSectors : always zero
	return bootsect.BPB_RsvdSecCnt + (bootsect.BPB_NumFATs * bootsect.BPB_FATSz32) + mbr.pt[pat_index].begin_lba;
}

DWORD FileSystem::getsectorbyclus(DWORD clus)const{
	return (clus - 2) * bootsect.BPB_SecPerClus + firstdatasector();
}

//sizeのデータを収めるのに必要なクラスタ数
DWORD FileSystem::getcluscountbysize(DWORD size)const{
	return (size + getclustersize() - 1) / getclustersize();
}

//sizeの場所が所属するクラスタのインデックス
DWORD FileSystem::getclusindexbysize(DWORD size)const{
	return (size / getclustersize());
}

//n番目のFATクラスタエントリのセクター
DWORD FileSystem::getfatsector(DWORD n)const{
	return bootsect.BPB_RsvdSecCnt + (n * bootsect.BPB_FATSz32) + mbr.pt[pat_index].begin_lba;
}


int FileSystem::ReadCluster(int clusternum){
	DWORD clus = getsectorbyclus(clusternum);
	if(ReadSector(m_drivenum, bootsect.BPB_SecPerClus
		, clus, readbuf, getclustersize(), bootsect.BPB_SecPerClus)){
		debugprint("error! ReadSector failed\n");
		ShowAll();
		hung();
	}
	return 1;
}


int FileSystem::FileList(DWORD dirclus){
	ReadCluster(dirclus);
	DIRENT *dirent = (DIRENT *)readbuf;

	int i = 0;
	for(i = 0; i < READBUFSIZE / sizeof(DIRENT); i++){
		if(dirent[i].DIR_Name[0] == '\0'){
			break;
		}
		char s[100] = "";
		int p = 0;
		for(int j = 0; j < 8; j++){
			if(dirent[i].DIR_Name[j] == '\x20'){
				break;
			}
			
			s[p++] = dirent[i].DIR_Name[j];
		}
		if(dirent[i].DIR_Name[8] != '\x20'){
			s[p++] = '.';
		}
		for(int j = 8; j < 11; j++){
			if(dirent[i].DIR_Name[j] == '\x20'){
				break;
			}
			s[p++] = dirent[i].DIR_Name[j];
		}
		//debugprint("%s %d\n", s, dirent[i].DIR_FileSize);
	}
	//debugprint("ab:%d %p\n", bootsect.BPB_BytsPerSec, &bootsect.BPB_BytsPerSec);

	return i;
}



FileSystem::File *FileSystem::CreateFile(
	const wchar_t *filename,
	DWORD  dwAccess,             // アクセス指定
	DWORD  dwShare,              // 共有方法
	DWORD psa,    // セキュリティ属性
	DWORD  dwCreateDisposition,  // 動作指定
	DWORD  dwFlagsAndAttributes, // フラグと属性
	HANDLE hTemplate             // テンプレートファイル
){
	//debugprint("test:%c %c %c\n", filename[1], filename[3], filename[4]);

	if(iswalpha(filename[0]) && filename[1] == L':'){
		//絶対パス指定
		if(towupper(filename[0]) == L'C'){
			int pos = 3, prevpos = pos;
			DWORD curclus = bootsect.BPB_RootClus;

			//debugprint("finding:%p\n", wcslen(filename) + 1);
			boost::scoped_array<wchar_t> finding(new wchar_t[wcslen(filename) + 1]);

			while(1){
				for(; filename[pos] != L'\\' && filename[pos] != L'/' && filename[pos] != L'\0'; pos++){
					finding[pos - prevpos] = filename[pos];
				}
				finding[pos - prevpos] = L'\0';
				pos++;
				prevpos = pos;

				bool found = false;
				DIRENT ent;
				if(!FindEnt(curclus, finding.get(), &ent)){
					//見つからなかった
					debugprint("not found %c %c %c\n", finding[0], finding[1], finding[2]);
					return NULL;
				}
				
				if(filename[pos - 1] == L'\0'){
					//最後のサブディレクトリ(ファイル)なら

					if(ent.DIR_Attr & ATTR_DIRECTORY){
						debugprint("opening directory... not implemented\n");
						hung();
					}

					return new File(this, filename, &ent, 0);
				}
				if((ent.DIR_Attr & ATTR_DIRECTORY) == 0){
					//最後のサブディレクトリ(ファイル)じゃないのに普通のファイルだった
					//c:\dir\file.txt\dir みたいな
					return NULL;
				}
				curclus = ent.getfirstclus();
			}
		}else{
			return 0;
		}
	}
}



void FileSystem::ConvertShortName(char *dest, const char *name){
	int p = 0;
	for(int j = 0; j < 8; j++){
		if(name[j] == '\x20'){
			break;
		}
		dest[p++] = name[j];
	}
	if(name[8] != '\x20'){
		dest[p++] = '.';
	}
	for(int j = 8; j < 11; j++){
		if(name[j] == '\x20'){
			break;
		}
		dest[p++] = name[j];
	}
	dest[p] = '\0';
}

bool FileSystem::FindEnt(DWORD dirclus, const wchar_t *finding, DIRENT *fdirent){
	char name[20];
	bool found = false;
	while(1){
		dirclus &= 0x0FFFFFFF;
		//クラスタチェーンの終了==最後のエントリまで探したけど見つからなかった
		if(dirclus >= 0x0ffffff8){
			break;
		}

		ReadCluster(dirclus);
		DIRENT *dirent = (DIRENT *)readbuf;
		int i = 0;
		for(; i < getclustersize() / sizeof(DIRENT); i++){
			if(dirent[i].DIR_Name[0] == 0xE5){//未使用エントリ
				continue;
			}
			if(dirent[i].DIR_Name[0] == 0x00){//最終エントリ
				break;
			}
			if(dirent[i].DIR_Attr & 0x0F){//long name エントリ
				continue;
			}
			ConvertShortName(name, dirent[i].DIR_Name);

			//ファイル名の比較
			wchar_t wname[50];
			mbstowcs(wname, name, sizeof(wname) / sizeof(wname[0]));
			if(wcslen(wname) == wcslen(finding)){
				if(wcsicmp(wname, finding) == 0){
					found = true;
					*fdirent = dirent[i];
					break;
				}
			}
			/*char test[300];
			for(int j = 0; j < 300; j++){
				if(!(test[j] = finding[j])){
					break;
				}
			}*/
			//debugprint("s:%s %s %d\n", name, test, found);

		}
		//目的のファイルが見つかったので次のサブディレクトリ探しへ
		if(found){
			break;
		}
		//このクラスタでディレクトリエントリが終わっていた
		if(i != getclustersize() / sizeof(DIRENT)){
			break;
		}
		dirclus = GetNextCluster(dirclus);
	}
	return found;
}

DWORD FileSystem::GetNextCluster(DWORD clus){
	clus &= 0x0fffffff;

	DWORD sector = getfatsector() + (clus * 4 / bootsect.BPB_BytsPerSec);
	ReadSector(m_drivenum, 1, sector, readbuf, bootsect.BPB_BytsPerSec, 1);

	DWORD next = ((DWORD *)readbuf)[clus % (bootsect.BPB_BytsPerSec / 4)];
	//debugprint("n:%d -> %d\n", clus, next);

	return next;
}
