/*!
  @file           Data_Chain.hpp
  @author         UweH
  @brief          class Data_Chain is defined here

  There is only a update iterator, but a readonly iterator may be usefull.

\if EMIT_LICENCE
    ========== licence begin  GPL
    Copyright (c) 2000-2005 SAP AG

    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 2
    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, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end

\endif
*/
#ifndef Data_Chain_HPP
#define Data_Chain_HPP

#include "SAPDBCommon/ErrorsAndMessages/SAPDBErr_Assertions.hpp"
#include "SAPDBCommon/Tracing/SAPDBTrace_Usage.hpp"
#include "KernelCommon/Kernel_VTrace.hpp"
#include "DataAccess/Data_Types.hpp"
#include "DataAccess/Data_Exceptions.hpp"
#include "DataAccess/Data_Messages.hpp"
#include "DataAccess/Data_ChainPage.hpp"
#include "DataAccess/Data_PageAccessManager.hpp"

/*!
   @interface      Data_IBreakable
   @brief          This is a breakable resource.
 */
class Data_IBreakable // PTS 1114994 UH 2002-03-22 new
{
public:
    /// true is returned if successfull
    virtual bool Break() = 0;
    /// true is returned if successfull. All resources are allocated like before Break().
    virtual bool Continue() = 0;
    /// Writes relevant contents into vtrace.
    virtual void WriteToTrace (const char* title = NULL) const = 0;
};

/*!
   @class          Data_ChainIterator
   @brief          This cannot be defined within Data_Chain.
 */
template <class T> class Data_ChainIterator
{
public:
    /*!
       @brief The iterater is initialized.
       
       It points to the root of the given chain.
       If the chain is not created, the iterator is invalid.
     */
    Data_ChainIterator<T> (Data_PageAccessManager& PAM)
    : m_PAM(PAM)
    {}
    /*!
       @returns        true, if the iterator was successfully set to the given PageNo.
       @brief          The iterator is set to a new position.
       
       If the iterator pointed to a page this was released before.
       An exception DATA_PAM_ERROR might be thrown.
     */
    bool Set (const Data_PageNo&           PageNo,
              const Data_PageRecoveryMode &RecoveryMode,
              Data_AccessMode              AccessMode)
    {
        if ( m_Page.IsAssigned() )
        {
            if ( m_Page.PageNo()       != PageNo
                 ||
                 m_Page.RecoveryMode() != RecoveryMode )
                m_PAM.ReleasePage (m_Page);
            else
                switch (AccessMode)
                {
                case Data_ForRead:
                    break;
                case Data_ForUpdate:
                    if ( m_Page.AccessMode() == Data_ForRead )
                        m_PAM.ReleasePage (m_Page);
                    break;
                case Data_ForStructureChange:
                    if ( m_Page.AccessMode() != Data_ForStructureChange )
                        m_PAM.ReleasePage (m_Page);
                    break;
                }
        }
        
        if ( PageNo.IsValid() && ! m_Page.IsAssigned() )
            return m_PAM.GetPage (m_Page,
                                  PageNo,
                                  RecoveryMode,
                                  AccessMode);

        return true;
    }
    /*!
       @brief The iterator is set to the given Page.
       
       If the iterator pointed to a page this was released before.
       An exception DATA_PAM_ERROR might be thrown.
     */
    void Set (T& Page)
    {
        if ( m_Page.IsAssigned() )
            m_PAM.ReleasePage (m_Page);

        m_Page.Assign (Page);
    }
    /*!
       @brief The iterator is set to the clone of the given Page.
       
       If the iterator pointed to a page this was released before.
     */
    void Clone (T& Page)
    {
        if ( m_Page.IsAssigned() )
            m_PAM.ReleasePage (m_Page);

        m_Page.Clone(Page);
    }
    /// This returns the Page, which is currently referenced.
    T& operator * ()
    {
        return m_Page;
    }
    /*!
       @brief The iterator is set to the next page in the chain.
       
       An exception DATA_PAM_ERROR might be thrown.
       After the operation, IsValid() may return false.
       This indicates the end of the chain.
     */
    Data_ChainIterator<T>& operator ++ ()
    {
        const Data_PageNo NextPageNo = m_Page.NextPageNo();
        
        m_PAM.ReleasePage (m_Page);

        if ( NextPageNo.IsValid() )
            (void) m_PAM.GetPage (m_Page,
                                  NextPageNo,
                                  m_Page.RecoveryMode(),
                                  m_Page.AccessMode());
        return *this;
    }
    /*!
       @brief The iterator is set to the prev page in the chain.
       
       An exception DATA_PAM_ERROR might be thrown.
       After the operation, IsValid() may return false.
       This indicates the end of the chain.
     */
    Data_ChainIterator<T>& operator -- ()
    {
        const Data_PageNo PrevPageNo = m_Page.PrevPageNo();

        m_PAM.ReleasePage (m_Page);

        if ( PrevPageNo.IsValid() )
            (void) m_PAM.GetPage (m_Page,
                                  PrevPageNo,
                                  m_Page.RecoveryMode(),
                                  m_Page.AccessMode());
        return *this;
    }
    /// returns true, if it can be dereferenced.
    bool IsValid() const
    {
        return m_Page.IsAssigned();
    }
    /*!
        @brief This releases all allocated internal members.
        
        An exception DATA_PAM_ERROR might be thrown.
     */

    void Invalidate()
    {
        if ( m_Page.IsAssigned() )
            m_PAM.ReleasePage (m_Page);
    }

private:

    /// This identifier is the main entry point to a persistent page chain.
    T m_Page;
    /// This is used to access pages.
    Data_PageAccessManager& m_PAM;
};

