///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoFileHandler.cc
// ------------------
// Cego database file handler implementation
//     
// Design and Implementation by Bjoern Lemke               
//     
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoFileHandler
// 
// Description: The filehandler class provides access to all datafiles. Datafiles consists
//              of a defined number of pages managed by the this class.
//              At the header of a datafile, a bitmap is stored for used page information
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

#include <lfcbase/Exception.h>

#include "CegoFileHandler.h"
#include "CegoBufferPage.h"

#define BIT_PER_BYTE 8
#define FILEHEADSIZE sizeof(unsigned) + sizeof(FileType) + sizeof(unsigned) + sizeof(PageIdType)

extern bool __fsyncOn;

CegoFileHandler::CegoFileHandler(const Chain& logFile, const Chain& progName) : CegoModule(logFile, progName)
{
    for (unsigned i = 0; i<FILMNG_MAXDATAFILE; i++)
    {
	_isReg[i]=false;
	_tabSetId[i]=0;
	_fhList[i]=0;
	_buMask[i]=0;
	_commitMask[i]=0;
	_pageOffset[i]=0;
	_numPages[i]=0;
    }
    _appendFid = 0;
    _isReadOnly = false;
    _modId = getModId("CegoFileHandler");
}

CegoFileHandler::~CegoFileHandler()
{
    for (unsigned i = 0; i<FILMNG_MAXDATAFILE; i++)
    {
	if ( _fhList[i] != 0 )
	{
	    _fhList[i]->close();
	    delete _fhList[i];
	}
	if ( _buMask[i] != 0 )
	{
	    delete _buMask[i];
	}
	if ( _commitMask[i] != 0 )
	{
	    delete _commitMask[i];
	}	
    }    
}

void CegoFileHandler::setReadOnly(bool isReadOnly)
{
    _isReadOnly = isReadOnly;
}

void CegoFileHandler::setBackup(unsigned fileId, bool m)
{
    if ( m == true )
    {
	_buMask[fileId] = new unsigned[ (  getNumPages(fileId) / (BIT_PER_BYTE * sizeof(unsigned))) + 1];

	unsigned long i;
	
	for (i = 0; i < ( getNumPages(fileId) / (BIT_PER_BYTE * sizeof(unsigned))) + 1; i++)
	{
	    _buMask[fileId][i] = 0;
	}
	_fbmMask[fileId] = false;
    }
    else
    {
	if ( _buMask[fileId] != 0 )
	{
	    delete _buMask[fileId];
	    _buMask[fileId] = 0;
	}
    }
}

bool CegoFileHandler::needPageBackup(PageIdType pageId)
{
    unsigned fileId = getFileIdForPageId(pageId);

    // check if backup is activated for file
    if ( _buMask[fileId] )
    {
	// is page already marked, no need to backup page
	if ( isPageMarked(pageId, fileId) )
	    return false;
	return true;	
    }

    return false;
}

void CegoFileHandler::markPage(PageIdType pageId)
{
    unsigned fileId = getFileIdForPageId(pageId);
    
    if ( _buMask[fileId] == 0 )
    {
	Chain msg = Chain("No backup mode for fileId ") + Chain(fileId);
	throw Exception(EXLOC, msg);
    }

    unsigned bmid = ( pageId - _pageOffset[fileId] ) / ( BIT_PER_BYTE * sizeof(unsigned));
    unsigned bmoffset = ( pageId - _pageOffset[fileId] ) % ( BIT_PER_BYTE * sizeof(unsigned));
    
    unsigned bm = _buMask[fileId][bmid];
    
    unsigned f = ~(~(unsigned)0 << 1);
    f = f << bmoffset;
    
    _buMask[fileId][bmid] = bm | f;
}

