/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
/*****************************************************************************
*
*   $/Plasma20/Sources/Plasma/NucleusLib/pnUtils/Private/pnUtPriQ.h
*   
***/

#ifndef PLASMA20_SOURCES_PLASMA_NUCLEUSLIB_PNUTILS_PRIVATE_PNUTPRIQ_H
#define PLASMA20_SOURCES_PLASMA_NUCLEUSLIB_PNUTILS_PRIVATE_PNUTPRIQ_H

#include "Pch.h"

/****************************************************************************
*
*   Macros
*
***/

#define PRIORITY_TIME(class)            TPriorityTime< class >
#define PRIORITY_NUMERIC(class,type)    TPriorityNumeric< class,type >

#define PRIQ(class,priority)            TPriorityQueue< class,priority >
#define PRIQDECL(class,priority,field)  TPriorityQueueDecl< class,priority,offsetof(class,field) >
#define PRIQDYN(class,priority)         TPriorityQueueDyn< class,priority >


/****************************************************************************
*
*   class TPriorityQueue
*
***/

template<class C, class P>
class TBasePriority;

template<class C, class P>
class TPriorityQueue {

public:
    TPriorityQueue ();
    ~TPriorityQueue ();

    C * const & operator[] (unsigned index) const;

    void Clear ();
    unsigned Count () const;
    C * Delete (C * object);
    C * Dequeue ();
    void Enqueue (C * object);
    C * const * Ptr () const;
    C * Root () const;
    C * const * Term () const;
    void UnlinkAll ();

public:
    // Intentionally unimplemented
    TPriorityQueue (TPriorityQueue const &);
    TPriorityQueue const & operator= (TPriorityQueue const &);

protected:
    void SetLinkOffset (int offset);

private:
    unsigned IndexChild  (unsigned index) const;
    unsigned IndexParent (unsigned index) const;
    void Link (unsigned index);
    P * Priority (C * object);
    P const * Priority (C const * object) const;
    void Remove (unsigned index);
    void Unlink (unsigned index);

    enum { LINK_OFFSET_UNINIT = 0xdddddddd };

    int m_linkOffset;
    ARRAY(C *) m_array;

    friend class TBasePriority<C,P>;
};

//===========================================================================
template<class C, class P>
inline C * const & TPriorityQueue<C,P>::operator[] (unsigned index) const {
    return m_array[index];
}

//===========================================================================
template<class C, class P>
inline TPriorityQueue<C,P>::TPriorityQueue () :
    m_linkOffset(LINK_OFFSET_UNINIT) {
}

//===========================================================================
template<class C, class P>
inline TPriorityQueue<C,P>::~TPriorityQueue () {
    UnlinkAll();
}

//===========================================================================
template<class C, class P>
inline void TPriorityQueue<C,P>::Clear () {

    // Deleting an object could cause other objects in the queue to be deleted
    // so we can't make any assumptions about indices or counts of items in the array
    while (C * head = Dequeue())
        delete head;

    m_array.Clear();
}

//===========================================================================
template<class C, class P>
inline unsigned TPriorityQueue<C,P>::Count () const {
    return m_array.Count();
}

//===========================================================================
template<class C, class P>
C * TPriorityQueue<C,P>::Delete (C * object) {

    // get the object's priority queue and position
    P * priority = Priority(object);
    const TPriorityQueue<C,P> * queue = priority->GetLink();
    unsigned index = priority->GetIndex();

    // delete the object
    delete object;

    // return the next object in that queue
    if (queue && (index < queue->Count()))
        return (*queue)[index];
    else
        return nil;

}

//===========================================================================
template<class C, class P>
C * TPriorityQueue<C,P>::Dequeue () {
    if (!m_array.Count())
        return nil;
    C * value = m_array[0];
    Remove(0);
    return value;
}

