//======================================================================================================================
//
//  This file is part of waLBerla. waLBerla 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 3 of
//  the License, or (at your option) any later version.
//
//  waLBerla 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 waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file RecvBuffer.h
//! \ingroup core
//! \author Klaus Iglberger
//! \brief Implementation of a MPI receive buffer.
//!
//! Copyright (C) 2009 Klaus Iglberger
//! Taken from "pe Physics Engine" with small changes
//
//======================================================================================================================

#pragma once

#include "waLBerlaDefinitions.h"
#include "BufferSizeTrait.h"
#include "SendBuffer.h"

#include "core/debug/Debug.h"
#include "core/Sanitizer.h"

#include <boost/mpl/logical.hpp>
#include <boost/type_traits/is_enum.hpp>
#include <boost/type_traits/is_arithmetic.hpp>
#include <boost/utility/enable_if.hpp>

#include <algorithm>


namespace walberla {
namespace mpi {

//======================================================================================================================
//
//  CLASS DEFINITION
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\brief Implementation of a MPI receive buffer.
// \ingroup mpi
//
// The RecvBuffer class is a special purpose implementation for the MPI communication
// functionality. It offers a convenient and safe access to the received data values
// even for mixed-type communication. The following example gives an impression of the
// usage of the RecvBuffer class:

   \code
   using namespace mpi;

   // Preparing a receive buffer for an incoming message of 32 bytes
   RecvBuffer buffer;
   buffer.resize( 32 );

   // Receiving a MPI message from process 0 in a blocking MPI_Recv() function
   MPI_Status status;
   MPI_Recv( buffer.ptr(), 32, MPI_BYTE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, status );

   // Extracting a double and an integer from the buffer
   double d;
   int i;
   buffer >> d >> i;
   \endcode
// For another example see also the Buffer Unit Test in File BufferTest.cpp
//
// Note that the order of data values in the receive buffer is depending on the message
// sent via MPI. See also the SendBuffer class description for the sender side of the MPI
// communication.
*/
template< typename T = unsigned char >  // Element type
class GenericRecvBuffer
{
public:
   //**Type definitions*************************************************************************************************
   typedef T  ElementType;  //!< Type of the receive buffer elements.
   //*******************************************************************************************************************

   //**Constructors*****************************************************************************************************
   /*!\name Constructors */
   //@{
   explicit inline GenericRecvBuffer();
            inline GenericRecvBuffer( const GenericRecvBuffer & rb );
   template< typename G >
   explicit inline GenericRecvBuffer( GenericSendBuffer<T,G> & sb );
   //@}
   //*******************************************************************************************************************

   //**Destructor*******************************************************************************************************
   /*!\name Destructor */
   //@{
   inline ~GenericRecvBuffer();
   //@}
   //*******************************************************************************************************************

   //**Assignment operator**********************************************************************************************
   /*!\name Assignment operator */
   //@{
   GenericRecvBuffer& operator=( const GenericRecvBuffer& sb );
   template< typename G >
   GenericRecvBuffer& operator=( const GenericSendBuffer<T,G> & sb );
   //@}
   //*******************************************************************************************************************

   //**Get functions****************************************************************************************************
   /*!\name Get functions */
   //@{
   inline size_t maxSize () const;
   inline size_t size    () const;
   inline size_t capacity() const;
   inline bool   isEmpty () const;
   //@}
   //*******************************************************************************************************************

   //**Operators********************************************************************************************************
   /*!\name Operators */
   //@{
   template< typename V >
   typename boost::enable_if< boost::mpl::or_< boost::is_arithmetic<V>, boost::is_enum<V> >,
                              GenericRecvBuffer& >::type
   operator>>( V& value );
   //@}
   //*******************************************************************************************************************

   //**Utility functions************************************************************************************************
   /*!\name Utility functions */
   //@{
                          inline T*   ptr    () const;
                          inline void reserve( size_t newCapacity );
                          inline void resize ( size_t newSize     );
   template< typename V >        void peek   ( V& value ) const;
                          inline T *  skip   ( size_t elements    );
                          inline void clear  ();
                          inline void reset  ();
                          inline void readDebugMarker( const char * marker );
   //@}
   //*******************************************************************************************************************

private:
   //**Member variables*************************************************************************************************
   /*!\name Member variables */
   //@{
   size_t capacity_;  //!< The current size of the receive buffer.
   T* begin_;         //!< Pointer to the first element of the receive buffer.
   T* cur_;           //!< Pointer to the current element of the receive buffer.
   T* end_;           //!< Pointer to the last element of the receive buffer.
   //@}
   //*******************************************************************************************************************

