/*
 * Copyright (C) 2005-2020 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 "otbWrapperCommandLineLauncher.h"

// Single value parameter
#include "otbWrapperChoiceParameter.h"
#include "otbWrapperDirectoryParameter.h"
#include "otbWrapperInputFilenameParameter.h"
#include "otbWrapperOutputFilenameParameter.h"
#include "otbWrapperInputImageParameter.h"
#include "otbWrapperInputVectorDataParameter.h"
#include "otbWrapperOutputImageParameter.h"
#include "otbWrapperOutputVectorDataParameter.h"
#include "otbWrapperNumericalParameter.h"
#include "otbWrapperListViewParameter.h"
#include "otbWrapperAddProcessToWatchEvent.h"

// List value parameter
#include "otbWrapperInputImageListParameter.h"
#include "otbWrapperInputVectorDataListParameter.h"
#include "otbWrapperInputFilenameListParameter.h"
#include "otbWrapperStringListParameter.h"


#include "otbWrapperApplicationRegistry.h"
#include "otbWrapperTypes.h"
#include <itksys/RegularExpression.hxx>
#include <string>
#include <iostream>

using std::string;

namespace otb
{
namespace Wrapper
{

CommandLineLauncher::CommandLineLauncher() : /*m_Expression(""),*/ m_VExpression(), m_WatcherList(), m_ReportProgress(true)
{
  m_Application = nullptr;
  m_Parser      = CommandLineParser::New();
  m_LogOutput   = itk::StdStreamLogOutput::New();
  m_LogOutput->SetStream(std::cout);

  // Add the callback to be added when a AddProcessToWatch event is invoked
  m_AddProcessCommand = AddProcessCommandType::New();
  m_AddProcessCommand->SetCallbackFunction(this, &CommandLineLauncher::LinkWatchers);
}

CommandLineLauncher::~CommandLineLauncher()
{
  this->DeleteWatcherList();
  m_Application = nullptr;
  ApplicationRegistry::CleanRegistry();
}

void CommandLineLauncher::DeleteWatcherList()
{
  for (unsigned int i = 0; i < m_WatcherList.size(); i++)
  {
    delete m_WatcherList[i];
    m_WatcherList[i] = nullptr;
  }
  m_WatcherList.clear();
}


bool CommandLineLauncher::Load(const std::vector<std::string>& vexp)
{
  m_VExpression = vexp;
  return this->Load();
}


bool CommandLineLauncher::Load()
{
  if (this->CheckParametersPrefix() == false)
  {
    std::cerr << "ERROR: Parameters are set using \"-\", not \"--\"." << std::endl;
    return false;
  }

  if (this->CheckUnicity() == false)
  {
    std::cerr << "ERROR: At least one parameter is not unique in the expression." << std::endl;
    return false;
  }

  if (this->LoadPath() == false)
  {
    if (m_Parser->GetPathsAsString(m_VExpression).size() != 0)
    {
      std::cerr << "ERROR: At least one specified path within \"" << m_Parser->GetPathsAsString(m_VExpression) << "\" is invalid or doesn't exist."
                << std::endl;
      return false;
    }
  }

  return this->LoadApplication();
}

bool CommandLineLauncher::Execute()
{
  if (this->BeforeExecute() == false)
  {
    return false;
  }

  if (m_Application->Execute() == 0)
  {
    this->DisplayOutputParameters();
    return true;
  }
  else
    return false;
}

bool CommandLineLauncher::ExecuteAndWriteOutputNoCatch()
{
  if (this->BeforeExecute() == false)
  {
    return false;
  }

  if (m_Application->ExecuteAndWriteOutput() != 0)
  {
    return false;
  }

  this->DisplayOutputParameters();

  // After execution, write parameters to the xml file if requested
  const char* attrib = "-outxml";
  if (m_Parser->IsAttributExists(attrib, m_VExpression))
  {
    std::vector<std::string> outXMLValues = m_Parser->GetAttribut(attrib, m_VExpression);
    m_Application->SaveParametersToXML(outXMLValues[0]);
  }

  return true;
}

