/*
 * This file is part of the Score-P software (http://www.score-p.org)
 *
 * Copyright (c) 2009-2013,
 * RWTH Aachen University, Germany
 *
 * Copyright (c) 2009-2013,
 * Gesellschaft fuer numerische Simulation mbH Braunschweig, Germany
 *
 * Copyright (c) 2009-2014,
 * Technische Universitaet Dresden, Germany
 *
 * Copyright (c) 2009-2013,
 * University of Oregon, Eugene, USA
 *
 * Copyright (c) 2009-2013,
 * Forschungszentrum Juelich GmbH, Germany
 *
 * Copyright (c) 2009-2013,
 * German Research School for Simulation Sciences GmbH, Juelich/Aachen, Germany
 *
 * Copyright (c) 2009-2013,
 * Technische Universitaet Muenchen, Germany
 *
 * This software may be modified and distributed under the terms of
 * a BSD-style license.  See the COPYING file in the package base
 * directory for details.
 *
 */

/** @internal
 *
 *  @file
 *
 *  @brief      This layer is completely internal to the OTF2 library and should
 *              not be exposed to the end user. Its purpose is to abstract the
 *              handling of a memory buffer and to provide basic routines
 *              encapsulating the encoding and decoding of elementary data
 *              types. This layer will even be used when directly reading or
 *              writing from/to a file, since a certain amount of buffering is
 *              required to achieve a reasonable performance by reducing the
 *              number of I/O function calls as well as increasing the size of
 *              the data block to be read or written.
 */

#ifndef OTF2_BUFFER_H
#define OTF2_BUFFER_H


#include "otf2_id_map.h"


/* ___ Macros _______________________________________________________________ */



/** @brief Size of a chunk header in bytes.
 *  This is necessary to jump right behind the chunk header.
 *
 *  Please note: This macro directly depends on the implementation of
 *  otf2_buffer_write_header. So if you make any changes to that function
 *  you have to addapt this value accordingly.
 */
#define OTF2_CHUNK_HEADER_SIZE ( uint64_t )18

/** @brief Position of the last event information within the chunk header.
 *  This is necessary to write the number of the last event in a chunk when the
 *  chunk is completed.
 *
 *  Please note: This macro directly depends on the implementation of
 *  otf2_buffer_write_header. So if you make any changes to that function
 *  you have to addapt this value accordingly.
 */
#define OTF2_CHUNK_HEADER_LAST_EVENT ( uint64_t )10


#ifdef WORDS_BIGENDIAN
#define OTF2_HOST_ENDIANNESS OTF2_BIG_ENDIAN
#else
#define OTF2_HOST_ENDIANNESS OTF2_LITTLE_ENDIAN
#endif



/* ___ Type definitions. _____________________________________________________ */



/** @brief Defines if a buffer is chunked or not. Please see
    OTF2_ChunkMode_enum for a detailed description. */
typedef uint8_t OTF2_ChunkMode;

/** @brief Defines if a buffer is chunked or not. At the moment all event buffers
    are chunked, all definition buffer are not. */
enum OTF2_ChunkMode_enum
{
    /** The buffer is not chunked (e.g. all definition buffers). */
    OTF2_BUFFER_NOT_CHUNKED = 0,
    /** The buffer is chunked (e.g. all event buffers). */
    OTF2_BUFFER_CHUNKED     = 1
};


/** @brief Defines the kind of interaction with the memory buffer. Please see
    OTF2_BufferMode_enum for a detailed description. */
typedef uint8_t OTF2_BufferMode;

/** @brief Defines the kind of interaction with the memory buffer. */
enum OTF2_BufferMode_enum
{
    /** Buffer is in write-only mode (e.g. while tracing/profiling). */
    OTF2_BUFFER_WRITE  = 0,
    /** Buffer is in modify mode (e.g. while time synchronisation). This mode is
        similar to read mode but with the possibility to modify individual records
        (e.g. timestamps). If this mode was used, the resulting files are marked
        as 'modified' i.e. the library does not guarantee for correct results.
        So this mode is only for advanced users. */
    OTF2_BUFFER_MODIFY = 1,
    /** Buffer is in read-only mode (e.g. while analysing). */
    OTF2_BUFFER_READ   = 2
};


/** @brief The endianness modes. */
typedef uint8_t OTF2_EndiannessMode;
enum OTF2_EndiannessMode_enum
{
    OTF2_BIG_ENDIAN    = 0x23,
    OTF2_LITTLE_ENDIAN = 0x42
};


/** @brief Wrapper for the OTF2_InternalBufferEvents_enum
 */
typedef uint8_t OTF2_InternalBufferEvents;

/** @brief Defines internal memory buffer events. */
enum OTF2_InternalBufferEvents_enum
{
    /* Events with value 0 to 4 are used by OTF2 internal buffer events */

    /** OTF2 internal event to indicate the end of a memory chunk */
    OTF2_BUFFER_END_OF_CHUNK  = 0,
    /** OTF2 internal event to indicate the end of memory buffer */
    OTF2_BUFFER_END_OF_BUFFER = 1,
    /** OTF2 internal event to indicate that end of file is reached */
    OTF2_BUFFER_END_OF_FILE   = 2,
    /** OTF2 internal event to indicate internal chunk header */
    OTF2_BUFFER_CHUNK_HEADER  = 3,
    /** OTF2 event to indicate a timestamp event */
    OTF2_BUFFER_TIMESTAMP     = 5,
    /** OTF2 event to indicate an attribute list */
    OTF2_ATTRIBUTE_LIST       = 6
};


/** @brief Keeps all information needed for chunking.
    Please see otf2_chunks_struct for a detailed description. */
typedef struct otf2_chunk_struct otf2_chunk;


/** @brief Keeps all information needed for chunking. */
struct otf2_chunk_struct
{
    /** Pointer to the begin of the actual chunk. */
    uint8_t*    begin;
    /** Pointer to the end of the actual chunk. */
    uint8_t*    end;
    /** Number of the actual chunk (Number of first is One). */
    uint32_t    chunk_num;
    /** Number of the first event in the current chunk. */
    uint64_t    first_event;
    /** Number of the last event in the current chunk. */
    uint64_t    last_event;
    /** Pointer to the previous chunk. */
    otf2_chunk* prev;
    /** Pointer to the next chunk. */
    otf2_chunk* next;
};

/** @brief Keeps all information needed to save rewind points.
    Please see otf2_rewindlist_struct for a detailed description. */
typedef struct otf2_rewindlist_struct otf2_rewindlist;

