/*!
  @file           Data_ChainSplitSpaceForwardRead.hpp
  @author         UweH
  @brief          Defines classes to organize Split records over page chains.

\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_ChainSplitSpaceForwardRead_HPP
#define Data_ChainSplitSpaceForwardRead_HPP

#include "SAPDBCommon/ErrorsAndMessages/SAPDBErr_Assertions.hpp"
#include "SAPDBCommon/MemoryManagement/SAPDBMem_IRawAllocator.hpp"
#include "SAPDBCommon/Tracing/SAPDBTrace_Usage.hpp"
#include "DataAccess/Data_Exceptions.hpp"
#include "DataAccess/Data_Chain.hpp"
#include "DataAccess/Data_PageSplitSpace.hpp"
#include "DataAccess/Data_SplitSpace.hpp"

/// define simple boolean expression
#define FIRST_FREE   true

/*!
   @class Data_ChainSplitSpaceForwardRead
   @brief This chain of Split record supports forward reading.
 */
template <class PAGE>
class Data_ChainSplitSpaceForwardRead : public Data_Chain<PAGE>
{
public:
    /// define the record space
    typedef Data_SplitSpace<PAGE> RecordSpace;
public:
    /*!
        @class Iterator
        @brief Iterator for records in the chain.
     */
    class Iterator : public Data_IBreakable // PTS 1114994 2002-03-22
    {
    public:
        /// define the used container
        typedef Data_ChainSplitSpaceForwardRead<PAGE> Container;
    /// The container is a friend of its iterator so the iterator may access members of the container.
    friend class Data_ChainSplitSpaceForwardRead<PAGE>;
    public:
        /*!
            @brief          The iterater is not initialized and at first invalid.
            @param          PAM [in] The AccessManager to use by the Iterator.
            @param          Allocator [in] Is used for the Split space.
         */
        Iterator (Data_PageAccessManager& PAM,
                  SAPDBMem_IRawAllocator&  Allocator)
        : m_PAM                   (PAM),
          m_RecordIterator        (),
          m_RecordSpace           (Allocator),
          m_RecordSpaceIsComplete (false)
        {}
        /// deallocates everything
        ~Iterator ()
        {
            Delete();
        }
        /*!
           @brief  The iterater is initialized.
           @param  AccessMode [in] default ForUpdate
           @return false, if the iterator could not be used
         */
        bool Initialize(Data_AccessMode AccessMode = Data_ForUpdate)
        {
            bool initialized = m_RecordSpace.Initialize();
            if ( initialized )
                m_RecordSpace.SetAccessMode(AccessMode);
            return initialized;
        }
        /// The resources of the iterater are deleted.
        void Delete()
        {
            Invalidate();
            m_RecordSpace.Delete();
        }
        /*!
            @return true, if a part of a split space is available.

            If the part is complete must be tested outside.
         */
        bool IsValid() const
        {
            return m_RecordSpace.IsAssigned() && m_RecordIterator.IsValid();
        }
        /*!
            @brief This releases all allocated internal members.
            @param isOK [in] if set to false the pages are released without update
         */
        void Invalidate(bool isOK = true)
        {
            SAPDBTRACE_METHOD_DEBUG ("Data_ChainSplitSpaceForwardRead::Iterator::Invalidate", DataChain_Trace, 5);
            m_RecordSpaceIsComplete = false;
            m_RecordIterator.Invalidate();
            // PTS 1115979 UH 2002-05-29
            // PTS 1117126 UH 2002-08-07 removed UndoLastReservedSpace
            m_RecordSpace.Deassign(isOK);
        }
        /*!
            @brief  Set the iterator to the given position.
            @return true, if successfull
         */
        bool SetPosition (Data_PageNo     pno,
                          Data_PageOffset offset,
                          Data_AccessMode accessmode)
        {
            return Assign (pno, offset, accessmode);
        }
        /// This returns a page reference of the position, where the iterator points to.
        void GetPosition (Data_PageNo     &pno,
                          Data_PageOffset &offset)
        {
            // PTS 1115056 UH 2002-03-27 changed the assertion to return value
            if ( IsValid() )
            {
                PAGE       &page = m_RecordSpace.GetPage(0);
                SAPDB_UInt  length;
                SAPDB_Byte *valueptr;
                m_RecordSpace.GetPart (0, length, valueptr);
    
                pno    = page.PageNo();
                offset = page.GetOffset(valueptr, length);
            }
            else
            {
                pno    = Data_PageNo();
                offset = 0;
            }
        }
        /// This releases all resources (pages in datacache)
        virtual bool Break() // PTS 1114994 2002-03-22 returns bool
        {
            GetPosition ( m_BreakInfo.lastPno,
                          m_BreakInfo.lastOffset );
            m_BreakInfo.lastAccessMode = GetCurrentPage().AccessMode();
            Invalidate();
            return true;
        }
        /// This resumes the state of a previous Break().
        virtual bool Continue()
        {
            if ( ! SetPosition ( m_BreakInfo.lastPno,
                                 m_BreakInfo.lastOffset,
                                 m_BreakInfo.lastAccessMode ) )
            {
                WriteToTrace ("Continue failed");
            	return false; // PTS 1115056 UH 2002-03-27 changed the assertion to return value
            }
            // assign the record space again
            (void) this->operator * ();
             m_BreakInfo = BreakInfo();
             return true; // PTS 1115056 UH 2002-03-27 changed the assertion to return value
        }
        /// This returns the handle of the current page.
        PAGE& GetCurrentPage ()
        {
            return m_RecordSpace.GetCurrentPage();
        }
        /// This returns the record, which is currently referenced. Without assigning the complete record space.
        Data_SplitSpace<PAGE>& operator * ()
        {
            if ( ! m_RecordSpaceIsComplete )
                AssignRecordspace (m_RecordIterator);
            return m_RecordSpace;
        }
        /// The complete size is returned. 0 is returned, if the iterator is not valid.
        Data_SplitRecordLength GetSize()
        {
            // PTS 1117126 UH 2002-08-07 new
            if ( ! IsValid() )
                return 0;
            return m_RecordIterator.RestLength();
        }
        /// The iterator is set to the previous record and the internal record space is assigned.
        Iterator& operator ++ ()
        {
            SAPDBTRACE_METHOD_DEBUG ("Data_ChainSplitSpaceForwardRead::Iterator::++", DataChain_Trace, 5);

            SAPDBERR_ASSERT_STATE( IsValid() );

            // reorder and reassign the split record space
            m_RecordSpace.Reassign ();
            m_RecordSpaceIsComplete = false;
            
            SAPDBERR_ASSERT_STATE( m_RecordSpace.CurrentPart() == 0 );
            
            PAGE& Page = m_RecordSpace.GetCurrentPage();
            Page.Use (m_RecordIterator);

            ++m_RecordIterator;
            
            if ( ! m_RecordIterator.IsValid() )
            {
                Data_PageNo NextPageNo = Page.NextPageNo();

                if ( NextPageNo.IsValid() )
                {
                    const Data_PageNo auxPageNo = Page.PageNo();

                    m_PAM.ReleasePage (Page);

                    if ( ! m_PAM.GetPage (Page, NextPageNo) )
                    {
                        m_RecordSpace.WriteToTrace("++");
                        RTE_Crash( Data_Exception(__CONTEXT__,
                                                  DATA_CHAIN_IS_INCONSISTENT,
                                                  SAPDB_ToString(auxPageNo),
                                                  SAPDB_ToString(Page.PageNo()) ));
                    }

                    Page.Begin (m_RecordIterator);
                }
                else
                    m_RecordSpace.Deassign ();
            }
            return *this;
        }
        /// Write the important values from this class to the knltrace.
        virtual void WriteToTrace (const char* title = NULL) const
        {
            Kernel_VTrace() << "forwReadChainIter: Recordspace: "
                            << (m_RecordSpaceIsComplete?"complete":"not complete");
            Kernel_VTrace() << "BreakInfo: " << m_BreakInfo.lastPno << "." << m_BreakInfo.lastOffset
                            << ", accessmode: " << m_BreakInfo.lastAccessMode;
            m_RecordSpace.WriteToTrace(title);
        }
    private:
        /*!
            @brief  Set the iterator to a specified position.
            @return true, if the given PageNo was found.
            The record space is newly assigned to the specified page
            and dependent on bFirstFree the record is set to the first
            record in the page or to the first free position.
         */
        bool Assign (Data_PageNo     PageNo,
                     Data_PageOffset Offset,
                     Data_AccessMode AccessMode)
        {
            SAPDBTRACE_METHOD_DEBUG ("Data_ChainSplitSpaceForwardRead::Iterator::Assign", DataChain_Trace, 5);

            if ( m_RecordSpace.IsAssigned() )
            {
                if ( m_RecordSpace.GetCurrentPage().PageNo() == PageNo 
                     &&
                     m_RecordSpace.GetCurrentPage().AccessMode() == AccessMode )
                    m_RecordSpace.Reassign();
                else
                {
                    m_RecordSpace.Deassign();
                    m_RecordSpace.SetAccessMode(AccessMode);
                    if ( ! m_PAM.GetPage ( m_RecordSpace.UseNextPart(),
                                           PageNo,
                                           AccessMode) )
                        return false;
                }
            }
            else
            {
                m_RecordSpace.SetAccessMode(AccessMode);
                if ( ! m_PAM.GetPage ( m_RecordSpace.UseNextPart(),
                                       PageNo,
                                       AccessMode) )
                    return false;
            }       
            SAPDBERR_ASSERT_STATE( m_RecordSpace.CurrentPart() == 0 );
            
            if ( Offset == 0 )
                m_RecordSpace.GetCurrentPage().Begin (m_RecordIterator);
            else if ( Offset == SAPDB_MAX_UINT2 )
                m_RecordSpace.GetCurrentPage().End (m_RecordIterator);
            else
                m_RecordSpace.GetCurrentPage().Use (m_RecordIterator, Offset);

            m_RecordSpaceIsComplete = false;
            
            return true;
        }
        /// The internal record space of the Split record is initialized.
        void AssignRecordspace(Data_SplitSpaceForwardReadIterator& RecordIter)
        {
            SAPDBTRACE_METHOD_DEBUG ("Data_ChainSplitSpaceForwardRead::Iterator::AssignRecordSpace", DataChain_Trace, 5);

            if ( ! RecordIter.IsValid()
                 ||
                 RecordIter.PageIsEmpty() )
                 return;

            Data_SplitRecordLength TotalRestLength = RecordIter.RestLength();

            while ( TotalRestLength > 0 )
            {
                if ( m_RecordSpace.CurrentPart() > 0 )
                    TotalRestLength -= (RecordIter.Length() - sizeof(Data_SplitRecordLength));
                else
                    TotalRestLength -= RecordIter.Length();

                m_RecordSpace.SetPart (RecordIter.Length() - sizeof(Data_SplitRecordLength),
                                       *RecordIter);

                if ( TotalRestLength > 0 )
                {
                    ++RecordIter;

                    if ( ! RecordIter.IsValid() )
                    {
                        Data_PageNo NextPageNo = m_RecordSpace.GetCurrentPage().NextPageNo();

                        if ( ! NextPageNo.IsValid() )
                        {
                            m_RecordSpace.WriteToTrace("AssignRecordSpace");
                            RTE_Crash( Data_Exception(__CONTEXT__,
                                                      DATA_CHAIN_IS_INCONSISTENT,
                                                      SAPDB_ToString(m_RecordSpace.GetCurrentPage().PageNo()),
                                                      SAPDB_ToString(NextPageNo) ));
                        }

                        if ( ! m_PAM.GetPage (m_RecordSpace.UseNextPart(), NextPageNo) )
                        {
                            m_RecordSpace.WriteToTrace("AssignRecordSpace");
                            RTE_Crash( Data_Exception(__CONTEXT__,
                                                      DATA_CHAIN_IS_INCONSISTENT,
                                                      SAPDB_ToString(m_RecordSpace.GetCurrentPage().PageNo()),
                                                      SAPDB_ToString(NextPageNo) ));
                        }

                        m_RecordSpace.GetCurrentPage().Begin (RecordIter);
                    }
                }
            }
            if ( TotalRestLength != 0 )
            {
                m_RecordSpace.WriteToTrace("AssignRecordSpace");
                RTE_Crash( Data_Exception(__CONTEXT__,
                                          DATA_CHAIN_IS_INCONSISTENT,
                                          "TotalRestLength",
                                          SAPDB_ToString(TotalRestLength) ));
            }

            m_RecordSpaceIsComplete = true;
            
            SAPDBTRACE_IF (DataChain_Trace, 6, m_RecordSpace.WriteToTrace("AssignRecordSpace"));
        }
        /// Set the isAssigned flag. Is Needed for Reserve.
        void SetAssigned (bool IsAssigned)
        {
            m_RecordSpaceIsComplete = IsAssigned;
        }
        /// return the internal record space.
        RecordSpace &GetRecordSpace()
        {
            return m_RecordSpace;
        }
    private:
        /// The used page access manager
        Data_PageAccessManager& m_PAM;
        /// The internally used record iterator.
        Data_SplitSpaceForwardReadIterator m_RecordIterator;
        /// This refers to the space of the current record.
        RecordSpace m_RecordSpace;
        /// if this is true, the record space is correctly assigned.
        bool m_RecordSpaceIsComplete;
        /// the breakinfo
        struct BreakInfo
        {
            /// last pageno
            Data_PageNo     lastPno;
            /// last offset
            Data_PageOffset lastOffset;
            /// last accessmode
            Data_AccessMode lastAccessMode;
            /// nil constructor
            BreakInfo()
            {
                lastPno.Invalidate();
                lastOffset     = 0;
                lastAccessMode = Data_ForRead;
            }
        };
        /// this is used by Break() and Continue()
        BreakInfo m_BreakInfo;
    };
public:
    /*!
       @brief The parameter are stored.
       @param PAM [in] the page access manager which must be used
       @param RootId [in] this id defines the beginning of the page chain
     */
    Data_ChainSplitSpaceForwardRead (Data_PageAccessManager &PAM,
                                      Data_PageId             &RootId)
    : Data_Chain<PAGE> (PAM, RootId)
    {}
    /*!
       @brief The parameter are stored.
       @param PAM [in] the page access manager which must be used
       @param RootId [in] this id defines the beginning of the page chain
       @param LastPageNo [in] this id defines the known last page no.
     */
    Data_ChainSplitSpaceForwardRead (Data_PageAccessManager &PAM,
                                     Data_PageId             &RootId,
                                     Data_PageNo              LastPageNo)
    : Data_Chain<PAGE> (PAM, RootId, LastPageNo)
    {}