/// A persistent page chain handler, based on data pages. It is a double linked chain.
template <class T> class Data_Chain
{
public:
    /*!
       @param PAM    [in/out] This is the used page access manager
       @param RootId [in] this id defines the beginning of the page chain
       @brief If the RootId is invalid, than a new chain can be created.
       
       The RootId is saved internally for accessing the chain.
     */
    Data_Chain (Data_PageAccessManager& PAM,
                Data_PageId&             RootId)
    : m_PAM    (PAM),
      m_RootId (RootId)
    {}
    /*!
       @param PAM        [in/out] This is the used page access manager
       @param RootId     [in] this id defines the beginning of the page chain
       @param LastPageNo [in] This is used if the last page no is known.
       @brief If the RootId is invalid, than a new chain can be created.
       
       The RootId is saved internally for accessing the chain.
     */
    Data_Chain (Data_PageAccessManager &PAM,
                Data_PageId             &RootId,
                Data_PageNo              LastPageNo)
    : m_PAM        (PAM),
      m_RootId     (RootId),
      m_LastPageNo (LastPageNo)
    {}
    /// Resets the internal members.
    void Invalidate()
    {
        m_RootId.Invalidate();
        m_LastPageNo.Invalidate();
    }
    /*!
       @param         Iter [out] This iterator points after to the root page.
       @brief         A new page chain is created now. The Iterator points to root page.

       Precondition:  The RootId must not be valid
       Postcondition: RootId is valid and LastPageNo is equal to RootId.
       A new root page is created and the Id is stored internally.
       An exception DATA_PAM_ERROR might be thrown, if no Root page could be created.
     */
    bool Create (Data_ChainIterator<T>& Iter)
    {
        SAPDBERR_ASSERT_STATE( ! m_RootId.IsValid() );

        if ( ! m_PAM.CheckSpace (1)) // PTS 1115170 UH 2002-04-09
            return false; // PTS 1121659 UH 2003-04-30
        
        m_PAM.NewPage (*Iter);

        (*Iter).PageId (m_RootId);
        
        m_LastPageNo = m_RootId.PageNo();

        return true;
    }
    /*!
       @brief All pages in the chain are dropped.
       
       There is no synchronization with the savepoint.
       If the RootId is invalid the drop does not fail.
       An exception DATA_PAM_ERROR might be thrown, if the chain is inconsistent.
     */
    void Drop ()
    {
        if ( m_RootPage.IsAssigned() )
            UnLock();

        Data_ChainIterator<T> Iter (m_PAM);
        Data_PageNo NextPageNo;

        (void) Iter.Set (m_RootId.PageNo(),
                         m_RootId.PageRecoveryMode(),
                         Data_ForUpdate);

        while ( Iter.IsValid() )
        {
            NextPageNo = (*Iter).NextPageNo ();
            m_PAM.FreePage (*Iter);
            Iter.Set (NextPageNo, m_RootId.PageRecoveryMode(), Data_ForUpdate);
        }

        m_RootId.Invalidate();
        m_LastPageNo.Invalidate();
    }
    /*!
       @brief The iterator is set to the root page of the chain.
       
       If the Iterator is invalid after the operation, the chain was not created.
     */
    void Begin (Data_ChainIterator<T>& Iter,
                Data_AccessMode        AccessMode)
    {
        if ( Iter.IsValid() )
        {
            if ( m_RootId.PageNo() != (*Iter).PageNo() )
                (void) Iter.Set ( m_RootId.PageNo(),
                                  m_RootId.PageRecoveryMode(),
                                  AccessMode);
        }
        else
            (void) Iter.Set ( m_RootId.PageNo(),
                              m_RootId.PageRecoveryMode(),
                              AccessMode );
    }
    /*!
       @brief The iterator is set to the last page of the chain.
       
       If the Iterator is invalid after the operation, the chain was not created.
     */
    void End (Data_ChainIterator<T>& Iter,
              Data_AccessMode        AccessMode)
    {
        if ( Iter.IsValid() )
        {
            if ( LastPageNo() != (*Iter).PageNo() )
                (void) Iter.Set ( LastPageNo(),
                                  m_RootId.PageRecoveryMode(),
                                  AccessMode );
        }
        else
            (void) Iter.Set ( LastPageNo(),
                              m_RootId.PageRecoveryMode(),
                              AccessMode );
    }
    /*!
       @brief A new page is inserted after the current page the iterator points to.
       
       After the operation, the iterator points to the new page.
       An exception DATA_PAM_ERROR might be thrown, if no new page could be created.
     */
    bool Insert (Data_ChainIterator<T>& Iter)
    {
        SAPDBERR_ASSERT_STATE( (*Iter).AccessMode() == Data_ForStructureChange );

        if ( ! m_PAM.CheckSpace (1) ) // PTS 1115170 UH 2002-04-09
            return false; // PTS 1121659 UH 2003-04-30

        T NewPage;

        (*Iter).Append (NewPage, m_LastPageNo);
        (*Iter).Deassign();
        (*Iter).Assign (NewPage);
        return true; // PTS 1121659 UH 2003-04-30
    }
    /// The page after the page referenced by the iterator is removed.
    void Delete (Data_ChainIterator<T>& Iter)
    {
        SAPDBERR_ASSERT_STATE( (*Iter).AccessMode() != Data_ForRead );
                               
        if ( ! (*Iter).NextPageNo().IsValid() ) return;

        // THIS->DEL->NEXT

        T PageToDelete;

        if ( ! m_PAM.GetPage ( PageToDelete,
                               (*Iter).NextPageNo(),
                               (*Iter).RecoveryMode(),
                               Data_ForUpdate) )
        {
            RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                       SAPDBERR_ASSERT_STATE_FAILED,
                       "Data_Chain::Delete(): GetPage() failed") );
        }

        // THIS->NEXT

        (*Iter).SetNextPageNo (PageToDelete.NextPageNo());

        if ( ! PageToDelete.NextPageNo().IsValid() )
            m_LastPageNo = (*Iter).PageNo();

        m_PAM.FreePage (PageToDelete);
    }
    /// The rootid of the current chain is returned.
    const Data_PageId& RootId() const
    {
        return m_RootId;
    }
    /*!
        @brief This determines the last pageno of the chain.
        
        If m_LastPageNo is set it is taken.
        If LastPageNo in the root page is set this is taken.
        Else it is searched via the NextPageNo().
        In all cases the internal lastpageno is set with the determined value.
        IF forceReadAllPages IS TRUE IT IS ALWAYS READ VIA NEXTPTR.
     */
    Data_PageNo& LastPageNo(bool forceReadAllPages = false)
    {
        if ( forceReadAllPages )
            m_LastPageNo.Invalidate();
            
        if ( ! m_LastPageNo.IsValid() )
        {
            // LastId could be found in the root page.

            Data_ChainIterator<T> iter (m_PAM);

            if ( ! GetPage (m_RootId.PageNo(), Data_ForRead, iter) )
            {
                Kernel_VTrace() << "Data_Chain::LastPageNo(): m_LastPageNo: " << m_LastPageNo
                                << ", m_RootPageNo: " << m_RootId.PageNo()
                                << ", forceReadAllPages: " << forceReadAllPages
                                << NewLine;
                RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                           SAPDBERR_ASSERT_STATE_FAILED,
                           "Data_Chain::LastPageNo(): Get root") );
            }

            if ( ! forceReadAllPages )
                m_LastPageNo = (*iter).LastPageNo();
            
            if ( m_LastPageNo.IsInvalid()
                 &&
                 (*iter).NextPageNo().IsValid() )
            {
                do
                {
                    m_LastPageNo = (*iter).NextPageNo();
                    ++iter;
                    if ( ! iter.IsValid() )
                    {
                        Kernel_VTrace() << "Data_Chain::LastPageNo(): m_LastPageNo: " << m_LastPageNo
                                        << ", m_RootPageNo: " << m_RootId.PageNo()
                                        << ", forceReadAllPages: " << forceReadAllPages
                                        << NewLine;
                        RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                                   SAPDBERR_ASSERT_STATE_FAILED,
                                   "Data_Chain::LastPageNo(): chain is corrupt") );
                    }
                }
                while ( (*iter).NextPageNo().IsValid() );
            }
            if ( m_LastPageNo.IsInvalid() )
                m_LastPageNo = m_RootId.PageNo();
            
            ReleasePage (iter);
        }
        return m_LastPageNo;
    }
    /*!
        @brief The internally stored LastPageNo is saved within the root page.
        
        Be carefull, if the internal last page no is not known it is determined !
     */
    void StoreLastPageNo()
    {
        Data_ChainIterator<T> rootIter (m_PAM);

        if ( ! GetPage (m_RootId.PageNo(),Data_ForUpdate,rootIter) )
        {
            RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                       SAPDBERR_ASSERT_STATE_FAILED,
                       "Data_Chain::StoreLastPageNo(): GetPage root") );
        }

        m_LastPageNo = LastPageNo(true);

        (*rootIter).SetLastPageNo (m_LastPageNo);

        ReleasePage (rootIter);
    }
    /// This returns  the internally used page access manager handler.
    Data_PageAccessManager& GetPageAccessManager()
    {
        return m_PAM;
    }
    /*!
        @brief The page chain is truncated from the given page no on until the end.
        
        The given page no identifies the new last page of the chain.
        All pages after that page are freed.
        The last page no in the root is updated too.
        PRE: The root page and all pages from given pno until the chains end
             must no be in access by the caller.
     */
    void Truncate (Data_PageNo     newLastPageNo,
                   Data_PageOffset newFirstFree)
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_Chain::Truncate", DataChain_Trace, 5);

        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "newLastPageNo: " << newLastPageNo << \
                                           ", newFirstFree: " << newFirstFree);

        SAPDBERR_ASSERT_STATE( newLastPageNo.IsValid() );
        
        T rootPage;
        
        if ( ! m_PAM.GetPage ( rootPage,
                               m_RootId.PageNo(),
                               m_RootId.PageRecoveryMode(),
                               Data_ForStructureChange) )
        {
            RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                       SAPDBERR_ASSERT_STATE_FAILED,
                       "Data_Chain::Truncate(): GetPage(root) failed") );
        }

        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "rootPageNo: " << m_RootId.PageNo());
        
        // PTS 1111584 TS 2001-08-30 begin

        Data_ChainIterator<T>   iter (m_PAM);
        Data_PageNo             deletePageNo;

        if ( (rootPage.NextPageNo().IsInvalid() )
             ||
             (rootPage.PageNo() == newLastPageNo) )
        {
            if ( newLastPageNo != rootPage.PageNo() )
            {
                RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                    SAPDBERR_ASSERT_STATE_FAILED,
                    "Data_Chain::Truncate(): invalid newLastPageNo") );
            }
            if ( newFirstFree > 0 )
                rootPage.SetFirstFreeOffset (newFirstFree);

            if ( rootPage.NextPageNo().IsInvalid()  )
                return;
            
            // prepare truncate of the remaining pages
            deletePageNo = rootPage.NextPageNo();
            rootPage.SetNextPageNo( Data_PageNo() );
        }
        else
        {   // the root page is not affected
            if ( ! iter.Set (newLastPageNo, m_RootId.PageRecoveryMode(), Data_ForUpdate) )
            {
                RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                    SAPDBERR_ASSERT_STATE_FAILED,
                    "Data_Chain::Truncate(): GetPage(newLastPageNo) failed") );
            }
            // PTS 1111344 UH 2001-08-10 begin
            
            deletePageNo = (*iter).NextPageNo();
            
            (*iter).SetNextPageNo          (Data_PageNo()); // invalidate next pointer
            if ( newFirstFree > 0 )
                (*iter).SetFirstFreeOffset (newFirstFree ); // truncate entries in page
            
            iter.Invalidate();
        }
        m_LastPageNo = newLastPageNo; // update pointer to last page
        // PTS 1111584 TS 2001-08-30 end

        if ( ! iter.Set (deletePageNo, m_RootId.PageRecoveryMode(), Data_ForUpdate) )
        {
            RTE_Crash( SAPDBErr_Exception(__FILE__, __LINE__,
                       SAPDBERR_ASSERT_STATE_FAILED,
                       "Data_Chain::Truncate(): GetPage(firstpage to delete) failed") );
        }

        while ( iter.IsValid() )
        {
            deletePageNo = (*iter).PageNo();
            const SAPDB_Int4 pageVersion = (*iter).PageConverterVersion();

            ++iter;
            
            m_PAM.FreePageNo (deletePageNo, RootId().PageRecoveryMode(), pageVersion);
        }

        iter.Invalidate();

        // PTS 1111344 UH 2001-08-10 end
    }
    /*!
        @brief All pages of the page are read and check individually.

        If isCold is true then the converter is updated.
     */
    virtual bool Verify (bool isCold)
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_Chain::Verify", DataChain_Trace, 5);
        Data_ChainIterator<T>  iter (m_PAM);
        Data_PageId            currentPageId = RootId();
        Begin (iter, Data_ForRead);

        bool        result = true;
        Data_PageNo prevPageNo;
        Data_PageNo firstErrorPageNo;
        
        while ( iter.IsValid() )
        {
            result = (*iter).Verify();
            
            if ( ! result )
            {
                RTE_Message( Data_Exception(__CONTEXT__, DATA_ERROR_VERIFY_PAGE,
                                            SAPDB_ToString((*iter).PageNo()),
                                            SAPDB_ToString((*iter).NextPageNo()),
                                            SAPDB_ToString((*iter).RootPageNo()),
                                            SAPDB_ToString(RootId().PageNo()),
                                            SAPDB_ToString(prevPageNo) ) );
                if ( firstErrorPageNo.IsInvalid() )
                    firstErrorPageNo = (*iter).PageNo();
            }
            if ( isCold )
            {            
                currentPageId.SetPageNo((*iter).PageNo());
                m_PAM.MarkPageIdAsUsed(currentPageId);
            }
            prevPageNo = (*iter).PageNo();
            ++iter;
        }
        
        if ( m_PAM.GetLastError() != 0 )
            RTE_Message( Data_Exception(__CONTEXT__, DATA_ERROR_VERIFY_CHAIN,
                                        SAPDB_ToString(RootId().PageNo()),
                                        SAPDB_ToString(m_PAM.GetLastError()),
                                        SAPDB_ToString(firstErrorPageNo) ) );
        return result;
    }
    /// Write all known information of the checin to the knltrace.
    void WriteToTrace(const char* title = 0) const
    {
        if ( title != 0 )
            Kernel_VTrace() << title;
        Kernel_VTrace() << "Data_Chain: mode: " << m_RootId.PageRecoveryMode()
                        << ", root: "           << m_RootId.PageNo()
                        << ", last: "           << m_LastPageNo;
    }
    /// This writes a message to the knldiag.
    void InfoMessage (const SAPDB_Char *title = NULL) const
    {
        RTE_Message(
            Data_Exception(__CONTEXT__, DATA_CHAIN_INFO,
                           (title==NULL?"Chain":title),
                           SAPDB_ToString(m_RootId.PageNo()),
                           SAPDB_ToString(m_LastPageNo),
                           (m_RootId.PageRecoveryMode().UseStaticPageAddressing()?"static":"dyn"),
                           (m_RootId.PageRecoveryMode().PageIsRecoverable()?"perm":"temp")));
    }

    /// This locks the chain. returns false, on any error.
    bool Lock(Data_AccessMode lockmode)
    {
        if ( IsLocked(lockmode) ) // PTS 1127083 UH 2004-01-14 added
            return true;
            
        const bool result = m_PAM.GetPage ( m_RootPage,
                                            m_RootId.PageNo(),
                                            m_RootId.PageRecoveryMode(),
                                            lockmode );
        m_RootPage.SetUnChanged();
        
        SAPDBERR_ASSERT_STATE( ! result || IsLocked(lockmode) );
        
        return result;
    }
    /// This unlocks the chain.
    void UnLock()
    {
        m_PAM.ReleasePage (m_RootPage);
    }
    /*!
        @brief returns true, if the chain is locked in the correct way.
        If forRead is requested, forUpdate will be ok too.
     */
    bool IsLocked(Data_AccessMode lockmode)
    {
        // PTS 1127083 UH 2004-01-14
        return m_RootPage.IsAssigned()
               &&
               m_RootPage.AccessMode() == lockmode;
    }
    /*!
        @brief Get a given page. If iter is invalid, the call has failed.
        The recoverymode is taken from the root.
     */
    bool GetPage (Data_PageNo            pageno,
                  Data_AccessMode        accessmode,
                  Data_ChainIterator<T> &iter)
    {
        if ( m_RootPage.IsAssigned()
             &&
             m_RootPage.PageNo() == pageno )
        {
            iter.Clone(m_RootPage);
            return true;
        }
        return iter.Set (pageno, m_RootId.PageRecoveryMode(), accessmode);
    }
    /// release the given page by iter.
    void ReleasePage (Data_ChainIterator<T> &iter)
    {
        if ( m_RootPage.IsAssigned()
             &&
             m_RootPage.PageNo() == (*iter).PageNo()
             &&
             (*iter).AccessMode() != Data_ForRead )
            m_RootPage.SetChanged();
            
        iter.Invalidate();
    }
protected:
    /// The first page of the chain is identified.
    Data_PageId& m_RootId;
    /// The last page of the chain is identified.
    /// This Id is stored within the first page of the chain.
    Data_PageNo m_LastPageNo;
    /// This is used to access pages.
    Data_PageAccessManager& m_PAM;
    /// This is used to realize a lock protocol.
    T m_RootPage;
};
#endif // Data_Chain_HPP
