//	VirtualDub - Video processing and capture application
//	System library component
//	Copyright (C) 1998-2005 Avery Lee, All Rights Reserved.
//
//	Beginning with 1.6.0, the VirtualDub system library is licensed
//	differently than the remainder of VirtualDub.  This particular file is
//	thus licensed as follows (the "zlib" license):
//
//	This software is provided 'as-is', without any express or implied
//	warranty.  In no event will the authors be held liable for any
//	damages arising from the use of this software.
//
//	Permission is granted to anyone to use this software for any purpose,
//	including commercial applications, and to alter it and redistribute it
//	freely, subject to the following restrictions:
//
//	1.	The origin of this software must not be misrepresented; you must
//		not claim that you wrote the original software. If you use this
//		software in a product, an acknowledgment in the product
//		documentation would be appreciated but is not required.
//	2.	Altered source versions must be plainly marked as such, and must
//		not be misrepresented as being the original software.
//	3.	This notice may not be removed or altered from any source
//		distribution.

#include <stdafx.h>
#include <vd2/system/cache.h>

///////////////////////////////////////////////////////////////////////////

VDCache::VDCache(IVDCacheAllocator *pAllocator)
	: mpAllocator(pAllocator)
	, mObjectCount(0)
	, mObjectLimit(16)
{
}

VDCache::~VDCache() {
	Shutdown();
}

void VDCache::Shutdown() {
	for(int i=0; i<kVDCacheStateCount; ++i) {
		ObjectList& ol = mLists[i];

		while(!ol.empty()) {
			VDCachedObject *pObject = static_cast<VDCachedObject *>(ol.back());
			ol.pop_back();

			pObject->OnCacheEvict();
			pObject->SetCache(NULL);	// will release object

			if (i != kVDCacheStateFree) {
				VDASSERT((int)--mObjectCount >= 0);
			}
		}
	}
}

int VDCache::GetStateCount(int state) {
	vdsynchronized(mLock) {
		return mLists[state].size();
	}
}

void VDCache::DumpListStatus(int state) {
	vdsynchronized(mLock) {
		ObjectList& ol = mLists[state];

		for(ObjectList::iterator it(ol.begin()), itEnd(ol.end()); it!=itEnd; ++it) {
			VDCachedObject *pObj = static_cast<VDCachedObject *>(*it);

			pObj->DumpStatus();
		}
	}
}

VDCachedObject *VDCache::Allocate(sint64 key) {
	VDCachedObject *pObj = NULL;

	vdsynchronized(mLock) {
		if (mObjectCount >= mObjectLimit - 1)
			Evict(mObjectLimit - 1);

		ObjectList& fl = mLists[kVDCacheStateFree];
		ObjectList& pl = mLists[kVDCacheStatePending];

		if (fl.empty()) {
			VDCachedObject *pNewObject = mpAllocator->OnCacheAllocate();

			pNewObject->SetCache(this);
			pNewObject->SetState(kVDCacheStateFree);

			fl.push_front(pNewObject);
		}

		++mObjectCount;

		pObj = static_cast<VDCachedObject *>(fl.front());
		VDASSERT(pObj->GetState() == kVDCacheStateFree);
		pObj->AddRef();
		pObj->SetState(kVDCacheStatePending);
		pObj->mHashKey = key;
		pl.splice(pl.begin(), fl, fl.fast_find(pObj));
		mHash.insert(pObj);
	}

	return pObj;
}

VDCachedObject *VDCache::Create(sint64 key, bool& is_new) {
	// The pending, ready, active, and complete lists are eligible for lookup.
	// The free and aborted lists are not.

	VDCachedObject *pObj = NULL;

	is_new = false;

	vdsynchronized(mLock) {
		pObj = static_cast<VDCachedObject *>(mHash[key]);

		if (pObj) {
			pObj->AddRef();

			VDASSERT(pObj->GetState() != kVDCacheStateFree);

			if (pObj->GetState() == kVDCacheStateIdle) {
				pObj->SetState(kVDCacheStateComplete);

				ObjectList& il = mLists[kVDCacheStateIdle];
				ObjectList& cl = mLists[kVDCacheStateComplete];

				cl.splice(cl.begin(), il, il.fast_find(pObj));
			}
		}

		if (!pObj) {
			is_new = true;
			pObj = Allocate(key);
		}
	}

	return pObj;
}

