/**
* @class CCPCDisc
* Classe permettant la gestion d'un disc CPC (utilisation de la sous-lib DSK)
* @author Thierry JOUIN
* @version 1.1
* @date 31/10/2001
*/

#include "CCPCDisc.h"

#include "CCPCDataDisc.h"
#include "CCPCSystemDisc.h"

#include "CError.h"

#ifdef WIN32
#define FILE_SEPARATOR '\\'
#else
#define FILE_SEPARATOR '/'
#endif

unsigned int CCPCDisc::no_block = (0-1);

bool SDiscFileEntryKey::operator()(const SDiscFileEntryKey &i_file1,const SDiscFileEntryKey &i_file2) const
{
	return ( (i_file1.user < i_file2.user) || ((i_file1.user == i_file2.user) && (strncmp(i_file1.name,i_file2.name,11) < 0)) );
}

void SDiscFileEntryKey::convertStringToSDiscFileEntryKey(const std::string &i_name, SDiscFileEntryKey &o_file)
{
	unsigned int i;
	unsigned int a = i_name.find(':');
	unsigned int b = i_name.find_last_of('.');
	unsigned int c = i_name.find(",S");
	unsigned int d = i_name.find(",P");
	
	std::string user,name,ext;
	
	if (a != std::string::npos)
		user = i_name.substr(0,a);
	
	if (b != std::string::npos)
    {
		name = i_name.substr(a+1,b-a-1);
		if (c != std::string::npos || d!= std::string::npos)
		{
			int bmax = (c < d) ? c : d;
			ext = i_name.substr(b+1,bmax-b-1);
		}
		else
			ext = i_name.substr(b+1,i_name.size()-b);
    }
	else
    {
		if (c != std::string::npos || d!= std::string::npos)
		{
			int bmax = (c < d) ? c : d;
			name = i_name.substr(a+1,bmax-b-1);
		}
		else
			name = i_name.substr(a+1,i_name.size()-b);
    }
	
	o_file.user = (unsigned char)atoi(user.c_str());
	if (name.find(FILE_SEPARATOR) != std::string::npos)
    {
		name = name.substr(name.find_last_of(FILE_SEPARATOR)+1,name.size() - name.find_last_of(FILE_SEPARATOR));
    }
	for (i=0;i<8;i++)
	{
		if (i<name.size())
			
			o_file.name[i]=toupper(name[i]);
		else
			o_file.name[i]=' ';
	}
	for (i=0;i<3;i++)
	{
		if (i<ext.size())
			o_file.name[i+8]=toupper(ext[i]);
		else
			o_file.name[i+8]=' ';
		
		o_file.system = c!=std::string::npos;
		o_file.writeProtected = d!=std::string::npos;
	}
}

CCPCDisc::CCPCDisc()
:_driver(NULL),
_catalogue(),
_catalogueBuffer(),
_catalogueChanged(false)
{
}

CCPCDisc::CCPCDisc(DSK_PDRIVER i_driver, const DSK_GEOMETRY &i_geometry)
:_driver(i_driver),
_geometry(i_geometry),
_catalogue(),
_catalogueBuffer(new char[_geometry.dg_secsize*4]),
_catalogueChanged(false)
{
}

CCPCDisc::~CCPCDisc()
{
	if (_catalogueBuffer != NULL)
		delete[] _catalogueBuffer;
}

CCPCDisc* CCPCDisc::openDisc(const std::string &i_filename, int i_inside)
{
	CCPCDisc* disc = NULL;
	DSK_PDRIVER driver;
	DSK_GEOMETRY geom;
	dsk_err_t e;
	
	e = dsk_open(&driver,i_filename.c_str(), NULL, NULL);
	ASSERTMSG( (e==DSK_ERR_OK) , "Error opening dsk :" << std::string(dsk_strerror(e)));
	
	e = dsk_set_forcehead(driver, i_inside);
	ASSERTMSG( (e==DSK_ERR_OK) , "Error opening dsk :" << std::string(dsk_strerror(e)));
	
	e = dsk_getgeom(driver,&geom);
	ASSERTMSG( (e==DSK_ERR_OK) , "Error opening dsk :" << std::string(dsk_strerror(e)));
	
	switch (geom.dg_secbase)
    {
    case 0xc1:
		{
			disc = new CCPCDataDisc(driver,geom);
			disc->readCatalogue();
			break;
		}
    case 0x41:
		{
			disc = new CCPCSystemDisc(driver,geom);
			disc->readCatalogue();
			break;
		}
    default:
		{
			ERRORMSG( "Error opening dsk :unkown type (sectorBase &" << std::hex << geom.dg_secbase << ")");
		}
    }
	
	return disc;
}

