#include "Archive.h"
//#include "util.h"
#include <string.h>

//extern void wlog(char*str,...);

ArchiveFile::ArchiveFile(Archive*archive,unsigned long FIIndex,unsigned char openflags)
{
	m_Archive = archive;
	m_FIIndex = FIIndex;
	m_OpenFlags = openflags;

	if(m_Archive->m_AT[m_Archive->m_FT[m_FIIndex].ATStart] == CL_UNUSED)
	{
		//exception
	}

	if(m_OpenFlags & OT_WRITE)
	{//release all alloceted additional clusters
		if(m_Archive->m_AT[m_Archive->m_FT[m_FIIndex].ATStart] != CL_ENDOFFILE)
		{
			unsigned long nClusters = m_Archive->m_FT[m_FIIndex].Size/m_Archive->m_Info.ClusterSize;

			//same than DeleteFile
			unsigned long pAT = m_Archive->m_AT[m_Archive->m_FT[m_FIIndex].ATStart];
			while(m_Archive->m_AT[pAT] != CL_ENDOFFILE)
			{
				unsigned long h = m_Archive->m_AT[pAT];
				m_Archive->m_AT[pAT] = CL_UNUSED;
				m_Archive->WriteATItemChanges(pAT);
				pAT = h;
			}
			m_Archive->m_AT[pAT] = CL_UNUSED;
			m_Archive->WriteATItemChanges(pAT);
			//

			m_Archive->m_AT[m_Archive->m_FT[m_FIIndex].ATStart] = CL_ENDOFFILE;
			m_Archive->WriteATItemChanges(m_Archive->m_FT[m_FIIndex].ATStart);
		}

		m_Archive->m_FT[m_FIIndex].Size = 0;
	}

	m_Archive->m_FT[m_FIIndex].State = FS_OPENED;
	m_Archive->WriteFTItemChanges(m_FIIndex);

	m_FPos = 0;
	m_iCluster = m_Archive->m_FT[m_FIIndex].ATStart;
}

ArchiveFile::~ArchiveFile(void)
{
	m_Archive->m_FT[m_FIIndex].State = FS_VALID;
	m_Archive->WriteFTItemChanges(m_FIIndex);
}

void ArchiveFile::Write(const void* data, unsigned long size)
{
	if(!size)
		return;

	if(m_OpenFlags != OT_PLUS && m_OpenFlags == OT_READ)
		return;

	unsigned long opos;
	if(m_OpenFlags == OT_APPAND)
	{
		opos = m_FPos;
		Seek(m_Archive->m_FT[m_FIIndex].Size);
	}

	if(m_iCluster == CL_ENDOFFILE)//appand cluster for file
	{
		m_iCluster = AppandCluster();
	}
	
	unsigned long pcl = m_FPos%m_Archive->m_Info.ClusterSize;
	unsigned long maxwritethiscluster = m_Archive->m_Info.ClusterSize-pcl;
	
	unsigned long odata = 0;
	while(size)
	{
		unsigned long nwrite = size;
		if(nwrite > maxwritethiscluster)
			nwrite = maxwritethiscluster;
		
		fseek(m_Archive->m_File,m_Archive->m_ClusterOffset+m_Archive->m_Info.ClusterSize*m_iCluster+pcl,SEEK_SET);
		fwrite((unsigned char*)data+odata,nwrite,1,m_Archive->m_File);
		size -= nwrite;
		odata += nwrite;

		if(m_FPos+nwrite > m_Archive->m_FT[m_FIIndex].Size)
		{
			m_Archive->m_FT[m_FIIndex].Size = m_FPos+nwrite;
			m_Archive->WriteFTItemChanges(m_FIIndex);
		}

		Seek(m_FPos + nwrite);

		if(maxwritethiscluster == nwrite && size)//still has to write: cluster change
		{
			if(m_iCluster == CL_ENDOFFILE)//appand cluster for file
			{
				m_iCluster = AppandCluster();
			}
		}
    
		pcl = 0;
		maxwritethiscluster = m_Archive->m_Info.ClusterSize;
	}

	if(m_OpenFlags == OT_APPAND)
	{
		Seek(opos);
	}
}