bool CegoFileHandler::isPageMarked(PageIdType pageId, unsigned fileId)
{
    if ( _buMask[fileId] == 0 )
    {
	Chain msg = Chain("No backup mode for fileId ") + Chain(fileId);
	throw Exception(EXLOC, msg);
    }
   
    unsigned bmid = ( pageId - _pageOffset[fileId] ) / ( BIT_PER_BYTE * sizeof(unsigned));
    unsigned bmoffset = ( pageId - _pageOffset[fileId] ) % ( BIT_PER_BYTE * sizeof(unsigned));
    
    unsigned bm = _buMask[fileId][bmid];    
        
    unsigned checkbit = bm >> bmoffset & ~(~(unsigned)0 << 1);    
    
    if (  checkbit )
    {
	return true;
    }
    return false;
}

PageIdType CegoFileHandler::getPageOffset(unsigned fileId)
{
    return _pageOffset[fileId];
}


PageIdType CegoFileHandler::getMaxPageOffset()
{
    PageIdType maxOffset=1;
    
    for (unsigned i = 0; i<FILMNG_MAXDATAFILE; i++)
    {
	if (  _pageOffset[i] + _numPages[i] > maxOffset )
	    maxOffset = _pageOffset[i] + _numPages[i];
    }
    return maxOffset;
}

unsigned CegoFileHandler::getFBMSize(unsigned fileId)
{
    return (_numPages[fileId] / (BIT_PER_BYTE * sizeof(unsigned))) + 1;
}

void CegoFileHandler::readFBM(unsigned fileId, unsigned *fbm, CegoLockHandler *pLockHandle)
{
    File *pF;

    try 
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try 
    {
	pF->seek(FILEHEADSIZE);

	unsigned numBitmap = (_numPages[fileId] / (BIT_PER_BYTE * sizeof(unsigned))) + 1;
	unsigned i;
	for (i = 0; i < numBitmap; i++)
	{
	    pF->readByte((char*)&fbm[i], sizeof(unsigned));
	}
		
	pLockHandle->unlockDataFile(fileId);
    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot read fbm"), e);
    }
}

void CegoFileHandler::writeFBM(unsigned fileId, unsigned *fbm, CegoLockHandler *pLockHandle)
{
    if ( _isReadOnly == true )
	return;
    
    File *pF;
    
    try 
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try 
    {	
	pF->seek(FILEHEADSIZE);

	unsigned numBitmap = (_numPages[fileId] / (BIT_PER_BYTE * sizeof(unsigned))) + 1;
	unsigned i;

	for (i = 0; i < numBitmap; i++)
	{
	    pF->writeByte((char*)&fbm[i], sizeof(unsigned));
	}

	if ( __fsyncOn )
	    pF->flush();
	
	pLockHandle->unlockDataFile(fileId);

    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot write fbm"), e);
    }
}

/* a new datafile is created using the initDataFile method. With the given tabSetId, path, fileId, size and type
   the file is created and initialized. Note, that after creation the datafile is still NOT registered for further usage.
   This must be done using the regDataFile method */

void CegoFileHandler::initDataFile(unsigned tabSetId, const Chain& path, unsigned fileId, unsigned numPages, PageIdType pageOffset, FileType ft)
{
    if ( _isReadOnly == true )
	return;
    
    File* pF = new File(path);
    
    if ( pF->exists() )
    {      
	Chain msg = Chain("Datafile ") + path + Chain(" already exists");
	delete pF;
	throw Exception( EXLOC, msg);
    }

    try 
    {
	pF->open(File::WRITE);
	
	pF->writeByte((char*)&tabSetId, sizeof(unsigned));
	pF->writeByte((char*)&ft, sizeof(FileType));
	// cout << "Init datafile " << path << " with numPages " << numPages << endl;
	pF->writeByte((char*)&numPages, sizeof(unsigned));
	
	pF->writeByte((char*)&pageOffset, sizeof(PageIdType));
		
	unsigned i;
	
	for (i = 0; i < (unsigned)((numPages / (BIT_PER_BYTE * sizeof(unsigned))) + 1); i++)
	{
	    unsigned bm = 0;
	    pF->writeByte((char*)&bm, sizeof(unsigned));
	}
	
	char* initBuf = new char[_pageSize];
	CegoBufferPage ip(initBuf, _pageSize);
	ip.initPage(CegoBufferPage::TABLE);
	    
	for (i = 0; i < numPages; i++)
	{
	    unsigned fixStat = 0;
	    pF->writeByte((char*)&fixStat, sizeof(unsigned));
	    pF->writeByte(initBuf, _pageSize);
	}
	delete[] initBuf;
	
	pF->close();
	delete pF;
    }
    catch ( Exception e )
    {
	delete pF;
	throw Exception(EXLOC, Chain("Cannot init datafile"), e);
    }
}

