///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoTableCache.cc
// -----------------
// Cego table cache implementation
//      
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoTableCache
// 
// Description: Table Cache Management
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

#include <lfcbase/ThreadLock.h>
#include <lfcbase/Sleeper.h>

#include "CegoTableCache.h"
#include "CegoXMLdef.h"
#include "CegoDatabaseManager.h"

static ThreadLock _cacheLock[TABMNG_MAXTABSET];
extern bool __lockStatOn;

CegoTableCache::TableCacheEntry::TableCacheEntry()
{
    _pCacheArray = 0;
    _numHit=0;
    _numUsed=0;
}

CegoTableCache::TableCacheEntry::TableCacheEntry(unsigned tabSetId, const Chain& tableName)
{
    _tabSetId = tabSetId;
    _tableName = tableName;
    _pCacheArray = 0;
    _numHit=0;
    _numRow=0;
    _numCol=0;
    _numUsed=0;
}

CegoTableCache::TableCacheEntry::TableCacheEntry(unsigned tabSetId, const Chain& tableName, ListT< ListT<CegoFieldValue> >* pCacheList)
{
    _tabSetId = tabSetId;
    _tableName = tableName;
    
    _pCacheArray = new CegoFieldValue**[ pCacheList->Size() ];
    ListT<CegoFieldValue>* pFVL = pCacheList->First();
    _numRow = 0;
    _numCol = 0;
    _numUsed=0;
    while ( pFVL )
    {
	CegoFieldValue** pCFVL = new CegoFieldValue*[pFVL->Size()];
	CegoFieldValue* pFV = pFVL->First();
	unsigned i = 0;
	while ( pFV )
	{
	    CegoFieldValue* pCFV = new CegoFieldValue(pFV->getLocalCopy());
	    pCFVL[i] = pCFV;
	    pFV = pFVL->Next();
	    i++;
	}
	_numCol = i;
       
	_pCacheArray[_numRow] = pCFVL;
	_numRow++;
	pFVL = pCacheList->Next();	
    }
    _numHit=1;
}

CegoTableCache::TableCacheEntry::~TableCacheEntry()
{
}

const unsigned CegoTableCache::TableCacheEntry::getSize() const
{
    unsigned s = sizeof(unsigned); // tabsSetId
    s += _tableName.length();

    for ( unsigned i=0; i<_numRow; i++ )
    {
	for ( unsigned j=0; j<_numCol; j++ )
	{
	    CegoFieldValue *pF = _pCacheArray[i][j];
	    s += pF->size();
	}
    }
    return s;	
}

unsigned CegoTableCache::TableCacheEntry::getHashPos(unsigned hashSize) const
{
    return _tableName.getHashPos(hashSize);
}

unsigned CegoTableCache::TableCacheEntry::getNumRows() const
{
    return _numRow;
}

unsigned CegoTableCache::TableCacheEntry::getNumCols() const
{
    return _numCol;
}

const Chain& CegoTableCache::TableCacheEntry::getTableName() const
{
    return _tableName;
}

unsigned CegoTableCache::TableCacheEntry::getTabSetId() const
{
    return _tabSetId;
}

CegoFieldValue*** CegoTableCache::TableCacheEntry::claimCache()
{
    _numUsed++;
    return _pCacheArray;
}

void CegoTableCache::TableCacheEntry::releaseCache()
{
    _numUsed--;
}

unsigned long CegoTableCache::TableCacheEntry::getHit() const
{
    return _numHit;
}

void CegoTableCache::TableCacheEntry::incHit()
{
    _numHit++;
}

bool CegoTableCache::TableCacheEntry::cleanCache()
{
    if ( _numUsed > 0 )
	return false;
    
    for ( unsigned i=0; i<_numRow; i++ )
    {
	for ( unsigned j=0; j<_numCol; j++ )
	    delete _pCacheArray[i][j];
	delete _pCacheArray[i];
    }
    delete _pCacheArray;
    _pCacheArray = 0;
    return true;
}

CegoTableCache::TableCacheEntry& CegoTableCache::TableCacheEntry::operator = ( const CegoTableCache::TableCacheEntry& tce)
{
    _tabSetId = tce._tabSetId;
    _tableName = tce._tableName;
    _pCacheArray = tce._pCacheArray;
    _numHit = tce._numHit;
    _numRow = tce._numRow;
    _numCol = tce._numCol;
    return (*this);
}