void ArchiveFile::Read(void* data, unsigned long size)
{
	if(EoF() || !size)
		return;

	if(m_OpenFlags != OT_PLUS && m_OpenFlags != OT_READ)
	{
		memset(data, 0, size);
		return;
	}

	if(m_FPos + size > m_Archive->m_FT[m_FIIndex].Size)
		size = m_Archive->m_FT[m_FIIndex].Size-m_FPos;

	unsigned long pcl = m_FPos%m_Archive->m_Info.ClusterSize;
	unsigned long maxreadthiscluster = m_Archive->m_Info.ClusterSize-pcl;
	
	unsigned long odata = 0;
	while(size)
	{
		unsigned long nread = size;
		if(nread > maxreadthiscluster)
			nread = maxreadthiscluster;
		
		fseek(m_Archive->m_File,m_Archive->m_ClusterOffset+m_Archive->m_Info.ClusterSize*m_iCluster+pcl,SEEK_SET);
		fread((unsigned char*)data+odata,nread,1,m_Archive->m_File);
		size -= nread;
		odata += nread;

		Seek(m_FPos + nread);

		pcl = 0;
		maxreadthiscluster = m_Archive->m_Info.ClusterSize;
	}
}

void ArchiveFile::Seek(unsigned long pos)
{
	if(pos == m_FPos)
		return;
	if(pos > m_Archive->m_FT[m_FIIndex].Size)
		pos = m_Archive->m_FT[m_FIIndex].Size;
	m_FPos = pos;
	unsigned long nClusters = m_FPos/m_Archive->m_Info.ClusterSize;
	m_iCluster = m_Archive->m_FT[m_FIIndex].ATStart;
	for(nClusters;nClusters;nClusters--)
		m_iCluster = m_Archive->m_AT[m_iCluster];
}

unsigned long ArchiveFile::AppandCluster(void)
{
	unsigned long pAT = m_Archive->m_FT[m_FIIndex].ATStart;

	while(m_Archive->m_AT[pAT] != CL_ENDOFFILE)
	{
		pAT = m_Archive->m_AT[pAT];
	}

	unsigned long cl = m_Archive->FindUnusedCluster();

	m_Archive->m_AT[pAT] = cl;
	m_Archive->WriteATItemChanges(pAT);

	m_Archive->m_AT[cl] = CL_ENDOFFILE;
	m_Archive->WriteATItemChanges(cl);

	return cl;
}

bool ArchiveFile::EoF(void)
{
	if(m_FPos >= m_Archive->m_FT[m_FIIndex].Size)
		return true;
	else
		return false;
}

Archive::Archive(XString fn)
{
	fopen_s(&m_File, fn.getbuffer(),"r+b");
	if(m_File)
	{
		fread(&m_Info,sizeof(ARCHIVEINFO),1,m_File);

		m_ClusterOffset = sizeof(ARCHIVEINFO);

		m_AT = new unsigned long[m_Info.ATSize];
		m_FT = new FILEINFO[m_Info.FTSize];
		
		unsigned long n = 0;
		unsigned long pAT = 0;
		while(1)
		{
			fseek(m_File,pAT*m_Info.ClusterSize+m_ClusterOffset,SEEK_SET);
			fread(m_AT+n*m_Info.ClusterSize/4,m_Info.ClusterSize,1,m_File);
			if(m_AT[pAT] == CL_ENDOFFILE)
				break;

			pAT = m_AT[pAT];
			n++;
		}

		n = 0;
		pAT = 1;
		while(1)
		{
			fseek(m_File,pAT*m_Info.ClusterSize+m_ClusterOffset,SEEK_SET);
			fread(m_FT+n*m_Info.ClusterSize/sizeof(FILEINFO),m_Info.ClusterSize,1,m_File);
			if(m_AT[pAT] == CL_ENDOFFILE)
				break;

			pAT = m_AT[pAT];
			n++;
		}

		int f=m_Info.Files;
		for(unsigned long i=0;i<m_Info.FTSize && f;i++)
		{
			if(m_FT[i].State != FS_DELETED)
			{
				if(m_FT[i].State == FS_OPENED)//last session terminated without closing the file
				{
					m_FT[i].State = FS_VALID;
					WriteFTItemChanges(i);
				}
				f--;
			}
		}
		if(f)//some files got lost!
		{
			m_Info.Files -= f;
			fseek(m_File,0,SEEK_SET);
			fwrite(&m_Info,sizeof(ARCHIVEINFO),1,m_File);
		}

		//for(int i=0;i<m_Info.FTSize;i++)
		//	wlog((char*)m_FT[i].Name);
		return;
	}

	memset(&m_Info, 0, sizeof(ARCHIVEINFO));

	m_ClusterOffset = 0;

	m_AT = NULL;
	m_FT = NULL;
}

