/*
 * Copyright (C) 2023 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.
 */

#pragma once

#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>

#include "common/libs/utils/contains.h"
#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/result.h"
#include "host/commands/cvd/cli/types.h"

namespace cuttlefish {

/**
 * Data structure to represent cvd user-facing flags
 *
 * Flag in flag_parser.h is more on parsing. gflags library would be
 * slowly depreicated. The cvd driver and selector flags are a specification for
 * a user-facing flag.
 */
template <typename T>
class CvdFlag {
 public:
  using GflagFactoryCallback =
      std::function<Flag(const std::string& name, T& value_out)>;
  CvdFlag(const std::string& name)
      : name_(name),
        gflag_factory_cb([](const std::string& name, T& value_out) {
          return GflagsCompatFlag(name, value_out);
        }) {}

  CvdFlag(const std::string& name, const T& default_value)
      : name_(name),
        default_value_(default_value),
        gflag_factory_cb([](const std::string& name, T& value_out) {
          return GflagsCompatFlag(name, value_out);
        }) {}

  std::string Name() const { return name_; }
  std::string HelpMessage() const { return help_msg_; }
  CvdFlag& SetHelpMessage(const std::string& help_msg) & {
    help_msg_ = help_msg;
    return *this;
  }
  CvdFlag SetHelpMessage(const std::string& help_msg) && {
    help_msg_ = help_msg;
    return *this;
  }
  bool HasDefaultValue() const { return default_value_ != std::nullopt; }
  Result<T> DefaultValue() const {
    CF_EXPECT(HasDefaultValue());
    return *default_value_;
  }

  CvdFlag& SetGflagFactory(GflagFactoryCallback factory) & {
    gflag_factory_cb = std::move(factory);
    return *this;
  }
  CvdFlag SetGflagFactory(GflagFactoryCallback factory) && {
    gflag_factory_cb = std::move(factory);
    return *this;
  }

  // returns CF_ERR if parsing error,
  // returns std::nullopt if parsing was okay but the flag wasn't given
  Result<std::optional<T>> FilterFlag(cvd_common::Args& args) const {
    const std::size_t args_initial_size = args.size();
    if (args_initial_size == 0) {
      return std::nullopt;
    }
    T value;
    CF_EXPECT(ConsumeFlags({gflag_factory_cb(name_, value)}, args),
              "Failed to parse --" << name_);
    if (args.size() == args_initial_size) {
      // not consumed
      return std::nullopt;
    }
    return value;
  }

  // Parses the arguments. If flag is given, returns the parsed value. If not,
  // returns the default value if any. If no default value, it returns CF_ERR.
  Result<T> CalculateFlag(cvd_common::Args& args) const {
    auto value_opt = CF_EXPECT(FilterFlag(args));
    if (!value_opt) {
      CF_EXPECT(default_value_ != std::nullopt);
      value_opt = default_value_;
    }
    return *value_opt;
  }

 private:
  const std::string name_;
  std::string help_msg_;
  std::optional<T> default_value_;
  /**
   * A callback function to generate Flag defined in
   * common/libs/utils/flag_parser.h. The name is this CvdFlag's name.
   * The value is a buffer that is kept in this object
   */
  GflagFactoryCallback gflag_factory_cb;
};

class CvdFlagProxy {
  friend class FlagCollection;

 public:
  enum class FlagType : std::uint32_t {
    kUnknown = 0,
    kBool,
    kInt32,
    kString,
  };

  static std::string ToString(const FlagType flag_type) {
    switch (flag_type) {
      case FlagType::kUnknown:
        return "kUnknown";
      case FlagType::kBool:
        return "bool";
      case FlagType::kInt32:
        return "std::int32_t";
      case FlagType::kString:
        return "std::string";
    }
  }

  template <typename T>
  CvdFlagProxy(CvdFlag<T>&& flag) : flag_{std::move(flag)} {}

  template <typename T>
  const CvdFlag<T>* GetFlag() const {
    return std::get_if<CvdFlag<T>>(&flag_);
  }

  template <typename T>
  CvdFlag<T>* GetFlag() {
    return std::get_if<CvdFlag<T>>(&flag_);
  }

  /*
   * If the actual type of flag_ is not handled by SelectorFlagProxy, it is a
   * developer error, and the Name() and HasDefaultValue() will returns
   * CF_ERR
   */
  Result<std::string> Name() const;
  Result<bool> HasDefaultValue() const;

  FlagType GetType() const;