void CegoFileHandler::resetDataFile(unsigned fileId)
{
    if ( _isReadOnly == true )
	return;

    File* pF;

    try 
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }
    
    pF->seek(FILEHEADSIZE);
        
    unsigned i;
    
    for (i = 0; i < (unsigned)(( _numPages[fileId] / (BIT_PER_BYTE * sizeof(unsigned)))) + 1; i++)
    {
	unsigned bm = 0;
	pF->writeByte((char*)&bm, sizeof(unsigned));
    }

    if ( __fsyncOn )
	pF->flush();   
}

/* the regDataFile method is used for datafile registration. The given file is checked
   for consistency and is registered */

void CegoFileHandler::regDataFile(unsigned tabSetId, const Chain& path, unsigned fileId, CegoLockHandler *pLockHandle)
{
    pLockHandle->lockDataFile(fileId, CegoLockHandler::READ);

    if ( _isReg[fileId] )
    {
	pLockHandle->unlockDataFile(fileId);

	if ( _path[fileId] == path )
	{
	    log(_modId, Logger::NOTICE, Chain("Data file ") + path + Chain(" already exists, skipping"));
	    return;
	}
	else
	{
	    Chain msg = Chain("Datafile Id ") + Chain(fileId) + Chain(" already occupied");
	    throw Exception(EXLOC, msg);
	}
    }

    unsigned checkTabSetId;
    unsigned numPage;
    PageIdType pageOffset;
    
    FileType ft;
	
    try
    {
	File checkFile(path);	    
	checkFile.open(File::READ);
	
	checkFile.readByte((char*)&checkTabSetId, sizeof(unsigned));	
	checkFile.readByte((char*)&ft, sizeof(FileType));
	checkFile.readByte((char*)&numPage, sizeof(unsigned));
	checkFile.readByte((char*)&pageOffset, sizeof(PageIdType));

	checkFile.close();	
    }
    catch ( Exception e )
    {
	Chain ferr;
	e.pop(ferr);
	pLockHandle->unlockDataFile(fileId);
	Chain msg = Chain("Cannot access datafile <") + path + Chain("> :") + ferr;
	throw Exception(EXLOC, msg);
    }
    
    if ( tabSetId != checkTabSetId )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, "Table Set Id does not match");
    }

    _pageOffset[fileId] = pageOffset;
    _isReg[fileId] = true;
    _path[fileId] = path;
    _tabSetId[fileId] = tabSetId;
    _fileType[fileId] = ft;
    _numPages[fileId] = numPage;

    pLockHandle->unlockDataFile(fileId);
}

/* all registered files of the corresponding table set are closed and unregistered 
   with the releaseFiles method */

void CegoFileHandler::releaseFiles(unsigned tabSetId)
{
    for (unsigned i = 0; i<FILMNG_MAXDATAFILE; i++)
    {
	if ( _tabSetId[i] == tabSetId )
	{
	    if ( _fhList[i] != 0 )
	    {
		_fhList[i]->close();
		delete _fhList[i];
		_fhList[i]=0;
	    }
	    _tabSetId[i]=0;
	    _isReg[i]=false;    
	}
    }
}

/* a single database page is written to file using the writePage method. With the corresponding fileId, pageId
   and the pointer to the data, the file handler can perform the write operation. The fixStat parameter indicates
   the number of bufferFix operations on the page and is normally used by the BufferPool class for the page
   relocation strategy */ 

