/*
 * 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.
 */

#include "host/commands/cvd/cli/flag.h"

#include "host/commands/cvd/utils/common.h"

namespace cuttlefish {

Result<std::string> CvdFlagProxy::Name() const {
  CF_EXPECT(GetType() != FlagType::kUnknown, "Unsupported flag type");
  auto decode_name = Overload{
      [](auto&& param) -> std::string { return param.Name(); },
  };
  return std::visit(decode_name, flag_);
}

CvdFlagProxy::FlagType CvdFlagProxy::GetType() const {
  auto decode_type = Overload{
      [](const CvdFlag<bool>&) -> FlagType { return FlagType::kBool; },
      [](const CvdFlag<std::int32_t>&) -> FlagType { return FlagType::kInt32; },
      [](const CvdFlag<std::string>&) -> FlagType { return FlagType::kString; },
      [](auto) -> FlagType { return FlagType::kUnknown; },
  };
  return std::visit(decode_type, flag_);
}

Result<bool> CvdFlagProxy::HasDefaultValue() const {
  CF_EXPECT(GetType() != FlagType::kUnknown, "Unsupported flag type of typeid");
  auto decode_default_value = Overload{
      [](auto&& flag) -> bool { return flag.HasDefaultValue(); },
  };
  return std::visit(decode_default_value, flag_);
}

std::vector<CvdFlagProxy> FlagCollection::Flags() const {
  std::vector<CvdFlagProxy> flags;
  flags.reserve(name_flag_map_.size());
  for (const auto& [name, flag] : name_flag_map_) {
    flags.push_back(flag);
  }
  return flags;
}

template <typename T>
static Result<std::optional<CvdFlagProxy::ValueVariant>> FilterKnownTypeFlag(
    const CvdFlag<T>& flag, cvd_common::Args& args) {
  std::optional<T> opt = CF_EXPECT(flag.FilterFlag(args));
  if (!opt) {
    return std::nullopt;
  }
  CvdFlagProxy::ValueVariant value_variant = *opt;
  return value_variant;
}

Result<std::optional<CvdFlagProxy::ValueVariant>> CvdFlagProxy::FilterFlag(
    cvd_common::Args& args) const {
  CF_EXPECT(GetType() != FlagType::kUnknown, "Unsupported flag type of typeid");
  std::optional<CvdFlagProxy::ValueVariant> output;
  auto filter_flag = Overload{
      [&args](const CvdFlag<std::int32_t>& int32_t_flag)
          -> Result<std::optional<ValueVariant>> {
        return FilterKnownTypeFlag(int32_t_flag, args);
      },
      [&args](const CvdFlag<bool>& bool_flag)
          -> Result<std::optional<ValueVariant>> {
        return FilterKnownTypeFlag(bool_flag, args);
      },
      [&args](const CvdFlag<std::string>& string_flag)
          -> Result<std::optional<ValueVariant>> {
        return FilterKnownTypeFlag(string_flag, args);
      },
      [](auto) -> Result<std::optional<ValueVariant>> {
        return CF_ERR("Invalid type is passed to FlagCollection::FilterFlags");
      },
  };
  output = CF_EXPECT(std::visit(filter_flag, flag_));
  return output;
}

Result<std::unordered_map<std::string, FlagCollection::FlagValuePair>>
FlagCollection::FilterFlags(cvd_common::Args& args) const {
  std::unordered_map<std::string, FlagCollection::FlagValuePair> output;
  for (const auto& [name, flag_proxy] : name_flag_map_) {
    auto value_opt = CF_EXPECT(flag_proxy.FilterFlag(args));
    if (!value_opt) {
      continue;
    }
    output.emplace(name,
                   FlagValuePair{.flag = flag_proxy, .value = *value_opt});
  }
  return output;
}

Result<std::unordered_map<std::string, FlagCollection::FlagValuePair>>
FlagCollection::CalculateFlags(cvd_common::Args& args) const {
  auto output = CF_EXPECT(FilterFlags(args));
  for (const auto& [name, flag_proxy] : name_flag_map_) {
    if (Contains(output, name)) {
      // the flag was given with a value, there is no need for update it
      continue;
    }
    if (!CF_EXPECT(flag_proxy.HasDefaultValue())) {
      continue;
    }
    switch (flag_proxy.GetType()) {
      case CvdFlagProxy::FlagType::kBool:
        output.emplace(
            name,
            FlagValuePair{.flag = flag_proxy,
                          .value = CF_EXPECT(flag_proxy.DefaultValue<bool>())});
        break;
      case CvdFlagProxy::FlagType::kInt32:
        output.emplace(
            name, FlagValuePair{.flag = flag_proxy,
                                .value = CF_EXPECT(
                                    flag_proxy.DefaultValue<std::int32_t>())});
        break;
      case CvdFlagProxy::FlagType::kString:
        output.emplace(
            name, FlagValuePair{.flag = flag_proxy,
                                .value = CF_EXPECT(
                                    flag_proxy.DefaultValue<std::string>())});
        break;
      default:
        return CF_ERR("Unsupported FlagType in "
                      << "--" << name);
    }
  }
  return output;
}

}  // namespace cuttlefish