bool CommandLineLauncher::ExecuteAndWriteOutput()
{
  // If testenv is used, do not catch exceptions
  if (m_Parser->IsAttributExists("-testenv", m_VExpression) == true)
  {
    return ExecuteAndWriteOutputNoCatch();
  }
  else
  {
    try
    {
      return ExecuteAndWriteOutputNoCatch();
    }
    catch (otb::ApplicationException& err)
    {
      // These are thrown with otbAppLogFATAL, a macro which logs a user
      // friendly error message before throwing. So log exception details only
      // in debug.
      m_Application->GetLogger()->Debug("Caught otb::ApplicationException during application execution:\n");
      m_Application->GetLogger()->Debug(string(err.what()) + "\n");
      return false;
    }
    catch (otb::ImageFileReaderException& err)
    {
      m_Application->GetLogger()->Debug("Caught otb::ImageFileReaderException during application execution:\n");
      m_Application->GetLogger()->Debug(string(err.what()) + "\n");
      m_Application->GetLogger()->Fatal(err.GetDescription() + string("\n"));
      return false;
    }
    catch (itk::ExceptionObject& err)
    {
      m_Application->GetLogger()->Debug("Caught itk::ExceptionObject during application execution:\n");
      m_Application->GetLogger()->Debug(string(err.what()) + "\n");
      m_Application->GetLogger()->Fatal(string(err.GetDescription()) + "\n");
      return false;
    }
    catch (std::exception& err)
    {
      m_Application->GetLogger()->Fatal(std::string("Caught std::exception during application execution: ") + err.what() + "\n");
      return false;
    }
    catch (...)
    {
      m_Application->GetLogger()->Fatal("Caught unknown exception during application execution.\n");
      return false;
    }
  }
}

bool CommandLineLauncher::BeforeExecute()
{
  if (m_Application.IsNull())
  {
    std::cerr << "ERROR: No loaded application." << std::endl;
    return false;
  }

  // Check if there's keys in the expression if the application takes
  // at least 1 mandatory parameter
  const std::vector<std::string> appKeyList = m_Application->GetParametersKeys(true);
  std::vector<std::string>       keyList    = m_Parser->GetKeyList(m_VExpression);

  if (appKeyList.size() != 0 && keyList.size() == 0)
  {
    std::cerr << "ERROR: Waiting for at least one parameter." << std::endl;
    std::cerr << std::endl;
    this->DisplayHelp();
    return false;
  }

  // if help is asked...
  if (m_Parser->IsAttributExists("-help", m_VExpression) == true)
  {
    std::vector<std::string> val;
    val = m_Parser->GetAttribut("-help", m_VExpression);

    if (val.empty())
    {
      this->DisplayHelp(true);
    }
    else
    {


      for (std::vector<std::string>::const_iterator it = val.begin(); it != val.end(); ++it)
      {
        Parameter::Pointer param = m_Application->GetParameterByKey(*it);
        if (param->GetRole() != Role_Output)
        {
          std::cerr << this->DisplayParameterHelp(param, *it, true) << std::endl;
        }
      }
    }
    return false;
  }

  // display OTB version
  if (m_Parser->IsAttributExists("-version", m_VExpression) == true)
  {
    std::cerr << "This is the " << m_Application->GetName() << " application, version " << OTB_VERSION_STRING << std::endl;
    return false;
  }

  // if we want to load test environnement
  if (m_Parser->IsAttributExists("-testenv", m_VExpression) == true)
  {
    this->LoadTestEnv();
  }

  // Check the key validity (ie. exist in the application parameters)
  std::string unknownKey;
  if (this->CheckKeyValidity(unknownKey) == false)
  {
    std::cerr << "ERROR: Parameter -" << unknownKey << " does not exist in the application." << std::endl;
    return false;
  }

  ParamResultType result = this->LoadParameters();

  if (result == MISSINGMANDATORYPARAMETER)
  {
    std::cerr << std::endl;
    this->DisplayHelp();
    return false;
  }
  else if (result != OKPARAM)
  {
    return false;
  }

  if (m_Application->IsDeprecated())
  {
    m_Application->GetLogger()->Warning("This application is deprecated and will be removed in a future OTB release.\n");
  }

  return true;
}

bool CommandLineLauncher::LoadPath()
{
  std::vector<std::string> pathList;
  // If users has set path...
  // if (m_Parser->GetPaths(pathList, m_Expression) == CommandLineParser::OK)
  if (m_Parser->GetPaths(pathList, m_VExpression) == CommandLineParser::OK)
  {
    for (unsigned i = 0; i < pathList.size(); i++)
    {
      ApplicationRegistry::AddApplicationPath(pathList[i]);
    }
  }
  else
  {
    return false;
  }

  return true;
}

