///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoLogManager.cc
// -----------------
// Cego database log manager implementation
//                                                         
// Design and Implementation by Bjoern Lemke               
//                                                         
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoLogManager
//
// Description: Cego database log management
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// LFC INCLUDES
#include <lfcbase/Exception.h>
#include <lfcbase/File.h>
#include <lfcbase/Net.h>
#include <lfcbase/Datetime.h>

// CEGO INCLUDES
#include "CegoLogManager.h"
#include "CegoXMLdef.h"
#include "CegoDefs.h"

// POSIX INCLUDES
#include <string.h>
#include <stdlib.h>

extern bool __fsyncOn;

CegoLogManager::CegoLogManager(const Chain& xmlDef, const Chain& logFile, const Chain& progName) : CegoXMLSpace(xmlDef, logFile, progName)
{
    for (unsigned i=0;i< TABMNG_MAXTABSET;i++)
    {
	_lsn[i]=0;
	_pLog[i]=0;
	_logActive[i] = false;
	_logBuf[i] = 0;
	_logBufLen[i] = 0;
    }
    _modId = getModId("CegoLogManager");
}

CegoLogManager::~CegoLogManager()
{
    for (unsigned i=0;i< TABMNG_MAXTABSET;i++)
    {
	if (_pLog[i])
	{
	    _pLog[i]->close();
	    delete _pLog[i];
	}
	if ( _logBuf[i] )
	    free(_logBuf[i]);
    }
}

void CegoLogManager::setCurrentLSN(unsigned tabSetId, unsigned long long lsn)
{
    _lsn[tabSetId] = lsn;
}

unsigned long long CegoLogManager::getCurrentLSN(unsigned tabSetId)
{
    return _lsn[tabSetId];
}

unsigned long long CegoLogManager::nextLSN(unsigned tabSetId)
{
    // we just increase, if log is active
    if ( _logActive[tabSetId] == false )
	return 0;

    _lsn[tabSetId]++;
    return _lsn[tabSetId];
}

Chain CegoLogManager::getArchiveLogName(const Chain& tableSet, unsigned long long lsn)
{
    Chain lsnStr = Chain("000000000000") + Chain(lsn);
    Chain lsnFix = lsnStr.subChain(lsnStr.length() - 12, lsnStr.length());
    Chain branch = getTableSetBackupBranch(tableSet);
    return tableSet + Chain(LOGMNG_ARCHLOGSEP) + branch + Chain(LOGMNG_ARCHLOGSEP) + lsnFix + LOGMNG_ARCHLOGSUFFIX;
}

void CegoLogManager::setLogFile(unsigned tabSetId, const Chain& logFile, bool readOnly)
{
    if (_pLog[tabSetId])
    {	
	if ( __fsyncOn )
	    _pLog[tabSetId]->flush();
	_pLog[tabSetId]->close();
	delete _pLog[tabSetId];
    }

    _logFile[tabSetId] = logFile;
    
    _pLog[tabSetId] = new File(logFile);

    if ( readOnly )
	_pLog[tabSetId]->open(File::READ);
    else
	_pLog[tabSetId]->open(File::READWRITE);

    _logSize[tabSetId] = _pLog[tabSetId]->Size();    

    _pLog[tabSetId]->readByte((char*)&_logOffset[tabSetId], sizeof(unsigned)); 
    _logPos[tabSetId] = sizeof(unsigned);
    _logActive[tabSetId] = false;
}

void CegoLogManager::initLog(unsigned tabSetId, unsigned size)
{
    if (_pLog[tabSetId] == 0)
    {
	Chain msg = "No logfile set up for tableset";
	throw Exception(EXLOC, msg);
    }

    _pLog[tabSetId]->seek(0);

    char wBuf[LOGMNG_WBUFSIZE];

    _logOffset[tabSetId] = sizeof(unsigned);
    
    _pLog[tabSetId]->writeByte((char*)&_logOffset[tabSetId], sizeof(unsigned));
    
    unsigned wBytes = sizeof(unsigned);
    while (wBytes < size )
    {
	unsigned n = wBytes + LOGMNG_WBUFSIZE > size ? size - wBytes : LOGMNG_WBUFSIZE;
	_pLog[tabSetId]->writeByte(wBuf, n);
	wBytes += n;
    }

    _pLog[tabSetId]->close();
    delete _pLog[tabSetId];
    _pLog[tabSetId]=0;    
}

