///////////////////////////////////////////////////////////////////////////////
//                                   
// CegoOutput.cc
// -------------
// Cego formatting data for output
//      
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoOutput
// 
// Description: The CegoOutput class converts query output to an appropriate format.
//              This may either be XML-based style for serving a client request or 
//              ascii-based tableformat for batchmode or client-side output
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// base includes
#include <lfcbase/Tokenizer.h>

// cego includes
#include "CegoOutput.h"
#include "CegoQueryHelper.h"

#define DEFRAWSEP ","

CegoOutput::CegoOutput()
{
    _pDbHandle = 0;
    _pDBMng = 0;
    _rawMode = false;
    _rawSep = Chain(DEFRAWSEP);
}

CegoOutput::CegoOutput(const ListT<CegoField>& schema, const Chain& format)
{
    _schema = schema;
    _format = format;
    _pDbHandle = 0;
    _pDBMng = 0;
    _maxRowPerMsg = 0;
    _maxBytePerMsg = 0;
    _rawMode = false;
    _rawSep = Chain(DEFRAWSEP);
}

CegoOutput::~CegoOutput()
{
}

void CegoOutput::setDbHandle(CegoDbHandler* pDbHandle, unsigned maxRowPerMsg, unsigned maxBytePerMsg)
{
    _pDbHandle = pDbHandle;
    _maxRowPerMsg = maxRowPerMsg;
    _maxBytePerMsg = maxBytePerMsg;    
}

void CegoOutput::setDBMng(CegoDatabaseManager *pDBMng)
{
    _pDBMng = pDBMng;
    _modId = pDBMng->getModId("CegoOutput");
}

void CegoOutput::setRawMode(bool isOn)
{
    _rawMode = isOn;
}

void CegoOutput::setRawSeparator(const Chain& rawSep)
{
    _rawSep = rawSep;
}

void CegoOutput::chainOut(const Chain& msg, long affCount)
{
    if ( _pDbHandle ) 
    {
	_pDbHandle->sendResponse(msg, affCount);
    }
    else if ( _pDBMng )
    {
	_pDBMng->log(_modId, Logger::NOTICE, msg);
    }
    else
    {
	cout << msg << endl;
    }    
}

void CegoOutput::procResultOut(const Chain& msg, const ListT<CegoProcVar>& outParamList, CegoFieldValue *pRetVal)
{
    if ( _pDbHandle ) 
    {	
	_pDbHandle->sendProcResult(msg, outParamList, pRetVal);
    }
    else if ( _pDBMng )
    {
	_pDBMng->log(_modId, Logger::NOTICE, msg);
    }
    else
    {
	cout << msg << endl;
    }
}

void CegoOutput::tabOut(const ListT< ListT<CegoFieldValue> >& fa)
{
    headOut();
    ListT<CegoFieldValue>* pFVL = fa.First();
    while ( pFVL )
    {
	rowOut(*pFVL);
	pFVL = fa.Next();
    }
    tailOut();
} 

void CegoOutput::headOut()
{   
    unsigned maxLen = 0;
    CegoField* pF;

    if ( _pDbHandle ) 
    {
	_pDbHandle->collectSchema(_schema, _format);	
	_rowCount = 0;
    }
    else if ( _pDBMng )
    {
	CegoField *pF;

	Chain msg;
	pF = _schema.First();
	while ( pF )
	{
	    Chain s = pF->getAttrName();
	    msg += s;
	    pF = _schema.Next();
	    if ( pF )
		msg += _rawSep;
	}	    
	_pDBMng->log(_modId, Logger::NOTICE, msg);
    }
    else
    {
	if ( _rawMode == true )
	{
	    CegoField *pF;
	    
	    pF = _schema.First();
	    
	    while ( pF )
	    {
		Chain s = pF->getAttrName();
		cout << s;
		pF = _schema.Next();
		if ( pF )
		    cout << _rawSep;
		else 
		    cout << endl;
	    }	    
	}
	else
	{
	    pF = _schema.First();
	    while (pF)
	    {
		maxLen = CegoQueryHelper::maxFieldSize(pF);
		cout << "+-" << fill("-", maxLen );	    
		pF = _schema.Next();
	    }
	    cout << "+" << endl;
	    
	    pF = _schema.First();
	    unsigned i=0;
	    while (pF)
	    {
		maxLen = CegoQueryHelper::maxFieldSize(pF);
		
		Chain tname;
		if (pF->getTableAlias().length() > 0)
		{
		    tname = pF->getTableAlias();
		}
		else
		{
		    tname = pF->getTableName();
		}
		
		cout << formatCell(i, tname, maxLen);
		
		i++;
		pF = _schema.Next();
	    }
	    
	    cout << "|" << endl;
	    
	    pF = _schema.First();
	    i=0;
	    while (pF)
	    {		
		maxLen = CegoQueryHelper::maxFieldSize(pF);	    		
		cout << formatCell(i, pF->getAttrName(), maxLen);
		
		i++;
		pF = _schema.Next();
	    }

	    cout << "|" << endl;
		
	    pF = _schema.First();
	    while (pF)
	    {	   
		maxLen = CegoQueryHelper::maxFieldSize(pF);	    
		cout << "+-" << fill("-", maxLen );	    
		pF = _schema.Next();
	    }
	    cout << "+" << endl;
	}
    }
}

