///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoAVLIndexCursor.cc
// ------------------
// Cego index cursor class implementation
//         
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2019 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoAVLIndexCursor
// 
// Description: Index cursors are used for reading tables based on an available index 
//              for the given attribute condition
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// cego includes
#include "CegoAVLIndexCursor.h"
#include "CegoTupleState.h"

#include <string.h>
#include <stdlib.h>

CegoAVLIndexCursor::CegoAVLIndexCursor(CegoTableManager *pTM, unsigned tabSetId, const Chain& indexName, CegoObject::ObjectType type, CegoAttrCond* pAttrCond, bool ignoreTouched, bool readUncommitted)
{
    _pTM = pTM;
    _indexName = indexName;
    _type = type;
    _pAttrCond = pAttrCond;

    _tabSetId = tabSetId;
    _ignoreTouched = ignoreTouched;
    _readUncommitted = readUncommitted;
    _cursorCached = false;
    _lockId = 0;
}

CegoAVLIndexCursor::~CegoAVLIndexCursor()
{
    abort();
    _pTM->releaseDataPtrUnlocked(_rootPage, false);
    _rootPage = CegoBufferPage();
}

bool CegoAVLIndexCursor::getFirst(ListT<CegoField>& fl, CegoDataPointer& dp)
{
    if ( fl.isEmpty() )
    {
	throw Exception(EXLOC, "Empty field list");
    }
    
    if ( _cursorCached == false )
    {
	CegoTableObject ioe;
	
	_pTM->getObject(_tabSetId, _indexName, _type, ioe);

	_idxSchema = ioe.getSchema();
	
	Chain tabName = ioe.getTabName();
	
	CegoObjectCursor* pC = _pTM->getObjectCursor(_tabSetId, tabName, _indexName, _type);

	_cachedPointer = (char*)pC->getFirst(_cachedLen, _rdp);
	pC->abort();
	delete pC;
	
	_pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, _rdp, _cachedPointer, _cachedLen, _rootPage);	
             
	_cursorCached = true;

    }

    // cout << "Read Locking " << _rdp.getFileId() << " " << _rdp.getPageId()  << endl;
    if ( _lockId == 0 )
	_lockId = _pTM->getLockHandler()->lockData(CegoObject::BTREE, _rdp.getPageId(), CegoLockHandler::READ);
	
    
    // get root entry
    unsigned len = _cachedLen;
    char* p = _cachedPointer;

    if ( p == 0 )
    {
	_eoc = true;
	return false;
    }
    
    _ie.setPtr(p, len);

    _idp = _ie.getRightBranch();

    CegoDataPointer nil;    
    if (_idp == nil)
    {
	_eoc = true;
	return false;
    }

    _eoc = false;

    _rootPassed = false;

    bool found = false;

    if (_pAttrCond)
    { 

	if ( _pAttrCond->getPrimaryCompMode() == CegoAttrComp::BTWN )
	    _pAttrCond->setPrimaryComparison( MORE_EQUAL_THAN );

	switch ( _pAttrCond->getPrimaryComparison() )
	{
	case NOT_EQUAL:
	case LESS_THAN:
	case LESS_EQUAL_THAN:
	    
	{
	    // go to the beginning

	    _pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);
	    
	    _ie.setPtr(p, len);
	    
	    while ( _ie.getLeftBranch() != nil)
	    {

		// Traversing to left
		_idp = _ie.getLeftBranch();		

		_pTM->releaseDataPtrUnlocked(_currentPage, false);
		_pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);
		
		_ie.setPtr(p, len);
	    }
	    
	    CegoComparison comp = compValue(_ie.getIdxPtr());
	    
	    if ( comp == LESS_THAN
		 || ( comp == EQUAL && _pAttrCond->getPrimaryComparison() == LESS_EQUAL_THAN )
		 || ( comp != EQUAL && _pAttrCond->getPrimaryComparison() == NOT_EQUAL ) )
	    {	       
		found = true;
	    }
	    else if ( _pAttrCond->getPrimaryComparison() == NOT_EQUAL )
	    {
		return getNext(fl, dp);
	    }
	    	    
	    break;
	}
	case MORE_THAN:
	{

	    // go to the smallest leaf

	    _pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);  

	    _ie.setPtr(p, len);
	    
	    while ( ! found )
	    {

		CegoDataPointer idp;
		
		CegoComparison comp = compValue( _ie.getIdxPtr());
				
		if ( comp == LESS_THAN || comp == EQUAL ) 
		{
		    idp = _ie.getRightBranch();				

		    if (_ie.getParent() == _rdp )
		    {
			_rootPassed = true;
		    }
 
		}
		else if ( comp == MORE_THAN )
		{
		    idp = _ie.getLeftBranch();
		    
		    if ( idp == nil )
			found = true;
		}
		else
		{
		    return getNext(fl, dp);
		}
		

		if ( idp == nil && ! found )    
		{
		    return getNext(fl, dp);
		}
		else if ( ! found )
		{
		    _idp = idp;
		    _pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, idp, p, len, _currentPage);
		    _ie.setPtr(p, len);
		}
	    }
	    break;	    
	}
	case EQUAL:
	case MORE_EQUAL_THAN:
	{
	    
	    // go to the smallest leaf

	    _pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage); 

	    _ie.setPtr(p, len);

	    while ( ! found )
	    {

		CegoDataPointer idp = _idp;
		
		CegoComparison comp = compValue(_ie.getIdxPtr()); 
				
		if ( comp == LESS_THAN ) 
		{
		    idp = _ie.getRightBranch();				

		    if (_ie.getParent() == _rdp )
		    {
			_rootPassed = true;
		    } 
		}
		else if ( comp == EQUAL ) 
		{
		    idp = _ie.getLeftBranch();

		    if ( idp == nil )
			found = true;
		} 
		else if ( comp == MORE_THAN ) 
		{		  		    
		    idp = _ie.getLeftBranch();		    
		}
				
		if ( idp == nil && ! found )    
		{
		    return getNext(fl, dp);
		}
		else if ( ! found )
		{
		    _idp = idp;
		    _pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, idp, p, len, _currentPage);
		    _ie.setPtr(p, len);

		}
	    }
	    break;	    
	}

	}
    }
    else
    {
	// go to the beginning
	
	_pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);
	
	_ie.setPtr(p, len);
	
	while ( _ie.getLeftBranch() != nil)
	{	    
	    _idp = _ie.getLeftBranch();			   
	    _pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);
	    _ie.setPtr(p, len);
	}
	    	
	found = true;
    }

	
    if (found )
    {

	char* p;
	unsigned len;
	
	dp = _ie.getData();


	_pTM->releaseDataPtrUnlocked(_dataPage, false);
	_pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, dp, p, len, _dataPage);
	
	// skipping tid
	
	unsigned long long tid;
	unsigned long long tastep;
	CegoTupleState ts;
	
	unsigned toff = _qh.decodeTupleHeader(tid, tastep, ts, p);

	char* tp = p + toff;
	unsigned tlen = len - toff;

	if (tid != 0)
	{
	    if ( _ignoreTouched )
	    {			
		if ( ts == CegoTupleState::INSERTED 
		     && tid == _pTM->getTID(_tabSetId) 
		     && tastep < _pTM->getTAStep(_tabSetId) )
		{
		    _qh.decodeFVL(fl, tp, tlen);
		    return true;   
		}
		else
		{
		    return getNext(fl,dp);
		}
	    }
	    else
	    {
		
		if ( ( _readUncommitted == true 
		       && ts == CegoTupleState::INSERTED )
		     || ( _readUncommitted == false 
			  && (( ts == CegoTupleState::INSERTED && tid == _pTM->getTID(_tabSetId)) 
			      || ( ts == CegoTupleState::DELETED && tid != _pTM->getTID(_tabSetId)))))
		{
		    _qh.decodeFVL(fl, tp, tlen);
		    return true;   
		}
		else
		{
		    return getNext(fl,dp);		    
		}
	    }
	}
	else
	{
	    _qh.decodeFVL(fl, tp, tlen);
	    return true;
	}
    }
    return false;
}

