/*
 * 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 mvdAlgorithm_h
#define mvdAlgorithm_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)
#include <string>

//
// ITK includes (sorted by alphabetic order)
#include "itkFixedArray.h"
#include "itkExceptionObject.h"
#include "itkVariableLengthVector.h"

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

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


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

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

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


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

namespace otb
{

/**
 * Convert an itk::VariableLengthVector< T2 > into a
 * itk::FixedArray< T1, N >.
 *
 * Elements of type T1 are (safely) statically casted into T2.
 *
 * An itk::RangeError exception instance is thrown if lengths/sizes of
 * both the containers are not equal.
 *
 * N.B.: Caller must ensure to that size N of itk::FixedArray< T1, N >
 * to match the variable size of the itk::VariableLengthVector< T1 >.
 */
template< typename T2, unsigned int N, typename T1 >
inline
itk::FixedArray< T2, N >
ToFixedArray( const itk::VariableLengthVector< T1 >& v );

/**
 * Convert an itk::VariableLengthVector< T2 > into a
 * itk::FixedArray< T1, N >.
 *
 * Elements of type T1 are (safely) statically casted into T2.
 *
 * An itk::RangeError exception instance is thrown if lengths/sizes of
 * both the containers are not equal.
 *
 * N.B.: Caller must ensure to that size N of itk::FixedArray< T1, N >
 * to match the variable size of the itk::VariableLengthVector< T1 >.
 */
template< typename T2, unsigned int N, typename T1 >
inline
itk::FixedArray< T2, N >&
ToFixedArray( itk::FixedArray< T2, N >& a,
	      const itk::VariableLengthVector< T1 >& v );

/**
 * Convert an itk::FixedArray< T2, N > into a
 * itk::VariableLengthVector< T1 >.
 *
 * Elements of type T1 are (safely) statically casted into T2.
 *
 * An itk::RangeError exception instance is thrown if lengths/sizes of
 * both the containers are not equal.
 */
template< typename T2, typename T1, unsigned int N >
inline
itk::VariableLengthVector< T2 >
ToVariableLengthVector( const itk::FixedArray< T1, N >& a );

/**
 * Convert an itk::FixedArray< T2, N > into a
 * itk::VariableLengthVector< T1 >.
 *
 * Elements of type T1 are (safely) statically casted into T2.
 *
 * An itk::RangeError exception instance is thrown if lengths/sizes of
 * both the containers are not equal.
 */
template< typename T2, typename T1, unsigned int N >
inline
itk::VariableLengthVector< T2 >&
ToVariableLengthVector( itk::FixedArray< T1, N >& a,
			const itk::VariableLengthVector< T1 >& v );
} // end namespace 'otb'

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

namespace mvd
{
/**
 * \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 );

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

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

/**
 * \brief Convert a StringVector object to a QStringList object.
 *
 * Because Qt uses a shallow mechanism to copy containers, the returned
 * QStringList is shallow-copied.
 */
inline
QStringList
ToQStringList( const StringVector& sv );

/**
 * \brief Append the content of a StringVector object to the content
 * of a QStringList object.
 *
 * Because Qt uses a shallow mechanism to copy containers, the returned
 * QStringList is shallow-copied.
 */
inline
QStringList&
AppendToQStringList( QStringList& qsl,
		     const StringVector& sv );

/**
 * \brief Convert and copy an STL std::string to a QString.
 *
 * The 8-bit data is converted to Unicode using the
 * QString::fromAscii() method.
 *
 * \param str The 8-bit STL string to convert.
 *
 * \return The Unicode converted QString.
 */

inline
QString
FromStdString( const std::string& str );

/**
 */
inline
std::string
ToLocalStdString( const QString & );

/**
 */
inline
const char *
ToLocalString( const QString & );

/**
 * \brief Convert and copy a QString to a STL std::string.
 *
 * The Unicode data is converted to 8-bit using the QString::toLatin1()
 * method.
 *
 * \param str The Unicode string to convert.
 *
 * \return The 8-bit converted STL std::string.
 */
inline
std::string
ToStdString( const QString& str );

/**
 */
inline
const char*
ToString( const QString& str );


/**
 * \return a std::string form various types.
 */
inline
std::string
ToStdString( unsigned int val );

inline
std::string
ToStdString( const SpacingType & spacing);

inline
std::string
ToStdString( const SizeType & size);

inline
std::string
ToStdString( const PointType & point);

template< typename T>
std::string
ToStdString( const std::vector<T> & vec);

/**
 */
template< typename T >
inline
QString
ToQString( const T& val );

/**
 */
template<>
inline
QString
ToQString< float >( const float& );

/**
 */
template<>
inline
QString
ToQString< double >( const double& );

/**
 */
inline
QString
ToHumanReadableSize( qint64 fize, bool isInBits =true );

} // end namespace 'mvd'.

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

