///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoLogReader.cc
// ----------------                           
// Cego log reading and printing
// 
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: main
// 
// Description: Utiliy program to read and list cego redo log files
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// LFC INCLUDES
#include <lfcbase/Exception.h>
#include <lfcbase/GetLongOpt.h>
#include <lfcbase/Chain.h>
#include <lfcbase/Datetime.h>
#include <lfcbase/Tokenizer.h>

// CEGO INCLUDES
#include "CegoDefs.h"
#include "CegoLogRecord.h"
#include "CegoQueryHelper.h"
#include "CegoLogManager.h"
#include "CegoTableObject.h"
#include "CegoKeyObject.h"
#include "CegoViewObject.h"
#include "CegoProcObject.h"
#include "CegoBTreeObject.h"
#include "CegoCheckObject.h"
#include "CegoTriggerObject.h"
#include "CegoTypeConverter.h"

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

#define PIPE_TIMEOUT 30 /* checking input pipe data timeout in ms */

#define USAGE "Usage: cglog --log=<logfile>\n\
          [ --version  ] [ --help ]"

extern char __lfcVersionString[];
extern char __lfcxmlVersionString[];
extern Chain __dateTimeFormat;

int main(int argc, char **argv)
{

    __dateTimeFormat = Chain(DEFAULTDATETIMEFORMAT1);
    
    try 
    {
	GetLongOpt longOpt(argc, argv);
	
	longOpt.addOpt("version");
	longOpt.addOpt("help");
	longOpt.addOpt("log");
	
	try
	{
	    longOpt.parseOpt(); 
	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    cerr << msg << endl;
	    cerr << USAGE << endl;
	    exit(1);	
	}
		
	if ( longOpt.isSet( Chain("help") ) )
	{	
	    cerr << USAGE << endl;
	    exit(0);
	}
	
	if ( longOpt.isSet( Chain("version") ) )
	{
	    cout << CEGO_PRODUCT << " Log Reader (" << sizeof(void*) * 8 << " bit), Version " << CEGO_VERSION 
		 << " [ lfc: " << __lfcVersionString  
		 << ", lfcxml: " <<  __lfcxmlVersionString << " ]" << endl;
	    cout << CEGO_COPYRIGHT << endl;
	    exit(0);
	}

	File *pLog = 0;
	
	if ( longOpt.isSet( Chain("log") ) == false )
	{
	    pLog = new File(File::IN);
	    if ( pLog->hasData(PIPE_TIMEOUT) == false )
	    {
		delete pLog;
		pLog = 0;
		throw Exception(EXLOC, "No log data");
	    }	  
	}
	else
	{	    
	    Chain logFileName = longOpt.getOptValue("log");	    
	    pLog = new File(logFileName);	
	    pLog->open(File::READ);
	}

	
	unsigned offset;
	pLog->readByte((char*)&offset, sizeof(unsigned));	
	
	unsigned pos = sizeof(unsigned);
	       
	while (pos < offset)
	{
	    unsigned len;
	    pLog->readByte((char*)&len, sizeof(unsigned));	
	    
	    // cout << "Read len : " << len << endl;
	    
	    pos += len + sizeof(unsigned);
	    
	    char* buf = new char[len];
	    
	    pLog->readByte(buf, len);
	    CegoLogRecord lr;
	    
	    lr.decode(buf);

	    delete[] buf;

	    Datetime dt( lr.getTS() );

	    Chain logData;
	    
	    cout << "LSN=" << lr.getLSN() << ":";
	    cout << "TS=" << dt.asChain() << ":";
	    cout << "TID=" << lr.getTID() << ":";
	    cout << "TAStep=" << lr.getTAStep() << ":";

	    cout << "LogAction=";

	    if ( lr.getObjName().length() > 0 )
	    {
		logData += Chain("Name=") + lr.getObjName() + Chain("\n");
		logData += Chain("Type=") + CEGO_OBJECT_MAP[(unsigned)lr.getObjType()] + Chain("\n");
	    }
	    
	    switch ( lr.getAction() )
	    {
	    case CegoLogRecord::LOGREC_CREATE:
	    case CegoLogRecord::LOGREC_ALTER:
	    {
		if ( lr.getAction() == CegoLogRecord::LOGREC_CREATE )
		    cout << "CREATE:" << endl;
		else
		    cout << "ALTER:" << endl;
		
		logData += Chain("--- Data Start ---\n");
	
		switch ( lr.getObjType() )
		{
		case CegoObject::TABLE:
		case CegoObject::SYSTEM:
		case CegoObject::AVLTREE:
		case CegoObject::PAVLTREE:
		case CegoObject::UAVLTREE:
		{
		    CegoTableObject to;
		    to.decode(lr.getData());
		    logData += to.getInfo();
		    break;
		}
		case CegoObject::PBTREE:
		case CegoObject::UBTREE:
		case CegoObject::BTREE:
		{
		    CegoBTreeObject bo;
		    bo.decode(lr.getData());
		    logData += bo.getInfo();
		    break;
		}
		case CegoObject::VIEW:
		{
		    CegoViewObject vo;
		    vo.decode(lr.getData());
		    logData += vo.getInfo();
		    break;		    
		}
		case CegoObject::PROCEDURE:
		{
		    CegoProcObject po;
		    po.decode(lr.getData());
		    logData += po.getInfo();
		    break;		    
		}
		case CegoObject::FKEY:
		{
		    CegoKeyObject ko;
		    ko.decode(lr.getData());
		    logData += ko.getInfo();
		    break;		    
		}
		case CegoObject::CHECK:
		{
		    CegoCheckObject co;
		    co.decode(lr.getData());
		    logData += co.getInfo();
		    break;		    
		}
		case CegoObject::TRIGGER:
		{
		    CegoTriggerObject to;
		    to.decode(lr.getData());
		    logData += to.getInfo();
		    break;		    
		}
		case CegoObject::ALIAS:
		{
		    CegoAliasObject ao;
		    ao.decode(lr.getData());
		    logData += ao.getInfo();
		    break;
		}
		case CegoObject::RBSEG:
		case CegoObject::JOIN:
		case CegoObject::UNDEFINED:
		{
		    // ignore
		    break;
		}
		}
		
		logData += Chain("--- Data End ---\n");
       
		break;
	    }

	    case CegoLogRecord::LOGREC_RENAME:
	    {
		cout << "RENAME:" << endl;
		
		logData += Chain("--- Data Start ---\n");
		Chain newObjName(lr.getData(), lr.getDataLen());
		logData += Chain("NewName=") + newObjName + Chain("\n"); 
		logData += Chain("--- Data End ---\n");
		break;
	    }
	    case CegoLogRecord::LOGREC_DROP:
	    {
		cout << "DROP:" << endl;
		logData += Chain("--- No data ---\n");
		break;
	    }
	    case CegoLogRecord::LOGREC_TRUNCATE:
	    {
		cout << "TRUNCATE:" << endl;
		logData += Chain("--- No data ---\n");
		break;
	    }
	    case CegoLogRecord::LOGREC_INSERT:
	    {
		cout << "INSERT:" << endl;	
		logData += Chain("--- Data Start ---\n");
		
		unsigned long long tid;
		unsigned long long tastep;
		CegoTupleState ts;

		ListT<CegoFieldValue> fvl;
		ListT<CegoBlob> blobList;
		ListT<CegoClob> clobList;

		unsigned toff = CegoQueryHelper::decodeTupleHeader(tid, tastep, ts, lr.getData());

		char* tp =  lr.getData() + toff;
		unsigned tlen = lr.getDataLen() - toff;

		CegoQueryHelper::decodeNativeFVL(fvl, blobList, clobList, tp, tlen);

		CegoFieldValue *pFV = fvl.First();
		while ( pFV )
		{
		    if ( pFV->getLength() > 0 )
		    {
			logData += pFV->valAsChain(false) + Chain("|");
		    }
		    
		    // cout << "Field " << pF->getId() << " Len=" << pF->getFieldValue().getLength() << endl;
		    pFV = fvl.Next();
		}
		logData += Chain("\n");
		if ( blobList.Size() > 0 )
		{
		    CegoBlob *pBlob = blobList.First();
		    while ( pBlob )
		    {
			logData += Chain("Blob [ ... ] ") + Chain(pBlob->getSize()) + Chain(" bytes\n");
			pBlob = blobList.Next();
		    }
		}
		if ( clobList.Size() > 0 )
		{
		    CegoClob *pClob = clobList.First();
		    while ( pClob )
		    {
			logData += Chain("Clob [") +  pClob->getHead() + Chain("] ") + Chain(pClob->getSize()) + Chain(" bytes\n");
			pClob = clobList.Next();
		    }
		}

		logData += Chain("--- Data End ---\n");
		
		break;
	    }
	    case CegoLogRecord::LOGREC_DELETE:
	    {
		cout << "DELETE:" << endl;
		
		logData += Chain("--- Log Data Start ---\n");

		CegoPredicate *pPred = 0;
		Chain tableAlias;

		CegoQueryHelper::decodeDelRec(tableAlias, pPred, lr.getData(), lr.getDataLen());

		logData += Chain("TableAlias=") + tableAlias + Chain("\n");
		
		if ( pPred )
		{
		    logData += Chain("where ") + pPred->toChain(0) + Chain("\n");
		}
		
		logData += Chain("--- Log Data End ---\n");
		
		break;
	    }
	    case CegoLogRecord::LOGREC_UPDATE:
	    {
		cout << "UPDATE:" << endl;		
		logData += Chain("--- Data Start ---\n");	
		Chain tableAlias;
		CegoPredicate *pPred = 0;
		ListT<CegoField> updList;
		ListT<CegoExpr*> exprList;
		bool returnOnFirst;
		
		CegoQueryHelper::decodeUpdRec(tableAlias,
					      pPred, 
					      updList,
					      exprList,
					      returnOnFirst,
					      lr.getData(), lr.getDataLen(), 0, 0);
				
		logData += Chain("TableAlias=") + tableAlias + Chain("\n");
		CegoField *pF = updList.First();
		CegoExpr **pExpr = exprList.First();
		while ( pF && pExpr )
		{
		    logData += Chain("set ") + pF->getAttrName() + Chain("=") +  (*pExpr)->toChain(0) + Chain("\n");
		    pF = updList.Next();
		    pExpr = exprList.Next();
		}
		if ( pPred )
		    logData += Chain("where ") + pPred->toChain(0) + Chain("\n");
		logData += Chain("--- Data End ---\n");
		
		break;
	    }
	    case CegoLogRecord::LOGREC_BEGIN:
	    {
		cout << "BEGIN:" << endl;
		logData += Chain("--- No data ---\n");
		break;
	    }
	    case CegoLogRecord::LOGREC_COMMIT:
	    {
		cout << "COMMIT:" << endl;
		logData += Chain("--- Data Start ---\n");	
		logData += Chain("TID=") + Chain(lr.getTID()) + Chain("\n");
		logData += Chain("--- Data End ---\n");
		break;
	    }
	    case CegoLogRecord::LOGREC_ABORT:
	    {
		cout << "ABORT:" << endl;
		logData += Chain("--- Data Start ---\n");	
		logData += Chain("TID=") + Chain(lr.getTID()) + Chain("\n");
		logData += Chain("--- Data End ---\n");
		break;
	    }	    
	    case CegoLogRecord::LOGREC_SYNC:
	    {
		cout << "SYNC:" << endl;
		logData += Chain("--- No data ---\n");
		break;
	    }
	    case CegoLogRecord::LOGREC_BUPAGE:
	    {
		cout << "BUPAGE:" << endl;
		logData += Chain("--- Data Start ---\n");
		logData += Chain("[... page data ...]\n");
		logData += Chain("--- Data End ---\n");;
		break;
	    }
	    case CegoLogRecord::LOGREC_BUFBM:
	    {
		cout << "BUFBM:" << endl;
		logData += Chain("--- Data Start ---\n");
		logData += Chain("[... file bitmap ...]\n");
		logData += Chain("--- Data End ---\n");;
		break;
	    }
	    case CegoLogRecord::LOGREC_BUFIN:
	    {
		cout << "BUFIN:" << endl;
		logData += Chain("--- No data ---\n");
		break;
	    }
	    case CegoLogRecord::LOGREC_ADDCOUNTER:
	    {
		cout << "ADDCOUNTER:" << endl;
		logData += Chain("--- Data Start ---\n");
		Chain counterName(lr.getData(), lr.getDataLen());		
		logData += Chain("Counter=") + counterName + Chain("\n");
		cout << "--- Data End ---" << endl;
		break;
	    }
	    case CegoLogRecord::LOGREC_DELCOUNTER:
	    {
		cout << "DELCOUNTER:" << endl;
		logData += Chain("--- Data Start ---\n");
		Chain counterName(lr.getData(), lr.getDataLen());
		logData += Chain("Counter=") + counterName + Chain("\n");

		logData += Chain("--- Data End ---\n");
		break;
	    }
	    default:
		cout << "UNKNOWN:" << endl;
		logData += Chain(" -- Unknown logaction ---\n");
		break;   		             	
	    }

	    Tokenizer logTok(logData, "\n");

	    Chain logLine;
	    while ( logTok.nextToken(logLine) )
	    {
		cout << "   " << logLine << endl;
	    }
	}

	pLog->close();
	delete pLog;
	
    }
    catch ( Exception e)
    {
	Chain msg;
	e.pop(msg);
	cerr << msg << endl;
	exit(1);
    }
    exit(0);
}
