// Copyright 2014 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "include/command_line.h"

#include <algorithm>
#include <ostream>
#include <string>

#include "include/macros.h"
#include "include/string_util.h"

namespace gestures {

CommandLine* CommandLine::current_process_commandline_ = nullptr;

namespace {

const CommandLine::CharType kSwitchTerminator[] = "--";
const CommandLine::CharType kSwitchValueSeparator[] = "=";

// Since we use a lazy match, make sure that longer versions (like "--") are
// listed before shorter versions (like "-") of similar prefixes.
// Unixes don't use slash as a switch.
const CommandLine::CharType* const kSwitchPrefixes[] = {"--", "-"};
size_t switch_prefix_count = arraysize(kSwitchPrefixes);

size_t GetSwitchPrefixLength(const std::string& string) {
  for (size_t i = 0; i < switch_prefix_count; ++i) {
    std::string prefix(kSwitchPrefixes[i]);
    if (string.compare(0, prefix.length(), prefix) == 0)
      return prefix.length();
  }
  return 0;
}

// Fills in |switch_string| and |switch_value| if |string| is a switch.
// This will preserve the input switch prefix in the output |switch_string|.
bool IsSwitch(const std::string& string,
              std::string* switch_string,
              std::string* switch_value) {
  switch_string->clear();
  switch_value->clear();
  size_t prefix_length = GetSwitchPrefixLength(string);
  if (prefix_length == 0 || prefix_length == string.length())
    return false;

  const size_t equals_position = string.find(kSwitchValueSeparator);
  *switch_string = string.substr(0, equals_position);
  if (equals_position != std::string::npos)
    *switch_value = string.substr(equals_position + 1);
  return true;
}

// Append switches and arguments, keeping switches before arguments.
void AppendSwitchesAndArguments(CommandLine& command_line,
                                const CommandLine::StringVector& argv) {
  bool parse_switches = true;
  for (size_t i = 1; i < argv.size(); ++i) {
    std::string arg = argv[i];
    arg = TrimWhitespaceASCII(arg);

    std::string switch_string;
    std::string switch_value;
    parse_switches &= (arg != kSwitchTerminator);
    if (parse_switches && IsSwitch(arg, &switch_string, &switch_value)) {
      command_line.AppendSwitchASCII(switch_string, switch_value);
    } else {
      command_line.AppendArgASCII(arg);
    }
  }
}

}  // namespace

CommandLine::CommandLine()
    : argv_(1),
      begin_args_(1) {}

CommandLine::~CommandLine() {}

// static
bool CommandLine::Init(int argc, const char* const* argv) {
  if (current_process_commandline_) {
    // If this is intentional, Reset() must be called first. If we are using
    // the shared build mode, we have to share a single object across multiple
    // shared libraries.
    return false;
  }

  current_process_commandline_ = new CommandLine();
  current_process_commandline_->InitFromArgv(argc, argv);
  return true;
}

// static
void CommandLine::Reset() {
  delete current_process_commandline_;
  current_process_commandline_ = nullptr;
}

// static
CommandLine* CommandLine::ForCurrentProcess() {
  if (!current_process_commandline_) {
    fprintf(stderr, "FATAL: Command line not initialized!\n");
    abort();
  }
  return current_process_commandline_;
}


void CommandLine::InitFromArgv(int argc,
                               const CommandLine::CharType* const* argv) {
  StringVector new_argv;
  for (int i = 0; i < argc; ++i)
    new_argv.push_back(argv[i]);
  InitFromArgv(new_argv);
}

void CommandLine::InitFromArgv(const StringVector& argv) {
  argv_ = StringVector(1);
  switches_.clear();
  begin_args_ = 1;
  SetProgram(argv.empty() ? "" : argv[0]);
  AppendSwitchesAndArguments(*this, argv);
}

std::string CommandLine::GetProgram() const {
  return argv_[0];
}

void CommandLine::SetProgram(const std::string& program) {
  argv_[0] = TrimWhitespaceASCII(program);
}

bool CommandLine::HasSwitch(const std::string& switch_string) const {
  return switches_.find(switch_string) != switches_.end();
}

std::string CommandLine::GetSwitchValueASCII(
    const std::string& switch_string) const {
  SwitchMap::const_iterator result =
    switches_.find(switch_string);
  return result == switches_.end() ? std::string() : result->second;
}

void CommandLine::AppendSwitch(const std::string& switch_string) {
  AppendSwitchASCII(switch_string, std::string());
}

void CommandLine::AppendSwitchASCII(const std::string& switch_string,
                                     const std::string& value) {
  std::string switch_key(switch_string);
  std::string combined_switch_string(switch_string);
  size_t prefix_length = GetSwitchPrefixLength(combined_switch_string);
  switches_[switch_key.substr(prefix_length)] = value;
  // Preserve existing switch prefixes in |argv_|; only append one if necessary.
  if (prefix_length == 0)
    combined_switch_string = kSwitchPrefixes[0] + combined_switch_string;
  if (!value.empty())
    combined_switch_string += kSwitchValueSeparator + value;
  // Append the switch and update the switches/arguments divider |begin_args_|.
  argv_.insert(argv_.begin() + begin_args_++, combined_switch_string);
}

CommandLine::StringVector CommandLine::GetArgs() const {
  // Gather all arguments after the last switch (may include kSwitchTerminator).
  StringVector args(argv_.begin() + begin_args_, argv_.end());
  // Erase only the first kSwitchTerminator (maybe "--" is a legitimate page?)
  StringVector::iterator switch_terminator =
      std::find(args.begin(), args.end(), kSwitchTerminator);
  if (switch_terminator != args.end())
    args.erase(switch_terminator);
  return args;
}

void CommandLine::AppendArgASCII(const std::string& value) {
  argv_.push_back(value);
}

}  // namespace gestures