bool CegoAVLIndexCursor::getNext(ListT<CegoField>& fl, CegoDataPointer& dp)
{
    if ( _eoc )
	return false;

    if ( fl.isEmpty() )
    {
	throw Exception(EXLOC, "Empty field list");
    }

    do {

	CegoDataPointer nil;
	
	char* p;
	unsigned len;    

	bool attrMatch = false;

	if (_ie.getParent() == _rdp )
	{
	    _rootPassed = true;
	}
	
	if (_ie.getRightBranch() != nil) 
	{
	    
	    _idp = _ie.getRightBranch();	   
	    _pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);
	    
	    _ie.setPtr(p, len);
	    
	    while ( _ie.getLeftBranch() != nil)
	    {
		_idp = _ie.getLeftBranch();		
		_pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);
		_ie.setPtr(p, len);
	    }	
	}
	else
	{
	    // searching appropriate parent
	    
	    if ( _ie.getParent() == _rdp &&  _rootPassed )
	    {
		
		_pTM->releaseDataPtrUnlocked(_currentPage, false);
		_currentPage = CegoBufferPage();		
		_eoc = true;
		return false;
	    }
	    
	    CegoDataPointer pdp = _ie.getParent();
	    
	    _pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, pdp, p, len, _currentPage);

	    _ie.setPtr(p, len);
	    
	    bool entryFound = false;
	    while ( ! entryFound )
	    {
		
		if (_ie.getLeftBranch() == _idp)
		{
		    _idp = pdp;
		    entryFound = true;
		}
		else
		{
		    
		    if ( _ie.getParent() == _rdp )
		    {
			if ( _rootPassed )
			{
			    abort();
			    _eoc = true;
			    return false;
			}
			else
			{
			    
			    _idp = _ie.getRightBranch();			    
			    _pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage);
			    _ie.setPtr(p, len);
			    
			    while ( _ie.getLeftBranch() != nil)
			    {				
				_idp = _ie.getLeftBranch();
				_pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, _idp, p, len, _currentPage); 
				_ie.setPtr(p, len);
			    }
			    entryFound = true;
			}
		    }
		    else
		    {
			_idp = pdp;
			
			pdp = _ie.getParent();
			_pTM->releaseAndClaimDataPtrUnlocked(_currentPage, false, _tabSetId, CegoBufferPool::SYNC, pdp, p, len, _currentPage);
			_ie.setPtr(p, len);			
		    }
		}
	    }	    
	}
        
	if (_pAttrCond)
	{

	    char *p = _ie.getIdxPtr();
	    CegoComparison comp;
	    comp = compValue(p);

	    if ( _pAttrCond->getPrimaryCompMode() == CegoAttrComp::BTWN )
	    {
		_pAttrCond->setPrimaryComparison(LESS_EQUAL_THAN);
	    }
	  	  
	    switch (_pAttrCond->getPrimaryComparison())
	    {
		
	    case EQUAL:
	    {	  
		switch (comp)
		{
		case EQUAL:
		    attrMatch=true;
		    break;
		case LESS_THAN:
		case MORE_THAN:
		case LESS_EQUAL_THAN:
		case MORE_EQUAL_THAN:
		case NOT_EQUAL:
		    break;
		}
		break;
	    }
	    case NOT_EQUAL:
	    {
		switch (comp)
		{
		case LESS_THAN:
		    attrMatch=true;
		    break;
		case MORE_THAN:
		    attrMatch=true;
		    break;
		case EQUAL:
		    return getNext(fl, dp);
		case LESS_EQUAL_THAN:
		case MORE_EQUAL_THAN:
		case NOT_EQUAL:		    
		    break;
		}
		break;
	    }
	    case LESS_THAN:
	    {		
		switch (comp)
		{
		case LESS_THAN:
		    attrMatch=true;
		    break;
		case EQUAL:
		case MORE_THAN:
		case LESS_EQUAL_THAN:
		case MORE_EQUAL_THAN:
		case NOT_EQUAL:		    		    
		    break;
		}
		break;
	    }
	    case MORE_THAN:	     
	    {
		switch (comp)
		{
		case MORE_THAN:
		    attrMatch=true;
		    break;
		case EQUAL:
		case LESS_THAN:
		case LESS_EQUAL_THAN:
		case MORE_EQUAL_THAN:
		case NOT_EQUAL:	
		    break;		
		}	
		break;       
	    }
	    case LESS_EQUAL_THAN:
	    {
		switch (comp)
		{
		case EQUAL:
		    attrMatch=true;
		    break;
		case LESS_THAN:
		    attrMatch=true;
		    break;
		case MORE_THAN:
		case LESS_EQUAL_THAN:
		case MORE_EQUAL_THAN:
		case NOT_EQUAL:
		    break;
		}
		break;
	    }
	    
	    case MORE_EQUAL_THAN:
	    {
		switch (comp)
		{
		case EQUAL:
		    attrMatch=true;
		    break;
		case MORE_THAN:
		    attrMatch=true;
		    break;
		case LESS_THAN:
		case LESS_EQUAL_THAN:
		case MORE_EQUAL_THAN:
		case NOT_EQUAL:
		    break;
		}
		break;
	    }
	    }	    
	}
	else
	{
	    attrMatch = true;
	}
	
	if ( attrMatch )
	{
	    
	    char* p;
	    unsigned len;
	    	    
	    dp = _ie.getData();
	    
	    _pTM->releaseDataPtrUnlocked(_dataPage, false);
	    _pTM->claimDataPtrUnlocked(_tabSetId, CegoBufferPool::SYNC, dp, p, len, _dataPage);
	    
	    // skipping tid
	    
	    unsigned long long tid;
	    unsigned long long tastep;
	    CegoTupleState ts;

	    unsigned toff = _qh.decodeTupleHeader(tid, tastep, ts, p);

	    char* tp = p + toff;
	    unsigned tlen = len - toff;
	    
	    if (tid != 0)
	    {
		if ( _ignoreTouched == true )
		{
		    if ( ts == CegoTupleState::INSERTED 
			 && tid == _pTM->getTID(_tabSetId) 
			 && tastep < _pTM->getTAStep(_tabSetId) )
		    {
			_qh.decodeFVL(fl, tp, tlen);
			return true;   
		    }
		    else
		    {		
			// ignore entry
		    }
		}
		else
		{

		    if ( ( _readUncommitted == true 
			   && ts == CegoTupleState::INSERTED )
			 || ( _readUncommitted == false 
			      && (( ts == CegoTupleState::INSERTED && tid == _pTM->getTID(_tabSetId)) 
				  || ( ts == CegoTupleState::DELETED && tid != _pTM->getTID(_tabSetId)))))
		    {
			_qh.decodeFVL(fl, tp, tlen);
			return true;
		    }
		    else
		    {
			// ignore entry
		    }
		}
	    }
	    else
	    {	       
		_qh.decodeFVL(fl, tp, tlen);
		return true;
	    }
	}
	else
	{
	    abort();
	    _eoc = true;
	    return false;
	}
    } while (1);
}

