/**********************************************************************
 *
 * MFFile.cpp
 * Copyright (C) 2003  UNESCO
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

////////////////////////////////////////////////////////////
// MfFile.cpp : Master file object methods using blocked I/O
//
////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "IsisDb.h"
#include "Checking.h"



//---------------------------------------------------------------------------------- 
// MfFile::MfFile() :
//
// Constructor, makes a Masterfile object that isn't connected to a file
//---------------------------------------------------------------------------------- 
MfFile::MfFile() : BlkFile(), hdr_created_(false), nextaddr_(MFCTLLEN)
{
    lastWrittenBlock_ = -1;
}
 
//----------------------------------------------------------------------------------
// int MfFile::OpenMf(const TCHAR *fname, 
//                    FileSystem::AccessMode mode /* = FileSystem::FILE_READWRITE */)
//
// Opens a Masterfile object
//---------------------------------------------------------------------------------- 
int MfFile::OpenMf(const TCHAR *fname, FileSystem::AccessMode mode
				                              /* = FileSystem::FILE_READWRITE */)
{
  TCHAR mstFileName[_MAX_PATH];
  _tcscpy(mstFileName, fname);

  if (!CFileBase::Exists(mstFileName)) {
    mstFileName[strlen(mstFileName) - 3] = 'M';
    mstFileName[strlen(mstFileName) - 2] = 'S';
    mstFileName[strlen(mstFileName) - 1] = 'T';
  }

	int rc;
	try
	{
	    rc = CFileBase::Open(mstFileName, mode);
		hdr_created_ = true;
	    ReadMfHdr();
		PrintMfHdr();
		Checking::CheckMfHeader(mstFileName, mfh_);
	}
	catch (CFileBaseException e)
	{
        throw CIsisDbException(ISIS_MF_OPEN_ERROR, __FILE__, __LINE__);
	}
    return rc;
}
 
//----------------------------------------------------------------------------------
// int MfFile::createMf(const TCHAR *fname, int  type)
//
// Creates a Masterfile object
//---------------------------------------------------------------------------------- 
int MfFile::CreateMf(const TCHAR *fname, int  type)
{
    return 0;
}
 
//////////////////////////////
MfFile::~MfFile()
{
}
 
//----------------------------------------------------------------------------------
// void  MfFile::ReadMfRecord(mg_s_long mfb, int mfp, MfRecord &m)
//
// Reads the master file record beginning at address (mfb,mfp) into m
//----------------------------------------------------------------------------------
bool  MfFile::ReadMfRecord(mg_s_long mfb, int mfp, MfRecord &m)
{
	return m.Read(*this, mfb, mfp);
}

//-------------------------------------------------------------------------------
//  void WriteMfRecord(mg_s_long mfb, int mfp, MfRecord &m)
//
//  Helper protected method, which writes the Master file record m at address 
//  (mfb, mfp)   mfb (1:MAXMFB),   mfp (0: MFBLKSIZE)
//-------------------------------------------------------------------------------
void  MfFile::WriteMfRecord(mg_s_long mfb, int mfp, MfRecord &m)
{
	m.Write(*this, mfb, mfp);
} 


//--------------------------------------------------------------------------------
// void CreateMfRecord(MfRecord &m, mg_s_long &mfb, int &mfp)
//
//  Create a new record. The MFN to be assigned is obtained from the field nxtmfn_
//  in the Master file header. Then addMfRecord is called for adding the record
//  at the end of the master file, at the position indicated by the fields
//  nxtmfb_/nxtmfb_. The next MFN to be assigned is updated.
//
//--------------------------------------------------------------------------------
void  MfFile::CreateMfRecord(MfRecord &m, mg_s_long &mfb, int &mfp)
{
}

void  MfFile::AppendNewBlock()
{
}

//--------------------------------------------------------------------------------
// void AddMfRecord(MfRecord &m, mg_s_long &mfb, int &mfp)
//
// Add the record at the end of the master file, at the position indicated by
// the fields nxtmfb_/nxtmfp_. (nxtmfp_ is in range [1:MFBLKSIZE])
// Return the address where the record was stored into mfb & mfp
//--------------------------------------------------------------------------------
void  MfFile::AddMfRecord(MfRecord &m, mg_s_long &mfb, int &mfp)
{
}
 
 
//--------------------------------------------------------------------------------
// void UpdateMfRecord(MfRecord &m, mg_s_long &mfb, int &mfp)
//
// Update a Master file record.If possible, i.e. if the record length was not
// increased, the record is written back at its original location otherwise it is
// written at the end of the file. In both cases, MFBWB/MFBWP are not changed.
// 
//--------------------------------------------------------------------------------
void  MfFile::UpdateMfRecord(MfRecord &m, mg_s_long &mfb, int &mfp)
{
}


/////////////////////////////////////////////////////////////////////////////////////
// methods for dumping file and record headers

 
void MfFile::PrintMfHdr(SMfHeader &h)
// Prints header record
{
}
 
//////////////////////////////
 