//===========================================================================
template<class C, class P>
void TPriorityQueue<C,P>::Enqueue (C * object) {
    P * priority = Priority(object);

    // Verify that the object is not already linked into a priority queue.
    // The original implementation of this function silently refused to
    // enqueue at a new priority if the object was already in this queue.
    // Since this behavior requires callers to check whether the object is
    // already enqueued, we now simply assert that.
    ASSERT(!priority->IsLinked());

    unsigned index  = m_array.Add(object);
    unsigned parent = IndexParent(index);

    // shift value toward root
    while (index && priority->IsPriorityHigher(*Priority(m_array[parent]))) {
        m_array[index] = m_array[parent];
        Link(index);
        index  = parent;
        parent = IndexParent(index);
    }

    // assign and link the new value
    m_array[index] = object;
    Link(index);
}

//===========================================================================
template<class C, class P>
inline unsigned TPriorityQueue<C,P>::IndexChild (unsigned index) const {
    return (index << 1) + 1;
}

//===========================================================================
template<class C, class P>
inline unsigned TPriorityQueue<C,P>::IndexParent (unsigned index) const {
    return (index - 1) >> 1;
}

//===========================================================================
template<class C, class P>
inline void TPriorityQueue<C,P>::Link (unsigned index) {
    Priority(m_array[index])->Link(this, index);
}

//===========================================================================
template<class C, class P>
inline P * TPriorityQueue<C,P>::Priority (C * object) {
    ASSERT(m_linkOffset != LINK_OFFSET_UNINIT);
    return (P *)((uint8_t *)object + m_linkOffset);
}

//===========================================================================
template<class C, class P>
inline P const * TPriorityQueue<C,P>::Priority (C const * object) const {
    ASSERT(m_linkOffset != LINK_OFFSET_UNINIT);
    return (P const *)((uint8_t const *)object + m_linkOffset);
}

//===========================================================================
template<class C, class P>
inline C * const * TPriorityQueue<C,P>::Ptr () const {
    return m_array.Ptr();
}

//===========================================================================
template<class C, class P>
void TPriorityQueue<C,P>::Remove (unsigned index) {

    // reset the priority link fields
    Unlink(index);

    // save the terminal leaf node
    C * value    = m_array.Pop();
    P * priority = Priority(value);

    const unsigned count = m_array.Count();
    if (count == index)
        return;

    // rebalance upwards from the position of the deleted entry
    unsigned parent;
    unsigned entry = index;
    if (entry && priority->IsPriorityHigher(*Priority(m_array[parent = IndexParent(entry)]))) {
        do {
            m_array[entry] = m_array[parent];
            Link(entry);
            entry = parent;
        } while (entry && priority->IsPriorityHigher(*Priority(m_array[parent = IndexParent(entry)])));
        m_array[entry] = value;
        Link(entry);

        entry    = index;
        value    = m_array[index];
        priority = Priority(value);
    }

    // rebalance downwards from the position of the deleted entry
    for (;;) {
        unsigned child = IndexChild(entry);
        if (child >= count)
            break;

        unsigned sibling = child + 1;
        if ( (sibling < count) &&
             (Priority(m_array[sibling])->IsPriorityHigher(*Priority(m_array[child]))) )
            child = sibling;

        if (priority->IsPriorityHigher(*Priority(m_array[child])))
            break;

        m_array[entry] = m_array[child];
        Link(entry);
        entry = child;
    }

    m_array[entry] = value;
    Link(entry);
}

//===========================================================================
template<class C, class P>
inline C * TPriorityQueue<C,P>::Root () const {
    return m_array.Count() ? m_array[0] : nil;
}

//===========================================================================
template<class C, class P>
inline void TPriorityQueue<C,P>::SetLinkOffset (int offset) {
    ASSERT(m_linkOffset == LINK_OFFSET_UNINIT);
    m_linkOffset = offset;
}

//===========================================================================
template<class C, class P>
inline C * const * TPriorityQueue<C,P>::Term () const {
    return m_array.Term();
}

