///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoGroupSpace.cc
// ------------------
// Cego group space implementation
//     
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoGroupSpace
//
// Description: Utility class for grouping tuples
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// LFC INCLUDES
#include <lfcbase/Exception.h>

// CEGO INCLUDES
#include "CegoGroupSpace.h"
#include "CegoDataType.h"
#include "CegoTableManager.h"
#include "CegoTypeConverter.h"

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

CegoGroupSpace::CegoGroupSpace()
{
    _pAVL = new AVLTreeT<CegoGroupNode>;
    _pCountAgg = new CegoAggregation(false);
}

CegoGroupSpace::~CegoGroupSpace()
{
    if ( _pAVL )
	delete _pAVL;	
    if ( _pCountAgg )
	delete _pCountAgg;
}

void CegoGroupSpace::initGroupSpace(const ListT<CegoField>& schema, ListT<CegoAggregation*>& aggList, unsigned long long maxOrderSize)
{
    _aggList = aggList;
    _maxOrderSize = maxOrderSize;
    _orderSize = 0;
    _groupSchema.Empty();
    _aggSchema.Empty();

    unsigned id=1;
    
    CegoField* pF = schema.First();
    while ( pF )
    {
	pF->setId(id);
	
	_groupSchema.Insert(*pF);

	id++;
	pF = schema.Next();
    }

    /* we add count aggregation in any case */
    _pCountAgg->setAggregationId(_aggList.Size() + 1);
    _aggList.Insert( _pCountAgg );

    CegoAggregation** pAgg = _aggList.First();
    while ( pAgg )
    {
	CegoField f;
	f.setId(id);
	(*pAgg)->setAggregationId(id);

	f.setType(LONG_TYPE);
	f.setAttrName((*pAgg)->toChain(0));

	// _groupSchema.Insert(f);
	_aggSchema.Insert(f);
	
	id++;
	pAgg = _aggList.Next();
    }

    _groupingOffset = schema.Size() + 1;

    unsigned i=1;
    pF = _groupSchema.First();
    while ( pF )
    {
	if ( i < _groupingOffset )
	{
	    // pF->setTableAlias("GROUPING");
	}
	else
	{
	    if ( _aggList[i-_groupingOffset]->getType() == CegoAggregation::AVG )
	    {
		pF->setTableAlias(AVG_GROUP);
	    }
	    else if ( _aggList[i-_groupingOffset]->getType() == CegoAggregation::MIN )
	    {
		pF->setTableAlias(MIN_GROUP);
	    }
	    else if ( _aggList[i-_groupingOffset]->getType() == CegoAggregation::MAX )
	    {
		pF->setTableAlias(MAX_GROUP);
	    }
	    else if ( _aggList[i-_groupingOffset]->getType() == CegoAggregation::SUM )
	    {
		pF->setTableAlias(SUM_GROUP);
	    }
	    else
	    {
		pF->setTableAlias(COUNT_GROUP);
	    }
	}
	
	i++;
	pF = _groupSchema.Next();
    }
}

void CegoGroupSpace::resetGroupSpace()
{    
    _pAVL->Empty();
    _orderSize = 0;
}

ListT<CegoField> CegoGroupSpace::getSchema()
{
    return _groupSchema;
}

void CegoGroupSpace::insertTuple(ListT<CegoField>& groupTuple)
{
    // we create a copy of addKey with local field values
    // this is required, since field values references might be invalidated during the grouping query
        
    CegoField* pSF = _groupSchema.First();
    while (pSF)
    {
	CegoField* pTF;
	if ( (pTF = groupTuple.Find(CegoField(pSF->getTableAlias(), pSF->getAttrName()))) != 0 )
	{
	    pSF->setValue(pTF->getValue().getLocalCopy());
	}
	pSF = _groupSchema.Next();
    }
    
    CegoGroupNode n(_groupSchema);
    CegoGroupNode* pN = _pAVL->Find(n);
    if ( pN )
    {
	ListT<CegoField> newGroupValue = aggGrouping(pN->getGrouping(), groupTuple);
	pN->setGrouping(newGroupValue);
    }
    else
    {
	ListT<CegoField> groupedValues = initGrouping(groupTuple);
	CegoGroupNode nn(_groupSchema, groupedValues);
	
	_orderSize += sizeof(nn);

	if ( _orderSize > _maxOrderSize )
	{
	    throw Exception(EXLOC, "Order size exceeded");
	}
	
	_pAVL->Insert(nn);
    }
}