void CegoFileHandler::writePage(PageIdType pageId, unsigned fixStat, char* pageData, CegoLockHandler *pLockHandle)
{   
    if ( _isReadOnly == true )
	throw Exception(EXLOC, Chain("Cannot write page in read only mode"));
        
    unsigned fileId = getFileIdForPageId(pageId);
    
    File* pF;

    try 
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try 
    {
	unsigned long offset = FILEHEADSIZE
	    +  ((_numPages[fileId] / ( BIT_PER_BYTE * sizeof(unsigned) )) + 1) * sizeof(unsigned);
		
	pF->seek(offset + ( pageId - _pageOffset[fileId] ) * ( _pageSize + sizeof(unsigned)));
	pF->writeByte((char*)&fixStat, sizeof(unsigned));    
	pF->writeByte(pageData, _pageSize);
	
	if ( _buMask[fileId] )
	    markPage(pageId);

	if ( __fsyncOn )
	    pF->flush();
	
	pLockHandle->unlockDataFile(fileId);
    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot write data page"), e);
    }
}

/* pages from datafiles are read by using the readPage method. The method seeks to the appropriate offset
   in the corresponding datafile and reads the page data into the given data pointer */

void CegoFileHandler::readPage(PageIdType pageId, unsigned& tabSetId, unsigned& fixStat, char* pageData, CegoLockHandler *pLockHandle)
{
    unsigned fileId = getFileIdForPageId(pageId);
    
    File *pF;

    try 
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try 
    {
	pF->seek(0);
	pF->readByte((char*)&tabSetId, sizeof(unsigned));
	
	unsigned long offset = FILEHEADSIZE 
	    +  ((_numPages[fileId] / ( BIT_PER_BYTE * sizeof(unsigned) )) + 1) * sizeof(unsigned) ;

	pF->seek(offset + ( pageId - _pageOffset[fileId] ) * ( _pageSize + sizeof(unsigned)) );
	
	pF->readByte((char*)&fixStat, sizeof(unsigned));
	
	unsigned long rb = pF->readByte(pageData, _pageSize);
	while ( (unsigned)rb < _pageSize )
	{	  
	    unsigned long rrb = pF->readByte((char*)((long long)pageData + rb), _pageSize - rb);
	       
	    if ( rrb == 0 )
	      throw Exception(EXLOC, Chain("Incomplete file read"));
	    
	    rb+=rrb;
	}
	
	pLockHandle->unlockDataFile(fileId);
    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot read data page"), e);
    }
}

/* the claimPage method allocates a given page in the given file. 
   Normally this method is used for "well known entry pages", e.g. the system catalog page
   which is used as an entry point. */
 
void CegoFileHandler::claimPage(PageIdType pageId, CegoLockHandler *pLockHandle)
{
    if ( _isReadOnly == true )
	throw Exception(EXLOC, Chain("Cannot claim page in read only mode"));

    unsigned fileId = getFileIdForPageId(pageId);

    File *pF;

    try
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try 
    {	
	unsigned bmid = ( pageId - _pageOffset[fileId] ) / ( BIT_PER_BYTE * sizeof(unsigned));
	unsigned bmoffset = ( pageId - _pageOffset[fileId] ) % ( BIT_PER_BYTE * sizeof(unsigned));

	pF->seek(FILEHEADSIZE + bmid * sizeof(unsigned));
	
	unsigned bm;
	pF->readByte((char*)&bm, sizeof(unsigned));
	
	unsigned f = ~(~(unsigned)0 << 1);
	f = f << bmoffset;
	
	bm = bm | f;
	
	pF->seek(FILEHEADSIZE + bmid * sizeof(unsigned));
	pF->writeByte((char*)&bm, sizeof(unsigned));

	if ( _buMask[fileId] )
	    _fbmMask[fileId] = true;

	if ( __fsyncOn )
	    pF->flush();
	
	pLockHandle->unlockDataFile(fileId);
    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot claim data page"), e);
    }

    // write page data to datafile to avoid, that cleanup procedure frees the page
    
    char* initBuf = new char[_pageSize];
    CegoBufferPage ip(initBuf, _pageSize);
    ip.initPage(CegoBufferPage::TABLE);
    
    writePage(pageId, 1, initBuf, pLockHandle);

    delete[] initBuf;    
}