Archive::Archive(unsigned char ClusterSizeExp,XString fn)
{
	if(ClusterSizeExp < 5)
		ClusterSizeExp = 5;

	m_Filename = fn;
	fopen_s(&m_File, fn.getbuffer(),"w+b");

	m_Info.ClusterSize = 1<<ClusterSizeExp;
	m_Info.Clusters = 0;
	m_Info.Files = 0;
	m_Info.FTSize = 0;
	m_Info.ATSize = 0;

	fwrite(&m_Info,sizeof(ARCHIVEINFO),1,m_File);

	m_ClusterOffset = sizeof(ARCHIVEINFO);

	m_AT = NULL;
	m_FT = NULL;

	ExtendAT();
	ExtendFT();

	//for(int i=0;i<m_Info.FTSize;i++)
	//	wlog((char*)m_FT[i].Name);
}

Archive::~Archive(void)
{
	if(m_File)
		fclose(m_File);

	delete [] m_AT;
	delete [] m_FT;
}

ArchiveFile* Archive::OpenFile(XString fn,unsigned char openflags)
{
	if(fn.getlen() > 22)
		return NULL;

	//for(int i=0;i<m_Info.FTSize;i++)
	//	wlog((char*)m_FT[i].Name);

	if(m_Info.Files)
	{
		for(unsigned long i=0,f=m_Info.Files;i<m_Info.FTSize && f;i++)
		{
			if(m_FT[i].State != FS_DELETED)
			{
				if(!strcmp(((char*)(m_FT+i))+1,fn.getbuffer()))
				{
					if(m_FT[i].State == FS_VALID)
						return new ArchiveFile(this,i,openflags);
					else
						return NULL;
				}
				f--;
			}
		}
	}

	if(openflags & OT_READ)
		return NULL;

	unsigned long newFileInfo = FindUnusedFileInfo();

	m_FT[newFileInfo].State = FS_VALID;

	memcpy(((char*)(m_FT+newFileInfo))+1,fn.getbuffer(),fn.getlen());

	unsigned long cl = FindUnusedCluster();
	m_FT[newFileInfo].ATStart = cl;
	m_AT[cl] = CL_ENDOFFILE;

	m_FT[newFileInfo].Size = 0;
	
	WriteFTItemChanges(newFileInfo);

	WriteATItemChanges(cl);

	m_Info.Files++;
	fseek(m_File,0,SEEK_SET);
	fwrite(&m_Info,sizeof(ARCHIVEINFO),1,m_File);

	//for(int i=0;i<m_Info.FTSize;i++)
	//	wlog((char*)m_FT[i].Name);

	return new ArchiveFile(this,newFileInfo,openflags);
}

void Archive::DeleteFile(XString fn)
{
	if(fn.getlen() > 22)
		return;

	if(m_Info.Files)
	{
		for(unsigned long i=0;i<m_Info.FTSize;i++)
		{
			if(!strcmp(((char*)(m_FT+i))+1,fn.getbuffer()))
			{
				if(m_FT[i].State == FS_VALID)
				{
					m_FT[i].State = FS_DELETED;

					WriteFTItemChanges(i);

					//set allocated clusterstates to unused
					unsigned long pAT = m_FT[i].ATStart;
					while(m_AT[pAT] != CL_ENDOFFILE)
					{
						unsigned long h = m_AT[pAT];
						m_AT[pAT] = CL_UNUSED;
						WriteATItemChanges(pAT);
						pAT = h;
					}	
					m_AT[pAT] = CL_UNUSED;
					WriteATItemChanges(pAT);

					m_Info.Files--;
					fseek(m_File,0,SEEK_SET);
					fwrite(&m_Info,sizeof(ARCHIVEINFO),1,m_File);
				}
				return;
			}
		}
	}
}

void Archive::Defragment(void)
{
}

void Archive::AddCluster(void)
{
	m_Info.Clusters++;
	fseek(m_File,0,SEEK_SET);
	fwrite(&m_Info,sizeof(ARCHIVEINFO),1,m_File);

	fseek(m_File,0,SEEK_END);
	unsigned char h=0;
	for(unsigned long i=0;i<m_Info.ClusterSize;i++)
		fwrite(&h,1,1,m_File);
}