CCPCDisc* CCPCDisc::createDisc(const std::string &i_filename, const TDisc &i_type, int i_inside)
{
	CCPCDisc *d=NULL;
	switch (i_type)
    {
    case Data:
		{
			d = new CCPCDataDisc();
			d->create(i_filename, i_inside);
			break;
		}
    case System:
		{
			d = new CCPCDataDisc();
			d->create(i_filename, i_inside);
			break;
		}
    }
	return d;
}

void CCPCDisc::close()
{
	if (_catalogueChanged)
		writeCatalogue();
	dsk_close(&_driver);
}

void CCPCDisc::readCatalogue()
{
	// On charge le buffer catalogue
	readCatalogueBuffer();
	
	char *currentEntry = _catalogueBuffer;
	// On parcours toutes les entrees du catalogue
	for (unsigned int e=0;e<64;e++)
    {
		SDiscFileEntryKey name;
		SDiscFileEntry nameValues;
		
		name.user = (unsigned char) currentEntry[0];
		
		strncpy(name.name,(char*)currentEntry+1,11);
		
		// On recupere les bit d'indications
		name.writeProtected = ((name.name[8] & 128) != 0);
		name.system = ((name.name[9] & 128) != 0);
		
		bool validName = true;
		
		// On 'nettoie' le nom
		for (unsigned int i=0;i<11;i++)
			validName = validName && ((name.name[i] & 127)>=' ') && (name.name[i]!=(char)229);
		
		if (validName)
		{
			std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::iterator pos = _catalogue.find(name);
			
			for (unsigned int i=0;i<11;i++)
				name.name[i]=name.name[i] & 127;
			
			if (pos == _catalogue.end())
			{
				nameValues.blocks = std::vector<unsigned int>(256,CCPCDisc::no_block);
				nameValues.size = 0;
				nameValues.nbRecord = 0;
				_catalogue[name]=nameValues;
			}
			
			unsigned char ordreChargement = currentEntry[12];
			
			for(unsigned int b=16;b<32;b++)
			{
				if (currentEntry[b] != 0)
					if (_catalogue[name].blocks[ordreChargement*16+(b-16)] == CCPCDisc::no_block || name.user == 229)
					{
						_catalogue[name].blocks[ordreChargement*16+(b-16)]=currentEntry[b];
						_catalogue[name].size++;
					}
					else
					{
						PRINTVAR(std::string(name.name,11));
						ERRORMSG("Invalid entry order !");
					}
			}
			_catalogue[name].nbRecord += currentEntry[0xf];
		}
		currentEntry += 32;
    }
}

void CCPCDisc::writeCatalogue()
{
	if (_catalogueChanged)
    {
		char* pCatBuffer = _catalogueBuffer;
		
		// On vide le catalogue courant
		for (unsigned int i=0;i<_geometry.dg_secsize*4;i++)
			_catalogueBuffer[i]=(char)0xe5;
		
		// On en recrée un nouveau
		for (std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::const_iterator it=_catalogue.begin();it!=_catalogue.end();it++)
		{
			int nbEntry = (int)ceil(((float)it->second.size)/16.0);
			char name[11];
			char ordreChargement=0;
			unsigned int nbRecord = it->second.nbRecord;
			unsigned int nbRecordMaxPerEntry = (_geometry.dg_secsize*2*16)/128;
			
			memcpy(name,it->first.name,11);
			name[8] = it->first.writeProtected ? name[8]|128 : name[8];
			name[9] = it->first.system ? name[9]|128 : name[9];
			for (int j=0;j<nbEntry;j++)
			{
				for (unsigned int k=0;k<32;k++)
					pCatBuffer[k]=0;
				
				pCatBuffer[0] = (char)it->first.user;
				memcpy(pCatBuffer+1,name,11);
				pCatBuffer[12] = ordreChargement;
				pCatBuffer[15] = (nbRecord > nbRecordMaxPerEntry) ? nbRecordMaxPerEntry : nbRecord;
				for (unsigned int b=16;b<32;b++)
				{
					unsigned int bI = ordreChargement*16+b-16;
					if (bI < it->second.size)
						pCatBuffer[b] = it->second.blocks[bI];
				}
				pCatBuffer += 32;
				ordreChargement++;
				nbRecord -= nbRecordMaxPerEntry;
			}
			//io_os << (unsigned int)it->first.user << ":" << std::string(it->first.name,0,11) << " " << it->second.size << "Kb" << std::endl;
		}
		writeCatalogueBuffer();
    }
}

