/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef mvdStream_h
#define mvdStream_h

//
// Configuration include.
//// Included at first position before any other ones.
#include "ConfigureMonteverdi.h"

#include "OTBMonteverdiCoreExport.h"

/*****************************************************************************/
/* INCLUDE SECTION                                                           */

//
// Qt includes (sorted by alphabetic order)
//// Must be included before system/custom includes.
#include <QtCore>

//
// System includes (sorted by alphabetic order)

//
// ITK includes (sorted by alphabetic order)
#include "itkHistogram.h"
#include "itkSize.h"

//
// OTB includes (sorted by alphabetic order)

//
// Monteverdi includes (sorted by alphabetic order)
#include "mvdAlgorithm.h"
#include "mvdSystemError.h"
#include "mvdTypes.h"


/*****************************************************************************/
/* PRE-DECLARATION SECTION                                                   */

//
// External classes pre-declaration.
namespace
{
}

namespace mvd
{

/**
 * \brief TODO.
 */
enum StreamTag
{
  STREAM_TAG_SIZE = 0,
  STREAM_TAG_VECTOR,
  STREAM_TAG_VARIABLE_LENGTH_VECTOR,
  //
  STREAM_TAG_COUNT
};

/**
 * \brief TODO.
 */
extern
const char* STREAM_TAG_NAMES[ STREAM_TAG_COUNT ];

//
// Internal classes pre-declaration.
} // end of namespace 'mvd'.


/*****************************************************************************/
/* FUNCTIONS DECLARATION.                                                    */


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

namespace mvd
{

/**
 * \brief Check status Qt stream and throw SystemError exception if
 * status is not Ok.
 */
inline
void
CheckStreamStatus( QTextStream& stream );

/**
 * \brief Write a text tag followed by a white space to the stream.
 *
 * \param stream Stream to write into.
 * \param name Tag name to write.
 * \param enqueueWS Enqueue a whitespace following the tag name.
 *
 * \return stream instance.
 */
inline
QTextStream&
WriteStreamTag( QTextStream& stream,
		const QString& name,
		bool enqueueWS =true );

/**
 * \brief Read a text tag followed by a whitespace from the stream and
 * check it against an expected value.
 *
 * \param stream Stream to read from.
 * \param tag Text tag which has been read.
 * \param expected The expected tag name. If non-empty, value read
 * from stream is checked against expected value; an
 * std::runtime_exception is thrown if both values doest not match.
 * \param skipWS Skip whitespace following tag name.
 *
 * \return stream instance.
 */
inline
QTextStream&
ReadStreamTag( QTextStream& stream,
	       QString& tag,
	       const QString& expected =QString(),
	       bool skipWS =true );

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

/**
 * \brief Write an StreamTag followed by a whitespace separator into a
 * QTextStream.
 *
 * \param stream QTextStream into which to write data.
 * \param tag StreamTag data to write.
 *
 * \return stream instance.
 */
inline
QTextStream&
operator << ( QTextStream& stream,
	      StreamTag tag );

/**
 * \brief Read a StreamTag followed by a whitespace from a
 * QTextStream.
 *
 * \param stream QTextStream from which to read data.
 * \param tag StreamTag to data read.
 *
 * \return stream instance.
 */
inline
QTextStream&
operator >> ( QTextStream& stream,
	      StreamTag& tag );

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

/**
 * \brief Write an std::vector< T, std::allocator< T > > into a QTextStream.
 *
 * \param stream QTextStream into which to write data.
 * \param vector std::vector< T, std::allocator< T > > data to write.
 *
 * \return stream instance.
 */
template< typename T >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const std::vector< T, std::allocator< T > >& vector );

/**
 * \brief
 */
template<>
inline
QTextStream&
operator << < double >( QTextStream& stream,
			const std::vector< double >& vector );

/**
 * \brief
 */
template<>
inline
QTextStream&
operator << < float >( QTextStream& stream,
		       const std::vector< float >& vector );

/**
 * \brief Read an std::vector< T, Alloc > from a QTextStream.
 *
 * \param stream QTextStream from which to read data.
 * \param vector std::vector< T, Alloc > container to read data into.
 *
 * \return stream instance.
 */