/** @brief Keeps all information needed to save rewind points. */
struct otf2_rewindlist_struct
{
    /** Rewind region id (derived from region handle) to identify the rewind point. */
    uint32_t         id;
    /** Pointer to a saved copy of the OTF2_Buffer at the time of the rewind point. */
    OTF2_Buffer*     buffer;
    /** Pointer to a saved copy of the current otf2_chunk_struct of the OTF2_Buffer. */
    otf2_chunk*      chunk;
    /** Pointer to the previous rewind point. */
    otf2_rewindlist* prev;
    /** Pointer to the next rewind point. */
    otf2_rewindlist* next;
};

/** @brief Keeps all necessary information about the buffer. */
struct OTF2_Buffer
{
    OTF2_Archive* archive;

    /** The controlling object of this buffer. */
    void* operator;

    /** Mode of interaction with the buffer: write, modify or read. */
    OTF2_BufferMode buffer_mode;
    /** Defines if a buffer is chunked or not. Buffers keeping event records are
        always chunked, buffers keeping definitions are always not chunked. */
    OTF2_ChunkMode chunk_mode;

    /** Size of internal chunks in bytes (may be that for events or defs). */
    uint64_t chunk_size;

    /** Pointer to a file the data is stored. */
    OTF2_File* file;

    /** endianness mode of this buffer */
    OTF2_EndiannessMode endianness_mode;

    OTF2_FileType       file_type;
    OTF2_LocationRef    location_id;

    /** Last written timestamp. */
    OTF2_TimeStamp time;
    /** Position of last read timestamp. */
    uint8_t*       time_pos;


    /** Current writing position. */
    uint8_t* write_pos;
    /** Current reading position. */
    uint8_t* read_pos;

    /** Current start of record data. */
    uint8_t* record_data_pos;

    /** Pointer to the list of chunks. */
    otf2_chunk* chunk_list;
    /** Pointer to the actual chunk. */
    otf2_chunk* chunk;

    /** Size of the memory buffer in bytes. */
    uint64_t memory;

    /** Defines if buffer is finalized i.e. writing to buffer is finished. */
    bool finalized;

    /** Storage for the memory system to store per buffer allocator data */
    void* allocator_buffer_data;

    /** Pointer to the list of rewind points. */
    otf2_rewindlist* rewindlist;

    /** Pointer to the list of old chunks */
    otf2_chunk* old_chunk_list;
};


/* ___ Convenience function macros. __________________________________________ */



/* Switch of indention for all function macros. */
/* *INDENT-OFF* */

/** @name Convenience macros for functions where compressed and uncompressed
 *  version does exactly the same i.e. there is no compression possible.
 *  Please note: If you do implement one of these functions you also have to
 *  implement an uncompressed version and remove the macro below.
 *  @{ */
#define OTF2_Buffer_WriteUint8Full(b,v)  OTF2_Buffer_WriteUint8(b,v)
#define OTF2_Buffer_WriteUint16Full(b,v) OTF2_Buffer_WriteUint16(b,v)

#define OTF2_Buffer_WriteInt8Full(b,v)   OTF2_Buffer_WriteInt8(b,v)
#define OTF2_Buffer_WriteInt16Full(b,v)  OTF2_Buffer_WriteInt16(b,v)

#define OTF2_Buffer_WriteFloatFull(b,v)  OTF2_Buffer_WriteFloat(b,v)
#define OTF2_Buffer_WriteDoubleFull(b,v) OTF2_Buffer_WriteDouble(b,v)


#define OTF2_Buffer_ReadUint8Full(b,v)  OTF2_Buffer_ReadUint8(b,v)
#define OTF2_Buffer_ReadUint16Full(b,v) OTF2_Buffer_ReadUint16(b,v)

#define OTF2_Buffer_ReadInt8Full(b,v)   OTF2_Buffer_ReadInt8(b,v)
#define OTF2_Buffer_ReadInt16Full(b,v)  OTF2_Buffer_ReadInt16(b,v)

#define OTF2_Buffer_ReadFloatFull(b,v)  OTF2_Buffer_ReadFloat(b,v)
#define OTF2_Buffer_ReadDoubleFull(b,v) OTF2_Buffer_ReadDouble(b,v)

/* redirector for writing and reading an OTF2_AttributeValue
 * not ready for general use */
#define OTF2_Buffer_ReadAttributeValue( buffer, attribute_value ) \
    otf2_attribute_value_read_from_buffer( attribute_value, \
                                           type, \
                                           buffer, \
                                           NULL, NULL )
#define OTF2_Buffer_WriteAttributeValue( buffer, attribute_value ) \
    otf2_attribute_value_write_to_buffer( attribute_value, \
                                          type, \
                                          buffer )

/* redirector for writing and reading an OTF2_IdMap */
#define OTF2_Buffer_ReadIdMap(buffer, id_map)  otf2_id_map_read(buffer, id_map)
#define OTF2_Buffer_WriteIdMap(buffer, id_map)  otf2_id_map_write(buffer, id_map)

/** @} */

/* *INDENT-ON* */



/* ___ Interactions with the buffer handle. _________________________________ */



OTF2_Buffer*
OTF2_Buffer_New( OTF2_Archive*    archive,
                 void*            operator_,
                 uint64_t         chunkSize,
                 OTF2_BufferMode  bufferMode,
                 OTF2_ChunkMode   chunkMode,
                 OTF2_FileType    fileType,
                 OTF2_LocationRef location );

OTF2_ErrorCode
OTF2_Buffer_Delete( OTF2_Buffer* bufferHandle );


OTF2_ErrorCode
OTF2_Buffer_SwitchMode( OTF2_Buffer*    bufferHandle,
                        OTF2_BufferMode bufferMode );

OTF2_ErrorCode
otf2_buffer_open_file( OTF2_Buffer* bufferHandle );

OTF2_ErrorCode
OTF2_Buffer_SetLocationID( OTF2_Buffer*     bufferHandle,
                           OTF2_LocationRef location );

/* ___ Size operations for essential data types. ___________________________ */


static inline size_t
otf2_buffer_size_timestamp( uint64_t value )
{
    return 8;
}


static inline size_t
otf2_buffer_size_string( const char* string )
{
    return strlen( string ) + 1;
}


static inline size_t
otf2_buffer_size_uint8( uint8_t value )
{
    return 1;
}


size_t
otf2_buffer_array_size_uint8( uint32_t       numberOfMembers,
                              const uint8_t* members );


static inline size_t
otf2_buffer_size_int8( int8_t value )
{
    return 1;
}


static inline size_t
otf2_buffer_size_uint16( uint16_t value )
{
    return 2;
}


size_t
otf2_buffer_array_size_uint16( uint32_t        numberOfMembers,
                               const uint16_t* members );


static inline size_t
otf2_buffer_size_int16( int16_t value )
{
    return 2;
}


static inline size_t
otf2_buffer_size_uint32full( uint32_t value )
{
    return 4;
}


