/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <osl/diagnose.h>
#include "stgavl.hxx"

StgAvlNode::StgAvlNode()
{
    pLeft = pRight = NULL;
    nBalance = nId = 0;
}

StgAvlNode::~StgAvlNode()
{
    delete pLeft;
    delete pRight;
}

StgAvlNode* StgAvlNode::Find( StgAvlNode* pFind )
{
    if ( pFind )
    {
        StgAvlNode* p = this;
        while( p )
        {
            short nRes = p->Compare( pFind );
            if( !nRes )
                return p;
            else p = ( nRes < 0 ) ? p->pLeft : p->pRight;
        }
    }
    return NULL;
}

// find point to add node to AVL tree and returns
// +/0/- for >/=/< previous

short StgAvlNode::Locate
    ( StgAvlNode* pFind,
      StgAvlNode** pPivot, StgAvlNode **pParent, StgAvlNode** pPrev )
{
    short nRes = 0;
    StgAvlNode* pCur = this;

    OSL_ENSURE( pPivot && pParent && pPrev, "The pointers may not be NULL!" );
    *pParent = *pPrev = NULL;
    *pPivot = this;

    // search tree for insertion point
    if ( pFind )
    {
        while( pCur != NULL )
        {
            // check for pPivot
            if( pCur->nBalance != 0 )
                *pPivot = pCur, *pParent = *pPrev;
            // save pPrev location and see what direction to go
            *pPrev = pCur;
            nRes = pCur->Compare( pFind );
            if( nRes == 0 )
                break;
            else pCur = ( nRes < 0 ) ? pCur->pLeft : pCur->pRight;
        }
    }

    return( nRes );
}

// adjust balance factors in AVL tree from pivot down.
// Returns delta balance.

short StgAvlNode::Adjust( StgAvlNode** pHeavy, StgAvlNode* pNew )
{
    StgAvlNode* pCur = this;
    short nDelta;
    // no traversing
    OSL_ENSURE( pHeavy && pNew, "The pointers is not allowed to be NULL!" );
    if( pCur == pNew || !pNew )
        return nBalance;

    short nRes = Compare( pNew );
    if( nRes > 0 )
    {
        *pHeavy = pCur = pRight;
        nDelta = -1;
    }
    else
    {
        *pHeavy = pCur = pLeft;
        nDelta = 1;
    }
    nBalance = 0;
    while( pCur != pNew )
    {
        nRes = pCur->Compare( pNew );
        if( nRes > 0 )
        {
            // height of right increases by 1
            pCur->nBalance = -1;
            pCur = pCur->pRight;
        }
        else
        {
            // height of left increases by 1
            pCur->nBalance = 1;
            pCur = pCur->pLeft;
        }
    }
    nBalance = nBalance + nDelta;
    return nDelta;
}

// perform LL rotation and return new root

StgAvlNode* StgAvlNode::RotLL()
{
    OSL_ENSURE( pLeft, "The pointer is not allowed to be NULL!" );
    StgAvlNode *pHeavy = pLeft;
    pLeft = pHeavy->pRight;
    pHeavy->pRight = this;
    pHeavy->nBalance = nBalance = 0;
    return pHeavy;
}

// perform LR rotation and return new root

StgAvlNode* StgAvlNode::RotLR()
{
    OSL_ENSURE( pLeft && pLeft->pRight, "The pointer is not allowed to be NULL!" );
    StgAvlNode* pHeavy = pLeft;
    StgAvlNode* pNewRoot = pHeavy->pRight;

    pHeavy->pRight = pNewRoot->pLeft;
    pLeft = pNewRoot->pRight;
    pNewRoot->pLeft = pHeavy;
    pNewRoot->pRight = this;

    switch( pNewRoot->nBalance )
    {
        case 1:     // LR( b )
            nBalance = -1;
            pHeavy->nBalance = 0;
            break;
        case -1:    // LR( c )
            pHeavy->nBalance = 1;
            nBalance = 0;
            break;
        case 0:     // LR( a )
            nBalance = 0;
            pHeavy->nBalance = 0;
            break;
    }
    pNewRoot->nBalance = 0;
    return pNewRoot;
}

// perform RR rotation and return new root

StgAvlNode* StgAvlNode::RotRR()
{
    OSL_ENSURE( pRight, "The pointer is not allowed to be NULL!" );
    StgAvlNode* pHeavy = pRight;
    pRight = pHeavy->pLeft;
    pHeavy->pLeft = this;
    nBalance = pHeavy->nBalance = 0;
    return pHeavy;
}

// perform the RL rotation and return the new root

StgAvlNode* StgAvlNode::RotRL()
{
    OSL_ENSURE( pRight && pRight->pLeft, "The pointer is not allowed to be NULL!" );
    StgAvlNode* pHeavy = pRight;
    StgAvlNode* pNewRoot = pHeavy->pLeft;
    pHeavy->pLeft = pNewRoot->pRight;
    pRight = pNewRoot->pLeft;
    pNewRoot->pRight = pHeavy;
    pNewRoot->pLeft = this;
    switch( pNewRoot->nBalance )
    {
        case -1:    // RL( b )
            nBalance = 1;
            pHeavy->nBalance = 0;
            break;
        case 1:     // RL( c )
            pHeavy->nBalance = -1;
            nBalance = 0;
            break;
        case 0:     // RL( a )
            nBalance = 0;
            pHeavy->nBalance = 0;
            break;
    }
    pNewRoot->nBalance = 0;
    return pNewRoot;
}

// Remove a tree element. Return the removed element or NULL.