unsigned CegoLogManager::getLogOffset(unsigned tabSetId)
{
    return _logOffset[tabSetId];
}

void CegoLogManager::resetLog(unsigned tabSetId)
{
    _logOffset[tabSetId] = sizeof(unsigned);
    _pLog[tabSetId]->seek(0);
    _pLog[tabSetId]->writeByte((char*)&_logOffset[tabSetId], sizeof(unsigned));
    if ( __fsyncOn )
	_pLog[tabSetId]->flush();
}

void CegoLogManager::stopLog(unsigned tabSetId)
{
    _logActive[tabSetId] = false;
}

void CegoLogManager::startLog(unsigned tabSetId)
{
    if ( _pLog[tabSetId] != 0 )
    {
	_pLog[tabSetId]->seek(0);
	_pLog[tabSetId]->readByte((char*)&_logOffset[tabSetId], sizeof(unsigned));
	_pLog[tabSetId]->seek(_logOffset[tabSetId]);
    }
    _logActive[tabSetId] = true;
}

bool CegoLogManager::isActive(unsigned tabSetId)
{
    return _logActive[tabSetId];
}

CegoLogManager::LogResult CegoLogManager::logAction(unsigned tabSetId, CegoLogRecord& logRec, bool flushLog)
{
    if ( _logActive[tabSetId] == false )	
	return LOG_SUCCESS;
    
    // cout << "Logging " <<  _lsn[tabSetId] << " " << logRec << endl;

    if ( logRec.getLSN() == 0
	 && logRec.getAction() != CegoLogRecord::LOGREC_SYNC )

	/*
	 && logRec.getAction() != CegoLogRecord::LOGREC_BUFBM
	 && logRec.getAction() != CegoLogRecord::LOGREC_BUPAGE
	 && logRec.getAction() != CegoLogRecord::LOGREC_BUFIN ) */
    {
	logRec.setLSN(nextLSN(tabSetId));
    }
	
    Datetime ts;
    logRec.setTS( ts.asLong());
    
    unsigned len = logRec.getEncodingLength();

    // cout << "Logging record of len " << len << endl;

    if ( _logBuf[tabSetId] == 0 )
    {
	_logBuf[tabSetId] = (char*)malloc(len);
	_logBufLen[tabSetId] = len;
    }
    else
    {
	if ( _logBufLen[tabSetId] < len )
	{
	    // reallocate buffer
	    free ( _logBuf[tabSetId] );
	    _logBuf[tabSetId] = (char*)malloc(len);
	    _logBufLen[tabSetId] = len;	    
	}
    }
        
    logRec.encode(_logBuf[tabSetId]);

    bool isFull = false;
    
#ifdef CGDEBUG
    log(_modId, Logger::DEBUG, Chain("Logging local ( lsn=") + Chain(logRec.getLSN()) + Chain(", size=") + Chain(len) + Chain(")"));    
#endif

    if ( _logOffset[tabSetId] + len > _logSize[tabSetId] )
    {
	// is log is full, we anyway write the log entry to complete log for checkpoint
	isFull=true;
    }
    
    _pLog[tabSetId]->writeByte((char*)&len, sizeof(unsigned));
    _pLog[tabSetId]->writeByte(_logBuf[tabSetId], len);
    _logOffset[tabSetId] += len + sizeof(unsigned);
    
    _pLog[tabSetId]->seek(0);
    _pLog[tabSetId]->writeByte((char*)&_logOffset[tabSetId], sizeof(unsigned));
    _pLog[tabSetId]->seek(_logOffset[tabSetId]);
    
    if ( ( flushLog || isFull ) && __fsyncOn )
    {
	_pLog[tabSetId]->flush();
    }
    
    if ( isFull )
	return LOG_FULL;
    else
	return LOG_SUCCESS;
}