static inline size_t
otf2_buffer_size_uint32( uint32_t value )
{
    if ( value == 0 || value == UINT32_MAX )
    {
        return 1;
    }
    else if ( UINT32_C( 0x100 ) > value )
    {
        return 2;
    }
    else if ( UINT32_C( 0x10000 ) > value )
    {
        return 3;
    }
    else if ( UINT32_C( 0x1000000 ) > value )
    {
        return 4;
    }
    else
    {
        return 5;
    }
}


size_t
otf2_buffer_array_size_uint32( uint32_t        numberOfMembers,
                               const uint32_t* members );


static inline size_t
otf2_buffer_size_int32( int32_t value )
{
    if ( value == 0 )
    {
        return 1;
    }
    else if ( value >= 0 )
    {
        if ( INT32_C( 0x100 ) > value )
        {
            return 2;
        }
        else if ( INT32_C( 0x10000 ) > value )
        {
            return 3;
        }
        else if ( INT32_C( 0x1000000 ) > value )
        {
            return 4;
        }
        else
        {
            return 5;
        }
    }
    else
    {
        return 5;
    }
}


static inline size_t
otf2_buffer_size_uint64( uint64_t value )
{
    /* special compression for special values */
    if ( value == 0 || value == UINT64_MAX )
    {
        return 1;
    }
    else if ( UINT64_C( 0x100 ) > value )
    {
        return 2;
    }
    else if ( UINT64_C( 0x10000 ) > value )
    {
        return 3;
    }
    else if ( UINT64_C( 0x1000000 ) > value )
    {
        return 4;
    }
    else if ( UINT64_C( 0x100000000 ) > value )
    {
        return 5;
    }
    else if ( UINT64_C( 0x10000000000 ) > value )
    {
        return 6;
    }
    else if ( UINT64_C( 0x1000000000000 ) > value )
    {
        return 7;
    }
    else if ( UINT64_C( 0x100000000000000 ) > value )
    {
        return 8;
    }
    else
    {
        return 9;
    }
}


size_t
otf2_buffer_array_size_uint64( uint32_t        numberOfMembers,
                               const uint64_t* members );


static inline size_t
otf2_buffer_size_int64( int64_t value )
{
    if ( value == 0 )
    {
        return 1;
    }
    else if ( value >= 0 )
    {
        if ( INT64_C( 0x100 ) > value )
        {
            return 2;
        }
        else if ( INT64_C( 0x10000 ) > value )
        {
            return 3;
        }
        else if ( INT64_C( 0x1000000 ) > value )
        {
            return 4;
        }
        else if ( INT64_C( 0x100000000 ) > value )
        {
            return 5;
        }
        else if ( INT64_C( 0x10000000000 ) > value )
        {
            return 6;
        }
        else if ( INT64_C( 0x1000000000000 ) > value )
        {
            return 7;
        }
        else if ( INT64_C( 0x100000000000000 ) > value )
        {
            return 8;
        }
        else
        {
            return 9;
        }
    }
    else
    {
        return 9;
    }
}


static inline size_t
otf2_buffer_size_float32( float value )
{
    return 4;
}


static inline size_t
otf2_buffer_size_float64( double value )
{
    return 8;
}


static inline size_t
otf2_buffer_size_attribute_value( const OTF2_AttributeValue attributeValue )
{
    return 9;
}

size_t
otf2_buffer_array_size_attribute_value( uint32_t                   numberOfMembers,
                                        const OTF2_AttributeValue* members );

static inline size_t
otf2_buffer_size_id_map( const OTF2_IdMap* idMap )
{
    return otf2_id_map_get_size( idMap );
}


/* ___ Write operations for essential data types. ___________________________ */


/** @name Writing routines for basic data types.
 *
 *  Encode basic data types and write them with an internal compression to the
 *  buffer. Functions ending with 'Full' are writing uncompressed values. The
 *  uncompressed functions call should be used for values that may be altered
 *  later on e.g. timestamps. Other functions (without 'Full') write compressed
 *  if possible.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param value            Value to be written to the buffer.
 *
 *  @{
 */
static inline void
OTF2_Buffer_WriteInt8( OTF2_Buffer* bufferHandle,
                       const int8_t value )
{
    memcpy( bufferHandle->write_pos, &value, 1 );
    bufferHandle->write_pos++;
}

static inline void
OTF2_Buffer_WriteUint8( OTF2_Buffer*  bufferHandle,
                        const uint8_t value )
{
    memcpy( bufferHandle->write_pos, &value, 1 );
    bufferHandle->write_pos++;
}


static inline void
OTF2_Buffer_WriteInt16( OTF2_Buffer*  bufferHandle,
                        const int16_t value )
{
    memcpy( bufferHandle->write_pos, &value, 2 );
    bufferHandle->write_pos += 2;
}


static inline void
OTF2_Buffer_WriteUint16( OTF2_Buffer*   bufferHandle,
                         const uint16_t value )
{
    memcpy( bufferHandle->write_pos, &value, 2 );
    bufferHandle->write_pos += 2;
}


static inline void
OTF2_Buffer_WriteInt32( OTF2_Buffer*  bufferHandle,
                        const int32_t value )
{
    /* otf2_buffer_size_int32 returns the full size incl. the length byte. */
    uint8_t size = otf2_buffer_size_int32( value ) - 1;

    *bufferHandle->write_pos++ = size;

#ifdef WORDS_BIGENDIAN /* Store last bytes when big endian. */
    memcpy( bufferHandle->write_pos, ( const uint8_t* )&value + 4 - size, size );
#else /* Store first bytes when little endian. */
    memcpy( bufferHandle->write_pos, &value, size );
#endif      /* WORDS_BIGENDIAN. */

    bufferHandle->write_pos += size;
}


static inline void
OTF2_Buffer_WriteInt32Full( OTF2_Buffer*  bufferHandle,
                            const int32_t value )
{
    memcpy( bufferHandle->write_pos, &value, 4 );
    bufferHandle->write_pos += 4;
}


static inline void
OTF2_Buffer_WriteUint32( OTF2_Buffer*   bufferHandle,
                         const uint32_t value )
{
    /* otf2_buffer_size_uint32 returns the full size incl. the length byte. */
    uint8_t size = otf2_buffer_size_uint32( value ) - 1;

    /* special compression for special values */
    if ( 0 == size )
    {
        *bufferHandle->write_pos++ = value & 0xff;
        return;
    }

    *bufferHandle->write_pos++ = size;

#ifdef WORDS_BIGENDIAN /* Store last bytes when big endian. */
    memcpy( bufferHandle->write_pos, ( const uint8_t* )&value + 4 - size, size );
#else /* Store first bytes when little endian. */
    memcpy( bufferHandle->write_pos, &value, size );
#endif      /* WORDS_BIGENDIAN. */

    bufferHandle->write_pos += size;
}


