/**********************************************************************
 *
 * CacheMan.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.
 *
 *********************************************************************/


#include "stdafx.h"
#include "File.h"

#include "CacheMan.h"


CacheManager::CacheManager()
{
	maxblocks_ = 0;
}
CacheManager::CacheManager(unsigned blocksz, unsigned mxblks)
{
	PRECONDITION(maxblocks_ == 0);
	maxblocks_ = mxblks;
	nused_     = 0;
	blocksize_ = blocksz;
	buff_      = new char[blocksize_*maxblocks_];
	diskAddrs_ = new FileSystem::FAU_t[maxblocks_];
	useCounts_ = new unsigned[maxblocks_];
	theFile_   = NULL;
}
/*
 * Construct a cache manager for blocks of size blocksz and mxblks buffers
 */
void CacheManager::Create(unsigned blocksz, unsigned mxblks)
{
	PRECONDITION(maxblocks_ == 0);
	maxblocks_ = mxblks;
	nused_     = 0;
	blocksize_ = blocksz;
	buff_      = new char[blocksize_*maxblocks_];
	diskAddrs_ = new FileSystem::FAU_t[maxblocks_];
	useCounts_ = new unsigned[maxblocks_];
	theFile_   = NULL;
}

//
// Connect to a CFileBase
//
void CacheManager::Connect(CFileBase* file)
{
	PRECONDITION(nused_ == 0);
	theFile_   = file;
}
//
// Disconnect the cache from the CFileBase
//
void CacheManager::Disconnect()
{
	if (theFile_)
	{
		if (theFile_->IsOpen())
		    Flush();
	}
	theFile_   = NULL;
}

void CacheManager::Erase()
{
	Disconnect();
	delete[] useCounts_;
	delete[] diskAddrs_;
	delete[] buff_;
	maxblocks_ = 0;
}


CacheManager::~CacheManager()
{
	Disconnect();
	delete[] useCounts_;
	delete[] diskAddrs_;
	delete[] buff_;
}

bool CacheManager::Flush()
{
	PRECONDITION(theFile_ != NULL && theFile_->IsOpen());
	// Because we use "write through", this is all that's necessary:
	if (theFile_->Flush() == FILE_NO_ERROR)
		return true;
	return false;
}

void CacheManager::Invalidate()
{
	nused_ = 0;
}

bool CacheManager::Read(FileSystem::FAU_t locn, void* dat)
{
	PRECONDITION(dat != NULL);
	size_t islot = AgeAndFindSlot(locn);
	
	if (islot == INVALID_NPOS)
	{
        // Not in buffer;  we'll have to read it in from disk.
	    // Get a free slot.
		if ((islot = GetFreeSlot()) == INVALID_NPOS)
			return false;
		diskAddrs_[islot] = locn;
		if (!(theFile_->SeekTo(locn) == locn) || 
			!(theFile_->Fetch(buff_ + islot*blocksize_, blocksize_) == FILE_NO_ERROR))
			return false;
	}
	useCounts_[islot] = 0;
	memcpy(dat, buff_ + islot*blocksize_, blocksize_);
	return true;
}

bool CacheManager::Write(FileSystem::FAU_t locn, void* dat)
{
	PRECONDITION(dat != NULL);
	PRECONDITION(theFile_->ReadyForWriting());
	size_t islot = AgeAndFindSlot(locn);
	
	if (islot == INVALID_NPOS)
	{
        // Not in buffer; find a free slot.
		if ((islot = GetFreeSlot()) == INVALID_NPOS)
			return false;
		diskAddrs_[islot] = locn;
	}
	
	useCounts_[islot] = 0;
	memcpy(buff_ + islot*blocksize_, dat, blocksize_);
	if ((theFile_->SeekTo(locn) == locn) &&
		(theFile_->Store(buff_ + islot*blocksize_, blocksize_) == FILE_NO_ERROR))
		return true;
	return false;
}

//////////////////////////////////////////////////////////////////////////////////
// Private helper functions	

size_t CacheManager::AgeAndFindSlot(FileSystem::FAU_t locn)
{
	size_t islot = INVALID_NPOS;
	for (register unsigned i = 0; i < nused_; i++)
	{
		if (diskAddrs_[i] == locn)
			islot = i;
		useCounts_[i]++;		// Age the blocks
	}
	return islot;
}

bool CacheManager::Flush(unsigned)
{
	if (theFile_->Flush() == FILE_NO_ERROR)
		return true;
	return false;
}

size_t CacheManager::GetFreeSlot()
{
	size_t islot;
	
	if (nused_ < maxblocks_)
	{
		islot = nused_++;		// Found an unused slot.
	}
	else 
	{
		// No free slots; get the Least Recently Used block
		islot = LRU();

		if (theFile_->ReadyForWriting())
			if (!Flush(islot))
			    islot = INVALID_NPOS;
	}
	return islot;
}

size_t CacheManager::LRU() const
{
	size_t islot = 0;
	unsigned maxCount = useCounts_[0];
	for (register unsigned i = 1; i < nused_; i++)
	{
		if (useCounts_[i] > maxCount)
		{
			maxCount = useCounts_[islot = i]; 
		}
	}
	return islot;
}