/* any new required page can be requested using the allocatePage method.
   the required filetype must be specified. 
   the method returns the assigned file and page information */

void CegoFileHandler::allocatePage(unsigned tabSetId, FileType ft, PageIdType& pageId, CegoLockHandler *pLockHandle, unsigned* &fbm, unsigned& fbmSize, bool doAppend)
{    
    if ( _isReadOnly == true )
	throw Exception(EXLOC, Chain("Cannot allocate page in read only mode"));

    unsigned fid = 0;  
    if ( doAppend )
	fid = _appendFid;
   
    while ( fid < FILMNG_MAXDATAFILE )
    {
	if ( _isReg[fid] && _tabSetId[fid] == tabSetId && _fileType[fid] == ft )
	{	    
	    File* pF;
	    pF = getHandle(fid);
	    
	    pLockHandle->lockDataFile(fid, CegoLockHandler::WRITE);	
	    
	    try 
	    {
		unsigned ws = sizeof(unsigned)*BIT_PER_BYTE;

		unsigned k;

		if ( doAppend == false || _appendPos[fid] == 0)
		{
		    pF->seek(FILEHEADSIZE);
		    k = 0;
		    // we reset the append postion to zero, so next allocation with append = true will search from the beginning
		    _appendPos[fid] = 0;
		}
		else
		{		    
		    // cout << "Seek to " << 2*sizeof(int) + sizeof(FileType) + (_appendPos[fid] / ws ) * sizeof(unsigned) << endl;		    
		    pF->seek(FILEHEADSIZE + ( _appendPos[fid] / ws) * sizeof(unsigned));
		    k = _appendPos[fid];
		}
		
		// cout << "k=" << k << " numPages = " << _numPages[fid] << endl;

		bool isEop = k >= _numPages[fid];
		
		while (!isEop)
		{
		    unsigned bm;
		    
		    pF->readByte((char*)&bm, sizeof(unsigned));
		    
		    // printBitMap(bm);
		       
		    if ( (bm & ~0) != (unsigned)~0) 
		    {
			// we start next append search with this fid
			_appendFid = fid;

			// free page available
			unsigned l = 0;
			while (l < ws && ! isEop)
			{
			    if ( (k+l) < _numPages[fid])
			    {
				unsigned checkbit = bm >> l & ~(~(unsigned)0 << 1);
				if (checkbit == 0)
				{			    
				    pageId = k + l + _pageOffset[fid];

				    /*
				    cout << "Allocated page :" << endl;
				    cout << "Fid = " <<  fid << endl;
				    cout << "PageOffset=" << _pageOffset[fid] << endl;
				    cout << "PageId = " << pageId << endl;
				    */
				    
				    // cout << "FH: " << fid << "," <<  k << "," << l << " using pageId " << pageId << endl;
				    				    
				    unsigned f = ~(~(unsigned)0 << 1);
				    f = f << l;
				    // printBitMap(bm);
				    // cout << "1 bm=" << bm << endl;
				    bm = bm | f;
				    // printBitMap(bm);
				    
				    if ( _buMask[fid] )
				    {					
					if ( _fbmMask[fid] == false )
					{	       						      							
					    fbmSize = getFBMSize(fid);
					    fbm = new unsigned[fbmSize]; 							    
					    readFBM(fid, fbm, pLockHandle);
					}
					else
					{
					    fbmSize = 0;
					}
					
					_fbmMask[fid] = true;
				    }

				    // cout << "Writing bitmask to file .." << endl;
				    pF->seek(FILEHEADSIZE + k / BIT_PER_BYTE);
				    pF->writeByte((char*)&bm, sizeof(unsigned));
				    if ( __fsyncOn )
					pF->flush();
				    
				    pLockHandle->unlockDataFile(fid);

				    if ( _appendPos[fid] < k )
				    {
					_appendPos[fid] = k;
				    }
				    return;
				}
				l++;
			    }
			    else
			    {
				isEop = true;
			    }
			}	      
		    }
		    k+=ws;		    
		}
		pLockHandle->unlockDataFile(fid);	
	    }
	    catch ( Exception e )
	    {
		pLockHandle->unlockDataFile(fid);
		throw Exception(EXLOC, Chain("Cannot allocate file page"), e);
	    }		
	} 
	fid++;		    
    }

    if ( doAppend )
    {
	_appendFid=0;
	// if no more pages available with append mode, we make another try without append
	allocatePage(tabSetId, ft, pageId, pLockHandle, fbm, fbmSize, false);
    }
    else
    {    
	Chain fileType;
	if ( ft == DATAFILE )
	    fileType = Chain("Data");
	else if ( ft == SYSTEMFILE )
	    fileType = Chain("System");
	else if ( ft == TEMP )
	    fileType = Chain("Temp");	
	throw Exception(EXLOC, fileType + Chain(" file pages exceeded "));
    }
}