void MfFile::PrintMfRecord(MfRecord &m)
// Print a master file record structure
{
	m.Dump();
}

/////////////////////////////////////////////////////////////////////////////////////
// Methods for accessing records directly
 
//----------------------------------------------------------------------------------
// void MfFile::ReadMfRecord(mg_s_long daddr, MfRecord &m)
//
// Reads a master file record into the master record object m.
// Non blocked version
//----------------------------------------------------------------------------------
void MfFile::ReadMfRecord(mg_s_long daddr, MfRecord &m)
{
	m.Clear();
    ReadLeader(daddr, m);       // Read record leader (Leader is added to record)
	m.ExtractSubLeader();
    
	//TRACE("\nReadMfRecord -- mfn=%ld mfrl=%d", m.GetMfn(), m.GetMfrl());
	//assert(m.CheckIntegrity());
	int mfrl = m.GetMfrl();
	if (mfrl < 0) mfrl = -mfrl; 	// mfrl can be negative!!!!
	char* p = new char[mfrl];
    CFileBase::Fetch(p, mfrl, daddr);
	m.AddToRecord(p+GetLeaderSize(), mfrl-GetLeaderSize());
	delete[] p;
    nextaddr_ += mfrl;
 	int mfb = nextaddr_ / MFBLKSIZE + 1;
    int mfp = nextaddr_ % MFBLKSIZE;
    if (mfp > MFBLKSIZE - GetMinLeaderSize())
	{
		mfb++;
	    mfp = 0;
	}
	nextaddr_ = (mfb-1)*MFBLKSIZE + mfp;
	m.DecomposeRecord();
}

//---------------------------------------------------------------------------------
// void  MfFile::WriteMfRecord(mg_s_long daddr, MfRecord &m)
//
// Stores Master record object m to disk address daddr
// Non blocked version
//---------------------------------------------------------------------------------
void  MfFile::WriteMfRecord(mg_s_long daddr, MfRecord &m)
{
}
/////////////////////////////////////////////////////////////////////////////////////
// Master file locking methods


 
//-----------------------------------------------------------------------------------
// void MfFile::InitFileLockHdr(SMfLockHeader &hdr)
//
// Initialize a the lock header for a newly constructed file lock header.
//-----------------------------------------------------------------------------------
void MfFile::InitFileLockHdr(SMfLockHeader &hdr)
{
	hdr.lockProtect_ = (uint32_t)0;
	hdr.readLock_    = (uint32_t)0;
	hdr.writeLock_   = (uint32_t)0;
}

//-----------------------------------------------------------------------------------
// FileError MfFile::WriteFileLockHdr(const SMfLockHeader &hdr)
//
// Write the lock header to the file. 
// Returns a non-zero value to indicate an error condition or zero if successful.
//-----------------------------------------------------------------------------------
FileError MfFile::WriteFileLockHdr(const SMfLockHeader &hdr)
{
	
	return CFileBase::Store(&hdr, sizeof(SMfLockHeader), sizeof(SMfHeader));
}

//-----------------------------------------------------------------------------------
// FileError MfFile::ReadFileLockHdr(SMfLockHeader &hdr)
//
// Read the lock header from the file.
// Returns a non-zero value to indicate an error condition or zero if successful.
//-----------------------------------------------------------------------------------
FileError MfFile::ReadFileLockHdr(SMfLockHeader &hdr)
{
	
	return CFileBase::Fetch(&hdr, sizeof(SMfLockHeader), sizeof(SMfHeader));
}

