    /*

    Copyright (C) 2000 Stefan Westerfeld
                       stefan@space.twc.de

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    This library 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
    Library General Public License for more details.
   
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    */

#include "cache.h"
#include "debug.h"
#include "iomanager.h"
#include "dispatcher.h"
#include <iostream>
#include <assert.h>

using namespace std;
using namespace Arts;

bool CachedObject::isValid()
{
	return true;
}

void CachedObject::setKey(string key)
{
	_object_key = key;
}

string CachedObject::getKey()
{
	return _object_key;
}

void CachedObject::decRef()
{
	_ref_cnt--;
	time(&_lastAccess);
}

void CachedObject::incRef()
{
	_ref_cnt++;
}

int CachedObject::refCnt()
{
	return _ref_cnt;
}

time_t CachedObject::lastAccess()
{
	return(_lastAccess);
}

CachedObject::CachedObject(Cache *cache)
{
	_ref_cnt = 1;
	cache->add(this);
}

CachedObject::~CachedObject()
{
	assert(_ref_cnt == 0);
}

//------------------------- Cache implementation ---------------------------
long Cache::memused = 0;

CachedObject *Cache::get(string key)
{
	list<CachedObject *>::iterator i;

	for(i=objects.begin();i != objects.end(); i++)
	{
		if((*i)->getKey() == key && (*i)->isValid())
		{
			(*i)->incRef();
			return(*i);
		}
	}
	return 0;
}

void Cache::add(CachedObject *object)
{
	objects.push_back(object);
}

long Cache::cleanUp(long cacheLimit)
{
	time_t lastAccess;

	list<CachedObject *>::iterator i;
	long memory = 0;

	// delete all invalid unused entries (invalid entries that are still
	// in use, e.g. cached wav files which have changed on disk but are
	// still played can't be deleted!)
	
	for(i=objects.begin();i != objects.end(); i++)
	{
		CachedObject *co = (*i);

		if(co->refCnt() == 0 && !co->isValid())
		{
			objects.remove(co);
			delete co;
			i = objects.begin();
		}
	}

	for(i=objects.begin();i != objects.end(); i++)
	{
		memory += (*i)->memoryUsage();
	}

	bool freeok = true;
	while(memory > cacheLimit && freeok)
	{
		CachedObject *freeme;

		freeok = false;

		// only start freeing objects which have not been accessed
		// in the last 5 seconds

		time(&lastAccess);
		lastAccess -= 5;


		for(i=objects.begin();!freeok && (i != objects.end()); i++)
		{
			CachedObject *co = (*i);

			assert(co->refCnt() >= 0);
			if(co->refCnt() == 0 && (co->lastAccess() < lastAccess))
			{
				lastAccess = co->lastAccess();
				freeme = co;
				freeok = true;
			}
			else
			{
				//artsdebug("%d => %ld\n",co->refCnt(),co->lastAccess());
			}
		}

		if(freeok)
		{
			memory -= freeme->memoryUsage();
			objects.remove(freeme);
			delete(freeme);
		}
		else
		{
			//artsdebug("cache problem: memory overused, but nothing there to free\n");
		}
	}

	memused = memory/1024;
	return(memory);
}

Cache *Cache::_instance = 0;

Cache::Cache()
{
	assert(!_instance);
	_instance = this;
}

Cache::~Cache()
{
	list<CachedObject *>::iterator i;
	for(i=objects.begin(); i != objects.end(); i++)
		delete (*i);
	objects.clear();

	assert(_instance);
	_instance = 0;
}


Cache *Cache::the()
{
	if(!_instance) _instance = new Cache();
	return _instance;
}

void Cache::shutdown()
{
	if(_instance)
	{
		list<CachedObject *>::iterator i;
		long rcnt = 0;
		for(i=_instance->objects.begin(); i != _instance->objects.end(); i++)
			rcnt += (*i)->refCnt();

		if(rcnt == 0)
		{
			delete _instance;
			_instance = 0;
		}
		else
		{
			arts_warning("cache shutdown while still active objects in cache");
		}
	}
}

namespace Arts {			// internal helpers

// periodic cache clean
class CacheClean : public TimeNotify {
public:
	CacheClean();
	void notifyTime();
	virtual ~CacheClean();
};

// cache startup & shutdown
class CacheStartup :public StartupClass
{
public:
	void startup();
	void shutdown();
private:
	CacheClean *cacheClean;
};

}

CacheClean::CacheClean()
{
	Dispatcher::the()->ioManager()->addTimer(5000, this);
}

void CacheClean::notifyTime()
{
	// TODO: make this configurable
	Cache::the()->cleanUp(8192*1024);
}

CacheClean::~CacheClean()
{
	Dispatcher::the()->ioManager()->removeTimer(this);
}

void CacheStartup::startup()
{
	cacheClean = new CacheClean;	
}

void CacheStartup::shutdown()
{
	delete cacheClean;
	Cache::shutdown();
}

static CacheStartup cacheStartup;