/* if a page is no longer used, it can be released using the releasePage method.
   Normally this is used, in case of delete operations or in case of temporarily used pages */

void CegoFileHandler::releasePage(PageIdType pageId, CegoLockHandler *pLockHandle, unsigned* &fbm, unsigned& fbmSize)
{    
    if ( _isReadOnly == true )
	throw Exception(EXLOC, Chain("Cannot releases page in read only mode"));

    unsigned fileId = getFileIdForPageId(pageId);
    
    File* pF;
    
    try 
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }
    
    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try
    {
	if ( _buMask[fileId] )
	{	    
	    if ( _fbmMask[fileId] == false )
	    {		
		fbmSize = getFBMSize(fileId);
		fbm = new unsigned[fbmSize]; 
		readFBM(fileId, fbm, pLockHandle);
	    }
	}
	else
	{
	    fbmSize = 0;
	}
	       
	unsigned bmid = ( pageId - _pageOffset[fileId] ) / ( sizeof(unsigned) * BIT_PER_BYTE );
	unsigned bmoffset = ( pageId - _pageOffset[fileId] ) % ( sizeof(unsigned) * BIT_PER_BYTE) ;
	
	// cout << "FH release : " << bmid << "," << bmoffset << " using pageId " << pageId << endl;

	pF->seek(FILEHEADSIZE + ( bmid * sizeof(unsigned)));
	
	unsigned bm;
	pF->readByte((char*)&bm, sizeof(unsigned));
	
	unsigned f = ~(~(unsigned)0 << 1);
	f = f << bmoffset;

	// printBitMap(bm);
	// printBitMap(f);
	
	bm = bm & ~f;

	// cout << "Bitmap after " << endl;
	// printBitMap(bm);

	// cout << "Write to pos " << sizeof(int) + sizeof(FileType) + sizeof(int) + ( bmid * sizeof(int)) << endl;

	pF->seek(FILEHEADSIZE + ( bmid * sizeof(unsigned)));
	pF->writeByte((char*)&bm, sizeof(unsigned));
	if ( __fsyncOn )
	    pF->flush();
	
	if ( _buMask[fileId] )
	    _fbmMask[fileId] = true;
	
	pLockHandle->unlockDataFile(fileId);	
    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot release data page"), e);
    }
}

void CegoFileHandler::commitPageEntry(PageIdType pageId)
{
    unsigned fileId = getFileIdForPageId(pageId);

    if ( _commitMask[fileId] == 0 )
    {
    	_commitMask[fileId] = new unsigned[ (  getNumPages(fileId) / (BIT_PER_BYTE * sizeof(unsigned))) + 1];

	unsigned i;
	
	for (i = 0; i < (unsigned)(( getNumPages(fileId) / (BIT_PER_BYTE * sizeof(unsigned))) + 1); i++)
	{
	    _commitMask[fileId][i] = 0;
	}
    }
    
    unsigned bmid = ( pageId - _pageOffset[fileId] ) / ( BIT_PER_BYTE * sizeof(unsigned));
    unsigned bmoffset = ( pageId - _pageOffset[fileId] ) % ( BIT_PER_BYTE * sizeof(unsigned));
    
    unsigned bm = _commitMask[fileId][bmid];
	
    unsigned f = ~(~(unsigned)0 << 1);
    f = f << bmoffset;
    
    _commitMask[fileId][bmid] = bm | f;    
}

