////////////////////////////////////////////////////////////////////////////////
//                                                                             /
// 2012-2019 (c) Baical                                                        /
//                                                                             /
// This library is free software; you can redistribute it and/or               /
// modify it under the terms of the GNU Lesser General Public                  /
// License as published by the Free Software Foundation; either                /
// version 3.0 of the License, or (at your option) any later version.          /
//                                                                             /
// This library 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           /
// Lesser General Public License for more details.                             /
//                                                                             /
// You should have received a copy of the GNU Lesser General Public            /
// License along with this library.                                            /
//                                                                             /
////////////////////////////////////////////////////////////////////////////////
#pragma once

#include <new>
#include "pugixml.hpp"
using namespace pugi;

#include <cassert>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////CBNodeMem/////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CBNodeMem
{
    struct stNode
    {
        stNode  *pNext; 
    };                   

    struct stPack
    {
        tUINT8 *pData;
        stPack *pNext;
    };

protected:
    stNode  *m_pNodes;
    size_t   m_lInUse;
    stPack  *m_pPacks;
    size_t   m_szNode;
    size_t   m_szPack;
    size_t   m_szInPack;

public:
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    CBNodeMem(size_t i_szNode, size_t i_szInPack = 32)
        : m_pNodes(NULL)
        , m_lInUse(0)
        , m_pPacks(NULL)
        , m_szNode(i_szNode)
        , m_szPack(i_szInPack * i_szNode)
        , m_szInPack(i_szInPack)
    {
        if (i_szNode < sizeof(stNode))
        {
            m_szNode = sizeof(stNode);
            m_szPack = i_szInPack * m_szNode;
        }
    }

    ~CBNodeMem()
    {
        if (m_lInUse)
        {
        #if defined(_WIN32) || defined(_WIN64)
            wchar_t l_pError[64];
            swprintf_s(l_pError, LENGTH(l_pError), L"*** ERROR: %zu Nodes leaked", m_lInUse);
            OutputDebugStringW(l_pError);
        #elif defined(__linux__)
            printf("*** ERROR: %d Nodes leaked\n", (int)m_lInUse);
        #endif
        }

        while (m_pPacks)
        {
            stPack *l_pRelease = m_pPacks;
            m_pPacks = m_pPacks->pNext;
            free(l_pRelease);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void *Pull()
    {
        stNode *l_pReturn = NULL;

        if (!m_pNodes)
        {
            stPack *l_pNew = (stPack*)malloc(sizeof(stPack) + m_szPack);
            if (!l_pNew)
            {
                return NULL;
            }

            l_pNew->pNext = m_pPacks;
            l_pNew->pData = ((tUINT8*)l_pNew) + sizeof(stPack);
            m_pPacks = l_pNew;

            tUINT8 *l_pIter = l_pNew->pData;
            for (size_t l_szI = 0; l_szI < m_szInPack; l_szI++)
            {
                l_pReturn = (stNode *)l_pIter;
                l_pReturn->pNext = m_pNodes;
                m_pNodes = l_pReturn;

                l_pIter += m_szNode;
            }

        }

        l_pReturn = m_pNodes;
        m_pNodes = m_pNodes->pNext;
        m_lInUse ++;
        return l_pReturn;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void Push(void * i_pData)
    {
        if (NULL == i_pData)
        {
            return;
        }

        assert(0 != m_lInUse);
        m_lInUse--;

        stNode *l_pItem = (stNode*)i_pData;
        l_pItem->pNext  = m_pNodes;
        m_pNodes        = l_pItem;
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////CBRoot///////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CBRoot
{
protected:
    tBOOL       m_bInitialized;
    CBNodeMem  *m_pMem;
    CLock      *m_pCS;
public:
    CBRoot(CBNodeMem *i_pMem, CLock *i_pCS);

    Bk::IBNode  *GetChildFirst(pugi::xml_node *i_pXmlNode);
    Bk::IBNode  *GetChildFirst(const tXCHAR *i_pName, pugi::xml_node *i_pXmlNode);
    Bk::IBNode  *AddChildEmpty(const tXCHAR * i_pName, pugi::xml_node *i_pXmlNode);
    Bk::eResult  DelChild(pugi::xml_node *i_pXmlNode, Bk::IBNode *i_pNode);

protected:
    virtual ~CBRoot() {}
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////CBNode///////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CBNode
    : public Bk::IBNode
    , public CBRoot
{
    tINT32 volatile m_lRefCounter;
    pugi::xml_node  m_cXmlNode;
public:
    CBNode(CBNodeMem     *i_pFMC_Pool, 
           pugi::xml_node i_cXmlNode,
           CLock         *i_pCS
          );
    
    tINT32 __stdcall Add_Ref()
    {
        return ATOMIC_INC(&m_lRefCounter); 
    }

    tINT32 __stdcall Release()
    {
        tINT32 l_lResult = ATOMIC_DEC(&m_lRefCounter);
        // clean up internal object memory ...
        if (0 >= l_lResult)
        {
            //release object memory
            if (m_pMem)
            {
                m_pMem->Push(this);
            }
        }

        return l_lResult;
    }

    Bk::eResult __stdcall GetName(tXCHAR **o_pName);
    Bk::eResult __stdcall SetName(const tXCHAR *i_pName);

    Bk::eResult __stdcall Copy(Bk::IBNode *i_pSource);

    Bk::eResult __stdcall GetAttr(tUINT32 i_dwIndex, const tXCHAR *&o_rName);

    Bk::eResult __stdcall DelAttr(const tXCHAR *i_pName);

    Bk::eResult __stdcall GetAttrInt32(const tXCHAR *i_pName, tINT32 *o_pValue);

    Bk::eResult __stdcall GetAttrText(const tXCHAR  *i_pName, tXCHAR **o_pValue);

    Bk::eResult __stdcall SetAttrInt32(const tXCHAR *i_pName, tINT32 i_lValue);
    Bk::eResult __stdcall SetAttrText(const tXCHAR  *i_pName, const tXCHAR *i_pValue);

    Bk::eResult __stdcall GetNext(Bk::IBNode **o_pNode);
    Bk::eResult __stdcall GetNext(const tXCHAR *i_pName, Bk::IBNode **o_pNode);

    Bk::eResult __stdcall GetChildFirst(Bk::IBNode **o_pNode);
    Bk::eResult __stdcall GetChildFirst(const tXCHAR *i_pName, Bk::IBNode **o_pNode);
    Bk::eResult __stdcall AddChildEmpty(const tXCHAR  *i_pName, Bk::IBNode  **o_pNode);
    Bk::eResult __stdcall DelChild(Bk::IBNode *i_pNode);
    Bk::eResult __stdcall Del();

    pugi::xml_node Get_XmlNode() { return m_cXmlNode; }

protected:
    virtual ~CBNode() {}
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////CBDoc///////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CBDoc
    : public CBRoot
    , public IBDoc
{
private:
    pugi::xml_document m_cXmlDoc;
    CWString           m_cFileName;
public:
    CBDoc(const tXCHAR *i_pFileName);
    CBDoc(const void *i_pBuffer, size_t i_szBuffer);
    ~CBDoc();

    tBOOL Get_Initialized() 
    { 
        return m_bInitialized; 
    }

    Bk::eResult Save();
    Bk::eResult Save_As(const tXCHAR *i_pFile_Name);

    Bk::eResult GetChildFirst(Bk::IBNode ** o_pNode);
    Bk::eResult GetChildFirst(const tXCHAR * i_pName, Bk::IBNode ** o_pNode);
    Bk::eResult AddChildEmpty(const tXCHAR *i_pName, Bk::IBNode ** o_pNode);
    Bk::eResult DelChild(Bk::IBNode *i_pNode);

    void Release()
    {
        delete this;
    }
};