template< typename T, typename Alloc >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      std::vector< T, Alloc >& vector );

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

/**
 * \brief Write an itk::Array< T > into a QTextStream.
 *
 * \param stream QTextStream into which to write data.
 * \param array itk::Array< T > data to write.
 *
 * \return stream instance.
 */
template< typename T >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::Array< T >& array );

/**
 * \brief Read an itk::Array< T > from a QTextStream.
 *
 * \param stream QTextStream from which to read data.
 * \param array itk::Array< T > container to read data into.
 *
 * \return stream instance.
 */
template< typename T >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      itk::Array< T >& array );

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

/**
 * \brief Write an itk::VariableLengthVector< T > into a QTextStream.
 *
 * \param stream QTextStream into which to write data.
 * \param vector itk::VariableLengthVector< T > data to write.
 *
 * \return stream instance.
 */
template< typename T >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::VariableLengthVector< T >& vector );

/**
 */
template<>
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::VariableLengthVector< float >& vector );

/**
 */
template<>
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::VariableLengthVector< double >& vector );

/**
 * \brief Read an itk::VariableLengthVector< T > from a QTextStream.
 *
 * \param stream QTextStream from which to read data.
 * \param vector itk::VariableLengthVector< T > container to read data into.
 *
 * \return stream instance.
 */
template< typename T >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      itk::VariableLengthVector< T >& vector );

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

/**
 * \brief Write an itk::statistics::Histogram< T, FC > into a QTextStream.
 *
 * \param stream QTextStream into which to write data.
 * \param histogram itk::statistics::Histogram< T, FC > data to write.
 *
 * \return stream instance.
 */
template< typename T, typename FC >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::Statistics::Histogram< T, FC >& histogram );

/**
 * \brief Read an itk::statistics::Histogram< T, FC > from a QTextStream.
 *
 * \param stream QTextStream from which to read data.
 * \param histogram itk::statistics::Histogram< T, FC > container to
 * read data into.
 *
 * \return stream instance.
 */
template< typename T, typename FC >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      itk::Statistics::Histogram< T, FC >& histogram );

} // end namespace 'mvd'.


/*****************************************************************************/
/* INLINE SECTION                                                            */

namespace mvd
{

/*******************************************************************************/
inline
void
CheckStreamStatus( QTextStream& stream )
{
  if( stream.status()!=QTextStream::Ok )
    throw SystemError();
}

/*******************************************************************************/
inline
QTextStream&
WriteStreamTag( QTextStream& stream,
		const QString& name,
		bool queueWhiteSpace )
{
  stream << name;

  if( queueWhiteSpace )
    stream << " ";

  CheckStreamStatus( stream );

  return stream;
}

/*******************************************************************************/
inline
QTextStream&
ReadStreamTag( QTextStream& stream,
	       QString& name,
	       const QString& expected,
	       bool skipWhiteSpace )
{
  stream >> name;

  if( skipWhiteSpace )
    stream >> ws;

  CheckStreamStatus( stream );

  if( !expected.isEmpty() &&
      expected.compare( name, Qt::CaseInsensitive )!=0 )
    {
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Read tag '%1' does not match expected tag '%2'."
	)
	.arg( name )
	.arg( expected )
      )
    );
    }

  return stream;
}

/*******************************************************************************/
inline
QTextStream&
operator << ( QTextStream& stream,
	      StreamTag tag )
{
  WriteStreamTag( stream, STREAM_TAG_NAMES[ tag ] );

  return stream;
}

/*******************************************************************************/
inline
QTextStream&
operator >> ( QTextStream& stream,
	      StreamTag& tag )
{
  QString name;

  ReadStreamTag( stream, name );

  bool found = false;

  for( int i=0; i<STREAM_TAG_COUNT && !found; ++i )
    {
    if( name.compare( STREAM_TAG_NAMES[ i ], Qt::CaseInsensitive )==0 )
      {
      found = true;
      tag = static_cast< StreamTag >( i );
      }
    }

  if( !found )
    {
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Invalid tag name '%1'."
	)
	.arg( name )
      )
    );
    }

  return stream;
}