unsigned CegoFileHandler::cleanPages(CegoLockHandler *pLockHandle)
{
    if ( _isReadOnly == true )
	throw Exception(EXLOC, Chain("Cannot clean pages in read only mode"));

    unsigned numCleanup = 0;
    
    for (unsigned i = 0; i<FILMNG_MAXDATAFILE; i++)
    {
	if ( _isReg[i] && _fileType[i] == DATAFILE && _commitMask[i])
	{
	    
	    unsigned fbmSize = getFBMSize(i);
	    unsigned* fbm = new unsigned[fbmSize]; 							    
	    readFBM(i, fbm, pLockHandle);

	    bool cleanupNeeded = false;
	    
	    for ( unsigned j=0; j<fbmSize; j++ )
	    {
		// cout << "Checking mask " << _commitMask[i][j] << endl;
		
		if ( fbm[j] != _commitMask[i][j] )
		{
		    // cleanup required


		    cleanupNeeded = true;

		    numCleanup += mapDiff(fbm[i], _commitMask[i][j] );

		    /*
		    cout << "Commited :" << endl;
		    printBitMap(_commitMask[i][j]);

		    cout << "Written :" << endl;
		    printBitMap(fbm[j]);		    		    
		    */
		}
	    }

	    if ( cleanupNeeded )
		writeFBM(i, _commitMask[i], pLockHandle);
	    
	    delete[] fbm;
	}
    }
    return numCleanup;
}

/* returns the number of total pages for the given fileId */