    /// A new Chain is created. The PageIter points to the root.
    bool Create (Data_ChainIterator<PAGE>& RootPageIter)
    {
        return Data_Chain<PAGE>::Create (RootPageIter); // PTS 1121659 UH 2003-04-30
    }
    /// A new Chain is created.
    bool Create ()
    {
        Data_ChainIterator<PAGE> RootPageIter (this->GetPageAccessManager());
        return Create (RootPageIter); // PTS 1121659 UH 2003-04-30
    }
    /*!
       @return false if there is no space for one additional perm page
       @brief  true is returned if ReserveSpace() will find enough pages.

       The task can be supended !
     */
    bool CheckSpace()
    {
		// PTS 1115593 UH 2002-05-07 new
        SAPDBTRACE_METHOD_DEBUG ("Data_ChainSplitSpaceForwardRead::Checkspace", DataChain_Trace, 5);
        return this->m_PAM.CheckSpace (1); // PTS 1115170 UH 2002-04-09
    }


    /*!
       @brief  Space is reserved in the chain. The record iterator points to the Split space.
       @param  WantedSize [in] The amount of space needed.
       @param  FirstPartMiniumSize [in] if set to 0, no minimum is given
       @param  RecordIterator [out] A RecordIterator, which points to the knewly created space.
       @param  numberOfAddedPages [out]
       @return false if no space is available.
     */
    bool ReserveSpace (Data_SplitRecordLength  WantedSize,
                       Data_RecordLength       FirstPartMiniumSize,
                       Iterator&               RecordIterator,
                       SAPDB_UInt             &numberOfAddedPages ) // PTS 1121659 UH 2003-04-30
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_ChainSplitSpaceForwardRead::ReserveSpace", DataChain_Trace, 5);
        