bool CommandLineLauncher::LoadApplication()
{
  // Look for the module name
  std::string moduleName;
  if (m_Parser->GetModuleName(moduleName, m_VExpression) != CommandLineParser::OK)
  {
    std::cerr << "ERROR: Invalid module name: " << m_VExpression[0] << "." << std::endl;
    return false;
  }

  // Instantiate the application using the factory
  m_Application = ApplicationRegistry::CreateApplication(moduleName);

  if (m_Application.IsNull())
  {
    std::cerr << "ERROR: Could not find application \"" << moduleName << "\"" << std::endl;
    std::string modulePath = ApplicationRegistry::GetApplicationPath();
    std::cerr << "ERROR: Module search path: " << (modulePath.empty() ? "none (check OTB_APPLICATION_PATH)" : modulePath) << std::endl;

    std::vector<std::string> list = ApplicationRegistry::GetAvailableApplications();
    if (list.size() == 0)
    {
      std::cerr << "ERROR: Available modules: none." << std::endl;
    }
    else
    {
      std::cerr << "ERROR: Available modules:" << std::endl;
      for (std::vector<std::string>::const_iterator it = list.begin(); it != list.end(); ++it)
      {
        std::cerr << "\t" << *it << std::endl;
      }
    }
    return false;
  }
  else
  {
    // Attach log output to the Application logger
    m_Application->GetLogger()->AddLogOutput(m_LogOutput);

    // Add an observer to the AddedProcess event
    m_Application->AddObserver(AddProcessToWatchEvent(), m_AddProcessCommand.GetPointer());

    return true;
  }
}

CommandLineLauncher::ParamResultType CommandLineLauncher::LoadParameters()
{
  if (m_Application.IsNull())
  {
    itkExceptionMacro("No application loaded");
  }

  // Read parameters from xml file if provided
  const char* attrib = "-inxml";
  if (m_Parser->IsAttributExists(attrib, m_VExpression))
  {
    std::vector<std::string> inXMLValues = m_Parser->GetAttribut(attrib, m_VExpression);
    m_Application->LoadParametersFromXML(inXMLValues[0]);
  }

  // Check for the progress report parameter
  if (m_Parser->IsAttributExists("-progress", m_VExpression) == true)
  {
    std::vector<std::string> val = m_Parser->GetAttribut("-progress", m_VExpression);
    if (val.size() == 1 && (val[0] == "1" || val[0] == "true"))
    {
      m_ReportProgress = true;
    }
    else if (val.size() == 1 && (val[0] == "0" || val[0] == "false"))
    {
      m_ReportProgress = false;
    }
    else
    {
      std::cerr << "ERROR: Invalid value for parameter -progress. It must be 0, 1, false or true." << std::endl;
      return WRONGPARAMETERVALUE;
    }
  }

  const std::vector<std::string> appKeyList = m_Application->GetParametersKeys(true);
  // Loop over each parameter key declared in the application
  // FIRST PASS : set parameter values
  for (unsigned int i = 0; i < appKeyList.size(); i++)
  {
    const std::string        paramKey(appKeyList[i]);
    std::vector<std::string> values;

    ParameterType type = m_Application->GetParameterType(paramKey);

    const bool paramExists(m_Parser->IsAttributExists(std::string("-").append(paramKey), m_VExpression));

    // if param is a Group, don't do anything, ParamGroup don't have values
    if (type != ParameterType_Group)
    {
      // Get the attribute relative to this key as vector
      values = m_Parser->GetAttribut(std::string("-").append(paramKey), m_VExpression);

      // If the param does not exists in the cli, don't try to set a
      // value on it, an exception will be thrown later in this function
      if (paramExists)
      {
        // Check if there is a value associated to the attribute
        if (values.empty())
        {
          std::cerr << "ERROR: No value associated to parameter -" << paramKey << "." << std::endl;
          return INVALIDNUMBEROFVALUE;
        }

        if (type == ParameterType_InputVectorDataList || type == ParameterType_InputImageList || type == ParameterType_InputFilenameList ||
            type == ParameterType_StringList || type == ParameterType_ListView)
        {
          // Multiple values parameters
          m_Application->SetParameterStringList(paramKey, values);
        }
        else if (type == ParameterType_Choice || type == ParameterType_Float || type == ParameterType_Double || type == ParameterType_Int || type == ParameterType_Radius ||
                 type == ParameterType_Directory || type == ParameterType_String || type == ParameterType_InputFilename ||
                 type == ParameterType_OutputFilename || type == ParameterType_InputImage || type == ParameterType_OutputImage ||
                 type == ParameterType_InputVectorData || type == ParameterType_OutputVectorData || type == ParameterType_RAM || type == ParameterType_Bool)
        {
          // Single value parameter
          m_Application->SetParameterString(paramKey, values[0]);

          if (type == ParameterType_OutputImage)
          {
            // Check if pixel type is given
            if (values.size() == 2)
            {
              ImagePixelType pixType = ImagePixelType_float;
              if (!OutputImageParameter::ConvertStringToPixelType(values[1], pixType))
              {
                std::cerr << "ERROR: Invalid output type for parameter -" << paramKey << ": " << values[1] << "." << std::endl;
                return WRONGPARAMETERVALUE;
              }
              m_Application->SetParameterOutputImagePixelType(paramKey, pixType);
            }
            else if (values.size() > 2)
            {
              std::cerr << "ERROR: Too many values for parameter -" << paramKey << " (expected 2 or 1, got " << values.size() << ")." << std::endl;
              return INVALIDNUMBEROFVALUE;
            }
          }
        }
        // Call the DoUpdateParameter to update dependent params
        m_Application->UpdateParameters();
      }
    }
  }

  // SECOND PASS : checks
  for (const auto& paramKey : appKeyList)
  {
    // Check for missing mandatory parameters
    if (!CheckMissingMandatoryParameter(paramKey))
      return MISSINGMANDATORYPARAMETER;

    // Check and warn unused parameters
    CheckUnusedParameter(paramKey);

    // Check output paths are valid
    if (!CheckOutputPathsValidity(paramKey))
      return WRONGPARAMETERVALUE;
  }

  return OKPARAM;
}