CegoGroupCursor* CegoGroupSpace::getCursor()
{
    CegoGroupCursor *pGC = new CegoGroupCursor(_groupSchema, _pAVL);
    return pGC;
}

unsigned long long CegoGroupSpace::numAllocated() const
{
    return _orderSize;
}

ListT<CegoField> CegoGroupSpace::initGrouping(ListT<CegoField>& dataTuple)
{
    ListT<CegoField> initValue;

    CegoAggregation** pAgg = _aggList.First();
    unsigned i=_groupingOffset;
    while ( pAgg )
    {
	CegoField f;
	f.setId(i);

	// (*pAgg)->setInit(true);
	
	switch ( (*pAgg)->getType() )
	{	    
	case CegoAggregation::COUNT:
	{
	    f.setValue(CegoFieldValue(LONG_TYPE, Chain("1")));
	    break;
	}	
	case CegoAggregation::SUM:
	case CegoAggregation::AVG:
	case CegoAggregation::MIN:
	case CegoAggregation::MAX:
	{	    
	    f.setValue( getValueForAgg(*pAgg, dataTuple));
	    break;
	}	
	}

	initValue.Insert(f);
	
	i++;
	pAgg = _aggList.Next();
    }
    
    return initValue;
}

ListT<CegoField> CegoGroupSpace::aggGrouping(ListT<CegoField>& aggValues, ListT<CegoField>& dataTuple)
{   
    CegoAggregation** pAgg = _aggList.First();
    CegoField *pF = aggValues.First();

    while ( pAgg && pF )
    {
	(*pAgg)->setFieldValue(pF->getValue());
	pAgg = _aggList.Next();
	pF = aggValues.Next();
    }

    ListT<CegoField> aggValue;

    unsigned i=0;
    pAgg = _aggList.First();
    while ( pAgg  )
    {
	CegoQueryHelper::aggregateTuple(dataTuple, *pAgg );
	
	CegoField f;
	f.setAttrName( _aggSchema[i].getAttrName());
	f.setValue( (*pAgg)->getFieldValue() );
	f.setId(i + _groupingOffset);
	i++;
	aggValue.Insert(f);
	pAgg = _aggList.Next();
    }

    return aggValue;    
}

CegoFieldValue CegoGroupSpace::getValueForAgg(CegoAggregation* pAgg, ListT<CegoField>& dataTuple)
{	
    CegoField f;
    CegoFieldValue fv;

    CegoExpr *pAE = pAgg->getExpr();
    
    if ( pAE == 0 )
    {
	f.setAttrName(Chain("*"));
	fv.setType(LONG_TYPE);
    }
    else
    {
	ListT<CegoField>* fl[2];
	fl[0] = &dataTuple;
	fl[1] = 0;

	fv = pAE->evalFieldValue(fl, 0 );

	if ( fv.getValue() == 0 && pAgg->getType() != CegoAggregation::MIN )
	{
	    switch ( fv.getType() )
	    {
	    case INT_TYPE:
	    {			
		fv = CegoFieldValue(INT_TYPE, Chain("0"));
		break;
	    }
	    case LONG_TYPE:
	    {			
		fv = CegoFieldValue(LONG_TYPE, Chain("0"));
		break;
	    }
	    case VARCHAR_TYPE:
	    {
		fv = CegoFieldValue(VARCHAR_TYPE, Chain());
		break;
	    }
	    case DECIMAL_TYPE:
	    {
		fv = CegoFieldValue(DECIMAL_TYPE, Chain("0.0"));
		break;		   
	    }
	    case NULL_TYPE:
	    case FLOAT_TYPE:
	    case DOUBLE_TYPE:
	    case SMALLINT_TYPE:
	    case TINYINT_TYPE:
	    case BIGINT_TYPE:
	    case BOOL_TYPE:
	    case DATETIME_TYPE:
	    case BLOB_TYPE:
	    case CLOB_TYPE:
	    case PAGEID_TYPE:
	    {
		Chain msg = Chain("Aggregation not supported on datatype <") + CEGO_TYPE_MAP[ (unsigned) fv.getType()] + Chain(">");
		throw Exception(EXLOC, msg);
		break;
	    }
	    }
	}
    }
    
    return fv.getLocalCopy();
}