namespace mvd
{
/**
 * \brief Test condition on all elements in range.
 *
 * \param first First element of range to test.
 * \param last Upper (external) boundary of range to test.
 * \param pred Predicate testing element in range.
 *
 * \return true if predicate is true on all elements in the range
 * [first, last[ or if range is empty.
 */
template< typename TInputIterator, typename TUnaryPredicate >
inline
bool
AllOf( TInputIterator first, TInputIterator last, TUnaryPredicate pred );

/**
 * \brief Test if any of element in range fulfills condition.
 *
 * \param first First element of range to test.
 * \param last Upper (external) boundary of range to test.
 * \param pred Predicate testing element in range.
 *
 * \return true if predicated is true for, at least, one element in range
 * [first, last[. If range is empty, this function returns false.
 */
template< typename TInputIterator, typename TUnaryPredicate >
inline
bool
AnyOf( TInputIterator first, TInputIterator last, TUnaryPredicate pred );

/**
 * \brief Test if no element in range fulfills condition.
 *
 * \param first First element of range to test.
 * \param last Upper (external) boundary of range to test.
 * \param pred Predicate testing element in range.
 *
 * \return true if predicated is false for all elements in range [first, last[. If range is empty, this function returns true.
 */
template< typename TInputIterator, typename TUnaryPredicate >
inline
bool
NoneOf( TInputIterator first, TInputIterator last, TUnaryPredicate pred );

} // end namespace 'mvd'.

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

namespace otb
{
/*******************************************************************************/
template< typename T2, unsigned int N, typename T1 >
inline
itk::FixedArray< T2, N >
ToFixedArray( const itk::VariableLengthVector< T1 >& v )
{
  assert( v.Size()==N );

  throw itk::RangeError( __FILE__, __LINE__ );

  itk::FixedArray< T2, N > a;

  for( unsigned int i=0; i<N; ++i )
    a[ i ] = static_cast< T2 >( v[ i ] );

  return a;
}

/*******************************************************************************/
template< typename T2, unsigned int N, typename T1 >
inline
itk::FixedArray< T2, N >&
ToFixedArray( itk::FixedArray< T2, N >& a,
	      const itk::VariableLengthVector< T1 >& v )
{
  assert( v.Size()==N && v.Size()==a.Size() );

  throw itk::RangeError( __FILE__, __LINE__ );

  for( unsigned int i=0; i<N; ++i )
    a[ i ] = static_cast< T2>( v[ i ] );

  return a;
}

/*******************************************************************************/
template< typename T2, typename T1, unsigned int N >
inline
itk::VariableLengthVector< T2 >
ToVariableLengthVector( const itk::FixedArray< T1, N >& a )
{
  assert( a.Size()==N );

  throw itk::RangeError( __FILE__, __LINE__ );

  itk::VariableLengthVector< T2 > v;

  v.Reserve( N );

  for( unsigned int i=0; i<N; ++i )
    v[ i ] = static_cast< T2 >( a[ i ] );

  return v;
}

/*******************************************************************************/
template< typename T2, typename T1, unsigned int N >
inline
itk::VariableLengthVector< T2 >&
ToVariableLengthVector( itk::VariableLengthVector< T2 >& v,
			const itk::FixedArray< T1, N >& a )
{
  assert( a.Size()==N );

  throw itk::RangeError( __FILE__, __LINE__ );

  v.Reserve( N );

  for( unsigned int i=0; i<N; ++i )
    v[ i ] = static_cast< T2 >( a[ i ] );

  return v;
}

} // end namespace 'otb'.