unsigned CegoFileHandler::getNumPages(unsigned fileId)
{
    try 
    {
	// check valid fileId
	getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    return _numPages[fileId];
}

/* returns the number of used pages for the given fileId */

unsigned CegoFileHandler::getNumUsedPages(unsigned fileId, CegoLockHandler *pLockHandle)
{   
    File *pF;

    try 
    {
	pF = getHandle(fileId);
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try
    {	
	pF->seek(sizeof(unsigned) + sizeof(FileType));
	
	unsigned numPage;	
	pF->readByte((char*)&numPage, sizeof(unsigned));

	PageIdType pageOffset;
	pF->readByte((char*)&pageOffset, sizeof(PageIdType));
		
	unsigned p;
	
	unsigned usedPages = 0;

	for ( p = 0; p < (unsigned)((numPage / (BIT_PER_BYTE * sizeof(unsigned))) + 1); p++ )
	{
	    unsigned bm = 0;
	    pF->readByte((char*)&bm, sizeof(unsigned));
	    
	    // printBitMap(bm);
	    
	    unsigned i,j;
	    for (i = 0; i< (unsigned)sizeof(unsigned); i++)
	    {
		for (j=0; j < BIT_PER_BYTE; j++)
		{
		    unsigned one = 1;
		    
		    if ( one & bm )
		    {
			usedPages++;
		    }
		    bm = bm >> 1;
		}
	    }
	}

	pLockHandle->unlockDataFile(fileId);

	return usedPages;
    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot get number of pages for file"), e);
    }
}

////////////////////////////////////////////////
////////////// private methods /////////////////
////////////////////////////////////////////////

/* to proof, if a given pageId  is already in use,
   the isClaimed method is used */
 
bool CegoFileHandler::isClaimed(PageIdType pageId, CegoLockHandler *pLockHandle)
{
    unsigned fileId = getFileIdForPageId(pageId);
	
    File *pF;

    try 
    {
	pF = getHandle(fileId);	
    }
    catch ( Exception e )
    {
	throw Exception(EXLOC, Chain("Cannot get file handle"), e);
    }

    if ( pageId >= _pageOffset[fileId] + _numPages[fileId] )
    {
	throw Exception(EXLOC, Chain("Invalid page id"));
    }
    
    pLockHandle->lockDataFile(fileId, CegoLockHandler::WRITE);

    try 
    {   
	unsigned bmid = ( pageId - _pageOffset[fileId] ) / ( BIT_PER_BYTE * sizeof(unsigned));
	unsigned bmoffset = ( pageId - _pageOffset[fileId] ) % ( BIT_PER_BYTE * sizeof(unsigned));
	
	pF->seek(FILEHEADSIZE + bmid * sizeof(unsigned));
	
	unsigned bm;
	pF->readByte((char*)&bm, sizeof(unsigned));
	
	pLockHandle->unlockDataFile(fileId);
	
	unsigned checkbit = bm >> bmoffset & ~(~(unsigned)0 << 1);    
	
	if (  checkbit )
	{
	    return true;
	}
	return false;
    }
    catch ( Exception e )
    {
	pLockHandle->unlockDataFile(fileId);
	throw Exception(EXLOC, Chain("Cannot check data page for claimed"), e);
       
    }
}

unsigned CegoFileHandler::getFileIdForPageId(PageIdType pageId)
{
    unsigned i=0;
    
    while ( i < FILMNG_MAXDATAFILE )
    {
	if ( _isReg[i] == true && pageId >= _pageOffset[i] && pageId < _pageOffset[i] + _numPages[i] )
	{	    
	    return i;
	}
	i++;
    }
    throw Exception(EXLOC, Chain("Invalid page id ") + Chain(pageId));
}

/* getHandle returns the file handle for the given file id. The
   file id must be valid and registered. If already open, the method
   returns the file handle. Otherwise, the corresponding file is opened
   for reading and writing and the handle is returned. */

File* CegoFileHandler::getHandle(unsigned fileId)
{
    if ( fileId > FILMNG_MAXDATAFILE - 1 ) 
    {
	Chain msg = "File Id " + Chain(fileId)  + " out of valid range";
	throw Exception(EXLOC, msg);
    }

    if ( ! _isReg[fileId] )
    {
	Chain msg = "File Id " + Chain(fileId)  + " not registered";
	throw Exception(EXLOC, msg);
    }

    if ( _fhList[fileId] == 0 )
    {
	File* pF = new File(_path[fileId]);
	
	try
	{	    
	    if ( _isReadOnly == true )
		pF->open(File::READ);
	    else		
		pF->open(File::READWRITE);

	    _fhList[fileId] = pF;
	    _appendPos[fileId] = 0;
	}
	catch (Exception e)
	{
	    Chain ferr;
	    e.pop(ferr);
	    Chain msg = "Cannot get file handle " + Chain(fileId) + Chain(": ") + ferr;
	    throw Exception(EXLOC, msg);
	}
    }
    File *pF = _fhList[fileId];
    return pF;
}

////////////////////////////////////////////////
//////// debugging/testing methods /////////////
////////////////////////////////////////////////

void CegoFileHandler::printBitMap(unsigned bm)
{    
    printf("--- BM -----------\n");
    
    unsigned i,j;
    for (i = 0; i< (int)sizeof(unsigned); i++)
    {
	for (j=0; j < BIT_PER_BYTE; j++)
	{
	    unsigned one = 1;
	    
	    if ( one & bm )
		printf("1");
	    else
		printf("0");
	    bm = bm >> 1;
	    
	}
    }
    printf("\n");
    
    printf("--------------\n");    
}

unsigned CegoFileHandler::mapDiff(unsigned bm1, unsigned bm2)
{    
    unsigned numDiff = 0;
    
    unsigned i,j;
    for (i = 0; i< (unsigned)sizeof(unsigned); i++)
    {
	for (j=0; j < BIT_PER_BYTE; j++)
	{
	    unsigned one = 1;
	    
	    if ( ( one & bm1 ) != (  one & bm2 ) )
		numDiff++;

	    bm1 = bm1 >> 1;
	    bm2 = bm2 >> 1;
	}
    }
    return numDiff;
}
