// Copyright 2014 The Chromium OS 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 "brillo/flag_helper.h"

#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sysexits.h>

#include <base/base_switches.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>

namespace brillo {

Flag::Flag(const char* name,
           const char* default_value,
           const char* help,
           bool visible)
    : name_(name),
      default_value_(default_value),
      help_(help),
      visible_(visible) {
}

class HelpFlag : public brillo::Flag {
 public:
  HelpFlag() : Flag("help", "false", "Show this help message", true) {}

  bool SetValue(const std::string& /* value */) override { return true; };
  const char* GetType() const override { return "bool"; }
};

BoolFlag::BoolFlag(const char* name,
                   bool* value,
                   bool* no_value,
                   const char* default_value,
                   const char* help,
                   bool visible)
    : Flag(name, default_value, help, visible),
      value_(value),
      no_value_(no_value) {
}

bool BoolFlag::SetValue(const std::string& value) {
  if (value.empty()) {
    *value_ = true;
  } else {
    if (!value.compare("true"))
      *value_ = true;
    else if (!value.compare("false"))
      *value_ = false;
    else
      return false;
  }

  *no_value_ = !*value_;

  return true;
}

const char* BoolFlag::GetType() const {
  return "bool";
}

Int32Flag::Int32Flag(const char* name,
                     int* value,
                     const char* default_value,
                     const char* help,
                     bool visible)
    : Flag(name, default_value, help, visible), value_(value) {
}

bool Int32Flag::SetValue(const std::string& value) {
  return base::StringToInt(value, value_);
}

const char* Int32Flag::GetType() const {
  return "int";
}

Int64Flag::Int64Flag(const char* name,
                     int64_t* value,
                     const char* default_value,
                     const char* help,
                     bool visible)
    : Flag(name, default_value, help, visible), value_(value) {
}

bool Int64Flag::SetValue(const std::string& value) {
  return base::StringToInt64(value, value_);
}

const char* Int64Flag::GetType() const {
  return "int64";
}

UInt64Flag::UInt64Flag(const char* name,
                       uint64_t* value,
                       const char* default_value,
                       const char* help,
                       bool visible)
    : Flag(name, default_value, help, visible), value_(value) {
}

bool UInt64Flag::SetValue(const std::string& value) {
  return base::StringToUint64(value, value_);
}

const char* UInt64Flag::GetType() const {
  return "uint64";
}

DoubleFlag::DoubleFlag(const char* name,
                       double* value,
                       const char* default_value,
                       const char* help,
                       bool visible)
    : Flag(name, default_value, help, visible), value_(value) {
}

bool DoubleFlag::SetValue(const std::string& value) {
  return base::StringToDouble(value, value_);
}

const char* DoubleFlag::GetType() const {
  return "double";
}

StringFlag::StringFlag(const char* name,
                       std::string* value,
                       const char* default_value,
                       const char* help,
                       bool visible)
    : Flag(name, default_value, help, visible), value_(value) {
}

bool StringFlag::SetValue(const std::string& value) {
  value_->assign(value);

  return true;
}

const char* StringFlag::GetType() const {
  return "string";
}

namespace {
brillo::FlagHelper* instance_ = nullptr;
}  // namespace

FlagHelper::FlagHelper() : command_line_(nullptr) {
  AddFlag(std::unique_ptr<Flag>(new HelpFlag()));
}

FlagHelper::~FlagHelper() {
}

brillo::FlagHelper* FlagHelper::GetInstance() {
  if (!instance_)
    instance_ = new FlagHelper();

  return instance_;
}

void FlagHelper::ResetForTesting() {
  delete instance_;
  instance_ = nullptr;
}

void FlagHelper::Init(int argc,
                      const char* const* argv,
                      std::string help_usage) {
  brillo::FlagHelper* helper = GetInstance();
  if (!helper->command_line_) {
    if (!base::CommandLine::InitializedForCurrentProcess())
      base::CommandLine::Init(argc, argv);
    helper->command_line_ = base::CommandLine::ForCurrentProcess();
  }

  GetInstance()->SetUsageMessage(help_usage);

  GetInstance()->UpdateFlagValues();
}

void FlagHelper::UpdateFlagValues() {
  std::string error_msg;
  int error_code = EX_OK;

  // Check that base::CommandLine has been initialized.
  CHECK(base::CommandLine::InitializedForCurrentProcess());

  // If the --help flag exists, print out help message and exit.
  if (command_line_->HasSwitch("help")) {
    puts(GetHelpMessage().c_str());
    exit(EX_OK);
  }

  // Iterate over the base::CommandLine switches.  Update the value
  // of the corresponding Flag if it exists, or output an error message
  // if the flag wasn't defined.
  const base::CommandLine::SwitchMap& switch_map = command_line_->GetSwitches();

  for (const auto& pair : switch_map) {
    const std::string& key = pair.first;
    // Make sure we allow the standard logging switches (--v and --vmodule).
    if (key == switches::kV || key == switches::kVModule)
      continue;

    const std::string& value = pair.second;

    auto df_it = defined_flags_.find(key);
    if (df_it != defined_flags_.end()) {
      Flag* flag = df_it->second.get();
      if (!flag->SetValue(value)) {
        base::StringAppendF(
            &error_msg,
            "ERROR: illegal value '%s' specified for %s flag '%s'\n",
            value.c_str(),
            flag->GetType(),
            flag->name_);
        error_code = EX_DATAERR;
      }
    } else {
      base::StringAppendF(
          &error_msg, "ERROR: unknown command line flag '%s'\n", key.c_str());
      error_code = EX_USAGE;
    }
  }

  if (error_code != EX_OK) {
    puts(error_msg.c_str());
    exit(error_code);
  }
}

void FlagHelper::AddFlag(std::unique_ptr<Flag> flag) {
  defined_flags_.emplace(flag->name_, std::move(flag));
}

void FlagHelper::SetUsageMessage(std::string help_usage) {
  help_usage_.assign(std::move(help_usage));
}

std::string FlagHelper::GetHelpMessage() const {
  std::string help = help_usage_;
  help.append("\n\n");
  for (const auto& pair : defined_flags_) {
    const Flag* flag = pair.second.get();
    if (flag->visible_) {
      base::StringAppendF(&help,
                          "  --%s  (%s)  type: %s  default: %s\n",
                          flag->name_,
                          flag->help_,
                          flag->GetType(),
                          flag->default_value_);
    }
  }
  return help;
}

}  // namespace brillo