/*!
  @file           Data_ChainFixSizeSpace.hpp
  @author         UweH
  @brief          defines class Data_ChainFixSizeSpace

\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_ChainFixSizeSpace_HPP
#define Data_ChainFixSizeSpace_HPP

#include "DataAccess/Data_Exceptions.hpp"
#include "DataAccess/Data_Messages.hpp"
#include "DataAccess/Data_PageFixSizeSpace.hpp"
#include "DataAccess/Data_Chain.hpp"

/// only to hide a simple boolean true
#define FIRST_RECORD true

/*!
   @class Data_ChainFixSizeSpace
   @brief A persistent page chain handler, based on data pages.
 */
template <class PAGE> class Data_ChainFixSizeSpace : public Data_Chain<PAGE>
{
public:
    /// abbreviation
    typedef SAPDB_Byte*                        RecordSpace;
    /// abbreviation
    typedef Data_PageFixSizeSpace::Iterator    RecordIterator;
    /// abbreviation
    typedef Data_ChainIterator<PAGE>           PageIterator;
    /// abbreviation
    typedef Data_PageFixSizeSpace::RecordCount RecordId;
public:
    /*!
        @class          Iterator
        @brief          Iterator for records in the chain.
     */
    class Iterator
    {
	/// The iterator accesses directly members of a chain it points into.
    friend class Data_ChainFixSizeSpace<PAGE>;
    public:
        /// The iterater is not initialized and at first invalid.
        Iterator (Data_PageAccessManager& PAM)
        : m_PAM        (PAM),
          m_PageIter   (PAM)
        {}
        /// This is used to copy given iterator
        Iterator (const Iterator& iter)
        : m_PAM        (iter.m_PAM),
          m_PageIter   (iter.m_PAM)
        {}
        /// Returns true, if the iterator points to a record.
        bool IsValid() const
        {
            return m_PageIter.IsValid()
                   &&
                   m_RecordIter.IsValid();
        }
        /*!
            @brief This invalidates all members.
            @param isOK [in] if set to false the pages are released as not changed
         */
        void Invalidate(bool isOK = true)
        {
            m_RecordIter.Invalidate();
            m_PageIter.Invalidate();
        }
        /// This returns a page reference of the position, where the iterator points to.
        void GetPosition (Data_PageNo &pno,
                          RecordId    &recordid)
        {
            // PTS 1115056 UH 2002-03-27 changed the assertion to return value
            if ( IsValid() )
            {
                pno      = (*m_PageIter).PageNo();
                recordid = m_RecordIter.GetRecordId();
            }
            else
            {
                pno      = Data_PageNo();
                recordid = 0;
            }
        }
        /*!
           @brief   The iterator is set to the given position.
           @returns true, if position is reached.
         */
        bool SetPosition (Data_PageNo     pno,
                          RecordId        recordid,
                          Data_AccessMode accessmode)
        {
            Assign (pno, true, accessmode);
            if ( ! m_PageIter.IsValid() )
                return false;
            (*m_PageIter).SetToRecord (recordid, m_RecordIter);
            return m_RecordIter.IsValid();
        }
        /// The iterator releases its resources and saves current position.
        void Break()
        {
            GetPosition ( m_BreakInfo.lastPno,
                          m_BreakInfo.lastrecordid );
            m_BreakInfo.lastAccessMode = GetPage().AccessMode();
            Invalidate();
        }
        /*!
           @brief   The iterator takes and sets the saved position.
           @returns (bool) false, if the saved position could not be set.
         */
        bool Continue()
        {
            bool result = SetPosition ( m_BreakInfo.lastPno,
                                        m_BreakInfo.lastrecordid,
                                        m_BreakInfo.lastAccessMode );
            m_BreakInfo = BreakInfo();
            return result;
        }
        /*!
            @brief Gives the current page the iterator points to. The iterator must be valid.
         */
        PageIterator& GetPageIterator()
        {
            return m_PageIter;
        }
        /// Gives the current page the iterator points to. The iterator must be valid.
        PAGE& GetPage()
        {
            SAPDBERR_ASSERT_STATE( m_PageIter.IsValid() );
            return *m_PageIter;
        }
        /// returns SAPDB_UInt, MAX_UINT if invalid
        RecordId GetRecordId () const
        {
            if ( ! IsValid() )
                return SAPDB_MAX_UINT4;
            return m_RecordIter.GetRecordId();
        }
        /// This sets the internal recordid of the iterator, which can then be dereferenced.
        void SetRecordId (RecordId recordid)
        {
            SAPDBERR_ASSERT_STATE( m_PageIter.IsValid() );
            m_RecordIter.SetRecordId(recordid);
        }
        /// This returns the record, which is currently referenced.
        RecordSpace operator * ()
        {
            SAPDBERR_ASSERT_STATE( IsValid() );
            return *m_RecordIter;
        }
        /// If the iterator is valid it is set to the next record.
        Iterator& operator ++ ()
        {
            if( ! IsValid() )
                return *this;

            ++m_RecordIter;
            
            if ( ! m_RecordIter.IsValid() )
            {
                m_RecordIter.Invalidate();

                ++m_PageIter;

                if ( m_PageIter.IsValid() )
                    (*m_PageIter).Begin (m_RecordIter);
            }
            return *this;
        }
        /// If the iterator is valid it is set to the previous record.
        Iterator& operator -- ()
        {
            if( ! IsValid() )
                return *this;

            --m_RecordIter;
            
            if ( ! m_RecordIter.IsValid() )
            {
                --m_PageIter;

                if ( m_PageIter.IsValid() )
                    (*m_PageIter).Begin (m_RecordIter);
            }
            return *this;
        }
        /// Write all important data to the knltrace.
        void WriteToTrace (const char* title = 0)
        {
            Kernel_VTrace trace;
            if ( title != 0 )
                trace << title << ": ";
            else
                trace << "fixiter: ";

            if ( m_PageIter.IsValid() )
                trace << "@" << (*m_PageIter).PageNo();
            else
                trace << "@invalid";
                
            trace << "." << m_RecordIter.GetPosition()
                  << "[" << m_RecordIter.GetRecordId() << "]";
            trace << ", break@" << m_BreakInfo.lastPno << "["
                  << m_BreakInfo.lastrecordid << "]";
            trace << (!IsValid()?" (not valid)":"");
        }
    protected:
        /// this is used by the container. if an error occurred, the iterator is invalid.
        void Assign (Data_PageNo     PageNo,
                     bool            SetToFirstRecord,
                     Data_AccessMode AccessMode)
        {
            if ( m_PageIter.IsValid() )
            {
                if ( PageNo != (*m_PageIter).PageNo()
                     ||
                     (*m_PageIter).AccessMode() != AccessMode )
                {
                    Invalidate();
                    if ( ! m_PAM.GetPage ( (*m_PageIter),
                                           PageNo,
                                           (*m_PageIter).RecoveryMode(),
                                           AccessMode) )
                        Invalidate();
                }
            }
            else
            {
                if ( ! m_PAM.GetPage ( (*m_PageIter),
                                       PageNo,
                                       (*m_PageIter).RecoveryMode(),
                                       AccessMode) )
                    Invalidate();
            }
            if ( m_PageIter.IsValid() )
            {
                if ( SetToFirstRecord )
                    (*m_PageIter).Begin (m_RecordIter);
                else
                    (*m_PageIter).End (m_RecordIter);
            }
        }
    private:
        /// the page access manager handle
        Data_PageAccessManager& m_PAM;
        /// internal record iterator
        RecordIterator m_RecordIter;
        /// internal page iterator
        PageIterator m_PageIter;
    private:
        /// definition of a break handle
        struct BreakInfo
        {
            /// continue on this page
            Data_PageNo     lastPno;
            /// continue at this record
            RecordId        lastrecordid;
            /// continue to get the page with this access mode
            Data_AccessMode lastAccessMode;
            /// construct an invalid breakinfo
            BreakInfo()
            {
                lastPno.Invalidate();
                lastrecordid   = 0;
                lastAccessMode = Data_ForRead;
            }
        };
        /// this is the iterators break info
        BreakInfo m_BreakInfo;
    };
private:
    /// Checks if the given and assumed fix space size is identical to that written into the root page.
    void CheckFixRecordLength(PageIterator &iter)
    {
        if ( (*iter).GetFixRecordLength() != m_FixRecordLength )
        {
            this->InfoMessage("ChainFixSizeSpace");
            RTE_Crash(Data_Exception(__CONTEXT__, DATA_ERROR_CHAIN_FIX_SIZE_MISMATCH,
                                      SAPDB_ToString(m_FixRecordLength),
                                      SAPDB_ToString((*iter).GetFixRecordLength()) ));
        }
    }
public:
    /*!
       @param          PAM [in/out] must be used to access pages
       @param          RootId [in] this id defines the beginning of the page chain
       @param          FixRecordLength [in] 
       @brief          if the rootid is valid, the given fix-record-size is checked.
     */
    Data_ChainFixSizeSpace (Data_PageAccessManager &PAM,
                            Data_PageId             &RootId,
                            Data_RecordLength        FixRecordLength)
    : Data_Chain<PAGE>  (PAM, RootId),
      m_FixRecordLength (FixRecordLength)
    {}
    /*!
       @brief          if the rootid is valid, the given fix-record-size is checked.
       @param          PAM [in/out]  must be used to access pages
       @param          RootId [in] this id defines the beginning of the page chain
       @param          LastPageNo [in] this id defines the ending of the page chain
       @param          FixRecordLength [in] 
     */
    Data_ChainFixSizeSpace (Data_PageAccessManager &PAM,
                            Data_PageId             &RootId,
                            Data_PageNo              LastPageNo,
                            Data_RecordLength        FixRecordLength)
    : Data_Chain<PAGE>  (PAM, RootId, LastPageNo),
      m_FixRecordLength (FixRecordLength)
    {}
    /// A new Chain is created. The PageIter points to the root.
    bool Create (PageIterator &RootPageIter)
    {
        if ( ! Data_Chain<PAGE>::Create (RootPageIter) )
            return false; // PTS 1121659 UH 2003-04-30
        
        PAGE &page = *RootPageIter;
        
        page.SetFixRecordLength ( m_FixRecordLength );
        page.SetLastPageNo      ( page.PageNo()     );
        return true; // PTS 1121659 UH 2003-04-30
    }
    /// A new Chain is created.
    bool Create ()
    {
        PageIterator RootPageIter (this->GetPageAccessManager());
        return Create (RootPageIter); // PTS 1121659 UH 2003-04-30
    }
    /// The RecordIterator is set to the first record of the given page.
    void Set (Iterator&       iter,
              Data_PageNo     pageno,
              Data_AccessMode accessmode,
              bool            toFirstRecord)
    {
        iter.Assign( pageno, toFirstRecord, accessmode);
        #ifdef SAPDB_SLOW
		CheckFixRecordLength(iter.GetPageIterator());
	    #endif
    }
    /// The RecordIterator is set to the first record in the chain.
    void Begin (Iterator&       iter,
                Data_AccessMode accessmode)
    {
        Set ( iter, this->RootId().PageNo(), accessmode, true);
    }
    /*!
       @param          iter [out] A RecordIterator, which points to the newly created space.
       @param          useForStructureChange [in] default is true
       @brief          Space is reserved in the chain.

       The record iterator points to the new space.
       If no space was allocated, the iterator is invalid.
       The Chain is locked here but must explicitely Unlock()'ed after the
       space has been written !!
     */
    bool ReserveSpace (Iterator& iter,
                       bool      useForStructureChange = true)
    {
        if ( ! this->m_PAM.CheckSpace (1) ) // PTS 1115170 UH 2002-04-09
            return false; // PTS 1121659 UH 2003-04-30

        if ( ! this->Lock (useForStructureChange?Data_ForStructureChange:Data_ForUpdate) )
        {
            RTE_Crash( SAPDBErr_Exception(
                       __FILE__, __LINE__,
                       SAPDBERR_ASSERT_STATE_FAILED,
                       "Data_ChainFixSizeSpace::ReserveSpace: Lock()") );
        }

        PageIterator &pageiter = iter.m_PageIter;
        
        if ( ! GetPage (this->LastPageNo(), Data_ForUpdate, pageiter) )
        {    
            Kernel_VTrace() << "FixChain.reserve: root: " << this->RootId().PageNo()
                            << ", last: " << this->LastPageNo() << NewLine;
            RTE_Crash( SAPDBErr_Exception(
                       __FILE__, __LINE__,
                       SAPDBERR_ASSERT_STATE_FAILED,
                       "Data_ChainFixSizeSpace::ReserveSpace: Get last page") );
        }
        
        (*pageiter).ReserveSpace (iter.m_RecordIter);
        
        if ( ! iter.IsValid() )
        {
            PAGE newPage;
                        
            (*pageiter).Append (newPage, this->LastPageNo());

            newPage.SetFixRecordLength (m_FixRecordLength);

            ReleasePage (pageiter);
            
            pageiter.Set(newPage);

            (*pageiter).ReserveSpace (iter.m_RecordIter);
            
            if ( this->m_RootPage.IsAssigned() )
            {
                this->m_RootPage.SetLastPageNo (this->LastPageNo());
                this->m_RootPage.SetChanged();
            }
            
        }

        // PTS 1127083 UH 2004-01-14 removed Unlock()

        return true; // PTS 1121659 UH 2003-04-30
    }
    /*!
       @param iter [in] A RecordIterator, which points to the space to remove.
       @brief Space is removed in the chain.

       The iterator is set to the next valid position, if there is one.
       A page is only removed, if it is not the root or the last page.
     */
    void RemoveSpace (Iterator &iter)
    {
        PAGE &page = iter.GetPage();

        SAPDBERR_ASSERT_STATE( page.GetRecordCount() > 0 );

        page.DecrementRecordCount();

        if ( page.GetRecordCount() > 0
             ||
             this->RootId().PageNo() == page.PageNo() )
            ++iter;
        else
        {
            PageIterator &pageiter = iter.GetPageIterator();

            Data_PageNo prevPageNo = page.PrevPageNo();

            if ( prevPageNo.IsInvalid() )
            {
                Kernel_VTrace() << "FixChain: root: " << this->RootId().PageNo()
                                << ", pageno: " << page.PageNo() << NewLine;
                RTE_Crash( SAPDBErr_Exception(
                           __FILE__, __LINE__,
                           SAPDBERR_ASSERT_STATE_FAILED,
                           "Data_ChainFixSizeSpace::Removespace: prev pno is invalid.") );
            }

            pageiter.Invalidate();

            if ( ! this->Lock (Data_ForStructureChange) )
            {
                Kernel_VTrace() << "FixChain.remove: root: " << this->RootId().PageNo() << NewLine;
                RTE_Crash( SAPDBErr_Exception(
                           __FILE__, __LINE__,
                           SAPDBERR_ASSERT_STATE_FAILED,
                           "Data_ChainFixSizeSpace::Removespace: Lock()") );
            }
            
            if ( ! GetPage(prevPageNo, Data_ForUpdate, pageiter) )
            {
                Kernel_VTrace() << "FixChain.remove: root: " << this->RootId().PageNo()
                                << ", prevpageno: " << prevPageNo << NewLine;
                RTE_Crash( SAPDBErr_Exception(
                           __FILE__, __LINE__,
                           SAPDBERR_ASSERT_STATE_FAILED,
                           "Data_ChainFixSizeSpace::Removespace: get prev page") );
            }

            Delete (pageiter);

            if ( this->m_RootPage.IsAssigned() )
            {
                Data_PageNo NextPageNo = (*pageiter).NextPageNo();
                ReleasePage (pageiter);
                if ( NextPageNo.IsValid() )
                    (void) GetPage (NextPageNo, Data_ForUpdate, pageiter);
            }
            else
                ++pageiter;

            if ( ! pageiter.IsValid() )
            {
                // The removed page was the last page in the chain.

                if ( this->m_RootPage.IsAssigned() )
                {
                    this->m_RootPage.SetLastPageNo (prevPageNo);
                    this->m_RootPage.SetChanged();
                }

                iter.Invalidate();
            }
            else
            {            
                // The page iter points now to the next page after the removed page.

                (*pageiter).SetPrevPageNo(prevPageNo);
                (*pageiter).Begin (iter.m_RecordIter);
            }
            this->UnLock();
        }
    }
    /*!
        @brief  This returns the actual used fix space size
        @return size in bytes
     */
    Data_RecordLength GetSpaceSize () const
    {
        return m_FixRecordLength;
    }
private:
    /// the internal cached fix space size
    const Data_RecordLength m_FixRecordLength;
};
#undef FIRST_RECORD
#endif // Data_ChainFixSizeSpace_HPP