bool CommandLineLauncher::CheckOutputPathsValidity(const std::string& paramKey) const
{
  ParameterType type = m_Application->GetParameterType(paramKey);
  if (m_Application->HasValue(paramKey) && type == ParameterType_OutputFilename)
  {
    std::string    filename = m_Application->GetParameterString(paramKey);
    itksys::String path     = itksys::SystemTools::GetFilenamePath(filename);
    if (path != "" && !itksys::SystemTools::FileIsDirectory(path))
    {
      std::cerr << "ERROR: Directory doesn't exist : " << path << std::endl;
      return false;
    }
  }
  return true;
}

bool CommandLineLauncher::CheckMissingMandatoryParameter(const std::string& paramKey) const
{
  if (m_Application->IsParameterMissing(paramKey))
  {
    std::cerr << "ERROR: Missing mandatory parameter -" << paramKey << "." << std::endl;
    return false;
  }
  return true;
}

void CommandLineLauncher::CheckUnusedParameter(const std::string& paramKey) const
{
  // Check for ignored parameters
  if (m_Application->HasUserValue(paramKey))
  {

    // Find the position of the next dot
    unsigned int start = 0;
    auto         end   = paramKey.find_first_of('.', start);

    // Until we reach en of key
    while (end != std::string::npos)
    {
      // Extract key until the current dot
      const std::string& key = paramKey.substr(0, end);

      // Find the corresponding parameter type
      ParameterType type = m_Application->GetParameterType(key);

      // In any case update the position of current and next dot
      start = end + 1;
      end   = paramKey.find_first_of('.', start);

      // If the current parameter (paramKey) has a choice in it
      if (type == ParameterType_Choice)
      {
        const std::string& value = m_Application->GetParameterString(key);

        // Check that this choice is active
        if (paramKey.find(value) == std::string::npos)
        {
          std::cerr << "WARNING: Parameter -" << paramKey << " will be ignored because -" << key << " is " << value << "." << std::endl;

          break;
        }
      }
    }
  }
}