namespace mvd
{

/*******************************************************************************/
inline
QStringList
ToQStringList( const StringVector& sv )
{
  QStringList qsl;
  return AppendToQStringList( qsl, sv );
}

/*******************************************************************************/
inline
QStringList&
AppendToQStringList( QStringList& qsl, const StringVector& sv )
{
  for( StringVector::const_iterator it( sv.begin() );
       it!=sv.end();
       ++ it )
    {
    qsl.append( QString(it->c_str()) );
    }

  return qsl;
}

/*******************************************************************************/
inline
std::string
ToLocalStdString( const QString & str )
{
  return std::string( str.toLocal8Bit().constData() );
}

/*******************************************************************************/
inline
const char *
ToLocalString( const QString & str )
{
  return str.toLocal8Bit().constData();
}

/*******************************************************************************/
inline
QString
FromStdString( const std::string& str )
{
  return QString( str.c_str() );
}

/*******************************************************************************/
inline
std::string
ToStdString( const QString& str )
{
  return std::string( str.toLatin1().constData() );
}

/*******************************************************************************/
inline
const char*
ToString( const QString& str )
{
  return str.toLatin1().constData();
}

/*******************************************************************************/
inline
std::string
ToStdString( unsigned int val )
{
  std::ostringstream oss;
  oss << val;
  return oss.str();
}

/*******************************************************************************/
inline
std::string
ToStdString( const SpacingType & spacing)
{
  std::ostringstream oss;
  oss << spacing[0] << " , " << spacing[1];
  return oss.str();
}

/*******************************************************************************/
inline
std::string
ToStdString( const SizeType & size)
{
  std::ostringstream oss;
  oss << size[0] << " , " << size[1];
  return oss.str();
}

/*******************************************************************************/
inline
std::string
ToStdString( const PointType & point)
{
  std::ostringstream oss;
  oss << point[0] << " , " << point[1];
  return oss.str();
}

/*******************************************************************************/
template< typename T>
inline
std::string
ToStdString( const std::vector<T> & vec)
{
  std::ostringstream oss;
  typename std::vector<T>::const_iterator it = vec.begin();
  while( it != vec.end() )
    {
    oss<<*it<< " ";
    ++it;
    }

  return oss.str();
}

/*******************************************************************************/
template< typename T >
inline
QString
ToQString( const T& val )
{
  return QString( "%1" ).arg( val );
}

/*****************************************************************************/
template<>
inline
QString
ToQString< float >( const float& val )
{
  assert( !std::isnan( val ) );

  QString valf;

  // See IEEE-754
  // Ref. http://en.wikipedia.org/wiki/Single-precision_floating-point_format
  valf.sprintf( "%.9g", val );

#if 0
  std::cout
    << "ToString< float >(" << val << "): "
    << ToStdString( valf )
    << "\t"
    << valf.toFloat()
    << std::endl;
#endif

  if( valf.toFloat()!=val )
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::DatasetDescritor",
	  "Accuracy loss when converting float (%1) to string."
	)
	.arg( valf )
      )
    );

  return valf;
}

/*****************************************************************************/
template<>
inline
QString
ToQString< double >( const double& val )
{
  assert( !std::isnan( val ) );

  QString vald;

  // See IEEE-754
  // Ref. http://en.wikipedia.org/wiki/Double_precision
  vald.sprintf( "%.17g", val );

#if 0
  std::cout
    << "ToString< double >(" << val << "): "
    << ToStdString( vald )
    << "\t"
    << vald.toDouble()
    << std::endl;
#endif

  if( vald.toDouble()!=val )
    throw std::runtime_error(
      ToStdString(
	QCoreApplication::translate(
	  "mvd::DatasetDescriptor",
	  "Accuracy loss when converting double (%1) to string."
	)
	.arg( vald )
      )
    );

  return vald;
}

} // end namespace 'mvd'

namespace mvd
{

/*******************************************************************************/
template< typename TInputIterator, typename TUnaryPredicate >
inline
bool
AllOf( TInputIterator first, TInputIterator last, TUnaryPredicate pred )
{
  while( first!=last )
    {
    if( !pred( *first ) )
      return false;

    ++first;
    }

  return true;
}

/*******************************************************************************/
template< typename TInputIterator, typename TUnaryPredicate >
inline
bool
AnyOf( TInputIterator first, TInputIterator last, TUnaryPredicate pred )
{
  while( first!=last )
    {
    if( pred( *first ) )
      return true;

    ++first;
    }

  return false;
}

/*******************************************************************************/
template< typename TInputIterator, typename TUnaryPredicate >
inline
bool
NoneOf( TInputIterator first, TInputIterator last, TUnaryPredicate pred )
{
  while( first!=last )
    {
    if( pred( *first ) )
      return false;

    ++first;
    }

  return true;
}

/*******************************************************************************/
inline
QString
ToHumanReadableSize( qint64 size, bool isInBits )
{
  double thousand =
    isInBits ? 1024.0 : 1000.0;

  double remainder = static_cast< double >( size );

  if( size < thousand )
    return QString::number( size );

  remainder /= thousand;

  if( remainder < thousand )
    return QString("%1 KiB" ).arg( QString::number( remainder, 'g', 3 ) );

  remainder /= thousand;

  if( remainder < thousand )
    return QString("%1 MiB" ).arg( QString::number( remainder, 'g', 3 ) );

  remainder /= thousand;

  if( remainder < thousand )
    return QString("%1 GiB" ).arg( QString::number( remainder, 'g', 3 ) );

  remainder /= thousand;

  return QString("%1 TiB" ).arg( QString::number( remainder, 'g', 3 ) );
}

} // end namespace 'mvd'


#endif // mvdAlgorithm_h