static inline void
OTF2_Buffer_WriteUint32Full( OTF2_Buffer*   bufferHandle,
                             const uint32_t value )
{
    memcpy( bufferHandle->write_pos, &value, 4 );
    bufferHandle->write_pos += 4;
}


static inline void
OTF2_Buffer_WriteInt64( OTF2_Buffer*  bufferHandle,
                        const int64_t value )
{
    /* otf2_buffer_size_int64 returns the full size incl. the length byte. */
    uint8_t size = otf2_buffer_size_int64( value ) - 1;

    *bufferHandle->write_pos++ = size;

#ifdef WORDS_BIGENDIAN /* Store last bytes when big endian. */
    memcpy( bufferHandle->write_pos, ( const uint8_t* )&value + 8 - size, size );
#else /* Store first bytes when little endian. */
    memcpy( bufferHandle->write_pos, &value, size );
#endif      /* WORDS_BIGENDIAN. */

    bufferHandle->write_pos += size;
}


static inline void
OTF2_Buffer_WriteInt64Full( OTF2_Buffer*  bufferHandle,
                            const int64_t value )
{
    memcpy( bufferHandle->write_pos, &value, 8 );
    bufferHandle->write_pos += 8;
}


static inline void
OTF2_Buffer_WriteUint64( OTF2_Buffer*   bufferHandle,
                         const uint64_t value )
{
    /* otf2_buffer_size_uint364 returns the full size incl. the length byte. */
    uint8_t size = otf2_buffer_size_uint64( value ) - 1;

    /* special compression for special values */
    if ( 0 == size )
    {
        *bufferHandle->write_pos++ = value & 0xff;
        return;
    }

    *bufferHandle->write_pos++ = size;

#ifdef WORDS_BIGENDIAN /* Store last bytes when big endian. */
    memcpy( bufferHandle->write_pos, ( const uint8_t* )&value + 8 - size, size );
#else /* Store first bytes when little endian. */
    memcpy( bufferHandle->write_pos, &value, size );
#endif      /* WORDS_BIGENDIAN. */

    bufferHandle->write_pos += size;
}


static inline void
OTF2_Buffer_WriteUint64Full( OTF2_Buffer*   bufferHandle,
                             const uint64_t value )
{
    memcpy( bufferHandle->write_pos, &value, 8 );
    bufferHandle->write_pos += 8;
}


static inline void
OTF2_Buffer_WriteMetricValue( OTF2_Buffer*     bufferHandle,
                              OTF2_MetricValue value )
{
    UTILS_ASSERT( bufferHandle );
    OTF2_Buffer_WriteUint64( bufferHandle, value.unsigned_int );
}


static inline void
OTF2_Buffer_WriteFloat( OTF2_Buffer* bufferHandle,
                        const float  value )
{
    memcpy( bufferHandle->write_pos, &value, 4 );
    bufferHandle->write_pos += 4;
}


static inline void
OTF2_Buffer_WriteDouble( OTF2_Buffer* bufferHandle,
                         const double value )
{
    memcpy( bufferHandle->write_pos, &value, 8 );
    bufferHandle->write_pos += 8;
}


static inline void
OTF2_Buffer_WriteString( OTF2_Buffer* bufferHandle,
                         const char*  value )
{
    uint32_t str_length = strlen( value ) + 1;

    memcpy( bufferHandle->write_pos, value, str_length );

    bufferHandle->write_pos += str_length;
}
/** @} */


OTF2_ErrorCode
OTF2_Buffer_RequestNewChunk( OTF2_Buffer*   bufferHandle,
                             OTF2_TimeStamp time );

/** @brief Check if the buffer contains memory according to read data of the
 *         requested size.
 *
 *         The read position in the buffer is not changed by this function.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param size             Requested memory to read in bytes.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_GuaranteeRead( OTF2_Buffer* bufferHandle,
                           uint64_t     size )
{
    if ( bufferHandle->chunk->end - bufferHandle->read_pos < size )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Could not read record. Not enough memory left in buffer." );
    }

    return OTF2_SUCCESS;
}


/** @brief Check if the buffer contains memory according to write data of the
 *         requested size. It already accounts for the final
 *         OTF2_BUFFER_END_OF_CHUNK byte in the chunk.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param size             Requested memory to write in bytes.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_GuaranteeWrite( OTF2_Buffer* bufferHandle,
                            uint64_t     size )
{
    /* Ensures that we have space for the final chunk byte. */
    if ( bufferHandle->chunk->end - bufferHandle->write_pos <= size )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_SIZE_GIVEN,
                            "Requested size (%" PRIu64 ") to large for chunksize (%" PRIu64 ").",
                            size, bufferHandle->chunk_size );
    }

    return OTF2_SUCCESS;
}


/** @brief Check if the next record fits into the current chunk.
 *
 *  There are 3 possibilities:
 *
 *  (1) It fits: Do nothing.
 *
 *  (2) It does not fit and is not the last chunk: Go to next chunk, write the
 *      chunk header, and a current timestamp.
 *
 *  (3) It does not fit and is the last chunk: Flush and reset the buffer.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param time             Current timestamp.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_RecordRequest( OTF2_Buffer*   bufferHandle,
                           OTF2_TimeStamp time,
                           uint64_t       recordLength )
{
    /* Collect final record size, which includes the possible timestamp. */
    uint64_t required_size = recordLength;

    /* Add space for timestamp, if type implies a timestamp. */
    if ( otf2_file_type_has_timestamps( bufferHandle->file_type ) )
    {
        required_size += OTF2_BUFFER_TIMESTAMP_SIZE;
    }

    /* Check if record fits in the chunk. Furthermore ensure that there
     * is at least one byte left to write OTF2_BUFFER_END_OF_CHUNK. That's why
     * required_size needs to be smaller than free_memory. */
    uint64_t free_memory = bufferHandle->chunk->end - bufferHandle->write_pos;
    if ( required_size < free_memory )
    {
        return OTF2_SUCCESS;
    }

    OTF2_ErrorCode status = OTF2_Buffer_RequestNewChunk( bufferHandle, time );
    if ( status != OTF2_SUCCESS )
    {
        return UTILS_ERROR( status, "New chunk request failed!" );
    }

    /* If we got a new chunk, we still need to check, if the requested record
     * length fit into the chunk at all. */
    return OTF2_Buffer_GuaranteeWrite( bufferHandle, required_size );
}

/** @brief Get the free space left in the buffer in write mode.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline void
OTF2_Buffer_GetFree( OTF2_Buffer* bufferHandle,
                     uint64_t*    freeMemory )
{
    /* Ensure that there is at least one byte left to write
     * OTF2_BUFFER_END_OF_CHUNK. That's why we already substract 1 */
    *freeMemory = bufferHandle->chunk->end - bufferHandle->write_pos - 1;
}