void CommandLineLauncher::LinkWatchers(itk::Object* itkNotUsed(caller), const itk::EventObject& event)
{
  // Report the progress only if asked
  if (m_ReportProgress)
  {

    if (typeid(otb::Wrapper::AddProcessToWatchEvent) == typeid(event))
    {
      const AddProcessToWatchEvent* eventToWatch = dynamic_cast<const AddProcessToWatchEvent*>(&event);

      auto watch = new StandardOneLineFilterWatcher<>(eventToWatch->GetProcess(), eventToWatch->GetProcessDescription());
      m_WatcherList.push_back(watch);
    }
  }
}

void CommandLineLauncher::DisplayHelp(bool longHelp)
{
  std::cerr << std::endl;
  std::cerr << "This is the " << m_Application->GetName() << " application, version " << OTB_VERSION_STRING << std::endl << std::endl;

  if (m_Application->IsDeprecated())
  {
    std::cerr << "WARNING: This application is deprecated, it will be removed in a future OTB release." << std::endl;
    std::cerr << std::endl;
  }

  std::cerr << m_Application->GetDescription() << std::endl;

  if (longHelp)
  {
    std::cerr << "Tags: ";

    std::vector<std::string> tags = m_Application->GetDocTags();

    for (std::vector<std::string>::const_iterator it = tags.begin(); it != tags.end(); ++it)
    {
      std::cerr << *it << " ";
    }
    std::cerr << std::endl;

    std::cerr << std::endl;
    std::cerr << m_Application->GetDocLongDescription() << std::endl;
    std::cerr << std::endl;
  }
  else
  {
    std::string link = m_Application->GetDocLink();
    if (!link.empty())
    {
      std::cerr << "Complete documentation: " << link << " or -help" << std::endl;
      std::cerr << std::endl;
    }
  }

  std::cerr << "Parameters: " << std::endl;

  const std::vector<std::string> appKeyList = m_Application->GetParametersKeys(true);
  const unsigned int             nbOfParam  = appKeyList.size();

  unsigned int maxKeySize = GetMaxKeySize();

  //// progress report parameter
  std::string bigKey = "progress";
  for (unsigned int i = 0; i < maxKeySize - std::string("progress").size(); i++)
    bigKey.append(" ");

  for (unsigned int i = 0; i < nbOfParam; i++)
  {
    Parameter::Pointer param = m_Application->GetParameterByKey(appKeyList[i]);
    if (param->GetRole() != Role_Output)
    {
      std::cerr << this->DisplayParameterHelp(param, appKeyList[i]);
    }
  }

  std::cerr << "        -" << bigKey << " <boolean>        Report progress " << std::endl;
  bigKey = "help";
  for (unsigned int i = 0; i < maxKeySize - std::string("help").size(); i++)
    bigKey.append(" ");
  std::cerr << "        -" << bigKey << " <string list>    Display long help (empty list), or help for given parameters keys" << std::endl;

  std::cerr << std::endl;
  // std::string cl(m_Application->GetCLExample());


  std::cerr << "Use -help param1 [... paramN] to see detailed documentation of those parameters." << std::endl;
  std::cerr << std::endl;

  std::cerr << "Examples: " << std::endl;
  std::cerr << m_Application->GetCLExample() << std::endl;

  if (longHelp)
  {
    std::cerr << "Authors: " << std::endl << m_Application->GetDocAuthors() << std::endl;

    std::cerr << std::endl;
    std::cerr << "Limitations: " << std::endl << m_Application->GetDocLimitations() << std::endl;

    std::cerr << std::endl;
    std::cerr << "See also: " << std::endl << m_Application->GetDocSeeAlso() << std::endl;
    std::cerr << std::endl;
  }
}

void CommandLineLauncher::LoadTestEnv()
{
  // Set seed for rand and itk mersenne twister
  // srand(1);
  // itk::Statistics::MersenneTwisterRandomVariateGenerator::GetInstance()->SetSeed(121212);
}


