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

\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_ISplitSpace_HPP
#define Data_ISplitSpace_HPP

#include <string.h>
#include "SAPDBCommon/SAPDB_Types.hpp"
#include "SAPDBCommon/MemoryManagement/SAPDBMem_IRawAllocator.hpp"
#include "SAPDBCommon/Tracing/SAPDBTrace_Usage.hpp"
#include "KernelCommon/Kernel_VTrace.hpp"
#include "DataAccess/Data_Types.hpp"
/*!
   @interface Data_ISplitSpace
   @brief     This handle offers splitted space access.
 */
class Data_ISplitSpace
{
public:
    /// Definition of partno to identify one part of the split space.
    typedef SAPDB_Invalidatable<SAPDB_Int, -1> PartNo;
    /// return true, if at least one part is accessable
    virtual bool IsAssigned () const = 0;
    /// Return the number of the current part.
    virtual PartNo CurrentPart () const = 0;
    /// From the specified part is the length and a pointer returned.
    virtual void GetPart (PartNo       Part,
                          SAPDB_UInt  &Length,
                          SAPDB_Byte* &Value) const = 0;
    /// The sum of length of all used parts of the space is returned in bytes.
    virtual SAPDB_UInt Length() const = 0;
    
    /*!
    	@return  false, if space begin is not properly aligned
    	@brief   This checks if the split space begins at an aligned position
    */
    virtual bool CheckAlignment() const = 0;
    /// returns false, if given reference is not properly aligned
    virtual bool CheckAlignment(SAPDB_Byte const * reference) const = 0;
};
/*!
   @class  Data_SplitSpaceWriter
   @brief  This handle is used to write to a Data_ISplitSpace
 */

class Data_SplitSpaceWriter
{
public:
    /// redefine PartNo
    typedef Data_ISplitSpace::PartNo PartNo;
    /// Definition of the result
    enum Result
    {
        ok, moreSpaceAvailable, noMoreToWrite, spaceToSmall
    };
    /// This is used to write to a split space. Space must be assigned.
    Data_SplitSpaceWriter (Data_ISplitSpace &Space)
        : m_Space     (Space)
    {
        Reset();
    }
    /// This is used to reset the internal values if the split space was changed outside. Space must be assigned.
    void Reset ()
    {
        m_CurrentPart = 0;
        (void)m_Space.CheckAlignment();
        m_Space.GetPart (m_CurrentPart, m_CurrentLength, m_CurrentValue);
        m_CurrentPartRestLength = m_CurrentLength;
    }
    /*!
       @brief The specified space is reserved, if length can be allocated in one peace.
       @param Length [in] - length of space to reserve
       @param Space [out] - pointer to continous space.
       @param NewResult [out] - ok, moreSpaceAvailable or spaceToSmall
     */
    void Reserve (SAPDB_UInt   Length,
                  SAPDB_Byte* &Space,
                  Result      &NewResult)
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_SplitSpaceWriter::Reserve", DataChain_Trace, 5);
        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "length: " << Length);
        
        NewResult = ok;
        Space     = 0;
        
        if ( Length > m_CurrentPartRestLength )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "space too small: " << m_CurrentPartRestLength);
            NewResult = spaceToSmall;
            return;
        }

        Space = m_CurrentValue + (m_CurrentLength - m_CurrentPartRestLength);

        m_CurrentPartRestLength -= Length;

        if ( m_CurrentPartRestLength <= 0 )
            TakeNextPart ();

        if ( m_CurrentPartRestLength > 0 )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "more space available: " << m_CurrentPartRestLength);
            NewResult = moreSpaceAvailable;
        }
    }

    /*!
        @brief The given part (pointer+length) is written to underlaying the split space.
        @param SourcePart [in] - source value to copy
        @param SourcePartLength [in] - length of source value
        @param NewResult [out] 
        NewResult=ok means: everything, which should be copied was copied.
        NewResult=moreSpaceAvailable means: more can be moved to current space.
        NewResult=spaceToSmall means: The split space is not big enough.
     */
    void Write (const SAPDB_Byte* SourcePart,
                SAPDB_UInt        SourcePartLength,
                Result           &NewResult)
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_SplitSpaceWriter::Write", DataChain_Trace, 5);
        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "SourcePartLength: " << SourcePartLength);

        NewResult = ok;
        
        SAPDB_UInt CurrentCopyLength     = 0;
        SAPDB_UInt CurrentRestCopyLength = SourcePartLength;

        if( CurrentRestCopyLength <= 0 )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "no more to write: " << CurrentRestCopyLength);
            NewResult = noMoreToWrite;
            return;
        }

        do
        {
            if ( m_CurrentPartRestLength <= 0 )
                TakeNextPart ();

            if ( m_CurrentPartRestLength <= 0 )
            {
                SAPDBTRACE_WRITELN (DataChain_Trace, 6, "space too small: " << m_CurrentPartRestLength);
                NewResult = spaceToSmall;
                return;
            }

            CurrentCopyLength = CurrentRestCopyLength;

            if ( CurrentCopyLength > m_CurrentPartRestLength )
                CurrentCopyLength = m_CurrentPartRestLength;

            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "copy: " << CurrentCopyLength);
            memcpy ( m_CurrentValue + (m_CurrentLength  - m_CurrentPartRestLength),
                     SourcePart     + (SourcePartLength - CurrentRestCopyLength  ),
                     CurrentCopyLength);

            CurrentRestCopyLength   -= CurrentCopyLength;
            m_CurrentPartRestLength -= CurrentCopyLength;
        }
        while ( CurrentRestCopyLength > 0 );

        if ( m_CurrentPartRestLength <= 0 )
            TakeNextPart ();

        if ( m_CurrentPartRestLength > 0 )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "more space available: " << m_CurrentPartRestLength);
            NewResult = moreSpaceAvailable;
        }
    }
    /// Writes the members to the trace file.
    void WriteToTrace (const char* title = 0)
    {
        Kernel_VTrace trace;

        if ( title != NULL )
            trace << title << NewLine;

        trace << "writer: part: "  << m_CurrentPart
              << ", length: "      << m_CurrentLength
              << ", restlength: "  << m_CurrentPartRestLength
              << FlushLine;

        if ( ! m_Space.IsAssigned() )
        {
            trace << "not assigned." << FlushLine;
            return;
        }
            
        trace << "space: length: " << m_Space.Length()
              << FlushLine;
        PartNo      part;
        SAPDB_UInt  length;
        SAPDB_Byte* value;
        if ( m_Space.CurrentPart().IsValid() )
        {
            for (part = 0; part <= m_Space.CurrentPart(); ++part)
            {
                m_Space.GetPart (part, length, value);
                trace << "spacepart: " << part
                      << ", length: "  << length
                      << FlushLine;
            }
        }
    }