void Archive::ExtendAT(void)
{
	m_AT[1024];
	unsigned long oldATSize = m_Info.ATSize;
	unsigned long *oldAT = m_AT;
	m_Info.ATSize += m_Info.ClusterSize/sizeof(unsigned long);
	fseek(m_File,0,SEEK_SET);
	fwrite(&m_Info,sizeof(ARCHIVEINFO),1,m_File);

	m_AT = new unsigned long[m_Info.ATSize];
	if(oldAT)
	{
		memcpy(m_AT,oldAT,oldATSize*sizeof(unsigned long));
		delete oldAT;
	}
	memset(m_AT+m_Info.ATSize-m_Info.ClusterSize/sizeof(unsigned long), 0 ,m_Info.ClusterSize);

	unsigned long pAT = 0;
	if(oldAT)
	{
		int nclusters = m_Info.ATSize*sizeof(unsigned long)/m_Info.ClusterSize-2;

		for(nclusters;nclusters;nclusters--)
		{
			pAT = m_AT[pAT];
		}
		if(m_AT[pAT] == CL_ENDOFFILE)
		{
			m_AT[pAT] = FindUnusedCluster();
			m_AT[m_AT[pAT]] = CL_ENDOFFILE;

			//while(m_AT[pAT] >= m_Info.Clusters)
			//	AddCluster();

			WriteATItemChanges(pAT);
			WriteATItemChanges(m_AT[pAT]);

			//we dont need to wirte in the new cluster because its full of 0
		}
		else
		{
			//exception
		}
	}
	else
	{
		m_AT[0] = CL_ENDOFFILE;

		while(0 >= m_Info.Clusters)
			AddCluster();

		WriteATItemChanges(0);
	}
}

void Archive::ExtendFT(void)
{
	unsigned long oldFTSize = m_Info.FTSize;
	FILEINFO* oldFT = m_FT;
	m_Info.FTSize += m_Info.ClusterSize/sizeof(FILEINFO);
	fseek(m_File,0,SEEK_SET);
	fwrite(&m_Info,sizeof(ARCHIVEINFO),1,m_File);

	m_FT = new FILEINFO[m_Info.FTSize];
	if(oldFT)
	{
		memcpy(m_FT,oldFT,oldFTSize*sizeof(FILEINFO));
		delete oldFT;
	}
	memset(m_FT+m_Info.FTSize-m_Info.ClusterSize/sizeof(FILEINFO), 0, m_Info.ClusterSize);

	unsigned long pAT = 0;
	if(oldFT)
	{
		int nclusters = m_Info.FTSize*sizeof(FILEINFO)/m_Info.ClusterSize-2;
		for(nclusters;nclusters;nclusters--)
		{
			pAT = m_AT[pAT];
		}
		if(m_AT[pAT] == CL_ENDOFFILE)
		{
			m_AT[pAT] = FindUnusedCluster();
			m_AT[m_AT[pAT]] = CL_ENDOFFILE;

			//while(m_AT[pAT] >= m_Info.Clusters)
			//	AddCluster();

			WriteATItemChanges(pAT);
			WriteATItemChanges(m_AT[pAT]);

			//we dont need to wirte in the new cluster because its full of 0
		}
		else
		{
			//exception
		}
	}
	else
	{
		m_AT[1] = CL_ENDOFFILE;

		while(1 >= m_Info.Clusters)
			AddCluster();

		WriteATItemChanges(1);
	}
}

unsigned long Archive::FindUnusedCluster(void)
{
	unsigned long i;
	for(i=2;i<m_Info.ATSize;i++)
	{
		if(m_AT[i]==CL_UNUSED)
		{
			while(i >= m_Info.Clusters)
				AddCluster();

			return i;
		}
	}

	ExtendAT();
	return i+1;//+1 becaus extendat also needs a new cluster
}

unsigned long Archive::FindUnusedFileInfo(void)
{
	unsigned long i;
	for(i=0;i<m_Info.FTSize;i++)
	{
		if(m_FT[i].State == FS_DELETED)
			return i;
	}
	ExtendFT();
	return i;
}

void Archive::WriteFTItemChanges(unsigned long index)
{
	unsigned long nClusters = index*sizeof(FILEINFO)/m_Info.ClusterSize;
	unsigned long pAT = 1;
	for(nClusters;nClusters;nClusters--)
		pAT = m_AT[pAT];

	unsigned long CLPos = (index%(m_Info.ClusterSize/sizeof(FILEINFO)))*sizeof(FILEINFO);
    fseek(m_File,pAT*m_Info.ClusterSize+CLPos+m_ClusterOffset,SEEK_SET);
	fwrite(m_FT+index,sizeof(FILEINFO),1,m_File);
}

void Archive::WriteATItemChanges(unsigned long index)
{
	unsigned long nClusters = index*sizeof(unsigned long)/m_Info.ClusterSize;
	unsigned long pAT = 0;
	for(nClusters;nClusters;nClusters--)
		pAT = m_AT[pAT];

	unsigned long CLPos = (index%(m_Info.ClusterSize/sizeof(unsigned long)))*sizeof(unsigned long);
    fseek(m_File,pAT*m_Info.ClusterSize+CLPos+m_ClusterOffset,SEEK_SET);
	fwrite(m_AT+index,sizeof(unsigned long),1,m_File);
}