/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_LIBARTBASE_BASE_FLAGS_H_
#define ART_LIBARTBASE_BASE_FLAGS_H_

#include <forward_list>
#include <optional>
#include <string>
#include <variant>

#include "logging.h"

// This file defines a set of flags that can be used to enable/disable features within ART or
// otherwise tune ART's behavior. Flags can be set through command line options, server side
// configuration, system properties, or default values. This flexibility enables easier development
// and also larger experiments.
//
// The value is retrieved in the following oder:
//   1) server side (device config) property
//   2) system property
//   3) cmdline flag
//   4) default value
//
// The flags are defined in the Flags struct near the bottom of the file. To define a new flag, add
// a Flag field to the struct. Then to read the value of the flag, use gFlag.MyNewFlag().

#pragma clang diagnostic push
#pragma clang diagnostic error "-Wconversion"

namespace art {

// FlagMetaBase handles automatically adding flags to the command line parser. It is parameterized
// by all supported flag types. In general, this should be treated as though it does not exist and
// FlagBase, which is already specialized to the types we support, should be used instead.
template <typename... T>
class FlagMetaBase {
 public:
  FlagMetaBase(const std::string&& command_line_argument_name,
               const std::string&& system_property_name,
               const std::string&& server_setting_name) :
      command_line_argument_name_(command_line_argument_name),
      system_property_name_(system_property_name),
      server_setting_name_(server_setting_name) {}
  virtual ~FlagMetaBase() {}

  template <typename Builder>
  static void AddFlagsToCmdlineParser(Builder* builder) {
    for (auto* flag : ALL_FLAGS) {
      // Each flag can return a pointer to where its command line value is stored. Because these can
      // be different types, the return value comes as a variant. The cases list below contains a
      // lambda that is specialized to handle each branch of the variant and call the correct
      // methods on the command line parser builder.
      FlagValuePointer location = flag->GetCmdLineLocation();
      auto cases = {std::function<void()>([&]() {
        if (std::holds_alternative<std::optional<T>*>(location)) {
          builder = &builder->Define(flag->command_line_argument_name_.c_str())
                         .template WithType<T>()
                         .IntoLocation(std::get<std::optional<T>*>(location));
        }
      })...};
      for (auto c : cases) {
        c();
      }
    }
  }

  // Reload the value of the flags.
  //
  // DO NOT CALL this outside Runtime Init or Zygote post fork.
  // This is a convention, as we should strive to have a constant view
  // of the flags and not change the runtime behaviour midway during execution.
  static void ReloadAllFlags(const std::string& caller) {
    // Check the caller. This is a simple workaround to attract the attention
    // to a possible dangerous call to ReloadAllFlags, while avoid building
    // a lot of infra for it or having a complex friend definition.
    DCHECK(caller == "Init"
        || caller == "ZygoteHooks_nativePostForkChild"
        || caller == "ZygoteHooks_nativePostForkSystemServer"
        || caller == "test") << caller;
    for (auto* flag : ALL_FLAGS) {
      flag->Reload();
    }

    if (VLOG_IS_ON(startup)) {
      VLOG_STREAM(startup) << "Dumping flags for " << caller;
      DumpFlags(VLOG_STREAM(startup));
    }
  }

  // Dump all the flags info to the given stream.
  static void DumpFlags(std::ostream& oss) {
    for (auto* flag : ALL_FLAGS) {
      oss << "\n{\n";
      flag->Dump(oss);
      oss << "\n}";
    }
  }

 protected:
  using FlagValuePointer = std::variant<std::optional<T>*...>;
  // Return the pointer to the value holder associated with the cmd line location.
  virtual FlagValuePointer GetCmdLineLocation() = 0;
  // Reloads the flag values.
  virtual void Reload() = 0;
  // Dumps the flags info to the given stream.
  virtual void Dump(std::ostream& oss) const = 0;

  static std::forward_list<FlagMetaBase<T...>*> ALL_FLAGS;

  const std::string command_line_argument_name_;
  const std::string system_property_name_;
  const std::string server_setting_name_;
};

using FlagBase = FlagMetaBase<bool, int, std::string>;

template <>
std::forward_list<FlagBase*> FlagBase::ALL_FLAGS;

class FlagsTests;

// This class defines a flag with a value of a particular type.
template <typename Value>
class Flag : public FlagBase {
 public:
  // Create a new Flag. The name parameter is used to generate the names from the various parameter
  // sources. See the documentation on the Flags struct for an example.
  explicit Flag(const std::string& name, Value default_value = {});
  virtual ~Flag();


  // Returns the flag value.
  //
  // The value is retrieved in the following oder:
  //   1) server side (device config) property
  //   2) system property
  //   3) cmdline flag
  //   4) default value
  ALWAYS_INLINE Value GetValue() const {
    return std::get<0>(GetValueLocation());
  }

  ALWAYS_INLINE Value operator()() const {
    return GetValue();
  }

  // Returns the value and the location of that value for the given flag.
  ALWAYS_INLINE std::pair<Value, std::string> GetValueLocation() const {
    DCHECK(initialized_);
    if (from_server_setting_.has_value()) {
      return std::pair{from_server_setting_.value(), server_setting_name_};
    }
    if (from_system_property_.has_value()) {
      return std::pair{from_system_property_.value(), system_property_name_};
    }
    if (from_command_line_.has_value()) {
      return std::pair{from_command_line_.value(), command_line_argument_name_};
    }
    return std::pair{default_, "default_value"};
  }

  void Dump(std::ostream& oss) const override;

 protected:
  FlagValuePointer GetCmdLineLocation() override { return &from_command_line_; }


  // Reload the server-configured value and system property values. In general this should not be
  // used directly, but it can be used to support reloading the value without restarting the device.
  void Reload() override;

 private:
  bool initialized_;
  const Value default_;
  std::optional<Value> from_command_line_;
  std::optional<Value> from_system_property_;
  std::optional<Value> from_server_setting_;

  friend class TestFlag;
};

// This struct contains the list of ART flags. Flags are parameterized by the type of value they
// support (bool, int, string, etc.). In addition to field name, flags have a name for the parameter
// as well.
//
// Example:
//
//     Flag<int> WriteMetricsToLog{"my-feature-test.flag", 42};
//
// This creates a boolean flag that can be read through gFlags.WriteMetricsToLog(). The default
// value is false. Note that the default value can be left unspecified, in which the value of the
// type's default constructor will be used.
//
// The flag can be set through the following generated means:
//
// Command Line:
//
//     -Xmy-feature-test-flag=1
//
// Server Side (Phenotype) Configuration:
//
//     persist.device_config.runtime_native.my-feature-test.flag
//
// System Property:
//
//     setprop dalvik.vm.metrics.my-feature-test.flag 2
struct Flags {
  // Flag used to test the infra.
  // TODO: can be removed once we add real flags.
  Flag<int> MyFeatureTestFlag{"my-feature-test.flag", /*default_value=*/ 42};
};

// This is the actual instance of all the flags.
extern Flags gFlags;

}  // namespace art

#pragma clang diagnostic pop  // -Wconversion

#endif  // ART_LIBARTBASE_BASE_FLAGS_H_
