/*===================== begin_copyright_notice ==================================

Copyright (c) 2017 Intel Corporation

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


======================= end_copyright_notice ==================================*/
#pragma once

#include "Macros.h"
#include "Object.h"
#include "utility.h"
#include "Threading.h"

namespace iSTD
{    

// Macro used for calculation of next stack node (undefined at the bottom of file). 
// Rounding to the higher number is used.
#define CALCULATE_NEXT_NODE_SIZE( prevSize, growthFactor ) \
    ((prevSize * growthFactor + 99) / 100)

/*****************************************************************************\
Template Parameters:
    ElemType        - type of stored element.
    CAllocatorType  - allocator used for stack node allocations.
    BaseSize        - Size of statically allocated buffer. Base for further 
                      growth.
    GrowthFactor    - Defines the size of the next allocated stack node. 
                      E.g. if 100, size will not change, if 200 next node will 
                      be twice as large as the previous one.
\*****************************************************************************/
#define StackTemplateList \
    class ElemType, class CAllocatorType, DWORD BaseSize, DWORD GrowthFactor
#define StackDeclTemplateList \
    class ElemType, class CAllocatorType, DWORD BaseSize = 4, DWORD GrowthFactor = 135
#define CStackType \
    CStack< ElemType, CAllocatorType, BaseSize, GrowthFactor >

/*****************************************************************************\

Class:
    CStack

Description:
    Stack class that can be optimized for number of dynamic allocations. 
    It also supports storing limited number of elements (define with BaseSize 
    template parameter) without any heap allocations.

\*****************************************************************************/
template <StackDeclTemplateList> 
class CStack : public CObject<CAllocatorType>
{
    ISTD_DISALLOW_COPY_AND_ASSIGN( CStack );

    // Make sure we won't get size 0:
    C_ASSERT( CALCULATE_NEXT_NODE_SIZE(BaseSize, GrowthFactor) > 0 );

public:
    CStack();
    ~CStack();

    DWORD   GetCount( void ) const;
    bool    IsEmpty( void ) const;

    bool        Push(ElemType element);
    ElemType    Pop( void );
    ElemType    Top( void ) const;

    bool        Contains( const ElemType& elem ) const;

protected:
    /*************************************************************************\

    Class:
        CStackNode

    Description:
        Nodes are used to store elements on the heap. Nodes are linked with 
        LIFO queue with pointer the the parent (node under current).

    \*************************************************************************/
    class CStackNode : CObject<CAllocatorType>
    {
        ISTD_DISALLOW_COPY_AND_ASSIGN( CStackNode );

    public:
        static bool     Create( 
            DWORD size, 
            CStackNode*& pNewNode );

        static bool     Create( 
            DWORD size, 
            CStackNode* pParentNode, 
            CStackNode*& pNewNode );

        static void     Delete( 
            CStackNode*& pNode );

        bool    IsEmpty( void ) const;
        bool    IsFull( void ) const;

        DWORD   GetMaxSize( void ) const;

        CStackNode* GetParentNode( void ) const;

        void        Push(ElemType element);
        ElemType    Pop( void );
        ElemType    Top( void ) const;

        bool        Contains( const ElemType& elem ) const;

    private:
        CStackNode( DWORD size );
        CStackNode( DWORD size, CStackNode* pParentNode );
        ~CStackNode();

        bool    Initialize( void );

        const DWORD     m_cMaxSize;
        DWORD           m_Count;
        ElemType*       m_pElements;

        CStackNode*     m_pParentNode;
    };

    // Size of statically allocated buffer:
    static const DWORD m_cStaticSize = BaseSize;

    // Current number of elements:
    DWORD m_Count;

    // Top stack node. Null if all elements fits in m_StaticArray.
    CStackNode* m_pTopNode;

    // Static buffer for limited number of elements:
    ElemType m_StaticArray[m_cStaticSize];