   //**Utility functions************************************************************************************************
   /*!\name Utility functions */
   //@{
   template< typename V >
   typename boost::enable_if< boost::mpl::or_< boost::is_arithmetic<V>, boost::is_enum<V> >,
                              GenericRecvBuffer& >::type
   get( V& value );
   //@}
   //*******************************************************************************************************************



   //**Compile time checks**********************************************************************************************
   static_assert( boost::is_arithmetic<T>::value, "SendBuffer<T>: T has to be native datatype" ) ;
   //*******************************************************************************************************************
};
//**********************************************************************************************************************

typedef GenericRecvBuffer<> RecvBuffer;



//======================================================================================================================
//
//  CONSTRUCTORS
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\brief Standard constructor for RecvBuffer.
*/
template< typename T >  // Element type
inline GenericRecvBuffer<T>::GenericRecvBuffer()
   : capacity_( 0    )  // Capacity of the receive buffer
   , begin_   ( NULL )  // Pointer to the first element
   , cur_     ( NULL )  // Pointer to the current element
   , end_     ( NULL )  // Pointer to the last element
{}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Copy constructor for RecvBuffer.
//
// \param rb The receive buffer to be copied.
*/
template< typename T >  // Element type
inline GenericRecvBuffer<T>::GenericRecvBuffer( const GenericRecvBuffer& rb )
   : capacity_( rb.size()        )  // Capacity of the receive buffer
   , begin_   ( new T[capacity_] )  // Pointer to the first element
   , cur_     ( begin_           )  // Pointer to the current element
   , end_     ( begin_+capacity_ )  // Pointer to the last element
{
   for( size_t i=0; i<capacity_; ++i )
      cur_[i] = rb.cur_[i];
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Constructor for RecvBuffer.
//
// \param sb The send buffer whose content is transfered to this receive buffer.
*/
template< typename T >  // Element type
template< typename G >
inline GenericRecvBuffer<T>::GenericRecvBuffer( GenericSendBuffer<T,G> & sb )
   : capacity_( sb.capacity() )
   , begin_( sb.begin_ )
   , cur_( sb.begin_ )
   , end_( sb.end_ )
{
   sb.begin_ = new T[0];
   sb.cur_   = sb.begin_;
   sb.end_   = sb.begin_;
}
//**********************************************************************************************************************



//======================================================================================================================
//
//  DESTRUCTOR
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\brief Destructor for RecvBuffer.
*/
template< typename T >  // Element type
inline GenericRecvBuffer<T>::~GenericRecvBuffer()
{
   delete [] begin_;
}
//**********************************************************************************************************************




//======================================================================================================================
//
//  ASSIGNMENT OPERATOR
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\brief Copy assignment operator for RecvBuffer.
//
// \param rb The receive buffer to be copied.
// \return Reference to the assigned receive buffer.
*/
template< typename T >  // Element type
GenericRecvBuffer<T>& GenericRecvBuffer<T>::operator=( const GenericRecvBuffer& rb )
{
   if( &rb == this ) return *this;

   if( rb.size() > capacity_ ) {
      T* newBegin( new T[rb.size()] );
      end_ = std::copy( rb.cur_, rb.end_, newBegin );
      std::swap( begin_, newBegin );
      delete [] newBegin;

      capacity_ = rb.size();
      cur_ = begin_;
   }
   else {
      end_ = std::copy( rb.cur_, rb.end_, begin_ );
      cur_ = begin_;
   }

   return *this;
}

template< typename T >  // Element type
template< typename G >
GenericRecvBuffer<T>& GenericRecvBuffer<T>::operator=( const GenericSendBuffer<T,G> & sb )
{
   if( sb.size() > capacity_ ) {
      T* newBegin( new T[sb.size()] );
      end_ = std::copy( sb.begin_, sb.cur_, newBegin );
      std::swap( begin_, newBegin );
      delete [] newBegin;

      capacity_ = sb.size();
      cur_ = begin_;
   }
   else {
      end_ = std::copy( sb.begin_, sb.cur_, begin_ );
      cur_ = begin_;
   }

   return *this;
}
//**********************************************************************************************************************




//======================================================================================================================
//
//  GET FUNCTIONS
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\brief Returns the maximum possible size of the receive buffer.
//
// \return The maximum possible size.
*/
template< typename T >  // Element type
inline size_t GenericRecvBuffer<T>::maxSize() const
{
   return size_t(-1) / sizeof(T);
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Returns the current size of the receive buffer.
//
// \return The current size.
*/
template< typename T >  // Element type
inline size_t GenericRecvBuffer<T>::size() const
{
   return numeric_cast< size_t >( end_ - cur_ );
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Returns the capacity of the receive buffer.
//
// \return The capacity.
*/
template< typename T >  // Element type
inline size_t GenericRecvBuffer<T>::capacity() const
{
   return numeric_cast< size_t >( capacity_ );
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Returns \a true if the receive buffer is empty.
//
// \return \a true if the receive buffer is empty, \a false if it is not.
*/
template< typename T >  // Element type
inline bool GenericRecvBuffer<T>::isEmpty() const
{
   return cur_ >= end_;
}
//**********************************************************************************************************************




//======================================================================================================================
//
//  OPERATORS
//
//======================================================================================================================
//**********************************************************************************************************************
/*!\brief Implements operator>> without debugging system.
//
// \return Reference to the receive buffer.
*/
template< typename T >  // Element type
template< typename V >  // Type of the built-in data value
ATTRIBUTE_NO_SANITIZE_UNDEFINED
typename boost::enable_if< boost::mpl::or_< boost::is_arithmetic<V>, boost::is_enum<V> >,
                           GenericRecvBuffer<T> & >::type
GenericRecvBuffer<T>::get( V& value )
{
   // Compile time check that V is built-in data type
   static_assert( boost::is_arithmetic<V>::value || boost::is_enum<V>::value,
                            "RecvBuffer accepts only built-in data types");


   static_assert( sizeof(V) >= sizeof(T), "V has to be bigger than T" );
   static_assert( sizeof(V) %  sizeof(T) == 0, "V has to be divisible by T" );



   // Checking the validity of the read operation
   WALBERLA_ASSERT_LESS_EQUAL( cur_ + (sizeof(V) / sizeof(T)), end_ );

   // Extracting the data value
   V* tmp = reinterpret_cast<V*>( cur_ );
   value = *tmp;
   cur_  = reinterpret_cast<T*>( ++tmp );

   // Invariants check
   WALBERLA_ASSERT_LESS_EQUAL( cur_, end_);

   return *this;
}
//**********************************************************************************************************************



//**********************************************************************************************************************
/*!\brief Reads a built-in data value from the receive buffer.
//
// \return Reference to the receive buffer.
//
// This function extracts one data value of built-in data type from the receive buffer.
//
// \b Note: This operator may only be used for built-in data types. The attempt to use
// a user-defined data type results in a compile time error!
*/
template< typename T >  // Element type
template< typename V >  // Type of the built-in data value
typename boost::enable_if< boost::mpl::or_< boost::is_arithmetic<V>, boost::is_enum<V> >,
                           GenericRecvBuffer<T> & >::type
GenericRecvBuffer<T>::operator>>( V& value )
{
   readDebugMarker( typeid(V).name() );
   return get( value );

}
//**********************************************************************************************************************




//======================================================================================================================
//
//  UTILITY FUNCTIONS
//
//======================================================================================================================

//**********************************************************************************************************************
/*!\brief Returns a pointer to the first element of the receive buffer.
//
// \return Pointer to the first element of the receive buffer.
//
// This utility function enables the RecvBuffer to be used directly as receive buffer in MPI
// receive functions. Note however, that this operation is only allowed for a reinitialized
// receive buffer (for instance via the resize() function).

   \code
   using namespace pe;

   // Preparing a receive buffer for a message of 50 bytes
   RecvBuffer<byte> buffer;
   buffer.resize( 50 );

   // Receiving an MPI message from process 0 in a blocking MPI_Recv() function
   MPI_Status status;
   MPI_Recv( buffer.ptr(), buffer.size(), MPI_BYTE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, status );
   \endcode
*/
template< typename T >  // Element type
inline T* GenericRecvBuffer<T>::ptr() const
{
   WALBERLA_ASSERT_EQUAL( begin_, cur_);
   return begin_;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Setting the minimum capacity of the receive buffer.
//
// \param newCapacity The new minimum capacity of the receive buffer.
// \return void
//
// This function reserves at least \a newCapacity elements of data type \a T for the receive
// buffer. Note that this operation involves a complete reset of the receive buffer!
*/
template< typename T >  // Element type
void GenericRecvBuffer<T>::reserve( size_t newCapacity )
{
   if( newCapacity > capacity_ )
   {
      // Allocating a new array
      T* tmp = new T[newCapacity];

      // Replacing the old array
      std::swap( tmp, begin_ );
      delete [] tmp;

      // Adjusting the members
      capacity_ = newCapacity;
   }

   // Clearing the receive buffer
   clear();
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Changing the size of the receive buffer.
//
// \param newSize The new size of the receive buffer.
// \return void
//
// This function resizes the receive buffer to the given size \a newSize. Note that this
// operation does not preserve the current contents of the receive buffer!
*/
template< typename T >  // Element type
void GenericRecvBuffer<T>::resize( size_t newSize )
{
   if( newSize > capacity_ )
   {
      // Allocating a new array
      T* tmp = new T[newSize];

      // Replacing the old array
      std::swap( tmp, begin_ );
      delete [] tmp;

      // Adjusting the capacity
      capacity_ = newSize;
   }

   cur_ = begin_;
   end_ = begin_+newSize;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Reads a built-in data value from the receive buffer without extracting it.
//
// \return void
//
// This function reads the next built-in data value of type \a V from the receive buffer
// without extracting/removing it from the buffer.
*/
template< typename T >  // Element type
template< typename V >  // Type of the built-in data value
inline void GenericRecvBuffer<T>::peek( V& value ) const
{
   BOOST_STATIC_ASSERT( boost::is_arithmetic<V>::value );

   BOOST_STATIC_ASSERT( sizeof(V) > sizeof(T) );
   BOOST_STATIC_ASSERT( sizeof(V) % sizeof(T) == 0);

   // Checking the validity of the read operation
   WALBERLA_ASSERT_LESS_EQUAL( cur_ + BUFFER_DEBUG_OVERHEAD + sizeof(V)/sizeof(T), end_);

   // Extracting the data value
   V* tmp( reinterpret_cast<V*>( cur_ + BUFFER_DEBUG_OVERHEAD ) );
   value = *tmp;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Skipping the given number of elements.
//
// \param elements The number of elements to be skipped.
// \return void
//
// This function skips \a element receive buffer elements of type \a T.
*/
template< typename T >  // Element type
T * GenericRecvBuffer<T>::skip( size_t elements )
{
   auto previous = cur_;
   cur_ += elements;

   // Invariants check
   WALBERLA_ASSERT_LESS_EQUAL( cur_, end_ );

   return previous;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Clearing the receive buffer.
//
// \return void
//
// This function performs a complete reset of the receive buffer.
*/
template< typename T >  // Element type
inline void GenericRecvBuffer<T>::clear()
{
   cur_ = begin_;
   end_ = begin_;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Clearing the receive buffer.
//
// \return void
//
// This function performs a complete reset of the receive buffer - including the deletion of allocated memory!
*/
template< typename T >  // Element type
inline void GenericRecvBuffer<T>::reset()
{
   delete [] begin_;
   capacity_ = 0;
   begin_    = NULL;
   cur_      = NULL;
   end_      = NULL;
}
//**********************************************************************************************************************


//**********************************************************************************************************************
/*!\brief Reads debug marker and raises assert when marker does not match
//
//
*/
#ifdef WALBERLA_BUFFER_DEBUG

template< typename T >  // Element type
inline void GenericRecvBuffer<T>::readDebugMarker( const char * expectedMarker )
{
   bool valid = true;
   uint_t len = std::strlen( expectedMarker );
   // read the first BUFFER_DEBUG_OVERHEAD chars of the marker
   for( uint_t i = 0; i < len && i < BUFFER_DEBUG_OVERHEAD; ++i )
   {
      T readMarker;
      get( readMarker );
      if ( readMarker != T( expectedMarker[i] ))
         valid = false;
   }
   // If marker was shorter then BUFFER_DEBUG_OVERHEAD the rest should be '-'
   // to always have the same length
   for( uint_t i = len; i < BUFFER_DEBUG_OVERHEAD; ++i ) {
      T readMarker;
      get( readMarker );
      if ( readMarker != T('-') )
         valid = false;
   }

   WALBERLA_ASSERT( valid , "Packed and unpacked datatype do not match." );

}
#else

template< typename T >  // Element type
inline void GenericRecvBuffer<T>::readDebugMarker( const char *  ){}

#endif

//**********************************************************************************************************************




} // namespace mpi
} // namespace walberla