unsigned long long CegoLogManager::getMinLSN(unsigned tabSetId)
{
    if (_pLog[tabSetId] == 0)
    {
	Chain msg = "No logfile set up for tableset";
	throw Exception(EXLOC, msg);
    }

    _pLog[tabSetId]->seek(0); 
    _pLog[tabSetId]->readByte((char*)&_logOffset[tabSetId], sizeof(unsigned));	
    
    _logPos[tabSetId] = sizeof(unsigned);

    unsigned long long minlsn=0;

    if ( _logPos[tabSetId] < _logOffset[tabSetId] )
    {
	unsigned len;
	_pLog[tabSetId]->readByte((char*)&len, sizeof(unsigned));	

	if ( len > LOGMNG_RECBUFSIZE )
	{
	    Chain msg = "Log read buffer exceeded";
	    throw Exception(EXLOC, msg);		    
	}
	char buf[LOGMNG_RECBUFSIZE];		
	
	_pLog[tabSetId]->readByte(buf, len);
	    
	CegoLogRecord lr;
	lr.decode(buf);
	
	minlsn = lr.getLSN();

	_logPos[tabSetId] += len + sizeof(unsigned);

    }
    return minlsn;
}

unsigned long long CegoLogManager::getMaxLSN(unsigned tabSetId)
{
    if (_pLog[tabSetId] == 0)
    {
	Chain msg = "No logfile set up for tableset";
	throw Exception(EXLOC, msg);
    }

    _pLog[tabSetId]->seek(0);    
    _pLog[tabSetId]->readByte((char*)&_logOffset[tabSetId], sizeof(unsigned));	

#ifdef CGDEBUG
    log(_modId, Logger::DEBUG, Chain("Read logoffset ") + Chain(_logOffset[tabSetId]));
#endif
        
    _logPos[tabSetId] = sizeof(unsigned);

    unsigned long long maxlsn=0;

    while (_logPos[tabSetId] < _logOffset[tabSetId])
    {
	unsigned len;
	_pLog[tabSetId]->readByte((char*)&len, sizeof(unsigned));	

	if ( len > LOGMNG_RECBUFSIZE )
	{
	    Chain msg = "Log read buffer exceeded";
	    throw Exception(EXLOC, msg);		    
	}
	char buf[LOGMNG_RECBUFSIZE];		
	
	_pLog[tabSetId]->readByte(buf, len);
	    
	CegoLogRecord lr;
	lr.decode(buf);

	if ( lr.getLSN() > maxlsn )
	    maxlsn = lr.getLSN();

	_logPos[tabSetId] += len + sizeof(unsigned);

    }
    return maxlsn;
}

void CegoLogManager::seekToStart(unsigned tabSetId)
{
    if (_pLog[tabSetId] == 0)
    {
	Chain msg = "No logfile set up for tableset";
	throw Exception(EXLOC, msg);
    }

    _pLog[tabSetId]->seek(0);    
    _pLog[tabSetId]->readByte((char*)&_logOffset[tabSetId], sizeof(unsigned));    
    _logPos[tabSetId] = sizeof(unsigned);
}

bool CegoLogManager::logSeek(unsigned tabSetId, unsigned long long lsn)
{
    if (_pLog[tabSetId] == 0)
    {
	Chain msg = "No logfile set up for tableset";
	throw Exception(EXLOC, msg);
    }

    _pLog[tabSetId]->seek(0);    
    _pLog[tabSetId]->readByte((char*)&_logOffset[tabSetId], sizeof(unsigned));	
    
    _logPos[tabSetId] = sizeof(unsigned);

    unsigned long long loglsn=0;

    while (_logPos[tabSetId] < _logOffset[tabSetId])
    {
	unsigned len;
	_pLog[tabSetId]->readByte((char*)&len, sizeof(unsigned));	
	
	if ( len > LOGMNG_RECBUFSIZE )
	{
	    Chain msg = "Log read buffer exceeded";
	    throw Exception(EXLOC, msg);		    
	}
	char buf[LOGMNG_RECBUFSIZE];		
		
	_pLog[tabSetId]->readByte(buf, len);
	    
	CegoLogRecord lr;
	lr.decode(buf);
	
	loglsn = lr.getLSN();

	if (  loglsn == lsn ) 
	{
	    _pLog[tabSetId]->seek(_logPos[tabSetId]);
	    return true;
	}
	else if ( loglsn > lsn ) 
	{

	    Chain msg = "LSN=" + Chain(lsn) + " too small, not found in log";
	    throw Exception(EXLOC, msg);	
	} 

	_logPos[tabSetId] += len + sizeof(unsigned);
    }

    if ( loglsn + 1 == lsn )
    {	
	return false;	
    } 
    else
    {
	Chain msg = "LSN=" + Chain(lsn) + " too high, log delta is missing";
	throw Exception(EXLOC, msg);	
    }
}