void CegoOutput::rowOut(const ListT<CegoField>& fl)
{
    if ( _pDbHandle )
    {
	_pDbHandle->collectData(fl);
	_rowCount++;
	
	if ( _rowCount == _maxRowPerMsg || _pDbHandle->numCollected() > _maxBytePerMsg )
	{
	    _pDbHandle->sendCollectedData();
	    _rowCount=0;
	}
    }
    else if ( _pDBMng )
    {
	CegoField *pF;

	Chain msg;
	pF = fl.First();	
	while ( pF )
	{
	    Chain s = pF->getValue().valAsChain();
	    msg += s;
	    pF = fl.Next();
	    if ( pF )
		msg += _rawSep;
	}
	_pDBMng->log(_modId, Logger::NOTICE, msg);
    }
    else
    {	
	if ( _rawMode == false )
	{	    
	    CegoField *pF1;
	    CegoField *pF2;
	    
	    pF1 = fl.First();
	    pF2 = _schema.First();
	    
	    unsigned i=0;
	    _preFill=0;
	    while ( pF1 && pF2 )
	    {
		unsigned maxLen = CegoQueryHelper::maxFieldSize(pF2);
		
		Chain s = pF1->getValue().valAsChain();
		
		cout << formatCell(i, s, maxLen);

		_preFill+=maxLen + 1;
		
		i++;	    
		pF1 = fl.Next();
		pF2 = _schema.Next();
	    }
	    
	    cout << "|" << endl;
	}
	else
	{
	    CegoField *pF;
	    
	    pF = fl.First();
	    
	    while ( pF )
	    {
		Chain s = pF->getValue().valAsChain();
		cout << s;
		pF = fl.Next();
		if ( pF )
		    cout << _rawSep;
		else 
		    cout << endl;
	    }	    
	}
    }
}
    
void CegoOutput::rowOut(const ListT<CegoFieldValue>& fvl)
{
    if ( _pDbHandle )
    {
	_pDbHandle->collectData(_schema, fvl);
	_rowCount++;

	if ( _rowCount == _maxRowPerMsg || _pDbHandle->numCollected() > _maxBytePerMsg )
	{
	    _pDbHandle->sendCollectedData();
	    _rowCount=0;
	}
    }
    else if ( _pDBMng )
    {
	CegoFieldValue *pFV;
	Chain msg;
	
	pFV = fvl.First();	
	while ( pFV )
	{	    
	    Chain s = pFV->valAsChain();
	    msg += s;
	    
	    pFV = fvl.Next();
	    if ( pFV )
		msg += _rawSep;
	}
	_pDBMng->log(_modId, Logger::NOTICE, msg);
    }
    else
    {
	if ( _rawMode == false )
	{
	    CegoFieldValue *pFV;
	    CegoField *pF2;
	    
	    pFV = fvl.First();
	    pF2 = _schema.First();
	    
	    unsigned i=0;
	    _preFill=0;
	    while ( pFV && pF2 )
	    {
		unsigned maxLen = CegoQueryHelper::maxFieldSize(pF2);
		
		Chain s = pFV->valAsChain();
		cout << formatCell(i, s, maxLen);
		_preFill+=maxLen + 1;
		
		i++;
		pFV = fvl.Next();
		pF2 = _schema.Next();
	    }	    
	    cout << "|" << endl;
	}
	else
	{
	    CegoFieldValue *pFV;
	    
	    pFV = fvl.First();
	    
	    while ( pFV )
	    {	
		Chain s = pFV->valAsChain();
		cout << s;
		
		pFV = fvl.Next();
		if ( pFV )
		    cout << _rawSep;
		else 
		    cout << endl;
	    }	    
	}
    }
}
    
