/*
 * 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.
 */

#include "mvdI18nCoreApplication.h"


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

//
// Qt includes (sorted by alphabetic order)
//// Must be included before system/custom includes.

//
// System includes (sorted by alphabetic order)
#ifdef _WIN32
#include <windows.h>
#endif
#include <exception>

//
// OTB includes (sorted by alphabetic order)
#include "otbDEMHandler.h"

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


#define FORCE_QDEBUG 0
#define FORCE_QWARNING 0
#define FORCE_QCRITICAL 0
#define FORCE_QFATAL 0


namespace mvd
{
/*
  TRANSLATOR mvd::I18nCoreApplication

  Necessary for lupdate to be aware of C++ namespaces.

  Context comment for translator.
*/

/*****************************************************************************/
/* CONSTANTS                                                                 */

const char* I18nCoreApplication::DEFAULT_CACHE_DIR_NAME = "mvd2";

const char* I18nCoreApplication::DEFAULT_CACHE_RESULT_DIR_NAME = "result";

const char* I18nCoreApplication::DATASET_EXT = ".ds";

const char* I18nCoreApplication::SETTINGS_KEYS[ SETTINGS_KEY_COUNT ] =
{
  "geoidPath",
  "geoidPathActive",
  "overviewsEnabled",
  "overviewsSize",
  "resolutionAlgorithm",
  "resultsDir",
  "srtmDir",
  "srtmDirActive",
  "tileSize",
};

/*****************************************************************************/
/* STATIC IMPLEMENTATION SECTION                                             */

I18nCoreApplication* I18nCoreApplication::m_Instance = NULL;

/*******************************************************************************/
bool
I18nCoreApplication
::IsResultsDirValid( const QString& path )
{
  QDir dir( path );
  QFileInfo fileInfo( path );

  return
    fileInfo.exists() &&
    fileInfo.isDir() &&
    fileInfo.isReadable() &&
    fileInfo.isWritable();
}

/*******************************************************************************/
bool
I18nCoreApplication
::MakeDirTree( const QString& path, const QString& tree, QDir* dir )
{
  QDir pathDir( path );

  if( !pathDir.exists() )
    throw SystemError( ToStdString( QString( "('%1')" ).arg( path ) ) );

  QDir treeDir( pathDir.filePath( tree ) );
  if( treeDir.exists() )
    {
    if( dir!=NULL )
      {
      *dir = treeDir;
      }

    return false;
    }

  if( !pathDir.mkpath( tree ) )
    throw SystemError(
      ToStdString(
	QString( "('%1')" ).arg( pathDir.filePath( tree ) )
      )
    );

  if( !pathDir.cd( tree ) )
    throw SystemError(
      ToStdString(
	QString( "('%1')" ).arg(
	  pathDir.filePath( tree )
	)
      )
    );

  if( dir!=NULL )
    {
    *dir = pathDir;
    }

  return true;
}

/*****************************************************************************/
QString
I18nCoreApplication
::DatasetPathName( QString& name,
		   const QString& imageFilename )
{
  // convenient QFileInfo
  QFileInfo fileInfo( imageFilename );

  // get the md5 of the filename
  QByteArray result =
    QCryptographicHash::hash( fileInfo.absoluteFilePath().toLatin1(),
			      QCryptographicHash::Md5 );

  // MD5 hash-code.
  QString hash( result.toHex() );

  // store the md5 + the dataset extension at the end
  name = hash + I18nCoreApplication::DATASET_EXT;

  // Return hash-code for further use.
  return hash;
}

/*****************************************************************************/
VectorImageModel *
I18nCoreApplication
::LoadImageModel( const QString & filename,
                  int width,
                  int height,
                  QObject * p )
{
  try
    {
    VectorImageModel::EnsureValidImage( filename );
    }
  catch( ... )
    {
    throw;
    }

  VectorImageModel * imageModel = NULL;

  try
    {
    AbstractImageModel::BuildContext context( filename );

    imageModel = new VectorImageModel( p );

    imageModel->SetFilename( filename, width, height );

    imageModel->BuildModel( &context );
    }
  catch( ... )
    {
    delete imageModel;
    imageModel = NULL;

    throw;
    }

  return imageModel;
}

/*****************************************************************************/
void
I18nCoreApplication
::DeleteDatasetModel( const QString & path, const QString & hash )
{
  QFileInfo finfo( path, hash );

  QDir dir( finfo.filePath() );

  QFileInfoList fileInfos(
    dir.entryInfoList( QDir::NoDotAndDotDot | QDir::Files )
  );

  for( QFileInfoList::const_iterator it( fileInfos.begin() );
       it!=fileInfos.end();
       ++it )
    {
    if( !dir.remove( it->fileName() ) )
      throw SystemError(
        ToStdString(
          tr( "Failed to remove file '%1'." ).arg( it->filePath() )
        )
      );
    }

  QDir parentDir( dir );

  if( !parentDir.cdUp() )
    throw SystemError(
      ToStdString(
        tr( "Failed to access parent directory of '%1'." ).arg( dir.path() )
      )
    );

  if( !parentDir.rmdir( hash  ) )
    throw SystemError(
      ToStdString(
        tr( "Failed to remove dataset directory '%1'." ).arg( dir.path() )
      )
    );
}

/*****************************************************************************/
void
I18nCoreApplication
::HandleQtMessage( QtMsgType type ,
                   const QMessageLogContext & ,
                   const QString & message )
{
  std::string msg = ToStdString( message );

  switch( type )
    {
    //
    // DEBUG
    case QtDebugMsg:
#if ECHO_QDEBUG || FORCE_QDEBUG
#if _WIN32
      OutputDebugString( msg.c_str() );
      OutputDebugString( "\n" );
#endif
      std::cerr << msg << std::endl;
#endif
#if LOG_QDEBUG
      static_assert( false, "Not yet implemented!" );
#endif
      break;
    //
    // WARNING
    case QtWarningMsg:
#if ECHO_QWARNING || FORCE_QWARNING
#if _WIN32
      OutputDebugString( "WARNG> " );
      OutputDebugString( msg.c_str() );
      OutputDebugString( "\n" );
#endif
      std::cerr << "WARNG> " << msg << std::endl;
#endif
#if LOG_QWARNING
      static_assert( false, "Not yet implemented!" );
#endif
      break;
    //
    // CRITICAL
    case QtCriticalMsg:
#if ECHO_QCRITICAL || FORCE_QCRITICAL
#if _WIN32
      OutputDebugString( "ERROR> " );
      OutputDebugString( qPrintable(message) );
      OutputDebugString( "\n" );
#endif
      std::cerr << "ERROR> " << msg << std::endl;
#endif
#if LOG_QCRITICAL
      static_assert( false, "Not yet implemented!" );
#endif
#if THROW_QCRITICAL
      throw std::runtime_error(
	ToStdString(
	  tr( "Error: " )
	  .arg( message )
	)
      );
#endif
      break;
    //
    // FATAL
    case QtFatalMsg:
#if ECHO_QFATAL || FORCE_QFATAL
#if _WIN32
      OutputDebugString( "FATAL> " );
      OutputDebugString( qPrintable(message) );
      OutputDebugString( "\n" );
#endif
      std::cerr << "FATAL> " << msg << std::endl;
#endif
#if LOG_QFATAL
      static_assert( false, "Not yet implemented!" );
#endif
#if THROW_QFATAL
      throw std::runtime_error(
	ToStdString(
	  tr( "Fatal error: " )
	  .arg( message )
	)
      );
#endif
      abort();
      break;

    default:
      assert( false && "Unhandled message QtMsgType!" );
      break;
    }
}

/*****************************************************************************/
/* CLASS IMPLEMENTATION SECTION                                              */
/*****************************************************************************/
I18nCoreApplication
::I18nCoreApplication( QCoreApplication* qtApp ) :
  QObject( qtApp ),
  m_Settings( NULL ),
  m_Model( NULL ),
  m_IsRunningFromBuildDir( false )
{
  if( m_Instance!=NULL )
    {
    throw std::runtime_error(
      ToStdString(
        tr( "I18nCoreApplication is a singleton class!" )
      )
    );
    }

  if( qtApp==NULL )
    {
    throw std::invalid_argument(
      ToStdString(
	tr( "Class 'I18nCoreApplication' instance must be provided a "
	    "QCoreApplication' pointer at construction time!" )
      )
    );
    }

  qInstallMessageHandler( I18nCoreApplication::HandleQtMessage );

  m_Instance = this;
}

/*******************************************************************************/
I18nCoreApplication
::~I18nCoreApplication()
{
}

/*******************************************************************************/
void
I18nCoreApplication
::Initialize()
{
  // Initialize internationlization.
  InitializeLocale();

  //
  // Force numeric options of locale to "C"
  // See issue #635
  //
  // TODO: Check it is still needed here!
  setlocale( LC_NUMERIC, "C" );

  // Initialize QCoreApplication.
  virtual_InitializeCore();

  // Initialize settings.
  InitializeSettings();

  // Elevation setup
  ElevationSetup();
}

/*******************************************************************************/
void
I18nCoreApplication
::SetModel( AbstractModel* model )
{
  emit AboutToChangeModel( model );

  delete m_Model;

  m_Model = model;

  if( model!=NULL )
    m_Model->setParent( this );

  emit ModelChanged( m_Model );
}

/*******************************************************************************/
bool
I18nCoreApplication
::ElevationSetup()
{
  assert( !otb::DEMHandler::Instance().IsNull() );

  otb::DEMHandler::Pointer demHandlerInstance( otb::DEMHandler::Instance() );

  bool geoidUpdated = false;

  if( I18nCoreApplication::HasSettingsKey(
	I18nCoreApplication::SETTINGS_KEY_GEOID_PATH_ACTIVE ) &&
      I18nCoreApplication::RetrieveSettingsKey(
	I18nCoreApplication::SETTINGS_KEY_GEOID_PATH_ACTIVE ).toBool() )
    {
    qDebug() <<
      "Settings/GeoidFile:" <<
      I18nCoreApplication::RetrieveSettingsKey(
	I18nCoreApplication::SETTINGS_KEY_GEOID_PATH
      ).toString();

    try
      {
      QString filename(
	I18nCoreApplication::RetrieveSettingsKey(
	  I18nCoreApplication::SETTINGS_KEY_GEOID_PATH
	).toString()
      );

      geoidUpdated =
	demHandlerInstance->OpenGeoidFile(
	  QFile::encodeName(
	    filename
	  )
	);

      // BUGFIX: When geoid file has not been updated by
      // otb::DEMHandler, the filename may be erroneous and unchecked
      // so, add a check, in this case, to report input error to the
      // user.
      if( !geoidUpdated )
	{
	QFileInfo finfo( filename );

	if( !finfo.exists() )
	  throw std::runtime_error(
	    ToStdString(
	      tr( "Geoid file '%1' not found!" )
	      .arg( filename )
	    )
	  );
	}
      }
    catch( const std::exception & err )
      {
      qWarning()
	<< ToStdString( tr( "An error occurred while loading the geoid file, "
			    "no geoid file will be used:" ) ).c_str()
	<< err.what();

      throw;
      }
    }
  else
    geoidUpdated = true;

  if( I18nCoreApplication::HasSettingsKey(
	I18nCoreApplication::SETTINGS_KEY_SRTM_DIR_ACTIVE ) &&
      I18nCoreApplication::RetrieveSettingsKey(
	I18nCoreApplication::SETTINGS_KEY_SRTM_DIR_ACTIVE ).toBool() )
    {
    qDebug() <<
      "Settings/DEMDir:" <<
      I18nCoreApplication::RetrieveSettingsKey(
	I18nCoreApplication::SETTINGS_KEY_SRTM_DIR
      ).toString();

    try
      {
      demHandlerInstance->ClearDEMs();

      demHandlerInstance->OpenDEMDirectory(
	QFile::encodeName(
	  I18nCoreApplication::RetrieveSettingsKey(
	    I18nCoreApplication::SETTINGS_KEY_SRTM_DIR
	  )
	  .toString()
	)
      );
      }
    catch( const std::exception & err )
      {
      qWarning()
	<< ToStdString( tr( "An error occurred while loading the DEM directory, "
			    "no DEM will be used:" ) ).c_str()
	<< err.what();

      throw;
      }
    }
  else
    {
    otb::DEMHandler::Instance()->ClearDEMs();
    }

  return geoidUpdated;
}

/*******************************************************************************/
void
I18nCoreApplication
::InitializeLocale()
{
  {
  typedef QList< QByteArray > ByteArrayList;

  ByteArrayList codecs( QTextCodec::availableCodecs() );

  qDebug() << "Available codecs:";

  foreach( const QByteArray & codec, codecs )
    qDebug() << "\t" << codec;
  }

  // Literal strings to be translated are UTF-8 encoded because source
  // files are UTF-8 encoded.
   /*/////////////////////////////// Warning //////////////////////
  we need to replace those lines with a function of Qt5
  QTextCodec::setCodecForTr( QTextCodec::codecForName( "UTF-8" ) );
  //////////////////////////////////////////////////////////////////*/

  // QTextCodec::setCodecForLocale( QTextCodec::codecForName("UTF-8") );

  // QTextCodec::setCodecForCStrings( QTextCodec::codecForName("System") );
 /*/////////////////////////////// Warning //////////////////////
  we need to replace those lines with a function of Qt5
  qDebug()
    << "Codec for C-strings:"
    << ( QTextCodec::codecForCStrings()!=NULL
   ? QTextCodec::codecForCStrings()->name()
   : "none" );
  //////////////////////////////////////////////////////////////////*/

  qDebug()
    << "Codec for Locale:"
    << ( QTextCodec::codecForLocale()!=NULL
	 ? QTextCodec::codecForLocale()->name()
	 : "none" );

   /*/////////////////////////////// Warning //////////////////////
  we need to replace those lines with a function of Qt5
  qDebug()
    << "Codec for Tr:"
    << ( QTextCodec::codecForTr()!=NULL
   ? QTextCodec::codecForTr()->name()
   : "none" );
  //////////////////////////////////////////////////////////////////*/

  //
  // 1. default UI language is english (no translation).
  QLocale sys_lc( QLocale::system() );

  // Trace system locale.
  qDebug()
    << "Language:" << QLocale::languageToString( sys_lc.language() );

  qDebug()
    << "Country:" << QLocale::countryToString( sys_lc.country() );

  // Check system locale.
  if( sys_lc.language() == QLocale::C ||
      ( sys_lc.language() == QLocale::English &&
        sys_lc.country() == QLocale::UnitedStates ) )
    {
    return;
    }

  //
  // 2. Choose i18n path between build dir and install dir.

  // Begin from the executable path
  QDir bin_dir( QDir::cleanPath(QCoreApplication::applicationDirPath()) );

  // Go up in the directory hierarchy until we have a candidate install prefix
  bool prefixFound = false;
  QDir prefix( bin_dir );
  while ( prefix.cdUp() )
    {
    if ( QDir(prefix).cd( Monteverdi_INSTALL_BIN_DIR) )
      {
      prefixFound = true;
      break;
      }
    }

  if( !prefixFound )
    throw std::runtime_error(
      ToStdString(
	tr( "Failed to locate translation files directory 'i18n' in '%1'." )
	.arg( prefix.path() )
      )
    );

  QDir i18n_dir( prefix );

  // At this point the candidate install prefix can also be the build dir root
  if ( prefix.exists( Monteverdi_BUILD_DIR_FILE  )
       && i18n_dir.cd( "i18n" ) )
    {
    m_IsRunningFromBuildDir = true;

    // Report found build dir root
    qDebug() <<
      tr( "Running from build directory '%1'." )
      .arg( prefix.path() );

    qDebug()
      << tr( "Loading translation files from directory '%1'." )
      .arg( i18n_dir.path() );
    }
  else
    {
    m_IsRunningFromBuildDir = false;

    // Report found install prefix
    qDebug()
      << tr( "Running from install directory '%1'." )
      .arg( prefix.path() );

    if ( i18n_dir.cd( Monteverdi_INSTALL_DATA_I18N_DIR ) )
      {
      qDebug()
	<< tr( "Loading translation files from directory '%1'." )
	.arg( i18n_dir.path() );
      }
    else
      {
      throw std::runtime_error(
	ToStdString(
	  tr( "Failed to access translation-files directory '%1'" )
	  .arg( QDir::cleanPath(
		  prefix.path()
		  + QDir::separator()
		  + Monteverdi_INSTALL_DATA_I18N_DIR
		)
	  )
	)
      );
      }
    }

  try
    {
    //
    // 3.1 Stack Qt translator.
    LoadAndInstallTranslator(
      "qt_" + sys_lc.name(),
      QLibraryInfo::location( QLibraryInfo::TranslationsPath  )
    );
    }
  catch( std::exception& exc )
    {
    qWarning() << exc.what();
    }

  try
    {
    //
    // 3.2 Stack Monteverdi translator as prioritary over Qt translator.
    LoadAndInstallTranslator( sys_lc.name(), i18n_dir.path() );
    }
  catch( std::exception& exc )
    {
    qWarning() << exc.what();
    }

  // TODO: Record locale translation filename(s) used in UI component (e.g.
  // AboutDialog, Settings dialog, Information dialog etc.)
}

/*******************************************************************************/
void
I18nCoreApplication
::InitializeCore( const QString& appName,
		  const QString& appVersion,
		  const QString& orgName,
		  const QString& orgDomain )
{
  setObjectName( "Application" );

  //
  // Setup application tags.
  //
  QCoreApplication::setApplicationName(
    appName
  );
  QCoreApplication::setApplicationVersion(
    appVersion
  );

  //
  // Setup organization tags.
  //
  QCoreApplication::setOrganizationName( orgName );
  QCoreApplication::setOrganizationDomain( orgDomain );

#if 0
#ifndef Q_WS_MAC
    setWindowIcon( QIcon( QLatin1String( ":/images/application_icon" ) ) );
#endif
#endif
}

/*******************************************************************************/
void
I18nCoreApplication
::InitializeSettings()
{
  //
  // Create settings proxy.
  m_Settings = new QSettings(
    QSettings::IniFormat,
    QSettings::UserScope,
    QCoreApplication::organizationName(),
    QCoreApplication::applicationName(),
    this
  );

  //
  // Restore result directory.
  QVariant resultDir(
    RetrieveSettingsKey( I18nCoreApplication::SETTINGS_KEY_RESULTS_DIR )
  );

  if( !resultDir.isNull() )
    {
    QString resultPath( resultDir.toString() );

    qDebug() << "Settings/resultsDir:" << resultPath;

    if( I18nCoreApplication::IsResultsDirValid( resultPath  ) )
      {
      m_ResultsDir.setPath( resultPath );
      }
    }
}

/*******************************************************************************/
void
I18nCoreApplication
::LoadAndInstallTranslator( const QString& filename,
			    const QString& directory,
			    const QString& searchDelimiters,
			    const QString& suffix )
{
  QString filename_ext(
    filename +
    ( suffix.isNull()
      ? ".qm"
      : suffix )
  );

  // (a) Do need to new QTranslator() here!
  QTranslator* lc_translator = new QTranslator( this );

  if( !lc_translator->load( filename, directory, searchDelimiters, suffix ) )
    {
    delete lc_translator;
    lc_translator = NULL;

    throw std::runtime_error(
      ToStdString(
	tr( "Failed to load '%1' translation file from '%2'." )
	.arg( filename_ext )
	.arg( directory )
      )
    );
    }

  // (a) ...because QTranslator needs to be alive during the whole
  // lifespan of the application.
  QCoreApplication::installTranslator( lc_translator );

  QString message(
    tr( "Successfully loaded '%1' translation file from '%2'." )
    .arg( filename_ext )
    .arg( directory )
  );

  // TODO: Log locale translation filename used.

  qDebug() << message;
}

/*******************************************************************************/
/* SLOTS                                                                       */
/*******************************************************************************/

} // end namespace 'mvd'