bool CegoTableCache::TableCacheEntry::operator == ( const CegoTableCache::TableCacheEntry& tce)
{
    if ( _tableName == tce._tableName && _tabSetId == tce._tabSetId )
	return true;
    return false;
}

CegoTableCache::CegoTableCache(unsigned tabSetId, const Chain& cacheFilter, unsigned maxSize, unsigned maxEntry, unsigned hashRange, CegoDatabaseManager* pDBMng)
{
    _cacheLock[tabSetId].init(LCKMNG_LOCKWAITDELAY, __lockStatOn);
    _cacheLock[tabSetId].setId( Chain("TCACHE") + Chain("-") + Chain(tabSetId));
    _tabSetId = tabSetId;
    _cacheFilter = cacheFilter;
    _maxSize = maxSize;
    _maxEntry = maxEntry;
    _hashRange = hashRange;

    _usedSize = 0;
    _numFail = 0;
    
    _pDBMng = pDBMng;

    _pTableFilter = new Matcher(_cacheFilter);
    _pTableFilter->prepare();
    
    _pTableCache = new HashT<TableCacheEntry>(_maxEntry, _hashRange);
}

CegoTableCache::~CegoTableCache()
{
    clean();
    delete _pTableFilter;
    delete _pTableCache;
}

void CegoTableCache::PR()
{
    _cacheLock[_tabSetId].readLock(DBM_LOCKTIMEOUT);
}

void CegoTableCache::PW()
{
    _cacheLock[_tabSetId].writeLock(DBM_LOCKTIMEOUT);
}

void CegoTableCache::V()
{
    _cacheLock[_tabSetId].unlock();
}

Element* CegoTableCache::getCacheInfo()
{
    Element* pCacheInfo = new Element(XML_CACHEINFO_ELEMENT);

    Element *pN = new Element(XML_CACHE_ELEMENT);
    pN->setAttribute(XML_ATTRNAME_ATTR, Chain("CacheFilter"));
    pN->setAttribute(XML_VALUE_ATTR, _cacheFilter);
    pCacheInfo->addContent(pN);

    pN = new Element(XML_CACHE_ELEMENT);
    pN->setAttribute(XML_ATTRNAME_ATTR, Chain("MaxSize"));
    pN->setAttribute(XML_VALUE_ATTR, Chain(_maxSize));
    pCacheInfo->addContent(pN);

    pN = new Element(XML_CACHE_ELEMENT);
    pN->setAttribute(XML_ATTRNAME_ATTR, Chain("MaxEntry"));
    pN->setAttribute(XML_VALUE_ATTR, Chain(_maxEntry));
    pCacheInfo->addContent(pN);
    
    pN = new Element(XML_CACHE_ELEMENT);
    pN->setAttribute(XML_ATTRNAME_ATTR, Chain("HashRange"));
    pN->setAttribute(XML_VALUE_ATTR, Chain(_hashRange));
    pCacheInfo->addContent(pN);
    
    pN = new Element(XML_CACHE_ELEMENT);
    pN->setAttribute(XML_ATTRNAME_ATTR, Chain("UsedSize"));
    pN->setAttribute(XML_VALUE_ATTR, Chain(_usedSize));
    pCacheInfo->addContent(pN);

    unsigned numEntry = _pTableCache->numEntry();
    pN = new Element(XML_CACHE_ELEMENT);
    pN->setAttribute(XML_ATTRNAME_ATTR, Chain("NumEntry"));
    pN->setAttribute(XML_VALUE_ATTR, Chain(numEntry));
    pCacheInfo->addContent(pN);

    pN = new Element(XML_CACHE_ELEMENT);
    pN->setAttribute(XML_ATTRNAME_ATTR, Chain("NumFail"));
    pN->setAttribute(XML_VALUE_ATTR, Chain(_numFail));
    pCacheInfo->addContent(pN);
    
    return pCacheInfo;
}