private:
    /// this is used, to take the next part of the space.
    void TakeNextPart()
    {
        ++m_CurrentPart;

        if ( m_CurrentPart > m_Space.CurrentPart() )
            return;

        m_Space.GetPart (m_CurrentPart, m_CurrentLength, m_CurrentValue);

        m_CurrentPartRestLength = m_CurrentLength;
    }
private:
    /// The space, which should be accessed.
    Data_ISplitSpace &m_Space;
    /// Current part no of the space.
    PartNo m_CurrentPart;
    /// Current value of current part
    SAPDB_Byte* m_CurrentValue;
    /// Length of m_CurrentValue.
    SAPDB_UInt m_CurrentLength;
    /// current part rest length
    SAPDB_UInt m_CurrentPartRestLength;
};
/*!
   @class Data_SplitSpaceReader
   @brief This handle is used to read from a Data_ISplitSpace
 */
class Data_SplitSpaceReader
{
public:
    /// redefine PartNo
    typedef Data_ISplitSpace::PartNo PartNo;
    /// definition of the result
    enum Result
    {
        ok, moreSpaceAvailable, noMoreToRead, spaceToSmall, reservedSpaceNotAligned
    };
    /*!
       @brief This is used to read from a split space. Space must be assigned.
       @param Space [in/out] 
       @param SpaceCanBeReleased [in] default is false
     */