  template <typename T>
  Result<T> DefaultValue() const {
    const bool has_default_value = CF_EXPECT(HasDefaultValue());
    CF_EXPECT(has_default_value == true);
    const auto* ptr = CF_EXPECT(std::get_if<CvdFlag<T>>(&flag_));
    CF_EXPECT(ptr != nullptr);
    return ptr->DefaultValue();
  }

  // returns CF_ERR if parsing error,
  // returns std::nullopt if parsing was okay but the flag wasn't given
  template <typename T>
  Result<std::optional<T>> FilterFlag(cvd_common::Args& args) const {
    std::optional<T> output;
    const auto* ptr = CF_EXPECT(std::get_if<CvdFlag<T>>(&flag_));
    CF_EXPECT(ptr != nullptr);
    output = CF_EXPECT(ptr->FilterFlag(args));
    return output;
  }

  // Parses the arguments. If flag is given, returns the parsed value. If not,
  // returns the default value if any. If no default value, it returns CF_ERR.
  template <typename T>
  Result<T> CalculateFlag(cvd_common::Args& args) const {
    bool has_default_value = CF_EXPECT(HasDefaultValue());
    CF_EXPECT(has_default_value == true);
    const auto* ptr = CF_EXPECT(std::get_if<CvdFlag<T>>(&flag_));
    CF_EXPECT(ptr != nullptr);
    T output = CF_EXPECT(ptr->CalculateFlag(args));
    return output;
  }

  using ValueVariant = std::variant<std::int32_t, bool, std::string>;

  // Returns std::nullopt when the parsing goes okay but the flag wasn't given
  // Returns ValueVariant when the flag was given in args
  // Returns CF_ERR when the parsing failed or the type is not supported
  Result<std::optional<ValueVariant>> FilterFlag(cvd_common::Args& args) const;

 private:
  std::variant<CvdFlag<std::int32_t>, CvdFlag<bool>, CvdFlag<std::string>>
      flag_;
};

class FlagCollection {
 public:
  using ValueVariant = CvdFlagProxy::ValueVariant;

  Result<void> EnrollFlag(CvdFlagProxy&& flag) {
    auto name = CF_EXPECT(flag.Name());
    CF_EXPECT(!Contains(name_flag_map_, name),
              name << " is already registered.");
    name_flag_map_.emplace(name, std::move(flag));
    return {};
  }

  template <typename T>
  Result<void> EnrollFlag(CvdFlag<T>&& flag) {
    CF_EXPECT(EnrollFlag(CvdFlagProxy(std::move(flag))));
    return {};
  }

  Result<CvdFlagProxy> GetFlag(const std::string& name) const {
    const auto itr = name_flag_map_.find(name);
    CF_EXPECT(itr != name_flag_map_.end(),
              "Flag \"" << name << "\" is not found.");
    const CvdFlagProxy& flag_proxy = itr->second;
    return flag_proxy;
  }

  std::vector<CvdFlagProxy> Flags() const;

  struct FlagValuePair {
    CvdFlagProxy flag;
    ValueVariant value;
  };

  /* does not consider default values
   * so, if not default value and the flag wasn't given, it won't be found
   * in the returned map
   */
  Result<std::unordered_map<std::string, FlagValuePair>> FilterFlags(
      cvd_common::Args& args) const;

  /* considers default values
   * so, if the flag wasn't given, the default value will be used to fill
   * out the returned map. If a default value isn't available and the flag
   * isn't given either, the entry won't be in the returned map
   */
  Result<std::unordered_map<std::string, FlagValuePair>> CalculateFlags(
      cvd_common::Args& args) const;

  template <typename T>
  static Result<T> GetValue(const ValueVariant& value_variant) {
    auto* value_ptr = std::get_if<T>(std::addressof(value_variant));
    CF_EXPECT(value_ptr != nullptr,
              "GetValue template function was instantiated with a wrong type.");
    return *value_ptr;
  }

  template <typename T>
  static Result<T> GetValue(const FlagValuePair& flag_and_value) {
    std::string flag_type_string =
        CvdFlagProxy::ToString(flag_and_value.flag.GetType());
    auto* value_ptr = std::get_if<T>(std::addressof(flag_and_value.value));
    CF_EXPECT(value_ptr != nullptr,
              "The actual flag type is " << flag_type_string);
    return *value_ptr;
  }

 private:
  std::unordered_map<std::string, CvdFlagProxy> name_flag_map_;
};

}  // namespace cuttlefish