Element* CegoTableCache::getCacheList()
{
    Element* pCacheInfo = new Element(XML_CACHEINFO_ELEMENT);
    PR();

    TableCacheEntry *pCE = _pTableCache->First();	
    while ( pCE )
    {
	Element *pN = new Element(XML_CACHE_ELEMENT);

	pN->setAttribute(XML_POS_ATTR, _pTableCache->getPos());
	Chain tableSet;
	if ( _pDBMng )
	    tableSet = _pDBMng->getTabSetName(pCE->getTabSetId());
	pN->setAttribute(XML_ID_ATTR, pCE->getTableName() + Chain("@") + tableSet);
	pN->setAttribute(XML_NUMROWS_ATTR, Chain(pCE->getNumRows()));
	pN->setAttribute(XML_NUMHITS_ATTR, Chain(pCE->getHit()));
	pN->setAttribute(XML_SIZE_ATTR, Chain(pCE->getSize()));
	pCacheInfo->addContent(pN);
	pCE = _pTableCache->Next();
    }
    V();
    return pCacheInfo;
}

const Chain& CegoTableCache::getFilter() const
{
    return _cacheFilter;
}

void CegoTableCache::setFilter(const Chain& cacheFilter)
{
    _cacheFilter = cacheFilter;
}

unsigned CegoTableCache::getMaxSize() const
{
    return _maxSize;
}

void CegoTableCache::setMaxSize(unsigned maxSize)
{
    _maxSize = maxSize;
}

unsigned CegoTableCache::getUsedSize() const
{
    return _usedSize;
}

const unsigned CegoTableCache::getNumTableCache() const
{
    return _pTableCache->Size();
}

void CegoTableCache::invalidate(const Chain& tableName)
{
    bool isClean=false;
    
    while ( isClean == false )
    {
	isClean = true;
	
	PW();

	TableCacheEntry *pTCE = _pTableCache->First();
	while ( pTCE )
	{
	    if ( pTCE->getTabSetId() == _tabSetId && pTCE->getTableName() == tableName )
	    {
		unsigned s = pTCE->getSize();
		if  ( pTCE->cleanCache() )
		{
		    _usedSize = _usedSize - s;
		    _pTableCache->Remove(*pTCE);
		    pTCE = _pTableCache->First();
		}
		else
		{
		    // was not able to cleanup, cache still in use
		    // cout << "Cache still in use .." << endl;
		    isClean=false;
		    // we skip this entry and go to next
		    pTCE = _pTableCache->Next();
		}
	    }
	    else
	    {
		pTCE = _pTableCache->Next();
	    }
	}
	V();
    }
}

CegoFieldValue*** CegoTableCache::claimEntry(const Chain& tableName, unsigned& numRow, unsigned& numCol)
{    
    CegoFieldValue*** pCE = 0;
    PR();
    TableCacheEntry *pTCE = _pTableCache->Find( TableCacheEntry(_tabSetId, tableName));

    if ( pTCE )
    {
	pTCE->incHit();
	pCE = pTCE->claimCache();
	numRow = pTCE->getNumRows();
	numCol = pTCE->getNumCols();
    }
    V();
    
    return pCE;
}

void CegoTableCache::releaseEntry(const Chain& tableName)
{
    PR();
    TableCacheEntry *pTCE = _pTableCache->Find( TableCacheEntry(_tabSetId, tableName));

    if ( pTCE )
    {
	pTCE->releaseCache();
    }
    V();
}

bool CegoTableCache::matchEntry(const Chain& tableName)
{	    
    return _pTableFilter->match(tableName);
}

