#include "ProcessMan.h"
#include "filesystem.h"
#include "desctable.h"
#include "debug.h"
#include "naskfunc.h"
#include <string.h>

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

void ProcessMan::Init(){
	new ((void *)buf) ProcessMan();
}

ProcessMan::ProcessMan(void)
	:curpid(1), plist(0), sysdll(0)
{
	//Process sys(0, SYSTEM_PAGE_DIRECTORY_PHY);
	//plist.Add(sys);

	//sysdll.Add(DLL(L"c:\\windows\\system32\\ntdll.dll"));
	sysdll.Add(DLL(L"c:\\windows\\system32\\ntdll.dll"));
}


ProcessMan::~ProcessMan(void)
{
}


void ProcessMan::CreateProcess(const wchar_t *filename){
	//FileSystem::File *file = FileSystem::Inst().CreateFile(filename, 0, 0, 0, 0, 0, 0);
	DWORD newpid = curpid++;
	DWORD pagedir = MemMan::Inst().malloc(true, true, newpid);

	Process p(newpid, pagedir, sysdll);

	plist.Add(p);
}


Process::Process(DWORD pid, DWORD cr3, Vector<DLL> &sysdll):pid(pid),cr3(cr3),MainThread(NULL){
	DWORD *ppagedir = getppagedir();
	for(int i = 0; i < 0x400; i++){
		ppagedir[i] = 0;
	}

	//dt->set_gatedesc();
	for(DWORD i = 0; i < sysdll.GetCount(); i++){
		sysdll[i]->setpagedir(this);
	}


	DWORD eip = 0;
	if(!sysdll[0]->getprocaddr("entry", &eip)){
		debugprint("ntdll does not have func 'entry'");
		hung();
	}
	MainThread = ThreadMan::Inst().CreateThread(this, eip);

}

Process::~Process(){
}

DWORD Process::VirtualAlloc(DWORD addr, bool phy, DWORD phyaddr, bool isreadonly
	, DWORD pagecount, bool reserve, bool onlycommit)
{
	if(onlycommit){
		for(DWORD i = 0; i < pagecount; i++){
			DWORD *pagetableent = getpagetableent(addr + i * PAGE_SIZE);
			*pagetableent |= ePAGE_TABLE_ENT::COMMIT;
			
		}
	}else{
		DWORD i = addr;
		for(i = addr; i < END_OF_VMEM; i += PAGE_SIZE){
			bool empty = true;
			DWORD j = 0;
			for(j = 0; j < pagecount; j++){
				DWORD *pagetableent = getpagetableent(i + j * PAGE_SIZE);
				if(*pagetableent & ePAGE_TABLE_ENT::RESERVE){
					//予約済み
					empty = false;
					break;
				}else{
					//新たにコミットできる
				}
			}
			if(empty){
				break;
			}
			i += j * PAGE_SIZE;
		}
		if(i >= END_OF_VMEM){
			return 0;
		}
		//DWORD *pagetableent = getpagetableent(addr);
		for(DWORD j = 0; j < pagecount; j++){
			DWORD *pagetableent = getpagetableent(i + j * PAGE_SIZE);
			//新たにコミットする
			*pagetableent |= 0;
			if(!reserve){
				//本当に予約のときはRESERVEとCOMMITを両方立てる
				*pagetableent |= ePAGE_TABLE_ENT::COMMIT;
			}
			*pagetableent |= ePAGE_TABLE_ENT::RESERVE;
			*pagetableent |= (isreadonly ? 0 : ePAGE_TABLE_ENT::READ_WRITE);
			if(phy){
				//物理アドレスが指定されているときはそれを指すようにする
				*pagetableent |= phyaddr;
			}else{
				//最初の#PF時に物理メモリを割り当てる
				*pagetableent |= ePAGE_TABLE_ENT::NEW_PAGE;
			}
		}
	}else{
	}
}