//-----------------------------------------------------------------------------------
// int MfFile::LockFile(CFileMgrLockType l_type) 
//
// Lock the file and set the lock access mode to shared or exclusive.
// Returns a non-zero value if the file cannot be locked or the lock variable cannot
// be changed because it is exclusive or another thread is currently updating it. 
//-----------------------------------------------------------------------------------
int MfFile::LockFile(MfLockType l_type) 
{
	// Read the file lock to ensure that this file has a lock header
	// and to keep the header in sync during multiple file access.
	SMfLockHeader lock_header;
	if (ReadFileLockHdr(lock_header) != FILE_NO_ERROR)
		return fileError_;
	
	// Cannot modifiy this lock until the thread holding it releases the
	// lock header.
	if (lock_header.lockProtect_ != 0)
	{
		fileError_ = ISIS_MFLOCK_ACCESS_ERROR;
#ifdef __CPP_EXCEPTIONS__
		throw CFileBaseException(fileError_);
#else
		return fileError_;
#endif
	}
	
	switch (l_type)
	{
		case MF_WRITE_LOCK :
			// Cannot obtain an exclusive lock until all the threads in the
			// read lock queue have finished
			if ((lock_header.readLock_ != 0) || 
				(lock_header.writeLock_ != 0))
			{
				fileError_ = ISIS_MFLOCK_ERROR;
	#ifdef __CPP_EXCEPTIONS__
				throw CFileBaseException(fileError_);
	#else
				return fileError_;
	#endif
			}
			else 
			{
				lock_header.writeLock_ = 1;
			}
			
			lock_header.lockProtect_ = 1;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			
			// Skip over the read lock member
			SeekTo(sizeof(SMfHeader) + (sizeof(uint32_t) * 2));
			if (CFileBase::Store(&lock_header.writeLock_, sizeof(uint32_t)) !=
				FILE_NO_ERROR)
				return fileError_;
			
			lock_header.lockProtect_ = (uint32_t)0;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			break;
			
		case MF_READ_LOCK :
			// This lock is exclusively owned and cannot be read locked
			// until the thread holding this lock release the exclusive lock.
			if (lock_header.writeLock_ == 1)
			{
				fileError_ = ISIS_MFLOCK_ERROR;
	#ifdef __CPP_EXCEPTIONS__
				throw CFileBaseException(fileError_);
	#else
				return fileError_;
	#endif
			}
			
			if (lock_header.readLock_ < 0xFFFFFFFF)
			{
				lock_header.readLock_++;
			}
			else 
			{
				fileError_ = ISIS_MFLOCK_ERROR;
	#ifdef __CPP_EXCEPTIONS__
				throw CFileBaseException(fileError_);
	#else
				return fileError_;
	#endif
			}
			
			lock_header.lockProtect_ = 1;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			
			if (CFileBase::Store(&lock_header.readLock_, sizeof(uint32_t)) !=
				FILE_NO_ERROR)
				return fileError_; 
			
			lock_header.lockProtect_ = (uint32_t)0;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			break;
			
		default: // Invalid lock type specified
			fileError_ =  ISIS_INVALID_LOCK_TYPE;
	#ifdef __CPP_EXCEPTIONS__
			throw CFileBaseException(fileError_);
	#else
			return fileError_;
	#endif
	}
	
	return fileError_ = fileError_ = FILE_NO_ERROR;
}

//-----------------------------------------------------------------------------------
// int MfFile::UnlockFile(CFileMgrLockType l_type)
//
// Unlock the file. Returns a non-zero value if the file cannot be unlocked or the
// lock variable cannot be changed because it is exclusive or another thread is
// currently updating it. 
//-----------------------------------------------------------------------------------
int MfFile::UnlockFile(MfLockType l_type)
{
	
	// Read the file lock to ensure that this file has a lock header
	// and to keep the header in sync during multiple file access.
	SMfLockHeader lock_header;
	if (ReadFileLockHdr(lock_header) != FILE_NO_ERROR)
		return fileError_;
	
	// Cannot modifiy this lock until the thread holding it releases the
	// lock header.
	if (lock_header.lockProtect_ != 0)
	{
		fileError_ = ISIS_MFLOCK_ACCESS_ERROR;
#ifdef __CPP_EXCEPTIONS__
		throw CFileBaseException(fileError_);
#else
		return fileError_;
#endif
	}
	
	switch (l_type)
	{
		case MF_WRITE_LOCK :
			if (lock_header.writeLock_ == 0)
				return FILE_NO_ERROR;
			else
				lock_header.writeLock_ = 0;
			
			lock_header.lockProtect_ = 1;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			
			// Skip over the read lock member
			SeekTo(sizeof(SMfHeader) + (sizeof(uint32_t) * 2));
			if (CFileBase::Store(&lock_header.writeLock_, sizeof(uint32_t)) !=
				FILE_NO_ERROR)
				return fileError_;
			
			lock_header.lockProtect_ = (uint32_t)0;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			break;
			
		case MF_READ_LOCK :
			if (lock_header.readLock_ == 0)      // This record is not locked
				return fileError_ = FILE_NO_ERROR;
			else if (lock_header.readLock_  > 0) // Prevent read lock rollover
				lock_header.readLock_--;
			else
				lock_header.readLock_ = 0;
			
			lock_header.lockProtect_ = 1;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			
			if (CFileBase::Store(&lock_header.readLock_, sizeof(uint32_t)) !=
				FILE_NO_ERROR)
				return fileError_; 
			
			lock_header.lockProtect_ = (uint32_t)0;
			if (CFileBase::Store(&lock_header.lockProtect_, sizeof(uint32_t),
				sizeof(SMfHeader)) != FILE_NO_ERROR)
				return fileError_;
			break;
			
		default: // Invalid lock type specified
			fileError_ =  FMGR_INVALID_LOCK_TYPE;
	#ifdef __CPP_EXCEPTIONS__
			throw CFileBaseException(fileError_);
	#else
			return fileError_;
	#endif
	}
	
	return fileError_ = fileError_ = FILE_NO_ERROR;
}

//-----------------------------------------------------------------------------------
// FileError MfFile::ResetFileLock()
//
// Reset the file's lock header. This function will clear all the file lock fields
// without testing the lock or the lock protect. Returns a non-zero value to indicate
// an error condition or zero if successful.
//-----------------------------------------------------------------------------------
FileError MfFile::ResetFileLock()
{
	SMfLockHeader lock_header;
	InitFileLockHdr(lock_header);
	return WriteFileLockHdr(lock_header);
}