void CegoOutput::tailOut()
{
    if ( _pDbHandle )
    {
	if ( _rowCount > 0 )
	{
	    _pDbHandle->sendCollectedData();
	    _rowCount = 0;
	}
	_pDbHandle->sendFinishData();
    }
    else if ( _pDBMng )
    {
	// nothing to do
    }
    else if ( _rawMode == false )
    {
	unsigned maxLen = 0;
	CegoField *pF;
	
	pF = _schema.First();
	while (pF)
	{
	    maxLen = CegoQueryHelper::maxFieldSize(pF);
	    
	    cout << "+-" << fill("-", maxLen );
	    
	    pF = _schema.Next();
	}
	cout << "+" << endl;
    }
}

void CegoOutput::abort(const Chain& msg)
{
    if ( _pDbHandle )
    {
	_pDbHandle->sendErrorData(msg);
    }
    else if ( _pDBMng )
    {
	_pDBMng->log(_modId, Logger::LOGERR, msg);
    }
    else
    {
	cout << "Error : " << msg << endl;
    }
}

Chain CegoOutput::fill(const Chain& s, unsigned num)
{
    Chain fs = Chain();
    while (num > 0)
    {
	fs = fs + s;
	num--;
    }
    return fs;
}

Chain CegoOutput::formatCell(unsigned i, const Chain& s, unsigned maxLen, bool printSep)
{
    Chain cell;
    
    if ( _format.length() < 2 ) // _format == Chain() )
    {
	Chain normString;	
	if ( (unsigned)s.length()  > maxLen + 1 )
	    normString = s.subChain(1, maxLen - 3 ) + Chain("...");
	else
	    normString = s;

	if ( printSep )	    
	    cell = Chain("|") + fill(" ", maxLen - normString.visibleLength() + 1) +  normString + Chain(" ");
	else
	    cell = fill(" ", maxLen - normString.visibleLength() + 1) +  normString + Chain(" ");
    }
    else
    {
	if ( _format[i] == 'l' )
	{	    
	    Chain normString;
	    if ( (unsigned)s.length() > maxLen + 1 )
		normString = s.subChain(1, maxLen - 3 ) + Chain("...");
	    else
		normString = s;

	    if ( printSep )
		cell = Chain("| ") + normString + fill(" ", maxLen - normString.visibleLength() + 1);
	    else
		cell = normString + fill(" ", maxLen - normString.visibleLength() + 1);
	}
	else if ( _format[i] == 'r' )
	{   
	    Chain normString;
	    if ( (unsigned)s.length()  > maxLen + 1 )
		normString = s.subChain(1, maxLen - 3 ) + Chain("...");
	    else
		normString = s;

	    if ( printSep )
		cell = "|" + fill(" ", maxLen - normString.visibleLength() + 1) + normString + Chain(" ");
	    else
		cell = fill(" ", maxLen - normString.visibleLength() + 1) + normString + Chain(" ");
	}
	else if ( _format[i] == 'm' )
	{
	    Tokenizer lineTok(s, Chain("\n"));
	    Chain line;
	    bool isFirst = true;
	    while ( lineTok.nextToken(line) )
	    {
		Chain normString;
		if ( (unsigned)line.length()  > maxLen + 1 )
		    normString = line.subChain(1, maxLen - 3 ) + Chain("...");
		else
		    normString = line;

		if ( isFirst == false ) 
		{
		    if ( printSep )
			cell += Chain("|\n");
		    
		    if ( _preFill )
		    {
			if ( printSep )
			    cell += Chain("| ") + fill(" ", _preFill);
			else
			    cell += fill(" ", _preFill);
		    }
		}
		
		if ( printSep )
		    cell += Chain("| ") + normString + fill(" ", maxLen - normString.visibleLength() + 1);		
		else
		    cell += normString + fill(" ", maxLen - normString.visibleLength() + 1);
		
		isFirst = false;		  
	    }
	}		
    }
    return cell;
}