/** @brief Request memory from buffer.
 *
 *  Before a record is written, it must be ensured that there is enough space
 *  left in the buffer. If there is not enough memory left, it is tried to
 *  allocate more memory or flush the buffer. Therefore, this call should always
 *  succeed even if a buffer is full. If the call returns an error, buffer
 *  handling failed and the recording should be aborted.
 *
 *  Please note: This function must only be called from definition writers. For
 *  event writers this functionality is included in OTF2_Buffer_WriteTimeStamp.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param recordLength     Size of requested record in bytes.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_WriteMemoryRequest( OTF2_Buffer* bufferHandle,
                                uint64_t     recordLength )
{
    /* Handle chunking and buffer overflow. */
    OTF2_ErrorCode status = OTF2_Buffer_RecordRequest( bufferHandle,
                                                       0, recordLength );
    if ( status != OTF2_SUCCESS )
    {
        return UTILS_ERROR( status,
                            "Chunk handling failed!" );
    }

    return OTF2_SUCCESS;
}

/** @brief Write a timestamp if necessary.
 *
 *  Checks if the the passed timestamp is newer than the last written one. If so
 *  it writes the new timestamp to the buffer.
 *
 *  In addition it requests memory from the buffer.
 *  Before a record is written, it must be ensured that there is enough space
 *  left in the buffer. If there is not enough memory left, it is tried to
 *  allocate more memory or flush the buffer. Therefore, this call should always
 *  succeed even if a buffer is full. If the call returns an error, buffer
 *  handling failed and the recording should be aborted.
 *
 *  Please note: This function must only be called from event writers. For
 *  definition writers this functionality is included in
 *  OTF2_Buffer_WriteMemoryRequest.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param time             Time the event took place.
 *  @param recordLength     Size of requested record in bytes.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_WriteTimeStamp( OTF2_Buffer*         bufferHandle,
                            const OTF2_TimeStamp time,
                            uint64_t             recordLength )
{
    OTF2_ErrorCode status = OTF2_SUCCESS;

    /* Check the time order early, because we may write
       timestamps in OTF2_Buffer_RecordRequest, too */
    if ( time < bufferHandle->time )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_ARGUMENT,
                            "Passed timestamp is smaller than last written one! (%" PRIu64 " < %" PRIu64 ")",
                            time, bufferHandle->time );
    }

    /* Handle chunking and buffer overflow. */
    status = OTF2_Buffer_RecordRequest( bufferHandle, time, recordLength );
    if ( status != OTF2_SUCCESS )
    {
        return UTILS_ERROR( status,
                            "Chunk handling failed!" );
    }

    /* Check if actual timestamp is newer than the last written one. If so write
     * it to the buffer otherwise return.
     * Enable to write a timestamp zero at the beginning of the buffer. */
    if ( ( time > bufferHandle->time ) ||
         ( time == 0 && time == bufferHandle->time ) )
    {
        OTF2_Buffer_WriteUint8( bufferHandle, OTF2_BUFFER_TIMESTAMP );
        OTF2_Buffer_WriteUint64Full( bufferHandle, time );

        bufferHandle->time = time;
    }


    /* Increment number of events. In writing mode only one counter is
       necessary. So only 'first_event' is incremented. */
    bufferHandle->chunk->first_event++;

    return OTF2_SUCCESS;
}


/** @brief Write initial value to record length byte.
 *
 *  This function must be called when a record length byte should be written.
 *  For the moment this functions sets the record length byte to zero, because
 *  correct record length is not known at this point. After writing the complete
 *  record OTF2_Buffer_WriteFinalRecordLength must be called to write correct
 *  record length.
 *
 *  @param bufferHandle          Initialized OTF2_Buffer instance.
 *  @param estimatedRecordLength Expected record length.
 */
static inline void
OTF2_Buffer_WriteInitialRecordLength( OTF2_Buffer* bufferHandle,
                                      uint64_t     estimatedRecordLength )
{
    /* Decision if we will use one byte or nine bytes to encode record length
     * depends on estimate of real record length */
    if ( estimatedRecordLength < UINT8_MAX )
    {
        /* Records with a length ranging from 0 to 254 bytes can be encoded in one byte */
        OTF2_Buffer_WriteUint8( bufferHandle, 0 );
    }
    else
    {
        /* For records larger than 254 bytes the record length byte will be
         * set to 0xFFh and the record length is encoded in the immediately
         * following eight bytes. */
        OTF2_Buffer_WriteUint8( bufferHandle, UINT8_MAX );
        OTF2_Buffer_WriteUint64Full( bufferHandle, 0 );
    }
    /* Store position where current record data starts */
    bufferHandle->record_data_pos = bufferHandle->write_pos;
}


/** @brief Write final value to record length byte.
 *
 *  This function must be called after initializing record length byte by
 *  calling OTF2_Buffer_WriteInitialRecordLength. This function will calculate
 *  record length and write the result to record length byte if the record
 *  uses less than 255 bytes. For records with a length of 255 bytes or
 *  larger the initial value of record length byte is left unchanged and
 *  length information is encoded in the immediately following 8 bytes.
 *
 *  @param bufferHandle          Initialized OTF2_Buffer instance.
 *  @param estimatedRecordLength Expected record length.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_WriteFinalRecordLength( OTF2_Buffer* bufferHandle,
                                    uint64_t     estimatedRecordLength )
{
    /* Determine record length. */
    uint64_t record_length = bufferHandle->write_pos - bufferHandle->record_data_pos;

    if ( estimatedRecordLength < UINT8_MAX )
    {
        if ( record_length >= UINT8_MAX )
        {
            /* Wrong estimate of record length */
            return OTF2_ERROR_E2BIG;
        }
        /* Write record length byte (as uint8_t) one byte before the record
         * data was written. */
        uint8_t record_length_byte = record_length;
        memcpy( bufferHandle->record_data_pos - 1, &record_length_byte, 1 );
    }
    else
    {
        /* Special handling of records larger than 254 bytes,
         * record length byte remains unchanged,
         * length is encoded (as uint64_t) in the additional eight bytes
         * before the record data was written */
        memcpy( bufferHandle->record_data_pos - 8, &record_length, 8 );
    }
    /* Reset record data position */
    bufferHandle->record_data_pos = NULL;

    return OTF2_SUCCESS;
}


OTF2_ErrorCode
OTF2_Buffer_FlushBuffer( OTF2_Buffer* bufferHandle );


/* ___ Read operations for essential data types. ____________________________ */


/** Swap the bytes in an 2-byte word. */
static inline uint16_t
otf2_swap16( uint16_t v16 )
{
    return ( ( v16 >> 8 ) & 0xFFu ) | ( ( v16 & 0xFFu ) << 8 );
}

