// LAF Base Library
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifndef BASE_PROGRAM_OPTIONS_H_INCLUDED
#define BASE_PROGRAM_OPTIONS_H_INCLUDED
#pragma once

#include <iosfwd>
#include <stdexcept>
#include <string>
#include <vector>

namespace base {

class InvalidProgramOption : public std::runtime_error {
public:
  InvalidProgramOption(const std::string& msg) : std::runtime_error(msg) {}
};

class InvalidProgramOptionsCombination : public std::runtime_error {
public:
  InvalidProgramOptionsCombination(const std::string& msg) : std::runtime_error(msg) {}
};

class ProgramOptionNeedsValue : public std::runtime_error {
public:
  ProgramOptionNeedsValue(const std::string& msg) : std::runtime_error(msg) {}
};

class ProgramOptions {
public:
  class Option {
  public:
    Option(const std::string& name) : m_name(name), m_mnemonic(0) {}
    // Getters
    const std::string& name() const { return m_name; }
    const std::string& alias() const { return m_alias; }
    const std::string& description() const { return m_description; }
    const std::string& getValueName() const { return m_valueName; }
    char mnemonic() const { return m_mnemonic; }
    bool doesRequireValue() const { return !m_valueName.empty(); }
    // Setters
    Option& alias(const std::string& alias)
    {
      m_alias = alias;
      return *this;
    }
    Option& description(const std::string& desc)
    {
      m_description = desc;
      return *this;
    }
    Option& mnemonic(char mnemonic)
    {
      m_mnemonic = mnemonic;
      return *this;
    }
    Option& requiresValue(const std::string& valueName)
    {
      m_valueName = valueName;
      return *this;
    }

  private:
    std::string m_name; // Name of the option (e.g. "help" for "--help")
    std::string m_alias;
    std::string m_description; // Description of the option (this can be used when the help is
                               // printed).
    std::string m_valueName;   // Empty if this option doesn't require a value, or the name of the
                               // expected value.
    char m_mnemonic; // One character that can be used in the command line to use this option.

    friend class ProgramOptions;
  };

  class Value {
  public:
    Value(Option* option, const std::string& value) : m_option(option), m_value(value) {}
    const Option* option() const { return m_option; }
    const std::string& value() const { return m_value; }

  private:
    Option* m_option;
    std::string m_value;
  };

  typedef std::vector<Option*> OptionList;
  typedef std::vector<Value> ValueList;

  ProgramOptions();

  // After destructing the ProgramOptions, you cannot continue using
  // references to "Option" instances obtained through add() function.
  ~ProgramOptions();

  // Adds a option for the program. The options must be specified
  // before calling parse(). The returned reference must be used in
  // the ProgramOptions lifetime.
  Option& add(const std::string& name);

  // Detects which options where specified in the command line.
  void parse(int argc, const char* argv[]);

  // Reset all option values/flags.
  void reset();

  // Returns the list of available options for the user.
  const OptionList& options() const { return m_options; }

  // List of specified options/values in the command line.
  const ValueList& values() const { return m_values; }

  bool enabled(const Option& option) const;
  std::string value_of(const Option& option) const;

private:
  OptionList m_options;
  ValueList m_values;
};

} // namespace base

// Prints the program options correctly formatted to be read by
// the user. E.g. This can be used in a --help option.
std::ostream& operator<<(std::ostream& os, const base::ProgramOptions& po);

#endif