/*******************************************************************************/
template< typename T >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const std::vector< T, std::allocator< T > >& vector )
{
  typedef std::vector< T, std::allocator< T > > Vector;

  stream << STREAM_TAG_VECTOR << vector.size();

  for( typename Vector::const_iterator it( vector.begin() );
       it!=vector.end();
       ++it )
    {
    stream << " " << *it;
    }

  return stream;
}

/*******************************************************************************/
template<>
inline
QTextStream&
operator << < float >( QTextStream& stream,
		       const std::vector< float >& vector )
{
  typedef std::vector< float, std::allocator< float > > Vector;

  stream << STREAM_TAG_VECTOR << vector.size();

  for( Vector::const_iterator it( vector.begin() );
       it!=vector.end();
       ++it )
    {
    stream << " " << ToQString( *it );
    }

  return stream;
}

/*******************************************************************************/
template<>
inline
QTextStream&
operator << < double >( QTextStream& stream,
			const std::vector< double >& vector )
{
  typedef std::vector< double, std::allocator< double > > Vector;

  stream << STREAM_TAG_VECTOR << vector.size();

  for( Vector::const_iterator it( vector.begin() );
       it!=vector.end();
       ++it )
    {
    stream << " " << ToQString( *it );
    }

  return stream;
}

/*******************************************************************************/
template< typename T, typename Alloc >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      std::vector< T, Alloc >& vector )
{
  typedef std::vector< T, Alloc > Vector;

  QString type;
  ReadStreamTag( stream, type, STREAM_TAG_NAMES[ STREAM_TAG_VECTOR ] );

  typename Vector::size_type size = 0;

  stream >> size;

  vector.resize( size );

  for( typename Vector::iterator it( vector.begin() );
       it!=vector.end();
       ++it )
    {
    stream >> ws >> *it;
    }

  return stream;
}

/*****************************************************************************/
template< typename T >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::Array< T >& array )
{

  stream << STREAM_TAG_SIZE << array.GetSize();
  CheckStreamStatus( stream );

  for( CountType i=0; i<array.GetSize(); ++i )
    {
    stream << " " << array[ i ];
    CheckStreamStatus( stream );
    }

  return stream;
}

/*******************************************************************************/
template< typename T >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      itk::Array< T >& array )
{

  CountType dimension = 0;

  QString type;
  ReadStreamTag( stream, type, STREAM_TAG_NAMES[ STREAM_TAG_SIZE ] );

  stream >> dimension;

  if( dimension!=array.GetSize() )
    {
    throw std::length_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Array dimension (%1) does not match "
	  "expected dimension (%2)."
	)
	.arg( dimension )
	.arg( array.GetSize() )
      )
    );
    }

  for( CountType i=0; i<array.GetSize(); ++i )
    {
    stream >> ws >> array[ i ];
    CheckStreamStatus( stream );
    }

  return stream;
}

/*******************************************************************************/
template< typename T >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::VariableLengthVector< T >& vector )
{
  typedef typename itk::VariableLengthVector< T > Vector;

  // Write number of elements.
  stream << STREAM_TAG_VARIABLE_LENGTH_VECTOR << vector.Size();
  CheckStreamStatus( stream );

  // Write each element prefixed by a delimitting whitespace.
  for( typename Vector::ElementIdentifier i=0;
       i<vector.Size();
       ++i )
    {
    stream << " " << vector[ i ];
    CheckStreamStatus( stream );
    }

  return stream;
}

/*******************************************************************************/
template<>
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::VariableLengthVector< double >& vector )
{
  typedef itk::VariableLengthVector< double > Vector;

  // Write number of elements.
  stream << STREAM_TAG_VARIABLE_LENGTH_VECTOR << vector.Size();
  CheckStreamStatus( stream );

  // Write each element prefixed by a delimitting whitespace.
  for( Vector::ElementIdentifier i=0;
       i<vector.Size();
       ++i )
    {
    stream << " " << ToQString( vector[ i ] );
    CheckStreamStatus( stream );
    }

  return stream;
}

