parse_config_result
metashell::parse_config(int argc_,
                        const char* argv_[],
                        const std::map<std::string, engine_entry>& engines_,
                        iface::environment_detector& env_detector_,
                        std::ostream* out_,
                        std::ostream* err_)
{
  using boost::program_options::options_description;
  using boost::program_options::variables_map;
  using boost::program_options::store;
  using boost::program_options::notify;
  using boost::program_options::parse_command_line;
  using boost::program_options::value;

  data::config cfg;

  if (argc_ > 0)
  {
    cfg.metashell_binary = argv_[0];
  }

  const char** const minus_minus =
      std::find(argv_, argv_ + argc_, std::string("--"));
  if (minus_minus != argv_ + argc_)
  {
    cfg.extra_clang_args.insert(
        cfg.extra_clang_args.end(), minus_minus + 1, argv_ + argc_);
  }
  const int argc = minus_minus - argv_;

  std::string con_type("readline");
  cfg.use_precompiled_headers = true;

  std::string help_engine;

  const std::string engine_info =
      "The engine (C++ compiler) to use. Available engines: " +
      boost::algorithm::join(engines_ | boost::adaptors::map_keys, ", ") +
      ". Default: " + cfg.engine;

  options_description desc("Options");
  // clang-format off
  desc.add_options()
    ("help", "Display help")
    ("verbose,V", "Verbose mode")
    ("no_highlight,H", "Disable syntax highlighting")
    ("indent", "Enable indenting (experimental)")
    (
      "no_precompiled_headers",
      "Disable precompiled header usage."
      " (It needs clang++ to be available and writes to the local disc.)"
    )
    (
      "show_pragma_help",
      "Display help for pragmas in MarkDown format and exit."
    )
    (
      "show_mdb_help",
      "Display help for mdb commands in MarkDown format and exit"
    )
    (
      "disable_saving",
      "Disable saving the environment using the #msh environment save"
    )
    (
      "console", value(&con_type)->default_value(con_type),
      "Console type. Possible values: plain, readline, json"
    )
    ("nosplash", "Disable the splash messages")
    (
      "log", value(&cfg.log_file),
      "Log into a file. When it is set to -, it logs into the console."
    )
    ("engine", value(&cfg.engine), engine_info.c_str())
    ("help_engine", value(&help_engine), "Display help about the engine")
    ("preprocessor", "Starts the shell in preprocessor mode");
  // clang-format on

  using dec_arg = decommissioned_argument;
  using dec_type = decommissioned_argument::type;
  std::vector<dec_arg> dec_args{
      dec_arg{"include", 'I', dec_type::multiple_values},
      dec_arg{"define", 'D', dec_type::multiple_values},
      dec_arg{"std", dec_type::one_value},
      dec_arg{"no_warnings", 'w', dec_type::flag},
      dec_arg{'f', dec_type::one_value}, dec_arg{'s', dec_type::one_value},
      dec_arg{
          "clang", dec_type::one_value,
          "Please use \"--engine clang\" with the custom clang binary instead.",
          "{NAME} has been decommissioned. You can specify the clang binary to"
          " use by using the clang engine. For example:\n"
          "\n" +
              std::string(argv_[0]) +
              " --engine clang -- {VALUE} -std=c++0x -ftemplate-depth=256"
              " -Wfatal-errors" +
              (env_detector_.on_windows() ?
                   " -fno-ms-compatibility -U_MSC_VER" :
                   "")},
      dec_arg{
          "enable_saving", dec_type::flag,
          "Saving is enabled by default. To disable it, use --disable_saving.",
          "Saving is enabled by default."}};

  for (auto& a : dec_args)
  {
    a.add_to(desc);
  }

  try
  {
    variables_map vm;
    store(parse_command_line(argc, argv_, desc), vm);
    notify(vm);

    for (const auto& a : dec_args)
    {
      a.check(vm);
    }

    cfg.verbose = vm.count("verbose") || vm.count("V");
    cfg.syntax_highlight = !(vm.count("no_highlight") || vm.count("H"));
    cfg.indent = vm.count("indent") != 0;
    cfg.con_type = metashell::data::parse_console_type(con_type);
    cfg.use_precompiled_headers = !vm.count("no_precompiled_headers");
    cfg.saving_enabled = !vm.count("disable_saving");
    cfg.splash_enabled = vm.count("nosplash") == 0;
    cfg.preprocessor_mode = vm.count("preprocessor");
    if (vm.count("log") == 0)
    {
      cfg.log_mode = data::logging_mode::none;
    }
    else
    {
      cfg.log_mode = (cfg.log_file == "-") ? data::logging_mode::console :
                                             data::logging_mode::file;
    }

    if (vm.count("help"))
    {
      if (out_)
      {
        show_help(*out_, desc);
      }
      return parse_config_result::exit(false);
    }
    else if (vm.count("show_pragma_help"))
    {
      show_pragma_help();
      return parse_config_result::exit(false);
    }
    else if (vm.count("show_mdb_help"))
    {
      show_mdb_help();
      return parse_config_result::exit(false);
    }
    else if (vm.count("help_engine"))
    {
      show_engine_help(engines_, help_engine, out_, argv_[0]);
      return parse_config_result::exit(false);
    }
    else
    {
      return parse_config_result::start_shell(cfg);
    }
  }
  catch (const std::exception& e_)
  {
    if (err_)
    {
      *err_ << e_.what() << "\n\n";
      show_help(*err_, desc);
    }
    return parse_config_result::exit(true);
  }
}