/** Swap the bytes in an 4-byte word. */
static inline uint32_t
otf2_swap32( uint32_t v32 )
{
    return otf2_swap16( ( v32 >> 16 ) & 0xFFFFu ) |
           ( ( uint32_t )otf2_swap16( v32 & 0xFFFFu ) << 16 );
}

/** Swap the bytes in an 8-byte word. */
static inline uint64_t
otf2_swap64( uint64_t v64 )
{
    return otf2_swap32( ( v64 >> 32 ) & 0xFFFFFFFFu ) |
           ( ( uint64_t )otf2_swap32( v64 & 0xFFFFFFFFu ) << 32 );
}


static inline void
OTF2_Buffer_ReadUint64Full( OTF2_Buffer* bufferHandle,
                            uint64_t*    returnValue );

/** @brief Read a timestamp if necessary.
 *
 *  It checks first if the end of written data in the buffer is reached and
 *  handles this event (Please note: This is not implemented now, it just
 *  returns an error). After this it checks if the record is a timestamp. If so
 *  the timestamp is updated. It always delivers an actual timestamp in time.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param time             Pointer to the time that should be updated.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_ReadTimeStamp( OTF2_Buffer*    bufferHandle,
                           OTF2_TimeStamp* time )
{
    UTILS_ASSERT( bufferHandle );

    /* Check if end of buffer is reached. */
    if ( bufferHandle->read_pos >= bufferHandle->chunk->end )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_CALL,
                            "Tried to read after end of internal chunk !" );
    }

    /* Check if the next record is a timestamp. */
    if ( OTF2_BUFFER_TIMESTAMP == *bufferHandle->read_pos )
    {
        bufferHandle->read_pos++;
        OTF2_Buffer_ReadUint64Full( bufferHandle, &( bufferHandle->time ) );
        bufferHandle->time_pos = bufferHandle->read_pos
                                 - OTF2_BUFFER_TIMESTAMP_SIZE;
    }

    *time = bufferHandle->time;

    return OTF2_SUCCESS;
}


/** @name Reading routines for basic data types.
 *
 *  Read and decode basic data types from the buffer. Functions ending with
 *  'Full' are reading uncompressed values. The uncompressed functions call
 *  should be used for values that are per default not compressed e.g.
 *  timestamps.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param returnValue      Pointer to the read value.
 *
 *  @{
 */

static inline void
OTF2_Buffer_ReadUint8( OTF2_Buffer* bufferHandle,
                       uint8_t*     returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    memcpy( returnValue, bufferHandle->read_pos, 1 );
    bufferHandle->read_pos++;
}


static inline void
OTF2_Buffer_ReadInt8( OTF2_Buffer* bufferHandle,
                      int8_t*      returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    memcpy( returnValue, bufferHandle->read_pos, 1 );
    bufferHandle->read_pos++;
}


static inline void
OTF2_Buffer_ReadUint16( OTF2_Buffer* bufferHandle,
                        uint16_t*    returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    memcpy( returnValue, bufferHandle->read_pos, 2 );
    bufferHandle->read_pos += 2;

    if ( bufferHandle->endianness_mode != OTF2_HOST_ENDIANNESS )
    {
        *returnValue = otf2_swap16( *returnValue );
    }
}


static inline void
OTF2_Buffer_ReadInt16( OTF2_Buffer* bufferHandle,
                       int16_t*     returnValue )
{
    union
    {
        uint16_t u16;
        int16_t  s16;
    } u;

    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    OTF2_Buffer_ReadUint16( bufferHandle, &u.u16 );
    *returnValue = u.s16;
}


static inline OTF2_ErrorCode
OTF2_Buffer_ReadUint32( OTF2_Buffer* bufferHandle,
                        uint32_t*    returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    uint8_t size = *bufferHandle->read_pos++;

    if ( size == 0 )
    {
        *returnValue = 0;
        return OTF2_SUCCESS;
    }

    if ( size == 0xff )
    {
        *returnValue = UINT32_MAX;
        return OTF2_SUCCESS;
    }

    if ( size > 4 )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Invalid size in compressed length byte." );
    }

    /* Initialize all bytes with zero. */
    *returnValue = ( uint32_t )0;

    uint8_t* write_pos = ( uint8_t* )returnValue;
    if ( bufferHandle->endianness_mode == OTF2_BIG_ENDIAN )
    {
        /* Store value in last bytes when big endian. */
        write_pos += 4 - size;
    }

    memcpy( write_pos, bufferHandle->read_pos, size );

    bufferHandle->read_pos += size;

    if ( bufferHandle->endianness_mode != OTF2_HOST_ENDIANNESS )
    {
        *returnValue = otf2_swap32( *returnValue );
    }

    return OTF2_SUCCESS;
}


static inline OTF2_ErrorCode
OTF2_Buffer_ReadInt32( OTF2_Buffer* bufferHandle,
                       int32_t*     returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    union
    {
        uint32_t u32;
        int32_t  s32;
    } u;

    OTF2_ErrorCode status = OTF2_Buffer_ReadUint32( bufferHandle, &u.u32 );
    if ( status != OTF2_SUCCESS )
    {
        return status;
    }
    *returnValue = u.s32;

    return OTF2_SUCCESS;
}


static inline void
OTF2_Buffer_ReadUint32Full( OTF2_Buffer* bufferHandle,
                            uint32_t*    returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    memcpy( returnValue, bufferHandle->read_pos, 4 );
    bufferHandle->read_pos += 4;

    if ( bufferHandle->endianness_mode != OTF2_HOST_ENDIANNESS )
    {
        *returnValue = otf2_swap32( *returnValue );
    }
}


static inline void
OTF2_Buffer_ReadInt32Full( OTF2_Buffer* bufferHandle,
                           int32_t*     returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    union
    {
        uint32_t u32;
        int32_t  s32;
    } u;

    OTF2_Buffer_ReadUint32Full( bufferHandle, &u.u32 );
    *returnValue = u.s32;
}


static inline OTF2_ErrorCode
OTF2_Buffer_ReadUint64( OTF2_Buffer* bufferHandle,
                        uint64_t*    returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    uint8_t size = *bufferHandle->read_pos++;

    if ( size == 0 )
    {
        *returnValue = 0;
        return OTF2_SUCCESS;
    }

    if ( size == 0xff )
    {
        *returnValue = UINT64_MAX;
        return OTF2_SUCCESS;
    }

    if ( size > 8 )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Invalid size in compressed length byte." );
    }

    /* Initialize all bytes with zero. */
    *returnValue = ( uint64_t )0;

    uint8_t* write_pos = ( uint8_t* )returnValue;
    if ( bufferHandle->endianness_mode == OTF2_BIG_ENDIAN )
    {
        /* Store value in last bytes when big endian. */
        write_pos += 8 - size;
    }

    memcpy( write_pos, bufferHandle->read_pos, size );

    bufferHandle->read_pos += size;

    if ( bufferHandle->endianness_mode != OTF2_HOST_ENDIANNESS )
    {
        *returnValue = otf2_swap64( *returnValue );
    }

    return OTF2_SUCCESS;
}