void VDCache::Evict(uint32 level) {
	if (mObjectCount <= level)
		return;

	int maxfree = mObjectCount - level;

	ObjectList& il = mLists[kVDCacheStateIdle];
	ObjectList& al = mLists[kVDCacheStateAborting];

	while(maxfree-- > 0 && mObjectCount >= level && !il.empty()) {
		VDCachedObject *pObject = static_cast<VDCachedObject *>(il.back());
		VDASSERT(pObject->GetState() == kVDCacheStateIdle);
		
		pObject->SetState(kVDCacheStateAborting);
		al.splice(al.begin(), il, pObject);

		pObject->WeakAddRef();

		mLock.Unlock();

		pObject->OnCacheEvict();
		pObject->WeakRelease();			// Will move to free list.

		mLock.Lock();
	}
}

void VDCache::NotifyFree(VDCachedObject *pObject) {
	vdsynchronized(mLock) {
		int rc = pObject->GetRefCount();

		// This check is required because it is possible for a call to
		// Allocate() to sneak in before we acquire the lock.
		if (rc < 0x10000) {
			VDCacheState oldState = pObject->GetState();
			VDCacheState newState = oldState;

			if (rc & 0xfffe)
				newState = kVDCacheStateAborting;
			else if (pObject->IsValid())
				newState = kVDCacheStateIdle;
			else {
				VDVERIFY((int)--mObjectCount >= 0);
				newState = kVDCacheStateFree;
				mHash.erase(pObject);
			}

			if (newState != oldState) {
				pObject->SetState(newState);

				ObjectList& nl = mLists[newState];
				ObjectList& ol = mLists[oldState];
				nl.splice(nl.begin(), ol, ol.fast_find(pObject));
			}

			if (oldState == kVDCacheStatePending || oldState == kVDCacheStateReady)
				pObject->OnCacheAbortPending();
		}
	}
}

void VDCache::Schedule(VDCachedObject *pObject) {
	vdsynchronized(mLock) {
		VDCacheState oldState = pObject->GetState();

		VDASSERT(oldState == kVDCacheStatePending || oldState == kVDCacheStateActive);

		ObjectList& ol = mLists[oldState];
		ObjectList& nl = mLists[kVDCacheStateReady];

		nl.splice(nl.back(), ol, ol.fast_find(pObject));
		pObject->SetState(kVDCacheStateReady);
	}
}

VDCachedObject *VDCache::GetNextReady() {
	VDCachedObject *pObject = NULL;

	vdsynchronized(mLock) {
		ObjectList& rl = mLists[kVDCacheStateReady];
		ObjectList& al = mLists[kVDCacheStateActive];

		if (!rl.empty()) {
			pObject = static_cast<VDCachedObject *>(rl.front());
			VDASSERT(pObject->GetState() == kVDCacheStateReady);

			al.splice(al.end(), rl, rl.begin());

			pObject->SetState(kVDCacheStateActive);
			pObject->AddRef();
		}
	}

	return pObject;
}

void VDCache::MarkCompleted(VDCachedObject *pObject) {
	vdsynchronized(mLock) {
		VDCacheState oldState = pObject->GetState();
		VDASSERT(oldState == kVDCacheStatePending || oldState == kVDCacheStateActive);

		ObjectList& al = mLists[oldState];
		ObjectList& cl = mLists[kVDCacheStateComplete];

		if (!al.empty()) {
			cl.splice(cl.end(), al, al.fast_find(pObject));

			pObject = static_cast<VDCachedObject *>(cl.back());
			pObject->SetState(kVDCacheStateComplete);
		}
	}
}