/*
void CCPCDisc::readCatalogueEntry(void *i_buffer, SDiscFileEntryKey &o_name, SDiscFileEntry &o_nameValues)
{
unsigned char *buffer=(unsigned char*)i_buffer;
o_name.user = (unsigned char) buffer[0];

 strncpy(o_name.name,(char*)buffer+1,11);
 
  o_nameValues.writeProtected = ((o_name.name[8] & 128) != 0);
  o_nameValues.system = ((o_name.name[9] & 128) != 0);
  
   bool validName = true;
   
	for (unsigned int i=0;i<11;i++)
    validName = validName && ((o_name.name[i] & 127)>=' ') && (o_name.name[i]!=(char)229);
	
	 if (validName)
	 {
	 std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::iterator pos = _catalogue.find(o_name);
	 
      for (unsigned int i=0;i<11;i++)
	  o_name.name[i]=o_name.name[i] & 127;
	  
	   if (pos == _catalogue.end())
	   {
	   o_nameValues.blocks = std::vector<unsigned int>(256,CCPCDisc::no_block);
	   o_nameValues.size = 0;
	   _catalogue[o_name]=o_nameValues;
	   }
	   
		unsigned char ordreChargement = buffer[12];
		
		 for(unsigned int b=16;b<32;b++)
		 {
		 if (buffer[b] != 0)
		 if (_catalogue[o_name].blocks[ordreChargement*16+(b-16)] == CCPCDisc::no_block)
		 {
		 _catalogue[o_name].blocks[ordreChargement*16+(b-16)]=buffer[b];
		 _catalogue[o_name].size++;
		 }
		 else
		 {
		 WARNING("Invalid entry order !");
		 _catalogue[o_name].size++;
		 }
		 }
		 o_nameValues = _catalogue[o_name];
		 o_nameValues.nbRecord += buffer[0xf];
		 }
		 }
		 
		  
		   void CCPCDisc::writeCatalogueEntry(SDiscFileEntryKey &i_name, SDiscFileEntry &i_nameData)
		   {
		   std::vector<int> emptyEntryIndex;
		   for (unsigned int i=0;i<64;i++)
		   {
		   if (((unsigned char)_catalogueBuffer[i*32]) == 229)
		   emptyEntryIndex.push_back(i);
		   }
		   
			unsigned int nbEntry = (unsigned int)ceil(((double)i_nameData.size) / 16);
			
			 ASSERTMSG(nbEntry < emptyEntryIndex.size() , std::runtime_error, "Directory full");
			 
			  char name[11];
			  strncpy(name,i_name.name,11);
			  
			   for (unsigned int i=0;i<11;i++)
			   name[i] = toupper(name[i]);
			   
				name[8] = name[8] | (i_nameData.writeProtected ? 128 : 0);
				name[9] = name[9] | (i_nameData.system ? 128 : 0);
				
				 unsigned int nbRecord = i_nameData.nbRecord;
				 unsigned int nbRecordMaxPerEntry = (_geometry.dg_secsize*2*16)/128;
				 
				  unsigned int idxBlock = 0;
				  for (unsigned int i=0;i<nbEntry;i++)
				  {
				  unsigned int idxEntry = emptyEntryIndex[i] * 32;
				  
				   for (unsigned int j=0;j<32;j++)
				   _catalogueBuffer[idxEntry+j] = 0;
				   
					_catalogueBuffer[idxEntry+0]=i_name.user;
					memcpy(_catalogueBuffer+idxEntry+1,name,11);
					_catalogueBuffer[idxEntry+12] = i;
					_catalogueBuffer[idxEntry+0xf] = (nbRecord > nbRecordMaxPerEntry) ? nbRecordMaxPerEntry : nbRecord;
					for (unsigned int e=16;e<32;e++)
					if (idxBlock < i_nameData.size)
					_catalogueBuffer[idxEntry+e] = i_nameData.blocks[idxBlock++];
					else
					_catalogueBuffer[idxEntry+e] = 0;
					nbRecord -= nbRecordMaxPerEntry;
					}
					_catalogueChanged = true;
					}
					*/

