/*=========================================================================
 *
 *  Copyright UMC Utrecht and contributors
 *
 *  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.txt
 *
 *  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 "transformixlib.h"
#include "elastix.h" // For ConvertSecondsToDHMS and GetCurrentDateAndTime.

#ifdef _ELASTIX_USE_MEVISDICOMTIFF
#include "itkUseMevisDicomTiff.h"
#endif

#include "elxTransformixMain.h"
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <ctime>

#include "itkObject.h"
#include "itkDataObject.h"
#include <itksys/SystemTools.hxx>
#include <itksys/SystemInformation.hxx>

#include "itkTimeProbe.h"

namespace transformix
{

/**
 * ******************* Constructor ***********************
 */

TRANSFORMIX::TRANSFORMIX() : m_ResultImage(nullptr)
{
} // end Constructor


/**
 * ******************* Destructor ***********************
 */

TRANSFORMIX::~TRANSFORMIX()
{
  this->m_ResultImage = nullptr;
} // end Destructor


/**
 * ******************* GetResultImage ***********************
 */

TRANSFORMIX::ImagePointer
TRANSFORMIX::GetResultImage( void )
{
  return this->m_ResultImage;
} // end GetResultImage()


/**
 * ******************* TransformImage ***********************
 */

int
TRANSFORMIX::TransformImage(
  ImagePointer inputImage,
  std::vector< ParameterMapType > & parameterMaps,
  std::string outputPath,
  bool performLogging,
  bool performCout )
{
  /** Some typedef's.*/
  typedef elx::TransformixMain                        TransformixMainType;
  typedef TransformixMainType::Pointer                TransformixMainPointer;
  typedef TransformixMainType::ArgumentMapType        ArgumentMapType;
  typedef ArgumentMapType::value_type                 ArgumentMapEntryType;
  typedef elx::ElastixMain                            ElastixMainType;
  typedef ElastixMainType::DataObjectContainerType    DataObjectContainerType;
  typedef ElastixMainType::DataObjectContainerPointer DataObjectContainerPointer;

  /** Declare an instance of the Transformix class. */
  TransformixMainPointer transformix;

  DataObjectContainerPointer movingImageContainer = nullptr;;
  DataObjectContainerPointer resultImageContainer = nullptr;

  /** Initialize. */
  int             returndummy = 0;
  ArgumentMapType argMap;
  bool            outFolderPresent = false;
  std::string     outFolder        = "";
  std::string     logFileName      = "";

  std::string key;
  std::string value;

  if( !outputPath.empty() )
  {
    key   = "-out";
    value = outputPath;

    /** Make sure that last character of the output folder equals a '/'. */
    if( value.find_last_of( "/" ) != value.size() - 1  )
    {
      value.append( "/" );
    }

    outFolderPresent = true;
  }
  else
  {
    /** Put command line parameters into parameterFileList. */
    //there must be an "-out", this is checked later in code!!
    key   = "-out";
    value = "output_path_not_set";
  }

  /** Save this information. */
  outFolder = value;

  /** Attempt to save the arguments in the ArgumentMap. */
  if( argMap.count( key ) == 0 )
  {
    argMap.insert( ArgumentMapEntryType( key.c_str(), value.c_str() ) );
  }
  else if( performCout )
  {
    /** Duplicate arguments. */
    std::cerr << "WARNING!" << std::endl;
    std::cerr << "Argument " << key.c_str() << "is only required once." << std::endl;
    std::cerr << "Arguments " << key.c_str() << " " << value.c_str() << "are ignored" << std::endl;
  }

  if( performLogging )
  {
    /** Check if the output directory exists. */
    bool outFolderExists = itksys::SystemTools::FileIsDirectory( outFolder.c_str() );
    if( !outFolderExists )
    {
      if( performCout )
      {
        std::cerr << "ERROR: the output directory does not exist." << std::endl;
        std::cerr << "You are responsible for creating it." << std::endl;
      }
      return ( -2 );
    }
    else
    {
      /** Setup xout. */
      if( performLogging )
      {
        logFileName = outFolder + "transformix.log";
      }
    }
  }

  /** The argv0 argument, required for finding the component.dll/so's. */
  argMap.insert( ArgumentMapEntryType( "-argv0", "transformix" ) );

  /** Setup xout. */
  int returndummy2 = elx::xoutSetup( logFileName.c_str(), performLogging, performCout );
  if( returndummy2 && performCout )
  {
    if( performCout )
    {
      std::cerr << "ERROR while setting up xout." << std::endl;
    }
    return ( returndummy2 );
  }
  elxout << std::endl;

  /** Declare a timer, start it and print the start time. */
  itk::TimeProbe totaltimer;
  totaltimer.Start();
  elxout << "transformix is started at " << GetCurrentDateAndTime() << ".\n" << std::endl;

  /**
   * ********************* START TRANSFORMATION *******************
   */

  /** Set transformix. */
  transformix = TransformixMainType::New();

  /** Set stuff from input or needed for output */
  movingImageContainer                       = DataObjectContainerType::New();
  movingImageContainer->CreateElementAt( 0 ) = inputImage;
  transformix->SetMovingImageContainer( movingImageContainer );
  transformix->SetResultImageContainer( resultImageContainer );

  /** Run transformix. */
  returndummy = transformix->Run( argMap, parameterMaps );

  /** Check if transformix run without errors. */
  if( returndummy != 0 )
  {
    xl::xout[ "error" ] << "Errors occurred" << std::endl;
    return returndummy;
  }

  /** Get the result image */
  resultImageContainer = transformix->GetModifiableResultImageContainer();

  /** Stop timer and print it. */
  totaltimer.Stop();
  elxout << "\nTransformix has finished at " << GetCurrentDateAndTime() << "." << std::endl;
  elxout << "Elapsed time: " << ConvertSecondsToDHMS( totaltimer.GetMean(), 1 ) << ".\n" << std::endl;

  this->m_ResultImage = resultImageContainer->ElementAt( 0 );

  /** Clean up. */
  transformix = nullptr;
  TransformixMainType::UnloadComponents();

  /** Exit and return the error code. */
  return returndummy;

} // end TransformImage()


/**
 * ******************* TransformImage ***********************
 */

int
TRANSFORMIX::TransformImage(
  ImagePointer inputImage,
  ParameterMapType & parameterMap,
  std::string outputPath,
  bool performLogging,
  bool performCout )
{
  // Transform single parameter map to a one-sized vector of parameter maps and call other
  // transform method.
  std::vector< ParameterMapType > parameterMaps;
  parameterMaps.push_back( parameterMap );
  return TransformImage( inputImage, parameterMaps, outputPath, performLogging, performCout );
} // end TransformImage()

} // namespace transformix