    DECL_DEBUG_MUTEX( m_InstanceNotThreadSafe )
};

/*****************************************************************************\

Function:
   CStack constructor.

\*****************************************************************************/
template<StackTemplateList>
CStackType::CStack() 
    : m_Count( 0 ), 
      m_pTopNode( NULL ) 
{
    INIT_DEBUG_MUTEX( m_InstanceNotThreadSafe );
}

/*****************************************************************************\

Function:
   CStack destructor.

\*****************************************************************************/
template<StackTemplateList>
CStackType::~CStack()
{
    ACQUIRE_DEBUG_MUTEX_WRITE( m_InstanceNotThreadSafe );

    // Free stack nodes if there are any:
    while( m_pTopNode )
    {
        CStackNode* pParent = m_pTopNode->GetParentNode();
        CStackNode::Delete( m_pTopNode );
        m_pTopNode = pParent;
    }

    RELEASE_DEBUG_MUTEX_WRITE( m_InstanceNotThreadSafe );

    DELETE_DEBUG_MUTEX( m_InstanceNotThreadSafe );
}

/*****************************************************************************\

Function:
   CStack::GetCount

Description:
    Get current number of elements on the stack.

Input:

Output:
    DWORD

\*****************************************************************************/
template<StackTemplateList>
DWORD CStackType::GetCount( void ) const
{
    return m_Count;
}

/*****************************************************************************\

Function:
   CStack::Push

Description:
    Push element on the stack.

Input:
    ElemType element

Output:
    bool    - false for allocation failure

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::Push( ElemType element )
{
    ACQUIRE_DEBUG_MUTEX_WRITE( m_InstanceNotThreadSafe );

    bool success = true;

    if( m_Count < m_cStaticSize )
    {
        // Add to static array:
        m_StaticArray[m_Count++] = element;
    }
    else
    {
        // Element doesn't fit in static array.
        if( m_pTopNode == NULL )
        {
            // Allocate first stack node:
            const DWORD newSize = 
                CALCULATE_NEXT_NODE_SIZE( m_cStaticSize, GrowthFactor );
            success = CStackNode::Create( newSize, m_pTopNode );
        }
        else if( m_pTopNode->IsFull() )
        {
            // Top stack node full - allocate another:
            const DWORD newSize = 
                CALCULATE_NEXT_NODE_SIZE( m_pTopNode->GetMaxSize(), GrowthFactor );
            success = CStackNode::Create( newSize, m_pTopNode,  m_pTopNode );
        }

        if( success )
        {
            ++m_Count;
            m_pTopNode->Push( element );
        }
    }

    RELEASE_DEBUG_MUTEX_WRITE( m_InstanceNotThreadSafe );
    return success;
}

/*****************************************************************************\

Function:
   CStack::Pop

Description:
    Pop element from the stack.

Input:

Output:
    ElemType    - Top element.

\*****************************************************************************/
template<StackTemplateList>
ElemType CStackType::Pop( void )
{
    ACQUIRE_DEBUG_MUTEX_READ( m_InstanceNotThreadSafe );

    ASSERT( !IsEmpty() );

    ElemType elem;

    if( m_Count <= m_cStaticSize )
    {
        // Element from static array.
        elem = m_StaticArray[--m_Count];
    }
    else
    {
        // Element from stack node.
        if( m_pTopNode->IsEmpty() )
        {
            // Top node empty - proceed to next one.
            CStackNode* pParentNode = m_pTopNode->GetParentNode();
            CStackNode::Delete( m_pTopNode );
            m_pTopNode = pParentNode;

            ASSERT( m_pTopNode );
        }

        --m_Count;
        elem = m_pTopNode->Pop();
    }

    RELEASE_DEBUG_MUTEX_READ( m_InstanceNotThreadSafe );
    return elem;
}

/*****************************************************************************\

Function:
   CStack::Top

Description:
    Get the top element without popping it from the stack.

Input:

Output:
    ElemType    - Top element.

\*****************************************************************************/
template<StackTemplateList>
ElemType CStackType::Top( void ) const
{
    ACQUIRE_DEBUG_MUTEX_READ( m_InstanceNotThreadSafe );

    ASSERT( !IsEmpty() );

    ElemType elem;

    if( m_Count <= m_cStaticSize )
    {
        // Element from static array.
        elem = m_StaticArray[m_Count - 1];
    }
    else
    {
        // Element from stack node.
        if( m_pTopNode->IsEmpty() )
        {
            // Top node empty - proceed to next one.
            ASSERT( m_pTopNode->GetParentNode() );
            elem = m_pTopNode->GetParentNode()->Top();
        }
        else
        {
            elem = m_pTopNode->Top();
        }
    }

    RELEASE_DEBUG_MUTEX_READ( m_InstanceNotThreadSafe );
    return elem;
}

/*****************************************************************************\

Function:
   CStack::IsEmpty

Description:
    Check if stack is empty.

Input:

Output:
    bool

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::IsEmpty( void ) const
{
    return ( m_Count == 0 );
}

/*****************************************************************************\

Function:
   CStack::Contains

Description:
    Check if given element is already on the stack.

Input:
    elem        - Element to be checked.

Output:
    bool

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::Contains( const ElemType& elem ) const
{
    ACQUIRE_DEBUG_MUTEX_READ( m_InstanceNotThreadSafe );

    bool found = false;

    if( m_Count >= m_cStaticSize )
    {
        // Search stack nodes:
        CStackNode* pNode = m_pTopNode;
        while( pNode )
        {
            if( pNode->Contains( elem ) )
            {
                // Element found!
                found = true;
                break;
            }
            pNode = pNode->GetParentNode();
        }
    }
    
    if( !found )
    {
        // Search static array:
        DWORD elemNumber = iSTD::Min( m_cStaticSize, m_Count );
        while( elemNumber-- )
        {
            if( m_StaticArray[ elemNumber ] == elem )
            {
                // Element found!
                found = true;
                break;
            }
        }
    }

    RELEASE_DEBUG_MUTEX_READ( m_InstanceNotThreadSafe );
    return found;
}

/*****************************************************************************\

Function:
   CStack::CStackNode constructor

\*****************************************************************************/
template<StackTemplateList>
CStackType::CStackNode::CStackNode( DWORD size ) 
    : m_cMaxSize( size ), 
      m_Count( 0 ),
      m_pElements( NULL ),
      m_pParentNode( NULL )
{
}

/*****************************************************************************\

Function:
   CStack::CStackNode constructor

\*****************************************************************************/
template<StackTemplateList>
CStackType::CStackNode::CStackNode( DWORD size, CStackNode* pParentNode ) 
    : m_cMaxSize( size ), 
      m_Count( 0 ), 
      m_pElements( NULL ),
      m_pParentNode( pParentNode )
{
}

/*****************************************************************************\

Function:
   CStack::CStackNode destructor

\*****************************************************************************/
template<StackTemplateList>
CStackType::CStackNode::~CStackNode()
{
    CAllocatorType::Deallocate( m_pElements );
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Initialize

Description:
    Allocate element buffer.

Input:

Output:
    bool success

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::CStackNode::Initialize( void )
{
    m_pElements = 
        (ElemType*)CAllocatorType::Allocate( sizeof(ElemType) * m_cMaxSize );
    return ( m_pElements != NULL );
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Create

Description:
    Create new stack node.

Input:
    size

Output:
    pNewNode

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::CStackNode::Create( 
    DWORD size, 
    CStackNode*& pNewNode )
{
    bool success = true;
    pNewNode = new CStackNode( size );

    if( pNewNode )
    {
        pNewNode->Acquire();

        success = pNewNode->Initialize();

        if( success == false )
        {
            CStackNode::Delete( pNewNode );
        }
    }
    else
    {
        ASSERT(0);
        success = false;
    }
    return success;
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Create

Description:
    Create new stack node and attach it to given parent node.

Input:
    size
    pParentNode

Output:
    pNewNode

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::CStackNode::Create( 
    DWORD size, 
    CStackNode* pParentNode, 
    CStackNode*& pNewNode )
{
    bool success = true;
    pNewNode = new CStackNode( size, pParentNode );

    if( pNewNode )
    {
        pNewNode->Acquire();

        success = pNewNode->Initialize();

        if( success == false )
        {
            CStackNode::Delete( pNewNode );
        }
    }
    else
    {
        ASSERT(0);
        success = false;
    }
    return success;
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Delete

Description:
    Delete stack node.

Input:
    pNode

Output:

\*****************************************************************************/
template<StackTemplateList>
void CStackType::CStackNode::Delete( CStackNode*& pNode )
{
    CObject<CAllocatorType>::SafeRelease( pNode );
    pNode = NULL;
}

/*****************************************************************************\

Function:
   CStack::CStackNode::

Description:
    Check if stack node is empty.

Input:

Output:
    bool

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::CStackNode::IsEmpty( void ) const
{
    return ( m_Count == 0 );
}

/*****************************************************************************\

Function:
   CStack::CStackNode::IsFull

Description:
    Check if stack node is full.

Input:

Output:
    bool

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::CStackNode::IsFull( void ) const
{
    ASSERT( m_Count <= m_cMaxSize );
    return ( m_Count == m_cMaxSize );
}

/*****************************************************************************\

Function:
   CStack::CStackNode::GetMaxSize

Description:
    Get capacity of this stack node.

Input:

Output:
    DWORD m_cMaxSize

\*****************************************************************************/
template<StackTemplateList>
DWORD CStackType::CStackNode::GetMaxSize( void ) const
{
    return m_cMaxSize;
}

/*****************************************************************************\

Function:
   CStack::CStackNode::GetParentNode

Description:
    Get parent stack node.

Input:

Output:
    CStackNode* m_pParentNode

\*****************************************************************************/
template<StackTemplateList>
typename CStackType::CStackNode* CStackType::CStackNode::GetParentNode( void ) const
{
    return m_pParentNode;
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Push

Description:
    Push element on the stack node.

Input:
    ElemType element

Output:

\*****************************************************************************/
template<StackTemplateList>
void CStackType::CStackNode::Push(ElemType element)
{
    ASSERT( !IsFull() );
    m_pElements[m_Count++] = element;
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Pop

Description:
    Pop element from the stack node.

Input:

Output:
    ElemType    - Top element.

\*****************************************************************************/
template<StackTemplateList>
ElemType CStackType::CStackNode::Pop( void )
{
    ASSERT( !IsEmpty() );
    return m_pElements[--m_Count];
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Top

Description:
    Get the top element without popping it from the stack node.

Input:

Output:
    ElemType    - Top element.

\*****************************************************************************/
template<StackTemplateList>
ElemType CStackType::CStackNode::Top( void ) const
{
    ASSERT( !IsEmpty() );
    return m_pElements[m_Count - 1];
}

/*****************************************************************************\

Function:
   CStack::CStackNode::Contains

Description:
    Check if given element is already on the stack.

Input:
    elem        - Element to be checked.

Output:
    bool

\*****************************************************************************/
template<StackTemplateList>
bool CStackType::CStackNode::Contains( const ElemType& elem ) const
{
    DWORD elemNumber = m_Count;
    while( elemNumber-- )
    {
        if( m_pElements[elemNumber] == elem )
        {
            return true;
        }
    }

    return false;
}

#undef CALCULATE_NEXT_NODE_SIZE

} // namespace iSTD