std::string CommandLineLauncher::DisplayParameterHelp(const Parameter::Pointer& param, const std::string paramKey, bool longHelp)
{
  // Display the type the type
  ParameterType type = m_Application->GetParameterType(paramKey);

  std::string bigKey = paramKey;

  unsigned int maxKeySize = GetMaxKeySize();

  bool singleSelectionForListView = false;

  if (type == ParameterType_ListView)
  {
    ListViewParameter* tmp     = static_cast<ListViewParameter*>(param.GetPointer());
    singleSelectionForListView = tmp->GetSingleSelection();
  }

  std::ostringstream oss;

  if (m_Application->IsParameterMissing(paramKey))
  {
    oss << "MISSING ";
  }
  else
  {
    oss << "        ";
  }

  for (unsigned int i = 0; i < maxKeySize - paramKey.size(); i++)
    bigKey.append(" ");

  oss << "-" << bigKey << " ";

  // Display the type the parameter
  if (type == ParameterType_Radius || type == ParameterType_Int || type == ParameterType_RAM)
  {
    oss << "<int32>         ";
  }
  else if (type == ParameterType_Bool)
  {
    oss << "<boolean>       ";
  }
  else if (type == ParameterType_Float)
  {
    oss << "<float>         ";
  }
  else if (type == ParameterType_Double)
  {
    oss << "<double>         ";
  }
  else if (type == ParameterType_InputFilename || type == ParameterType_OutputFilename || type == ParameterType_Directory || type == ParameterType_InputImage ||
           type == ParameterType_InputVectorData || type == ParameterType_OutputVectorData || type == ParameterType_String || type == ParameterType_Choice ||
           (type == ParameterType_ListView && singleSelectionForListView))
  {
    oss << "<string>        ";
  }
  else if (type == ParameterType_OutputImage)
  {
    oss << "<string> [pixel]";
  }
  else if (type == ParameterType_Choice || (type == ParameterType_ListView && !singleSelectionForListView) || type == ParameterType_InputImageList ||
           type == ParameterType_InputVectorDataList || type == ParameterType_InputFilenameList || type == ParameterType_StringList)
  {
    oss << "<string list>   ";
  }
  else if (type == ParameterType_Group)
  {
    oss << "<group>         ";
  }
  else
    itkExceptionMacro("Not handled parameter type.");


  oss << " " << m_Application->GetParameterName(paramKey) << " ";

  if (type == ParameterType_OutputImage)
  {
    OutputImageParameter* paramDown = dynamic_cast<OutputImageParameter*>(param.GetPointer());
    std::string           defPixType("float");
    if (paramDown)
    {
      defPixType = OutputImageParameter::ConvertPixelTypeToString(paramDown->GetDefaultPixelType());
    }
    oss << " [pixel=uint8/uint16/int16/uint32/int32/float/double/cint16/cint32/cfloat/cdouble]";
    oss << " (default value is " << defPixType << ")";
  }

  if (type == ParameterType_Choice)
  {
    ChoiceParameter* paramDown = dynamic_cast<ChoiceParameter*>(param.GetPointer());
    if (paramDown)
    {
      std::vector<std::string> keys  = paramDown->GetChoiceKeys();
      std::vector<std::string> names = paramDown->GetChoiceNames();

      oss << "[";
      for (unsigned int i = 0; i < keys.size(); i++)
      {
        oss << keys[i];
        if (i != keys.size() - 1)
          oss << "/";
      }

      oss << "]";
    }
  }

  if (type != ParameterType_Group)
  {

    if (m_Application->IsMandatory(paramKey))
    {
      oss << " (mandatory";
    }
    else
    {
      oss << " (optional";

      if (m_Application->IsParameterEnabled(paramKey))
      {
        oss << ", on by default";
      }
      else
      {
        oss << ", off by default";
      }
    }

    if (m_Application->HasValue(paramKey))
    {
      if (m_Application->GetParameterAsString(paramKey).empty())
        oss << ", no default value";
      else
      {
        oss << ", default value is " << m_Application->GetParameterAsString(paramKey);
      }
    }
    oss << ")";
  }
  oss << std::endl;

  if (longHelp)
  {
    oss << "        ";
    for (unsigned int i = 0; i < maxKeySize; ++i)
      oss << " ";
    oss << "                   ";
    oss << m_Application->GetParameterDescription(paramKey) << std::endl;


    if (type == ParameterType_Choice)
    {
      std::vector<std::string> keys  = dynamic_cast<ChoiceParameter*>(param.GetPointer())->GetChoiceKeys();
      std::vector<std::string> names = dynamic_cast<ChoiceParameter*>(param.GetPointer())->GetChoiceNames();

      auto keyIt  = keys.begin();
      auto nameIt = names.begin();

      for (; keyIt != keys.end() && nameIt != names.end(); ++keyIt, ++nameIt)
      {
        oss << "        ";
        for (unsigned int i = 0; i < maxKeySize; ++i)
          oss << " ";
        oss << "                   ";
        oss << "- " << *nameIt << " (" << *keyIt << "): " << m_Application->GetParameterDescription(paramKey + "." + (*keyIt)) << std::endl;
      }
    }
  }
  return oss.str();
}