bool CegoLogManager::logRead(unsigned tabSetId, CegoLogRecord& logRec)
{
    if (_logPos[tabSetId] < _logOffset[tabSetId])
    {
	unsigned len;
	_pLog[tabSetId]->readByte((char*)&len, sizeof(unsigned));	

	if ( len > LOGMNG_RECBUFSIZE )
	{
	    Chain msg = "Log read buffer exceeded";
	    throw Exception(EXLOC, msg);		    
	}
	
	char buf[LOGMNG_RECBUFSIZE];
	
	_pLog[tabSetId]->readByte(buf, len);
	
	logRec.decode(buf);

	_logPos[tabSetId] += len + sizeof(unsigned);
	
	return true;
    }
    return false;
}

bool CegoLogManager::switchLogFile(unsigned tabSetId)
{   
    if ( _logActive[tabSetId] == false )
	return true;

    Chain tableSet = getTabSetName(tabSetId);
    
    ListT<Chain> lfList;
    ListT<unsigned> sizeList;
    ListT<Chain> statusList;
    
    getLogFileInfo(tableSet, lfList, sizeList, statusList);
    
    Chain *pLogFile = lfList.First();
    Chain *pStatus = statusList.First();
    
    bool found = false;
    while ( pLogFile && pStatus && found == false) 
    {	    
	if ( *pStatus == Chain(XML_ACTIVE_VALUE))
	{   	
	    Chain *pNextLogFile = lfList.Next();
	    Chain *pNextLogStatus = statusList.Next();
	    
	    if ( pNextLogFile == 0)
	    {
		pNextLogFile = lfList.First();
		pNextLogStatus = statusList.First();
	    }
	    
	    if ( isArchiveMode(tabSetId) )
	    {
		// if next logfile is still occupied, we skip switch and return false
		if ( *pNextLogStatus == Chain(XML_OCCUPIED_VALUE))
		{
		    return false;
		}
		
		// first we have to switch log file to synchronize with CegoLogThreadPool::shiftRedoLogs()
		setLogFile(tabSetId, *pNextLogFile, false);		    
		setLogFileStatus(tableSet, *pLogFile, XML_OCCUPIED_VALUE); 
	    }
	    else
	    {
		// switch log file first also for non archiving mode
		setLogFile(tabSetId, *pNextLogFile, false);
		setLogFileStatus(tableSet, *pLogFile, XML_FREE_VALUE); 
	    }
	    
	    setLogFileStatus(tableSet, *pNextLogFile, XML_ACTIVE_VALUE);
	    
	    log(_modId, Logger::NOTICE, Chain("Logfile switch to logfile ") + *pNextLogFile + Chain(" for tableSet ") +  tableSet);
			     		
	    found = true;
	}
	else
	{
	    pLogFile = lfList.Next();
	    pStatus = statusList.Next();
	}	
    }
    
    resetLog(tabSetId);
    startLog(tabSetId);	
    
    return true;
}

void CegoLogManager::setActiveLogFile(const Chain& tableSet)
{
    unsigned tabSetId = getTabSetId(tableSet);

    ListT<Chain> lfList;
    ListT<unsigned> sizeList;
    ListT<Chain> statusList;

    getLogFileInfo(tableSet, lfList, sizeList, statusList);
    
    Chain *pLogFile = lfList.First();
    Chain *pStatus = statusList.First();
    
    while ( pLogFile && pStatus ) 
    {	
	if ( *pStatus == Chain(XML_ACTIVE_VALUE))
	{
	    log(_modId, Logger::NOTICE, Chain("Setting active logfile to ") + Chain(*pLogFile) + Chain(" ..."));	
	    setLogFile(tabSetId, *pLogFile, false);	    
	    return;
	}
	else
	{
	    pLogFile = lfList.Next();
	    pStatus = statusList.Next();
	}	
    }   
}