/*******************************************************************************/
template<>
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::VariableLengthVector< float >& vector )
{
  typedef itk::VariableLengthVector< float > Vector;

  // Write number of elements.
  stream << STREAM_TAG_VARIABLE_LENGTH_VECTOR << vector.Size();
  CheckStreamStatus( stream );

  // Write each element prefixed by a delimitting whitespace.
  for( Vector::ElementIdentifier i=0;
       i<vector.Size();
       ++i )
    {
    stream << " " << ToQString( vector[ i ] );
    CheckStreamStatus( stream );
    }

  return stream;
}

/*******************************************************************************/
template< typename T >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      itk::VariableLengthVector< T >& vector )
{
  typedef typename itk::VariableLengthVector< T > Vector;

  QString type;
  ReadStreamTag( stream, type, STREAM_TAG_NAMES[ STREAM_TAG_VARIABLE_LENGTH_VECTOR ] );

  // Read number of elements.
  typename Vector::ElementIdentifier size = 0;
  stream >> size;
  CheckStreamStatus( stream );

  // Allocates vector elements.
  vector.SetSize( size );

  // Read each element prefixed by a delimitting whitespace.
  for( typename Vector::ElementIdentifier i=0;
       i<vector.Size();
       ++i )
    {
    stream >> ws >> vector[ i ];
    CheckStreamStatus( stream );
    }

  return stream;
}

/*******************************************************************************/
template< typename T, typename FC >
inline
QTextStream&
operator << ( QTextStream& stream,
	      const itk::Statistics::Histogram< T, FC >& histogram )
{
  typedef typename itk::Statistics::Histogram< T, FC > Histogram;

  WriteStreamTag( stream, "HISTOGRAM", false );
  stream << endl;

  WriteStreamTag( stream, "MEASUREMENT" );
  stream << histogram.GetSize() << endl;

#if 1
  WriteStreamTag( stream, "MINS" );
  stream << histogram.GetMins() << endl;

  WriteStreamTag( stream, "MAXS" );
  stream << histogram.GetMaxs() << endl;
#endif


#if 0
  typename Histogram::SizeType size( histogram.GetSize() );

  for( unsigned int dim=0; dim<N; ++dim )
    {
    for( unsigned long bin=0; bin<size[ dim ]; ++bin )
      {
      stream
	<< dim << " "
	<< bin << " "
	<< histogram.GetMins()[ dim ][ bin ] << " "
	<< histogram.GetMaxs()[ dim ][ bin ] << " "
	<< histogram.GetFrequency( bin, dim ) << endl;

      CheckStreamStatus( stream );
      }
    }

#else
  CountType size = histogram.Size();

  WriteStreamTag( stream, "SAMPLES" );
  stream << size << endl;
  CheckStreamStatus( stream );

  CountType count = 0;

  for( typename Histogram::ConstIterator it( histogram.Begin() );
       it!=histogram.End();
       ++it, ++count )
    {
    stream
      << it.GetInstanceIdentifier() << " "
      << ToQString( it.GetFrequency() ) << endl;

    CheckStreamStatus( stream );
    }

  WriteStreamTag( stream, "COUNT" );
  stream << count << endl;
  CheckStreamStatus( stream );

  if( count!=size )
    {
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Histogram sample count (%1) does not match "
	  "written sample count (%2)."
	)
	.arg( size )
	.arg( count )
      )
    );
    }
#endif

  return stream;
}