//===========================================================================
template<class C, class P>
inline void TPriorityQueue<C,P>::Unlink (unsigned index) {
    Priority(m_array[index])->Link(nil, 0);
}

//===========================================================================
template<class C, class P>
inline void TPriorityQueue<C,P>::UnlinkAll () {
    for (unsigned loop = m_array.Count(); loop--; )
        Unlink(loop);
    m_array.ZeroCount();
}


/****************************************************************************
*
*   TPriorityQueueDecl
*
***/

template<class C, class P, int linkOffset>
class TPriorityQueueDecl : public TPriorityQueue<C,P> {
public:
    TPriorityQueueDecl () { this->SetLinkOffset(linkOffset); }
};


/****************************************************************************
*
*   TPriorityQueueDyn
*
***/

template<class C, class P>
class TPriorityQueueDyn : public TPriorityQueue<C,P> {
public:
    void Initialize (int linkOffset) { this->SetLinkOffset(linkOffset); }
};


/****************************************************************************
*
*   class TBasePriority
*
***/

template<class C, class P>
class TBasePriority {

public:
    TBasePriority () : m_queue(nil), m_index(0) { }
    virtual ~TBasePriority () { Unlink(); }

    void Unlink   ()       { if (m_queue) m_queue->Remove(m_index); }
    bool IsLinked () const { return m_queue != nil; }

public:
    TBasePriority (const TBasePriority &);
    const TBasePriority & operator= (const TBasePriority &);

protected:
    void Relink ();

private:
    void Link (TPriorityQueue<C,P> * queue, unsigned index);
    const TPriorityQueue<C,P> * GetLink () const { return m_queue; }
    unsigned GetIndex () const { return m_index; }

private:
    TPriorityQueue<C,P> * m_queue;
    unsigned              m_index;

    friend class TPriorityQueue<C,P>;
};

//===========================================================================
template<class C, class P>
inline void TBasePriority<C,P>::Link (TPriorityQueue<C,P> * queue, unsigned index) {
    m_queue = queue;
    m_index = index;
}

//===========================================================================
template<class C, class P>
void TBasePriority<C,P>::Relink () {

    // cache m_queue, since m_queue->Remove() will set it to nil
    TPriorityQueue<C,P> * queue = m_queue;
    if (!queue)
        return;
    C * object = (*queue)[m_index];
    queue->Remove(m_index);
    queue->Enqueue(object);
}


/****************************************************************************
*
*   class TPriorityNumeric
*
***/

template<class C,class T>
class TPriorityNumeric : public TBasePriority< C, TPriorityNumeric<C,T> > {

public:
    TPriorityNumeric () : m_value(0) { }
    TPriorityNumeric (T value) : m_value(value) { }
    void Set (T value) {
        if (value == m_value)
            return;
        m_value = value;
        this->Relink();
    }
    T Get () const {
        return m_value;
    }
    bool IsPriorityHigher (const TPriorityNumeric<C,T> & source) {
        return m_value > source.m_value;
    }
    bool IsPriorityHigher (T value) const {
        return m_value > value;
    }

private:
    T m_value;
};

/****************************************************************************
*
*   class TPriorityTime
*
***/

template<class C>
class TPriorityTime : public TBasePriority< C, TPriorityTime<C> > {

public:
    TPriorityTime () : m_time(0) { }
    TPriorityTime (unsigned time) : m_time(time) { }

    void Set (unsigned time) {
        if (m_time == time)
            return;
        m_time = time;
        this->Relink();
    }
    unsigned Get () const {
        return m_time;
    }
    bool IsPriorityHigher (const TPriorityTime<C> & source) const {
        return (int)(m_time - source.m_time) < 0;
    }
    bool IsPriorityHigher (unsigned time) const {
        return (int)(m_time - time) < 0;
    }

private:
    unsigned m_time;
};
#endif