void CCPCDisc::addCatalogueEntry(const SDiscFileEntryKey &i_name, const SDiscFileEntry &i_nameData)
{
	std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::iterator iNorm = _catalogue.find(i_name);
	
	int nbEntry = (int)ceil(((float)i_nameData.size)/16.0);
	
	ASSERTMSG( (nbEntry < (64-getNbUsedEntry())) , "Directory Full");
	
	if (iNorm != _catalogue.end())
    {
		SDiscFileEntryKey name_bak = i_name;
		name_bak.name[8]='B';name_bak.name[9]='A';name_bak.name[10]='K';
		
		std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::iterator iBak = _catalogue.find(name_bak);
		
		if (iBak != _catalogue.end())
		{
			SDiscFileEntryKey name_bak_del = name_bak;
			name_bak_del.user=229;
			
			_catalogue[name_bak_del] = _catalogue[name_bak];
		}
		_catalogue[name_bak]=_catalogue[i_name];
    }
	_catalogue[i_name]=i_nameData;  
	
	_catalogueChanged = true;
	
}
void CCPCDisc::removeCatalogueEntry(const SDiscFileEntryKey &i_name)
{
	std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::iterator iNorm = _catalogue.find(i_name);
	
	if (iNorm != _catalogue.end())
    {
		SDiscFileEntryKey name_del = i_name;
		name_del.user = 229;
		_catalogue[name_del]=_catalogue[i_name];
		_catalogue.erase(iNorm);
    }
	else
    {
		WARNING("File not in the catalogue !");
    }
	_catalogueChanged = true;
}

int CCPCDisc::getNbUsedEntry() const
{
	int nbUsed = 0;
	for (std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::const_iterator i=_catalogue.begin();i!=_catalogue.end();i++)
    {
		nbUsed+=(int)ceil(((float)i->second.size)/16.0);
    }
	return nbUsed;
}

int CCPCDisc::getCatalogueEntryForBlock(unsigned int i_block) const
{
	int entry = -1;
	int numEntry = 0;
	std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::const_iterator e = _catalogue.begin();
	while (e!=_catalogue.end() && entry == -1)
    {
		if (e->first.user != 229)
			for (unsigned int i=0;i<e->second.blocks.size();i++)
				if (e->second.blocks[i] == i_block)
					entry = numEntry;
				e++;
				numEntry++;
    }
	return entry;
}

unsigned int CCPCDisc::getNbFiles() const
{
	return _catalogue.size();
}
std::string CCPCDisc::getFilename(const unsigned int i_id) const
{
	unsigned int i;
	ASSERT( (i_id<_catalogue.size()) );
	std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::const_iterator e = _catalogue.begin();
	for (i=0;i<i_id;i++)
		e++;
	
	std::ostrstream filename;
	//filename << (unsigned int)e->first.user << ":";
	i=0;
	while (i<8 && e->first.name[i] != ' ')
    {
		filename << e->first.name[i++]; 
    }
	i = 8;
	filename << '.';
	while (i<11 && e->first.name[i] != ' ')
		filename << e->first.name[i++];
	
	//filename << std::string(e->first.name,8);
	//filename << ".";
	//filename << std::string(e->first.name+8,3);
	filename << std::ends;
	return filename.str();
}
void CCPCDisc::catalogue(std::ostream &io_os) const
{
	io_os << "Catalogue : " << _catalogue.size() << std::endl;
	
	for (std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::const_iterator i=_catalogue.begin();i!=_catalogue.end();i++)
    {
		io_os.width(3);
		io_os << (unsigned int)i->first.user << ":" << std::string(i->first.name,0,8) << "." << std::string(i->first.name,8,3) << " ";
		if (i->first.system)
		{
			io_os << "S";
		}
		else
		{
			io_os << " ";
		}
		if (i->first.writeProtected)
		{
			io_os << "*";
		}
		else
		{
			io_os << " ";
		}
		io_os << i->second.size << "Kb" << std::endl;
		io_os.width(0);
    }
}