DWORD *Process::getpagetableent(DWORD addr){
	DWORD *ppagedir = getppagedir();
	DWORD *pagedir = &ppagedir[addr >> 22];
	if(*pagedir & ePAGE_DIR_ENT::PRESENT){
		//ディレクトリエントリ(ページテーブル)が既に存在していたらもう作らない
	}else{
		//ページテーブルがないので作成
		*pagedir = 0;
		*pagedir |= ePAGE_DIR_ENT::PRESENT;
		*pagedir |= ePAGE_DIR_ENT::READ_WRITE;
		//*pagedir |= ePAGE_DIR_ENT::;
		DWORD *pagetable = (DWORD *)MemMan::Inst().malloc(true, true, pid);
		for(int i = 0; i < 0x400; i++){
			pagetable[i] = 0;
		}
		*pagedir |= ((DWORD)pagetable) & ~0xfff;
	}
	DWORD *pagetable = (DWORD *)((*pagedir) & ~0xfff);
	DWORD *pagetableent = &pagetable[(addr >> 12) & 0x3ff];
	return pagetableent;
}


DLL::DLL(const wchar_t *filename){
	DWORD e_lfanew = 0;
	DWORD image_base = 0;
	file.reset(FileSystem::Inst().CreateFile(filename, 0, 0, 0, 0, 0, 0));
	if(file.get() == NULL){
		debugprint("could not open file in DLL::DLL");
		hung();
	}

	header.reset((BYTE *)MemMan::Inst().malloc(true, true, 0)
		, phymemdeleter(1));
	file->Read(header.get(), PAGE_SIZE < file->Size() ? PAGE_SIZE : file->Size());

	DWORD count = MemMan::neccesarypagecount(nt()->OptionalHeader.SizeOfImage);
	BYTE *newheader = (BYTE *)MemMan::Inst().malloc(true, true, 0
		, count);
	debugprint("size:%p %p\n", nt()->OptionalHeader.SizeOfImage, count);

	memset(newheader, 0, count * PAGE_SIZE);
	file->Seek(0, FileSystem::File::SEEK_ORG::SET);
	file->Read(newheader
		, nt()->OptionalHeader.SizeOfHeaders < file->Size() 
		? nt()->OptionalHeader.SizeOfHeaders : file->Size());
	debugprint("size2:%p\n", nt()->OptionalHeader.SizeOfHeaders);

	header.reset(newheader, phymemdeleter(count));

	//debugprint("test:%c%c %p\n", header.get(), header.get() + 1, nt()->FileHeader.NumberOfSections);

	for(int i = 0; i < nt()->FileHeader.NumberOfSections; i++){
		debugprint("s:%d %d %d\n", i, sec(i)->VirtualAddress, sec(i)->SizeOfRawData);
		file->Seek(sec(i)->PointerToRawData, FileSystem::File::SEEK_ORG::SET);
		file->Read(header.get() + sec(i)->VirtualAddress, sec(i)->SizeOfRawData);
	}
}

DLL::~DLL(){
}

bool DLL::getprocaddr(const char *name, DWORD *addr){
	IMAGE_EXPORT_DIRECTORY *e = (IMAGE_EXPORT_DIRECTORY *)(header.get()
		+ nt()->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	bool found = false;
	debugprint("get:%p %p %p\n", e->AddressOfFunctions, e->AddressOfNameOrdinals, e->Base);
	debugprint("dllname:%s\n", e->Name + header.get());

	DWORD *names = (DWORD *)(header.get() + e->AddressOfNames);
	for(DWORD i = 0; i < e->NumberOfNames; i++){
		char *s = (char *)(header.get() + names[i]);
		//debugprint("%s\n", s);
		if(strcmp(s, name) == 0){
			WORD *nameords = (WORD *)(header.get() + e->AddressOfNameOrdinals);
			*addr = ((DWORD *)(header.get() + e->AddressOfFunctions))[nameords[i]];
			*addr += nt()->OptionalHeader.ImageBase;
			found = true;
			break;
		}
	}
	return found;
}


void DLL::setpagedir(Process *p){
	DWORD base = nt()->OptionalHeader.ImageBase;
	p->VirtualAlloc(base, true, (DWORD)header.get(), true);
}