StgAvlNode* StgAvlNode::Rem( StgAvlNode** p, StgAvlNode* pDel, sal_Bool bPtrs )
{
    if( p && *p && pDel )
    {
        StgAvlNode* pCur = *p;
        short nRes = bPtrs ? short( pCur == pDel ) : short(pCur->Compare( pDel ));
        if( !nRes )
        {
            // Element found: remove
            if( !pCur->pRight )
            {
                *p = pCur->pLeft; pCur->pLeft = NULL;
            }
            else if( !pCur->pLeft )
            {
                *p = pCur->pRight; pCur->pRight = NULL;
            }
            else
            {
                // The damn element has two leaves. Get the
                // rightmost element of the left subtree (which
                // is lexically before this element) and replace
                // this element with the element found.
                StgAvlNode* last = pCur;
                StgAvlNode* l;
                for( l = pCur->pLeft;
                     l->pRight; last = l, l = l->pRight ) {}
                // remove the element from chain
                if( l == last->pRight )
                    last->pRight = l->pLeft;
                else
                    last->pLeft = l->pLeft;
                // perform the replacement
                l->pLeft = pCur->pLeft;
                l->pRight = pCur->pRight;
                *p = l;
                // delete the element
                pCur->pLeft = pCur->pRight = NULL;
            }
            return pCur;
        }
        else
        {
            if( nRes < 0 )
                return Rem( &pCur->pLeft, pDel, bPtrs );
            else
                return Rem( &pCur->pRight, pDel, bPtrs );
        }
    }
    return NULL;
}

// Enumerate the tree for later iteration

void StgAvlNode::StgEnum( short& n )
{
    if( pLeft )
        pLeft->StgEnum( n );
    nId = n++;
    if( pRight )
        pRight->StgEnum( n );
}

// Add node to AVL tree.
// Return sal_False if the element already exists.

sal_Bool StgAvlNode::Insert( StgAvlNode** pRoot, StgAvlNode* pIns )
{
    StgAvlNode* pPivot, *pHeavy, *pNewRoot, *pParent, *pPrev;
    if ( !pRoot )
        return sal_False;

    // special case - empty tree
    if( *pRoot == NULL )
    {
        *pRoot = pIns;
        return sal_True;
    }
    // find insertion point and return if already present
    short nRes = (*pRoot)->Locate( pIns, &pPivot, &pParent, &pPrev );
    if( !nRes )
        return sal_False;
    OSL_ENSURE( pPivot && pPrev, "The pointers may not be NULL!" );

    // add new node
    if( nRes < 0 )
        pPrev->pLeft = pIns;
    else
        pPrev->pRight = pIns;
    // rebalance tree
    short nDelta = pPivot->Adjust( &pHeavy, pIns );
    if( pPivot->nBalance >= 2 || pPivot->nBalance <= -2 )
    {
        pHeavy = ( nDelta < 0 ) ? pPivot->pRight : pPivot->pLeft;
        // left imbalance
        if( nDelta > 0 )
            if( pHeavy->nBalance == 1 )
                pNewRoot = pPivot->RotLL();
            else
                pNewRoot = pPivot->RotLR();
        // right imbalance
        else if( pHeavy->nBalance == -1 )
            pNewRoot = pPivot->RotRR();
        else
            pNewRoot = pPivot->RotRL();
        // relink balanced subtree
        if( pParent == NULL )
            *pRoot = pNewRoot;
        else if( pPivot == pParent->pLeft )
            pParent->pLeft = pNewRoot;
        else if( pPivot == pParent->pRight )
            pParent->pRight = pNewRoot;
    }
    return sal_True;
}

// Remove node from tree. Returns sal_True is found and removed.
// Actually delete if bDel

sal_Bool StgAvlNode::Remove( StgAvlNode** pRoot, StgAvlNode* pDel, sal_Bool bDel )
{
    if ( !pRoot )
        return sal_False;

    // special case - empty tree
    if( *pRoot == NULL )
        return sal_False;
    // delete the element
    pDel = Rem( pRoot, pDel, sal_False );
    if( pDel )
    {
        if( bDel )
            delete pDel;
        // Rebalance the tree the hard way
        // OS 22.09.95: Auf MD's Wunsch auskommentiert wg. Absturz
/*      StgAvlNode* pNew = NULL;
        while( *pRoot )
        {
            StgAvlNode* p = Rem( pRoot, *pRoot, sal_False );
            Insert( &pNew, p );
        }
        *pRoot = pNew;*/
        return sal_True;
    }
    else
        return sal_False;
}

// Move node to a different tree. Returns sal_True is found and moved. This routine
// may be called when the key has changed.

sal_Bool StgAvlNode::Move
    ( StgAvlNode** pRoot1, StgAvlNode** pRoot2, StgAvlNode* pMove )
{
    if ( !pRoot1 )
        return sal_False;

    // special case - empty tree
    if( *pRoot1 == NULL )
        return sal_False;
    pMove = Rem( pRoot1, pMove, sal_False );
    if( pMove )
        return Insert( pRoot2, pMove );
    else
        return sal_False;
}

////////////////////////// class AvlIterator /////////////////////////

// The iterator walks through a tree one entry by one.

StgAvlIterator::StgAvlIterator( StgAvlNode* p )
{
    pRoot = p;
    nCount = 0;
    nCur = 0;
    if( p )
        p->StgEnum( nCount );
}

StgAvlNode* StgAvlIterator::Find( short n )
{
    StgAvlNode* p = pRoot;
    while( p )
    {
        if( n == p->nId )
            break;
        else p = ( n < p->nId ) ? p->pLeft : p->pRight;
    }
    return p;
}

StgAvlNode* StgAvlIterator::First()
{
    nCur = -1;
    return Next();
}

StgAvlNode* StgAvlIterator::Next()
{
    return Find( ++nCur );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