void CCPCDisc::printInfo(std::ostream &io_os) const
{
	io_os << "Disc info : " << std::endl;
	
	scanBlock(io_os);
	
	std::vector<unsigned int> emptyBlocks;
	getEmptyBlock(emptyBlocks);
	io_os << "Free : " << emptyBlocks.size() << std::endl;
	for (unsigned int i=0;i<emptyBlocks.size();i++)
    {
		io_os << emptyBlocks[i] << ",";
    }
	io_os << std::endl;
}

CCPCFile* CCPCDisc::getFile(const std::string &i_filename) const
{
	SDiscFileEntryKey key;
	
	SDiscFileEntryKey::convertStringToSDiscFileEntryKey(i_filename,key);
	
	std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::const_iterator pos = _catalogue.find(key);
	
	ASSERTMSG( (pos!= _catalogue.end()) , "Unknown filename " << i_filename);  
	
	unsigned char *file = new unsigned char[pos->second.size*1024];
	for (unsigned int i=0;i<pos->second.size;i++)
		readBlock(pos->second.blocks[i],file+(1024*i));

	CCPCFile *fileOpen = CCPCFile::openFile(file,pos->second.size*1024);
	delete[] file;
	return fileOpen;
}

void CCPCDisc::putFile(const CCPCFile &i_file, const std::string &i_filename)
{
	std::vector<unsigned int> emptyBlock;
	getEmptyBlock(emptyBlock);
	
	unsigned int neededBlock = (unsigned int)ceil((double)i_file.getDatasSize() / (double)(_geometry.dg_secsize*2));
	unsigned int neededRecord = (unsigned int)ceil((double)i_file.getDatasSize() / (double)(128));
	
	ASSERTMSG( (neededBlock < emptyBlock.size()) , "Unable to add file to disc - Disc Full");
	
	unsigned char *fileBuffer = i_file.getDatas();
	
	unsigned char *block = new unsigned char[_geometry.dg_secsize*2];
	
	SDiscFileEntryKey fileEntryKey;
	SDiscFileEntry fileEntry;
	
	SDiscFileEntryKey::convertStringToSDiscFileEntryKey(i_filename,fileEntryKey);
	
	fileEntry.size = neededBlock;
	fileEntry.nbRecord = neededRecord;
	fileEntry.blocks.clear();
	
	for (unsigned int j=0;j<neededBlock;j++)
    {
		for (unsigned int k=0;k<_geometry.dg_secsize*2;k++)
			block[k]=0;
		memcpy(block,fileBuffer+(j*_geometry.dg_secsize*2),_geometry.dg_secsize*2);
		writeBlock(emptyBlock[j],block);
		fileEntry.blocks.push_back(emptyBlock[j]);
    }
	
	
	addCatalogueEntry(fileEntryKey,fileEntry);
	delete[] block;
}

/// Efface un fichier sur le disc
void CCPCDisc::eraseFile(const std::string &i_filename)
{
	SDiscFileEntryKey key;
	
	SDiscFileEntryKey::convertStringToSDiscFileEntryKey(i_filename,key);
	
	removeCatalogueEntry(key);
	
}

/// Renome un fichier sur le disc
void CCPCDisc::renameFile(const std::string &i_oldFilename, const std::string &i_newFilename)
{
	SDiscFileEntryKey from;
	SDiscFileEntryKey to;
	
	SDiscFileEntryKey::convertStringToSDiscFileEntryKey(i_oldFilename,from);
	SDiscFileEntryKey::convertStringToSDiscFileEntryKey(i_newFilename,to);
	
	std::map<SDiscFileEntryKey,SDiscFileEntry,SDiscFileEntryKey>::iterator iFrom = _catalogue.find(from);
	ASSERTMSG( iFrom != _catalogue.end() , "Unknown file " << i_oldFilename);
	
	addCatalogueEntry(to,_catalogue[from]);
	
	_catalogue.erase(iFrom);
	
}
