// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/fml/command_line.h"

namespace fml {

// CommandLine -----------------------------------------------------------------

CommandLine::Option::Option(const std::string& name) : name(name) {}

CommandLine::Option::Option(const std::string& name, const std::string& value)
    : name(name), value(value) {}

CommandLine::CommandLine() = default;

CommandLine::CommandLine(const CommandLine& from) = default;

CommandLine::CommandLine(CommandLine&& from) = default;

CommandLine::CommandLine(const std::string& argv0,
                         const std::vector<Option>& options,
                         const std::vector<std::string>& positional_args)
    : has_argv0_(true),
      argv0_(argv0),
      options_(options),
      positional_args_(positional_args) {
  for (size_t i = 0; i < options_.size(); i++)
    option_index_[options_[i].name] = i;
}

CommandLine::~CommandLine() = default;

CommandLine& CommandLine::operator=(const CommandLine& from) = default;

CommandLine& CommandLine::operator=(CommandLine&& from) = default;

bool CommandLine::HasOption(std::string_view name, size_t* index) const {
  auto it = option_index_.find(name.data());
  if (it == option_index_.end())
    return false;
  if (index)
    *index = it->second;
  return true;
}

bool CommandLine::GetOptionValue(std::string_view name,
                                 std::string* value) const {
  size_t index;
  if (!HasOption(name, &index))
    return false;
  *value = options_[index].value;
  return true;
}

std::vector<std::string_view> CommandLine::GetOptionValues(
    std::string_view name) const {
  std::vector<std::string_view> ret;
  for (const auto& option : options_) {
    if (option.name == name)
      ret.push_back(option.value);
  }
  return ret;
}

std::string CommandLine::GetOptionValueWithDefault(
    std::string_view name,
    std::string_view default_value) const {
  size_t index;
  if (!HasOption(name, &index))
    return {default_value.data(), default_value.size()};
  return options_[index].value;
}

// Factory functions (etc.) ----------------------------------------------------

namespace internal {

CommandLineBuilder::CommandLineBuilder() {}
CommandLineBuilder::~CommandLineBuilder() {}

bool CommandLineBuilder::ProcessArg(const std::string& arg) {
  if (!has_argv0_) {
    has_argv0_ = true;
    argv0_ = arg;
    return false;
  }

  // If we've seen a positional argument, then the remaining arguments are also
  // positional.
  if (started_positional_args_) {
    bool rv = positional_args_.empty();
    positional_args_.push_back(arg);
    return rv;
  }

  // Anything that doesn't start with "--" is a positional argument.
  if (arg.size() < 2u || arg[0] != '-' || arg[1] != '-') {
    bool rv = positional_args_.empty();
    started_positional_args_ = true;
    positional_args_.push_back(arg);
    return rv;
  }

  // "--" ends option processing, but isn't stored as a positional argument.
  if (arg.size() == 2u) {
    started_positional_args_ = true;
    return false;
  }

  // Note: The option name *must* be at least one character, so start at
  // position 3 -- "--=foo" will yield a name of "=foo" and no value. (Passing a
  // starting |pos| that's "too big" is OK.)
  size_t equals_pos = arg.find('=', 3u);
  if (equals_pos == std::string::npos) {
    options_.push_back(CommandLine::Option(arg.substr(2u)));
    return false;
  }

  options_.push_back(CommandLine::Option(arg.substr(2u, equals_pos - 2u),
                                         arg.substr(equals_pos + 1u)));
  return false;
}

CommandLine CommandLineBuilder::Build() const {
  if (!has_argv0_)
    return CommandLine();
  return CommandLine(argv0_, options_, positional_args_);
}

}  // namespace internal

std::vector<std::string> CommandLineToArgv(const CommandLine& command_line) {
  if (!command_line.has_argv0())
    return std::vector<std::string>();

  std::vector<std::string> argv;
  const std::vector<CommandLine::Option>& options = command_line.options();
  const std::vector<std::string>& positional_args =
      command_line.positional_args();
  // Reserve space for argv[0], options, maybe a "--" (if needed), and the
  // positional arguments.
  argv.reserve(1u + options.size() + 1u + positional_args.size());

  argv.push_back(command_line.argv0());
  for (const auto& option : options) {
    if (option.value.empty())
      argv.push_back("--" + option.name);
    else
      argv.push_back("--" + option.name + "=" + option.value);
  }

  if (!positional_args.empty()) {
    // Insert a "--" if necessary.
    if (positional_args[0].size() >= 2u && positional_args[0][0] == '-' &&
        positional_args[0][1] == '-')
      argv.push_back("--");

    argv.insert(argv.end(), positional_args.begin(), positional_args.end());
  }

  return argv;
}

}  // namespace fml