bool CegoTableCache::addEntry(const Chain& tableName, ListT< ListT<CegoFieldValue> >* pCacheList )
{
    if ( _pTableFilter->match(tableName) )
    {
	bool wasInserted = false;
	
	unsigned tryDelay = TABLECACHE_TRYDELAYBASE;
	unsigned tryCount = 0;
	
	while ( wasInserted == false && tryCount < TABLECACHE_MAXTRY)
	{
	    tryCount++;
	    
	    PW();
	    
	    try
	    {		
		TableCacheEntry *pTCE = _pTableCache->Find( TableCacheEntry(_tabSetId, tableName));
		
		// entry already exists ?
		if ( pTCE )
		{
		    V();
		    return true;
		}
		
		TableCacheEntry tce(_tabSetId, tableName, pCacheList);
		
		wasInserted = _pTableCache->Insert(tce);
		
		if ( wasInserted == false )
		{
		    TableCacheEntry *pRE = 0;
		    
		    unsigned pos;
		    unsigned long minHit = 0;
		    TableCacheEntry *pTCE = _pTableCache->FirstInRange(tce);
		    while ( pTCE )
		    {
			if ( minHit == 0 || pTCE->getHit() < minHit )
			{			
			    pos = _pTableCache->getRangePos();
			    pRE = pTCE;
			    minHit = pTCE->getHit();
			}
			
			pTCE = _pTableCache->NextInRange();
		    }
		    
		    if ( pRE )
		    {
			unsigned s = pRE->getSize();
			if ( pRE->cleanCache() )
			{
			    _usedSize = _usedSize - s;
			    
			    if ( _pTableCache->RemovePos(pos) == false )
			    {
				Chain msg = Chain("Cannot remove table cache entry ") + Chain(pRE->getTableName());
				throw Exception(EXLOC, msg);
			    }			
			}
		    }
		    else
		    {
			Chain msg = Chain("Cannot find appropriate table cache slot");
			throw Exception(EXLOC, msg);
		    }
		}
		else
		{
		    _usedSize = _usedSize + tce.getSize();
		}
	    }
	    catch ( Exception e )
	    {
		V();
		throw Exception(EXLOC, Chain("Cannot add table cache entry"), e);
	    }
	    
	    V();
	    
	    if ( wasInserted == false )
	    {
		Sleeper::milliSleep(tryDelay);
		tryDelay = tryDelay * 2;
	    }
	}
	
	if ( wasInserted == false )
	    _numFail++;
	
	return wasInserted;
    }

    // no match
    return false;
}
	   
void CegoTableCache::clean()
{
    bool isClean=false;
    
    while ( isClean == false )
    {
	isClean=true;
	
	PW();
       
	TableCacheEntry *pTCE = _pTableCache->First();

	while ( pTCE )
	{
	    if  ( pTCE->cleanCache() )
	    {
		_pTableCache->Remove(*pTCE);
		pTCE = _pTableCache->First();
	    }
	    else
	    {
		// was not able to cleanup, cache still in use
		// cout << "Cache still in use .." << endl;
		isClean=false;
		// we skip this entry and go to next
		pTCE = _pTableCache->Next();
	    }
	}
	
	V();
    }

    _usedSize = 0;
}

void CegoTableCache::getTCLockStat(unsigned& lockCount, unsigned long long &numRdLock, unsigned long long &numWrLock, unsigned long long &sumRdDelay, unsigned long long &sumWrDelay)
{
    lockCount = _cacheLock[_tabSetId].numLockTry();
    
    numRdLock = _cacheLock[_tabSetId].numReadLock();
    numWrLock = _cacheLock[_tabSetId].numWriteLock();
    sumRdDelay = 0;
    sumWrDelay = 0;

    if ( _cacheLock[_tabSetId].numReadLock() > 0 )
	sumRdDelay = _cacheLock[_tabSetId].sumReadDelay() / LCKMNG_DELRES;
    if ( _cacheLock[_tabSetId].numWriteLock() > 0 )
	sumWrDelay = _cacheLock[_tabSetId].sumWriteDelay() / LCKMNG_DELRES;
}

void CegoTableCache::verifyLock(CegoAdminHandler* pAH)
{
    unsigned timeout = 3000;
    
    Chain info;
    
    info = Chain("TableCache Lock TS ") + Chain(_tabSetId) + Chain(" - Read ...");
    if ( pAH )
	pAH->sendInfo(info);
	
    _cacheLock[_tabSetId].readLock(timeout);
    _cacheLock[_tabSetId].unlock();

    if ( pAH )
	pAH->sendInfo(Chain("ok\n"));

    info = Chain("TableCache Lock TS ") + Chain(_tabSetId) + Chain(" - Write ...");
    if ( pAH )
	pAH->sendInfo(info);
    
    _cacheLock[_tabSetId].writeLock(timeout);
    _cacheLock[_tabSetId].unlock();
    
    if ( pAH )
	pAH->sendInfo(Chain("ok\n"));

}