        SAPDBERR_ASSERT_STATE( WantedSize >= FirstPartMiniumSize );

        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "Wanted: " << WantedSize << ", FirstPartMin: " << FirstPartMiniumSize);

        // PTS 1115593 UH 2002-05-07

        if ( ! this->m_PAM.CheckSpace ( WantedSize/PAGE::MaxSpaceSize() ) ) // PTS 1121659 UH 2003-04-30
            return false; // PTS 1121659 UH 2003-04-30


        numberOfAddedPages = 0;
        // prepare the record space

        RecordIterator.Invalidate();
        
        if ( ! RecordIterator.Assign( this->LastPageNo(), SAPDB_MAX_UINT2, Data_ForUpdate ) )
        {
            this->InfoMessage("ChainSplitSpaceForwardRead");
            RTE_Crash( Data_Exception(__CONTEXT__,
                                      DATA_CHAIN_IS_INCONSISTENT,
                                      "-",
                                      SAPDB_ToString(this->LastPageNo()) ));
        }

        bool AllFits;
        bool FirstPartFits;
        
        RecordIterator.GetRecordSpace().GetCurrentPage().
            SpaceInfo (WantedSize, FirstPartMiniumSize, FirstPartFits, AllFits);
                                          
        if ( ! AllFits )
        {
            RecordIterator.Invalidate();
            if ( ! RecordIterator.Assign( this->LastPageNo(), SAPDB_MAX_UINT2, Data_ForStructureChange ) )
            {
                this->InfoMessage("ChainSplitSpaceForwardRead");
                RTE_Crash( Data_Exception(__CONTEXT__,
                                          DATA_CHAIN_IS_INCONSISTENT,
                                          "-",
                                          SAPDB_ToString(this->LastPageNo()) ));
            }
        }
        
        // fill the internal record space of the record iterator

        Data_RecordLength   CurrentReservedSize = 0;
        SAPDB_Byte         *CurrentSpace;

        do
        {
            RecordIterator.GetRecordSpace().GetCurrentPage().
                ReserveSpace (WantedSize, FirstPartMiniumSize,
                              CurrentReservedSize, CurrentSpace);

            SAPDBTRACE_WRITELN (DataChain_Trace, 6, \
                                "ReserveSpace(" << RecordIterator.GetRecordSpace().CurrentPart() \
                                << "): pno/CurrReserved/CurrReserved: " \
                                << RecordIterator.GetRecordSpace().GetCurrentPage().PageNo() << "/" \
                                << CurrentReservedSize << "/" << CurrentReservedSize);

            if ( CurrentReservedSize > 0 )
            {
                SAPDBERR_ASSERT_STATE( CurrentSpace != NULL );
                RecordIterator.GetRecordSpace().SetPart (CurrentReservedSize, CurrentSpace);
            }

            if ( WantedSize > 0 )
            {
                RecordIterator.GetRecordSpace().SetNewPageWasAppended();
                
                if ( CurrentReservedSize > 0 )
                {
					// PTS 1117126 UH 2002-08-07
					// UseNextPart() calls Resize() which reallocates the memory
					// So before UseNextPart() no reference into the memory of Space
                    // may be saved.
                    RecordSpace     &Space             = RecordIterator.GetRecordSpace();
                    const SAPDB_Int  partOfCurrentPage = Space.CurrentPart();
                    PAGE            &NextPage          = Space.UseNextPart();
                    PAGE            &CurrentPage       = Space.GetPage(partOfCurrentPage);

                    CurrentPage.Append (NextPage, this->LastPageNo());
                    NextPage.Begin(RecordIterator.m_RecordIterator);
                }
                else
                {
                    // the space was not big enough for FirstPartMiniumSize
                    // This is only entered at the first time once
                    PAGE& CurrentPage = RecordIterator.GetRecordSpace().GetCurrentPage();
                    // PTS 1133123 UH 2004-12-22 keep old last page
                    PAGE& OldPage = RecordIterator.GetRecordSpace().GetPrevPage();
                    
                    OldPage.Assign (CurrentPage);
                    OldPage.Append (CurrentPage, this->LastPageNo());
                    
                    CurrentPage.Begin(RecordIterator.m_RecordIterator);
                }
                ++numberOfAddedPages;
            }
        }
        while ( WantedSize > 0 );

        RecordIterator.SetAssigned (true);

        SAPDBTRACE_IF (DataChain_Trace, 6, RecordIterator.GetRecordSpace().WriteToTrace("ReserveSpace"));

        return true;
    }
    /// All changes done be ReserveSpace() are undone.
    void UndoReserveSpace (const RecordSpace &space)
    {
		// PTS 1117126 UH 2002-08-07 new
        // PTS 1133123 UH 2004-12-22 rewritten, input of iterator changed to space
        SAPDBTRACE_METHOD_DEBUG ("Data_ChainSplitSpaceForwardRead::UndoReserveSpace", DataChain_Trace, 5);
        this->LastPageNo() = space.GetFirstPageNo();
    }
    /// The RecordIterator is set to the first record in the chain. The internal RecordSpace is assigned.
    void Begin (Iterator&       RecordIterator,
                Data_AccessMode AccessMode)
    {
        if ( ! RecordIterator.Assign( this->RootId().PageNo(), 0, AccessMode) )
        {
            this->InfoMessage("ChainSplitSpaceForwardRead");
            RTE_Crash( Data_Exception(__CONTEXT__,
                                      DATA_CHAIN_IS_INCONSISTENT,
                                      "-",
                                      SAPDB_ToString(this->RootId().PageNo()) ));
        }
    }
};
#undef FIRST_FREE
#endif // Data_ChainSplitSpaceForwardRead_HPP