void CegoAVLIndexCursor::abort()
{    
    _pTM->releaseDataPtrUnlocked(_currentPage, false);
    _currentPage = CegoBufferPage();
    
    _pTM->releaseDataPtrUnlocked(_dataPage, false);
    _dataPage = CegoBufferPage();

    if ( _lockId )
    {
	_pTM->getLockHandler()->unlockData(CegoObject::BTREE, _lockId);
	_lockId = 0;
    }
}

void CegoAVLIndexCursor::reset()
{
    abort();
}

CegoComparison CegoAVLIndexCursor::compValue(char* idxVal)
{
    CegoField* pF = _idxSchema.First();
	     
    while ( pF ) 
    {

	unsigned flen;
	memcpy(&flen, idxVal, sizeof(unsigned));
	
	idxVal += sizeof(unsigned);
	
	CegoFieldValue fv;
	fv.setLength(flen);
	fv.setValue(idxVal);	
	if ( flen > 0 )
	    fv.setType(pF->getType());

	CegoAttrComp* pAC = _pAttrCond->getAttrCompSet().First();

	while ( pAC )
	{
	    if  ( (Chain)pAC->getAttrName() == (Chain)pF->getAttrName()  )
	    {	       
		if ( fv < pAC->getFieldValue() )
		{
		    return LESS_THAN;
		}
		
		if ( pAC->getCompMode() == CegoAttrComp::VAL
		     || pAC->getCompMode() == CegoAttrComp::ATTR )
		{
		    if ( fv > pAC->getFieldValue() )
		    {
			return MORE_THAN;
		    }
		}
		else if ( pAC->getCompMode() == CegoAttrComp::BTWN )
		{
		    if ( fv > pAC->getFieldValue2() )
		    {
			return MORE_THAN;
		    }
		}		
	    }
	    pAC = _pAttrCond->getAttrCompSet().Next();
	}		

	idxVal += flen;

	pF = _idxSchema.Next();
    }
    
    return EQUAL;
}