bool CommandLineLauncher::CheckUnicity()
{
  bool res = true;
  // Extract expression keys
  // std::vector<std::string> keyList = m_Parser->GetKeyList(m_Expression);
  std::vector<std::string> keyList = m_Parser->GetKeyList(m_VExpression);

  // Check Unicity
  for (unsigned int i = 0; i < keyList.size(); i++)
  {
    std::vector<std::string> listTmp = keyList;
    const std::string        keyRef  = keyList[i];
    listTmp.erase(listTmp.begin() + i);
    for (unsigned int j = 0; j < listTmp.size(); j++)
    {
      if (keyRef == listTmp[j])
      {
        res = false;
        break;
      }
    }
    if (!res)
      break;
  }

  return res;
}

bool CommandLineLauncher::CheckParametersPrefix()
{
  // Check if the chain " --" appears in the args, could be a common mistake
  for (std::vector<std::string>::iterator it = m_VExpression.begin(); it != m_VExpression.end(); ++it)
  {
    if (it->compare(0, 2, "--") == 0)
    {
      return false;
    }
  }
  return true;
}

bool CommandLineLauncher::CheckKeyValidity(std::string& refKey)
{
  bool res = true;
  // Extract expression keys
  std::vector<std::string> expKeyList = m_Parser->GetKeyList(m_VExpression);

  // Extract application keys
  std::vector<std::string> appKeyList = m_Application->GetParametersKeys(true);
  appKeyList.push_back("help");
  appKeyList.push_back("progress");
  appKeyList.push_back("testenv");
  appKeyList.push_back("version");
  appKeyList.push_back("inxml");
  appKeyList.push_back("outxml");

  // Check if each key in the expression exists in the application
  for (unsigned int i = 0; i < expKeyList.size(); i++)
  {
    refKey        = expKeyList[i];
    bool keyExist = false;
    for (unsigned int j = 0; j < appKeyList.size(); j++)
    {
      if (refKey == appKeyList[j])
      {
        keyExist = true;
        break;
      }
    }
    if (keyExist == false)
    {
      res = false;
      break;
    }
  }

  return res;
}

void CommandLineLauncher::DisplayOutputParameters()
{
  std::vector<std::pair<std::string, std::string>> paramList;
  paramList = m_Application->GetOutputParametersSumUp();
  if (paramList.size() == 0)
    return;

  std::ostringstream oss;
  for (unsigned int i = 0; i < paramList.size(); i++)
  {
    oss << paramList[i].first;
    oss << ": ";
    oss << paramList[i].second;
    oss << std::endl;
  }


  if (m_Parser->IsAttributExists("-testenv", m_VExpression))
  {
    std::vector<std::string> val = m_Parser->GetAttribut("-testenv", m_VExpression);
    if (val.size() == 1)
    {
      std::ofstream ofs(val[0]);
      if (!ofs.is_open())
      {
        fprintf(stderr, "Error, can't open file");
        itkExceptionMacro(<< "Error, can't open file " << val[0] << ".");
      }
      ofs << oss.str();
      ofs.close();
    }
  }

  std::cout << "Output parameters value:" << std::endl;
  std::cout << oss.str() << std::endl;
}

unsigned int CommandLineLauncher::GetMaxKeySize() const
{
  const std::vector<std::string> appKeyList = m_Application->GetParametersKeys(true);
  const unsigned int             nbOfParam  = appKeyList.size();

  unsigned int maxKeySize = std::string("progress").size();

  for (unsigned int i = 0; i < nbOfParam; i++)
  {
    if (m_Application->GetParameterRole(appKeyList[i]) != Role_Output)
    {
      if (maxKeySize < appKeyList[i].size())
        maxKeySize = appKeyList[i].size();
    }
  }

  return maxKeySize;
}
}
}