    Data_SplitSpaceReader (Data_ISplitSpace &Space,
                           bool              SpaceCanBeReleased = false) // PTS 1114994 UH 2002-04-17
        : m_Space             (Space)
        , m_SpaceCanBeReleased(SpaceCanBeReleased) // PTS 1114994 UH 2002-04-17
    {
        Reset();
    }
    /// This is used to reset the internal values if the split space was changed outside. Space must be assigned.
    void Reset ()
    {
        m_CurrentPart = 0;
        (void)m_Space.CheckAlignment();
        m_Space.GetPart (m_CurrentPart, m_CurrentLength, m_CurrentValue);
        m_CurrentPartRestLength = m_CurrentLength;
    }
    /*!
        @param Length [in] - length of space to reserve
        @param Space [out] - pointer to continous space.
        @param NewResult [out] - ok, moreSpaceAvailable or spaceToSmall
        @brief The specified space is reserved, if length can be allocated in one peace.
     */
    void Reserve (SAPDB_UInt   Length,
                  SAPDB_Byte* &Space,
                  Result      &NewResult,
                  bool         checkAlignment = false)
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_SplitSpaceReader::Reserve", DataChain_Trace, 5);
        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "Length: " << Length);

        NewResult = ok;
        
        if ( Length > m_CurrentPartRestLength )
        {
            NewResult = spaceToSmall;
            return;
        }

        Space = m_CurrentValue + (m_CurrentLength - m_CurrentPartRestLength);

        if ( checkAlignment && ! m_Space.CheckAlignment(Space) )
        { 
            NewResult = reservedSpaceNotAligned;
            WriteToTrace ("Data_SplitSpaceReader::Reserve() => reservedSpaceNotAligned");
            return;
        }

        m_CurrentPartRestLength -= Length;

        if ( m_CurrentPartRestLength <= 0 )
            TakeNextPart ();

        if ( m_CurrentPartRestLength > 0 )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "more space available: " << m_CurrentPartRestLength);
            NewResult = moreSpaceAvailable;
        }
    }
    /*!
        @brief This is used, if target space is not available.
        @param TargetPartLength [in] - length of value
        @param TargetPart [out] - value to copy in
        @param Allocator [in/out] - used, space must be allocated
        @param bAllocatorWasUsed [out] - true, if allocator was used, needed for destroy
        @param NewResult [out] Result may be ok - Everything, which should be copied was copied.
                         moreSpaceAvailable - More can be moved to current space.
                         noMoreToRead - no more to read
                         spaceToSmall - if allocator fails
     */
    void Read (SAPDB_UInt              TargetPartLength,
               SAPDB_Byte*            &TargetPart,
               SAPDBMem_IRawAllocator &Allocator,
               bool                   &bAllocatorWasUsed,
               Result                 &NewResult)
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_SplitSpaceReader::Read1", DataChain_Trace, 5);
        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "length: " << TargetPartLength);

        bAllocatorWasUsed = false;
        
        if ( ! m_SpaceCanBeReleased ) // PTS 1114994 UH 2002-04-17
        {
            Reserve (TargetPartLength, TargetPart, NewResult);
            if ( spaceToSmall != NewResult )
                return;
        }

        TargetPart = reinterpret_cast <SAPDB_Byte*> (Allocator.Allocate(TargetPartLength));
        if ( NULL == TargetPart )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "space too small: targetpart is nil");
            NewResult = spaceToSmall;
            return;
        }
        
        bAllocatorWasUsed = true;
        NewResult         = ok;
        
        SAPDB_UInt CurrentCopyLength     = 0;
        SAPDB_UInt CurrentRestCopyLength = TargetPartLength;

        if ( m_CurrentPart > m_Space.CurrentPart() )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "no more to read1: " << m_Space.CurrentPart());
            NewResult = noMoreToRead;
            return;
        }

        do
        {
            if ( m_CurrentPartRestLength <= 0 )
            {
                TakeNextPart ();
                if ( m_CurrentPartRestLength <= 0 )
                {
                    SAPDBTRACE_WRITELN (DataChain_Trace, 6, "no more to read2: " << m_CurrentPartRestLength);
                    NewResult = noMoreToRead;
                    return;
                }
            }

            CurrentCopyLength = CurrentRestCopyLength;

            if ( CurrentCopyLength > m_CurrentPartRestLength )
                CurrentCopyLength = m_CurrentPartRestLength;

            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "copy: " << CurrentCopyLength);
            memcpy ( TargetPart     + (TargetPartLength - CurrentRestCopyLength  ),
                     m_CurrentValue + (m_CurrentLength  - m_CurrentPartRestLength),
                     CurrentCopyLength);

            CurrentRestCopyLength   -= CurrentCopyLength;
            m_CurrentPartRestLength -= CurrentCopyLength;
        }
        while ( CurrentRestCopyLength > 0 );

        if ( m_CurrentPartRestLength <= 0 )
            TakeNextPart ();

        if ( m_CurrentPartRestLength >  0 )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "more space available: " << m_CurrentPartRestLength);
            NewResult = moreSpaceAvailable;
        }
    }
    /*!
        @brief this is used, if TargetPart is completely available.
        @param TargetPart [in] - value to copy in
        @param TargetPartLength [in] - length of value
        @param NewResult [out] Result may be: ok - Everything, which should be copied was copied.
                                           moreSpaceAvailable - More can be moved to current space.
                                           noMoreToRead - no more to read
     */
    void Read (SAPDB_Byte* TargetPart,
               SAPDB_UInt  TargetPartLength,
               Result     &NewResult)
    {
        SAPDBTRACE_METHOD_DEBUG ("Data_SplitSpaceReader::Read2", DataChain_Trace, 5);
        SAPDBTRACE_WRITELN (DataChain_Trace, 6, "length: " << TargetPartLength);

        NewResult = ok;
        
        SAPDB_UInt CurrentCopyLength     = 0;
        SAPDB_UInt CurrentRestCopyLength = TargetPartLength;

        if ( m_CurrentPart > m_Space.CurrentPart() )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "no more to read1: " << m_Space.CurrentPart());
            NewResult = noMoreToRead;
            return;
        }

        do
        {
            if ( m_CurrentPartRestLength <= 0 )
                TakeNextPart ();

            if ( m_CurrentPartRestLength <= 0 )
            {
                SAPDBTRACE_WRITELN (DataChain_Trace, 6, "no more to read2: " << m_CurrentPartRestLength);
                NewResult = noMoreToRead;
                return;
            }

            CurrentCopyLength = CurrentRestCopyLength;

            if ( CurrentCopyLength > m_CurrentPartRestLength )
                CurrentCopyLength = m_CurrentPartRestLength;

            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "copy: " << CurrentCopyLength);
            memcpy ( TargetPart     + (TargetPartLength - CurrentRestCopyLength  ),
                     m_CurrentValue + (m_CurrentLength  - m_CurrentPartRestLength),
                     CurrentCopyLength);

            CurrentRestCopyLength   -= CurrentCopyLength;
            m_CurrentPartRestLength -= CurrentCopyLength;
        }
        while ( CurrentRestCopyLength > 0 );

        if ( m_CurrentPartRestLength <= 0 )
            TakeNextPart ();

        if ( m_CurrentPartRestLength > 0 )
        {
            SAPDBTRACE_WRITELN (DataChain_Trace, 6, "more space available: " << m_CurrentPartRestLength);
            NewResult = moreSpaceAvailable;
        }
    }
    /// Writes the members to the trace file.
    void WriteToTrace (const char* title = 0) const
    {
        Kernel_VTrace trace;
        if ( title != NULL )
            trace << title << NewLine;
        trace << "part: "          << m_CurrentPart
              << ", length: "      << m_CurrentLength
              << ", restlength: "  << m_CurrentPartRestLength
              << (m_SpaceCanBeReleased?"releasable":"not releasable") // PTS 1114994 UH 2002-04-17
              << FlushLine;
        if ( ! m_Space.IsAssigned() )
        {
            trace << "not assigned." << FlushLine;
            return;
        }
        trace << "space: length: " << m_Space.Length()
              << FlushLine;
        PartNo      part;
        SAPDB_UInt  length;
        SAPDB_Byte* value;
        if ( m_Space.CurrentPart().IsValid() )
        {
            for (part = 0; part <= m_Space.CurrentPart(); ++part)
            {
                m_Space.GetPart (part, length, value);
                trace << "spacepart: " << part
                      << ", length: "  << length
                      << FlushLine;
            }
        }
    }
    /// returns false if the alignent of the given pointer is not correct
    bool CheckAlignment ( SAPDB_Byte * reference )
    {
        return m_Space.CheckAlignment(reference);
    }
private:
    /// this is used, to take the next part of the space.
    void TakeNextPart()
    {
        ++m_CurrentPart;
        
        if ( m_CurrentPart > m_Space.CurrentPart() )
            return;

        m_Space.GetPart (m_CurrentPart, m_CurrentLength, m_CurrentValue);

        m_CurrentPartRestLength = m_CurrentLength;
    }
private:
    /// The space, which should be accessed.
    Data_ISplitSpace &m_Space;
    /// Current part no of the space.
    PartNo m_CurrentPart;
    /// Current value of current part
    SAPDB_Byte* m_CurrentValue;
    /// Length of m_CurrentValue.
    SAPDB_UInt m_CurrentLength;
    /// current part rest length 
    SAPDB_UInt m_CurrentPartRestLength;
    /// space can be released
    bool m_SpaceCanBeReleased; // PTS 1114994 UH 2002-04-17
};
#endif // Data_ISplitSpace_HPP