static inline OTF2_ErrorCode
OTF2_Buffer_ReadInt64( OTF2_Buffer* bufferHandle,
                       int64_t*     returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    union
    {
        uint64_t u64;
        int64_t  s64;
    } u;

    OTF2_ErrorCode status = OTF2_Buffer_ReadUint64( bufferHandle, &u.u64 );
    if ( status != OTF2_SUCCESS )
    {
        return status;
    }
    *returnValue = u.s64;

    return OTF2_SUCCESS;
}


static inline void
OTF2_Buffer_ReadUint64Full( OTF2_Buffer* bufferHandle,
                            uint64_t*    returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    memcpy( returnValue, bufferHandle->read_pos, 8 );
    bufferHandle->read_pos += 8;

    if ( bufferHandle->endianness_mode != OTF2_HOST_ENDIANNESS )
    {
        *returnValue = otf2_swap64( *returnValue );
    }
}


static inline OTF2_ErrorCode
OTF2_Buffer_ReadMetricValue( OTF2_Buffer*      bufferHandle,
                             OTF2_MetricValue* returnValue )
{
    UTILS_ASSERT( returnValue );
    return OTF2_Buffer_ReadUint64( bufferHandle, &returnValue->unsigned_int );
}


static inline void
OTF2_Buffer_ReadInt64Full( OTF2_Buffer* bufferHandle,
                           int64_t*     returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    union
    {
        uint64_t u64;
        int64_t  s64;
    } u;

    OTF2_Buffer_ReadUint64Full( bufferHandle, &u.u64 );
    *returnValue = u.s64;
}


static inline void
OTF2_Buffer_ReadFloat( OTF2_Buffer* bufferHandle,
                       float*       returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    union
    {
        uint32_t u32;
        float    f;
    } u;

    OTF2_Buffer_ReadUint32Full( bufferHandle, &u.u32 );
    *returnValue = u.f;
}


static inline void
OTF2_Buffer_ReadDouble( OTF2_Buffer* bufferHandle,
                        double*      returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    union
    {
        uint64_t u64;
        double   d;
    } u;

    OTF2_Buffer_ReadUint64Full( bufferHandle, &u.u64 );
    *returnValue = u.d;
}


static inline OTF2_ErrorCode
OTF2_Buffer_ReadString( OTF2_Buffer* bufferHandle,
                        const char** returnValue )
{
    UTILS_ASSERT( bufferHandle );
    UTILS_ASSERT( returnValue );

    if ( NULL == memchr( bufferHandle->read_pos, '\0', bufferHandle->chunk->end - bufferHandle->read_pos ) )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Searching for string's terminating null byte failed!" );
    }

    *returnValue            = ( const char* )bufferHandle->read_pos;
    bufferHandle->read_pos += strlen( *returnValue ) + 1;

    return OTF2_SUCCESS;
}

/** @} */

/* ___ Skip operations for essential data types. ____________________________ */



/** @brief Skip an uncompressed data type.
 *
 *  This function is triggered by macros defined in the header named
 *  OTF2_Buffer_Skip<type> (e.g. unit8 or int64). Please refer to them
 *  to get detailed information on how to use them.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_Skip( OTF2_Buffer* bufferHandle,
                  uint64_t     size )
{
    UTILS_ASSERT( bufferHandle );

    if ( size >= ( bufferHandle->chunk->end - bufferHandle->read_pos ) )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_ARGUMENT,
                            "Could not skip. Invalid size argument." );
    }

    bufferHandle->read_pos += size;

    return OTF2_SUCCESS;
}


/** @brief Skip a compressed data type.
 *
 *  Warning: This function is not allowed to be used by any other module even
 *  though it is defined external. This function is triggered by macros defined
 *  in the header named OTF2_Buffer_Skip<type> (e.g. unit8 or int64). Please
 *  refer to them to get detailed information on how to use them.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_SkipCompressed( OTF2_Buffer* bufferHandle )
{
    UTILS_ASSERT( bufferHandle );

    /* Check if there is 1 byte available that contains length information */
    if ( 1 >= ( bufferHandle->chunk->end - bufferHandle->read_pos ) )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Reading length information of compressed data type failed!" );
    }

    /* Get number of bytes used for data payload */
    uint8_t size = bufferHandle->read_pos[ 0 ];

    /* 0xff is the special compression value for OTF2_UNDEFINED_UINT(32|64)
       nothing to skip here */
    if ( size == 0xff )
    {
        size = 0;
    }

    /* Valid compression size values never exceed 8 bytes */
    if ( size > 8 )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Invalid size in compressed length byte." );
    }

    /* Check if there is enough memory for data payload (including length byte) */
    if ( size + 1 >= ( bufferHandle->chunk->end - bufferHandle->read_pos ) )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Buffer does not contain enough memory to read compressed data type!" );
    }

    /* Skip compressed data type */
    bufferHandle->read_pos += size + 1;

    return OTF2_SUCCESS;
}

/** @} */

/* ___ Additional get and set routines. _____________________________________ */



OTF2_ErrorCode
OTF2_Buffer_ReadGetNextChunk( OTF2_Buffer* bufferHandle );

OTF2_ErrorCode
OTF2_Buffer_ReadGetPreviousChunk( OTF2_Buffer* bufferHandle );

OTF2_ErrorCode
OTF2_Buffer_ReadSeekChunk( OTF2_Buffer* bufferHandle,
                           uint64_t     position );

OTF2_ErrorCode
OTF2_Buffer_ReadSeekChunkTime( OTF2_Buffer* bufferHandle,
                               uint64_t     time,
                               bool*        found );