/*******************************************************************************/
template< typename T, typename FC >
inline
QTextStream&
operator >> ( QTextStream& stream,
	      itk::Statistics::Histogram< T, FC >& histogram )
{
  typedef itk::Statistics::Histogram< T, FC > Histogram;

  QString string;
  unsigned int N = histogram.GetMeasurementVectorSize();


  //
  // Histogram section.
  ReadStreamTag( stream, string, "HISTOGRAM" );

  //
  // Measurement-size section.
  ReadStreamTag( stream, string, "MEASUREMENT" );
  typename Histogram::SizeType size;
  size.SetSize(N);

  stream >> size;

  // Dimension of size is checked in operator >> ( QTextStream&,
  // itk::Size< T > ).

  histogram.Initialize( size );

#if 1
  //
  // Bin mins.
  ReadStreamTag( stream, string, "MINS" );
  typename Histogram::BinMinContainerType mins;
  stream >> mins;

  //
  // Bin maxes.
  ReadStreamTag( stream, string, "MAXS" );
  typename Histogram::BinMaxContainerType maxs;
  stream >> maxs;

  if( mins.size()!=N )
    {
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Histogram mins dimension (%1) does not match "
	  "expected dimension (%2)."
	)
	.arg( mins.size() )
	.arg( N )
      )
    );
    }

  if( maxs.size()!=N )
    {
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Histogram maxs dimension (%1) does not match "
	  "expected dimension (%2)."
	)
	.arg( maxs.size() )
	.arg( N )
      )
    );
    }

  //
  // Initialize histogram mins & maxs within single pass.
  for( typename Histogram::BinMaxContainerType::size_type dim=0;
       dim<maxs.size();
       ++dim )
    {
    for( typename Histogram::BinMaxContainerType::value_type::size_type bin=0;
	 bin<maxs[ dim ].size();
	 ++bin )
      {
      histogram.SetBinMin( dim, bin, mins[ dim ][ bin ] );
      histogram.SetBinMax( dim, bin, maxs[ dim ][ bin ] );
      }
    }
#endif

#if 0
  //
  // Bins.
  for( unsigned int dim=0; dim<N; ++dim )
    {
    for( unsigned long bin=0; bin<size[ dim ]; ++bin )
      {
      // Dimension.
      unsigned int d = 0;

      stream >> d >> ws;
      CheckStreamStatus( stream );
      if( d!=dim )
	{
	throw std::runtime_error(
	  ToStdString(
	    QCoreApplication::translate(
	      "mvd::TextStream",
	      "Histogram dimension (%1) does not match "
	      "expected dimension (%2)."
	    )
	    .arg( d )
	    .arg( dim )
	  )
	);
	}

      // Bin.
      unsigned long b = 0;

      stream >> b >> ws;
      CheckStreamStatus( stream );
      if( b!=bin )
	{
	throw std::runtime_error(
	  ToStdString(
	    QCoreApplication::translate(
	      "mvd::TextStream",
	      "Histogram bin count (%1) does not match "
	      "expected bin count (%2)."
	    )
	    .arg( b )
	    .arg( bin )
	  )
	);
	}

      // Min.
      typename Histogram::BinMinContainerType::value_type::value_type min;

      stream >> min >> ws;
      CheckStreamStatus( stream );

      histogram.SetBinMin( dim, bin, min );

      // Max.
      typename Histogram::BinMaxContainerType::value_type::value_type max;

      stream >> max >> ws;
      CheckStreamStatus( stream );

      histogram.SetBinMax( dim, bin, max );

      // Frequency.
      typename Histogram::AbsoluteFrequencyType freq( 0 );

      stream >> freq;
      CheckStreamStatus( stream );

      typename Histogram::MeasurementType measurement(
	histogram.GetMeasurement( b, d )
      );

      typename Histogram::IndexType index( histogram.GetIndex( measurement ) );

      histogram.SetFrequency( index, freq );
      }
    }

#else
  CountType samples = 0;

  ReadStreamTag( stream, string, "SAMPLES" );
  stream >> samples;
  CheckStreamStatus( stream );

  CountType space = histogram.Size();

  if( samples!=space )
    {
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Histogram sample count (%1) does not match "
	  "expected sample count (%2)."
	)
	.arg( samples )
	.arg( space )
      )
    );
    }

  CountType i = 0;
  for( ; i<samples; ++i )
    {
    typename Histogram::InstanceIdentifier id( 0 );
    typename Histogram::AbsoluteFrequencyType freq( 0 );

    stream >> id >> ws >> freq;
    CheckStreamStatus( stream );

    histogram.SetFrequency( id, freq );
    }

  CountType count = 0;

  ReadStreamTag( stream, string, "COUNT" );
  stream >> count;
  CheckStreamStatus( stream );

  if( count!=i )
    {
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::TextStream",
	  "Read sample count (histogram) (%1) does not match "
	  "written sample count (%2)."
	)
	.arg( i )
	.arg( count )
      )
    );
    }

#endif

  return stream;
}

} // end namespace 'mvd'

#endif // mvdStream_h