///////////////////////////////////////////////////////////////////////////

VDCachedObject::VDCachedObject()
	: mpCache(nullptr)
	, mRefCount(0)
{
}

int VDCachedObject::AddRef() {
	int rv = (mRefCount += 0x10000);

	return rv >> 16;
}

int VDCachedObject::Release() {
	int rv = (mRefCount -= 0x10000);

	VDASSERT(rv >= 0);

	if (rv < 0x10000) {
		if (!rv)
			delete this;
		else if (mpCache)
			mpCache->NotifyFree(this);
	}

	return rv >> 16;
}

void VDCachedObject::WeakAddRef() {
	mRefCount += 2;
}

void VDCachedObject::WeakRelease() {
	int rv = (mRefCount -= 2);

	VDASSERT((rv & 0xffff) < 0x8000);

	if (rv < 2) {
		if (!rv)
			delete this;
		else
			mpCache->NotifyFree(this);
	}
}

void VDCachedObject::SetCache(VDCache *pCache) {
	mpCache = pCache;
	if (pCache)
		++mRefCount;
	else {
		if (!--mRefCount)
			delete this;
	}
}

///////////////////////////////////////////////////////////////////////////

VDPool::VDPool(IVDPoolAllocator *pAllocator)
	: mpAllocator(pAllocator)
	, mObjectCount(0)
	, mObjectLimit(16)
{
}

VDPool::~VDPool() {
	Shutdown();
}

void VDPool::Shutdown() {
	for(int i=0; i<kVDPoolStateCount; ++i) {
		ObjectList& ol = mLists[i];

		while(!ol.empty()) {
			VDPooledObject *pObject = static_cast<VDPooledObject *>(ol.back());
			ol.pop_back();

			pObject->SetPool(NULL);	// will release object

			VDASSERT((int)--mObjectCount >= 0);
		}
	}
}

VDPooledObject *VDPool::Allocate() {
	VDPooledObject *pObj = NULL;

	vdsynchronized(mLock) {
		ObjectList& fl = mLists[kVDPoolStateFree];
		ObjectList& pl = mLists[kVDPoolStateActive];

		if (fl.empty()) {
			VDPooledObject *pNewObject = mpAllocator->OnPoolAllocate();

			pNewObject->SetPool(this);

			fl.push_front(pNewObject);
			++mObjectCount;
		}

		pObj = static_cast<VDPooledObject *>(fl.front());
		pObj->AddRef();
		pl.splice(pl.begin(), fl, fl.fast_find(pObj));
	}

	return pObj;
}

void VDPool::NotifyFree(VDPooledObject *pObject) {
	vdsynchronized(mLock) {
		// This check is required because it is possible for a call to
		// Allocate() to sneak in before we acquire the lock.

		if (pObject->GetRefCount() < 2) {
			mLists[kVDPoolStateActive].erase(pObject);

			if (mObjectCount > mObjectLimit) {
				delete pObject;
				--mObjectCount;
			} else
				mLists[kVDPoolStateFree].push_back(pObject);
		}
	}
}

///////////////////////////////////////////////////////////////////////////

VDPooledObject::VDPooledObject()
	: mpPool(nullptr)
	, mRefCount(0)
{
}

int VDPooledObject::AddRef() {
	return (mRefCount += 2) >> 1;
}

int VDPooledObject::Release() {
	int rv = (mRefCount -= 2);

	VDASSERT(rv >= 0);

	if (rv < 2) {
		if (!rv)
			delete this;
		else if (mpPool)
			mpPool->NotifyFree(this);
	}

	return rv >> 1;
}

void VDPooledObject::SetPool(VDPool *pPool) {
	mpPool = pPool;
	if (pPool)
		++mRefCount;
	else {
		if (!--mRefCount)
			delete this;
	}
}