/** @brief Get position.
 *
 *  Checks the mode of interaction with the buffer and stores either the actual
 *  write or read position in the given pointer.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param position         Pointer the returned position.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline void
OTF2_Buffer_GetPosition( OTF2_Buffer* bufferHandle,
                         uint8_t**    position )
{
    UTILS_ASSERT( bufferHandle );

    if ( bufferHandle->buffer_mode == OTF2_BUFFER_WRITE )
    {
        *position = bufferHandle->write_pos;
    }
    else
    {
        *position = bufferHandle->read_pos;
    }
}


/** @brief Set position.
 *
 *  Checks the mode of interaction with the buffer and sets either the actual
 *  write or read position or both to the given position.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param position         Pointer to the position.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_SetPosition( OTF2_Buffer* bufferHandle,
                         uint8_t*     position )
{
    UTILS_ASSERT( bufferHandle );

    if ( ( position < bufferHandle->chunk->begin ) ||
         ( position >= bufferHandle->chunk->end ) )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_ARGUMENT,
                            "Position points outside the buffer's memory!" );
    }


    if ( bufferHandle->buffer_mode == OTF2_BUFFER_WRITE )
    {
        bufferHandle->write_pos = position;
        return OTF2_SUCCESS;
    }

    if ( bufferHandle->buffer_mode == OTF2_BUFFER_MODIFY )
    {
        bufferHandle->write_pos = position;
        bufferHandle->read_pos  = position;
        return OTF2_SUCCESS;
    }

    bufferHandle->read_pos = position;
    return OTF2_SUCCESS;
}


/** @brief Get position of the begin of the current chunk.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param position         Pointer to the returned position.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_GetBeginOfChunk( OTF2_Buffer* bufferHandle,
                             uint8_t**    position )
{
    UTILS_ASSERT( bufferHandle );

    *position = bufferHandle->chunk->begin + OTF2_CHUNK_HEADER_SIZE;

    return OTF2_SUCCESS;
}


/** @brief Get the number of first and last event in the current chunk.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param firstEvent       Pointer to the returned number of the first event.
 *  @param lastEvent        Pointer to the returned number of the last event.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_GetNumberEvents( OTF2_Buffer* bufferHandle,
                             uint64_t*    firstEvent,
                             uint64_t*    lastEvent )
{
    UTILS_ASSERT( bufferHandle );

    *firstEvent = bufferHandle->chunk->first_event;
    *lastEvent  = bufferHandle->chunk->last_event;

    return OTF2_SUCCESS;
}


/** @brief Get position of last read timestamp.
 *
 *  The position of the last read timestamp is the valid timestamp for the
 *  current position as well.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param timePos          Pointer to the returned position.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_GetPositionTimeStamp( OTF2_Buffer* bufferHandle,
                                  uint8_t**    timePos )
{
    UTILS_ASSERT( bufferHandle );

    if ( bufferHandle->buffer_mode == OTF2_BUFFER_WRITE )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_CALL,
                            "This is not allowed in writing mode!" );
    }

    *timePos = bufferHandle->time_pos;

    return OTF2_SUCCESS;
}


OTF2_ErrorCode
OTF2_Buffer_SetPositionTimeStamp( OTF2_Buffer* bufferHandle,
                                  uint8_t*     timePos );


OTF2_ErrorCode
OTF2_Buffer_RewriteTimeStamp( OTF2_Buffer*   bufferHandle,
                              OTF2_TimeStamp time );


/* ___ Routines to manage the rewind list. __________________________________ */


OTF2_ErrorCode
OTF2_Buffer_StoreRewindPoint( OTF2_Buffer* bufferHandle,
                              uint32_t     id );

OTF2_ErrorCode
OTF2_Buffer_ClearRewindPoint( OTF2_Buffer* bufferHandle,
                              uint32_t     id );

OTF2_ErrorCode
OTF2_Buffer_Rewind( OTF2_Buffer* bufferHandle,
                    uint32_t     id );



/* ___ Routines to check buffer consistency . _______________________________ */



/** @brief Read record length information and check if buffer contains enough
 *         memory according to this information.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *  @param recordLength     Returns length of recent record. Set this argument to
 *                          NULL if you are not interested in this information.
 *
 *  @return                 Returns OTF2_SUCCESS if successful. If recordLength
 *                          parameter is not NULL, then record length information
 *                          read for recent record will be set to this variable.
 *                          Otherwise an error code will be returned.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_GuaranteeRecord( OTF2_Buffer* bufferHandle,
                             uint64_t*    recordLength )
{
    UTILS_ASSERT( bufferHandle );

    /* Check if there is enough memory for record length byte */
    if ( bufferHandle->chunk->end - bufferHandle->read_pos <= 1 )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Could not read record. Not enough memory left in buffer." );
    }

    /* Read first record length byte */
    uint8_t small_record_length;
    OTF2_Buffer_ReadUint8( bufferHandle, &small_record_length );
    if ( small_record_length != UINT8_MAX )
    {
        /* Record length is encoded in this byte */

        /* Check if the buffer contains enough bytes according to record length information */
        if ( bufferHandle->chunk->end - bufferHandle->read_pos <= small_record_length )
        {
            return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                                "Could not read record. Not enough memory left in buffer." );
        }

        if ( recordLength != NULL )
        {
            *recordLength = small_record_length;
        }
    }
    else
    {
        /* Length of large records is encoded in additional 8 bytes */

        /* Read additional 8 bytes to get length of large records */
        if ( bufferHandle->chunk->end - bufferHandle->read_pos <= 8 )
        {
            return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                                "Could not read record. Not enough memory left in buffer." );
        }

        uint64_t large_record_length;
        OTF2_Buffer_ReadUint64Full( bufferHandle, &large_record_length );

        /* Check if the buffer contains enough bytes according to record length information */
        if ( bufferHandle->chunk->end - bufferHandle->read_pos <= large_record_length )
        {
            return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                                "Could not read record. Not enough memory left in buffer." );
        }

        if ( recordLength != NULL )
        {
            *recordLength = large_record_length;
        }
    }

    return OTF2_SUCCESS;
}

/** @brief Check if the buffer contains enough memory according to read a (compressed) integer value.
 *         The read position in the buffer is not changed by this function.
 *
 *  @param bufferHandle     Initialized OTF2_Buffer instance.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 */
static inline OTF2_ErrorCode
OTF2_Buffer_GuaranteeCompressed( OTF2_Buffer* bufferHandle )
{
    UTILS_ASSERT( bufferHandle );

    /* Check if there is enough memory for compressed header byte */
    if ( bufferHandle->chunk->end - bufferHandle->read_pos <= 1 )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Could not read record. Not enough memory left in buffer." );
    }

    uint8_t size = bufferHandle->read_pos[ 0 ];

    /* 0xff is the special compression value for OTF2_UNDEFINED_UINT(32|64)
       nothing to guarantee here */
    if ( size == 0xff )
    {
        size = 0;
    }

    /* Valid compression size values never exceed 8 bytes */
    if ( size > 8 )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Invalid size in compressed length byte." );
    }

    /* Check if there is enough memory for record payload (including compression size byte ) */
    if ( bufferHandle->chunk->end - bufferHandle->read_pos <= size + 1 )
    {
        return UTILS_ERROR( OTF2_ERROR_INVALID_DATA,
                            "Could not read record. Not enough memory left in buffer." );
    }

    return OTF2_SUCCESS;
}


#endif /* !OTF2_BUFFER_H */
