/**
 * @file cli_impl.hpp
 * @author Matthew Amidon
 *
 * Implementation of templated functions of the CLI class.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#ifndef MLPACK_CORE_UTIL_CLI_IMPL_HPP
#define MLPACK_CORE_UTIL_CLI_IMPL_HPP

// In case it has not already been included.
#include "cli.hpp"
#include "prefixedoutstream.hpp"

// Include option.hpp here because it requires CLI but is also templated.
#include "option.hpp"

namespace mlpack {

/**
 * @brief Adds a parameter to CLI, making it accessibile via GetParam &
 *     CheckValue.
 *
 * @tparam T The type of the parameter.
 * @param identifier The name of the parameter, eg foo.
 * @param description A string description of the parameter.
 * @param alias Short name of the parameter.
 * @param required If required, the program will refuse to run unless the
 *     parameter is specified.
 * @param input If true, the parameter is an input parameter (not an output
 *     parameter).
 */
template<typename T>
void CLI::Add(const std::string& identifier,
              const std::string& description,
              const std::string& alias,
              const bool required,
              const bool input)
{
  // Temporarily define color code escape sequences.
  #ifndef _WIN32
    #define BASH_RED "\033[0;31m"
    #define BASH_CLEAR "\033[0m"
  #else
    #define BASH_RED ""
    #define BASH_CLEAR ""
  #endif

  // Temporary outstream object for detecting duplicate identifiers.
  util::PrefixedOutStream outstr(std::cerr,
        BASH_RED "[FATAL] " BASH_CLEAR, false, true /* fatal */);

  #undef BASH_RED
  #undef BASH_CLEAR

  // Define identifier and alias maps.
  gmap_t& gmap = GetSingleton().globalValues;
  amap_t& amap = GetSingleton().aliasValues;

  // If found in current map, print fatal error and terminate the program.
  if (gmap.count(identifier))
    outstr << "Parameter --" << identifier << "(-" << alias << ") "
           << "is defined multiple times with same identifiers." << std::endl;
  if (amap.count(alias))
    outstr << "Parameter --" << identifier << "(-" << alias << ") "
           << "is defined multiple times with same alias." << std::endl;

  // Must make use of boost syntax here.
  std::string progOptId =
          alias.length() ? identifier + "," + alias : identifier;

  // Add the alias, if necessary
  AddAlias(alias, identifier);

  // Add the option to boost program_options.
  GetSingleton().AddOption<T>(progOptId.c_str(), description.c_str());

  // Make sure the appropriate metadata is inserted into gmap.
  ParamData data;
  T tmp = T();

  data.desc = description;
  data.name = identifier;
  data.tname = TYPENAME(T);
  data.value = boost::any(tmp);
  data.wasPassed = false;

  gmap[identifier] = data;

  // If the option is required, add it to the required options list.
  if (required)
    GetSingleton().requiredOptions.push_front(identifier);

  // Depending on whether or not the option is input or output, add it to the
  // appropriate list.
  if (input)
    GetSingleton().inputOptions.push_front(identifier);
  else
    GetSingleton().outputOptions.push_front(identifier);
}

// We specialize this in cli.cpp.
template<>
bool& CLI::GetParam<bool>(const std::string& identifier);

/**
 * @brief Returns the value of the specified parameter.
 *   If the parameter is unspecified, an undefined but
 *   more or less valid value is returned.
 *
 * @tparam T The type of the parameter.
 * @param identifier The full name of the parameter.
 *
 * @return The value of the parameter.  Use CLI::CheckValue to determine if it's
 *     valid.
 */
template<typename T>
T& CLI::GetParam(const std::string& identifier)
{
  // Used to ensure we have a valid value.
  T tmp = T();

  // Used to index into the globalValues map.
  std::string key = std::string(identifier);
  gmap_t& gmap = GetSingleton().globalValues;

  // Now check if we have an alias.
  amap_t& amap = GetSingleton().aliasValues;
  if (amap.count(key))
    key = amap[key];

  // What if we don't actually have any value?
  if (!gmap.count(key))
  {
    gmap[key] = ParamData();
    gmap[key].value = boost::any(tmp);
    *boost::any_cast<T>(&gmap[key].value) = tmp;
  }

  // What if we have meta-data, but no data?
  boost::any val = gmap[key].value;
  if (val.empty())
    gmap[key].value = boost::any(tmp);

  return *boost::any_cast<T>(&gmap[key].value);
}

template<typename T>
void CLI::AddOption(
    const char* optId,
    const char* descr,
    const typename boost::disable_if<IsStdVector<T>>::type* /* junk */)
{
  desc.add_options()(optId, po::value<T>(), descr);
}

template<typename T>
void CLI::AddOption(
    const char* optId,
    const char* descr,
    const typename boost::enable_if<IsStdVector<T>>::type* /* junk */)
{
  desc.add_options()(optId, po::value<T>()->multitoken(), descr);
}

} // namespace mlpack

#endif
