/*
 *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_
#define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_

#include <stdint.h>

#include <initializer_list>
#include <map>
#include <set>
#include <string>
#include <vector>

#include "absl/strings/string_view.h"
#include "absl/types/optional.h"

// Field trial parser functionality. Provides funcitonality to parse field trial
// argument strings in key:value format. Each parameter is described using
// key:value, parameters are separated with a ,. Values can't include the comma
// character, since there's no quote facility. For most types, white space is
// ignored. Parameters are declared with a given type for which an
// implementation of ParseTypedParameter should be provided. The
// ParseTypedParameter implementation is given whatever is between the : and the
// ,. If the key is provided without : a FieldTrialOptional will use nullopt.

// Example string: "my_optional,my_int:3,my_string:hello"

// For further description of usage and behavior, see the examples in the unit
// tests.

namespace webrtc {
class FieldTrialParameterInterface {
 public:
  virtual ~FieldTrialParameterInterface();
  std::string key() const { return key_; }

 protected:
  // Protected to allow implementations to provide assignment and copy.
  FieldTrialParameterInterface(const FieldTrialParameterInterface&) = default;
  FieldTrialParameterInterface& operator=(const FieldTrialParameterInterface&) =
      default;
  explicit FieldTrialParameterInterface(absl::string_view key);
  friend void ParseFieldTrial(
      std::initializer_list<FieldTrialParameterInterface*> fields,
      absl::string_view trial_string);
  void MarkAsUsed() { used_ = true; }
  virtual bool Parse(absl::optional<std::string> str_value) = 0;

  virtual void ParseDone() {}

  std::vector<FieldTrialParameterInterface*> sub_parameters_;

 private:
  std::string key_;
  bool used_ = false;
};

// ParseFieldTrial function parses the given string and fills the given fields
// with extracted values if available.
void ParseFieldTrial(
    std::initializer_list<FieldTrialParameterInterface*> fields,
    absl::string_view trial_string);

// Specialize this in code file for custom types. Should return absl::nullopt if
// the given string cannot be properly parsed.
template <typename T>
absl::optional<T> ParseTypedParameter(absl::string_view);

// This class uses the ParseTypedParameter function to implement a parameter
// implementation with an enforced default value.
template <typename T>
class FieldTrialParameter : public FieldTrialParameterInterface {
 public:
  FieldTrialParameter(absl::string_view key, T default_value)
      : FieldTrialParameterInterface(key), value_(default_value) {}
  T Get() const { return value_; }
  operator T() const { return Get(); }
  const T* operator->() const { return &value_; }

  void SetForTest(T value) { value_ = value; }

 protected:
  bool Parse(absl::optional<std::string> str_value) override {
    if (str_value) {
      absl::optional<T> value = ParseTypedParameter<T>(*str_value);
      if (value.has_value()) {
        value_ = value.value();
        return true;
      }
    }
    return false;
  }

 private:
  T value_;
};

// This class uses the ParseTypedParameter function to implement a parameter
// implementation with an enforced default value and a range constraint. Values
// outside the configured range will be ignored.
template <typename T>
class FieldTrialConstrained : public FieldTrialParameterInterface {
 public:
  FieldTrialConstrained(absl::string_view key,
                        T default_value,
                        absl::optional<T> lower_limit,
                        absl::optional<T> upper_limit)
      : FieldTrialParameterInterface(key),
        value_(default_value),
        lower_limit_(lower_limit),
        upper_limit_(upper_limit) {}
  T Get() const { return value_; }
  operator T() const { return Get(); }
  const T* operator->() const { return &value_; }

 protected:
  bool Parse(absl::optional<std::string> str_value) override {
    if (str_value) {
      absl::optional<T> value = ParseTypedParameter<T>(*str_value);
      if (value && (!lower_limit_ || *value >= *lower_limit_) &&
          (!upper_limit_ || *value <= *upper_limit_)) {
        value_ = *value;
        return true;
      }
    }
    return false;
  }

 private:
  T value_;
  absl::optional<T> lower_limit_;
  absl::optional<T> upper_limit_;
};

class AbstractFieldTrialEnum : public FieldTrialParameterInterface {
 public:
  AbstractFieldTrialEnum(absl::string_view key,
                         int default_value,
                         std::map<std::string, int> mapping);
  ~AbstractFieldTrialEnum() override;
  AbstractFieldTrialEnum(const AbstractFieldTrialEnum&);

 protected:
  bool Parse(absl::optional<std::string> str_value) override;

 protected:
  int value_;
  std::map<std::string, int> enum_mapping_;
  std::set<int> valid_values_;
};

// The FieldTrialEnum class can be used to quickly define a parser for a
// specific enum. It handles values provided as integers and as strings if a
// mapping is provided.
template <typename T>
class FieldTrialEnum : public AbstractFieldTrialEnum {
 public:
  FieldTrialEnum(absl::string_view key,
                 T default_value,
                 std::map<std::string, T> mapping)
      : AbstractFieldTrialEnum(key,
                               static_cast<int>(default_value),
                               ToIntMap(mapping)) {}
  T Get() const { return static_cast<T>(value_); }
  operator T() const { return Get(); }

 private:
  static std::map<std::string, int> ToIntMap(std::map<std::string, T> mapping) {
    std::map<std::string, int> res;
    for (const auto& it : mapping)
      res[it.first] = static_cast<int>(it.second);
    return res;
  }
};

// This class uses the ParseTypedParameter function to implement an optional
// parameter implementation that can default to absl::nullopt.
template <typename T>
class FieldTrialOptional : public FieldTrialParameterInterface {
 public:
  explicit FieldTrialOptional(absl::string_view key)
      : FieldTrialParameterInterface(key) {}
  FieldTrialOptional(absl::string_view key, absl::optional<T> default_value)
      : FieldTrialParameterInterface(key), value_(default_value) {}
  absl::optional<T> GetOptional() const { return value_; }
  const T& Value() const { return value_.value(); }
  const T& operator*() const { return value_.value(); }
  const T* operator->() const { return &value_.value(); }
  explicit operator bool() const { return value_.has_value(); }

 protected:
  bool Parse(absl::optional<std::string> str_value) override {
    if (str_value) {
      absl::optional<T> value = ParseTypedParameter<T>(*str_value);
      if (!value.has_value())
        return false;
      value_ = value.value();
    } else {
      value_ = absl::nullopt;
    }
    return true;
  }

 private:
  absl::optional<T> value_;
};

// Equivalent to a FieldTrialParameter<bool> in the case that both key and value
// are present. If key is missing, evaluates to false. If key is present, but no
// explicit value is provided, the flag evaluates to true.
class FieldTrialFlag : public FieldTrialParameterInterface {
 public:
  explicit FieldTrialFlag(absl::string_view key);
  FieldTrialFlag(absl::string_view key, bool default_value);
  bool Get() const;
  explicit operator bool() const;

 protected:
  bool Parse(absl::optional<std::string> str_value) override;

 private:
  bool value_;
};

template <typename T>
absl::optional<absl::optional<T>> ParseOptionalParameter(
    absl::string_view str) {
  if (str.empty())
    return absl::optional<T>();
  auto parsed = ParseTypedParameter<T>(str);
  if (parsed.has_value())
    return parsed;
  return absl::nullopt;
}

template <>
absl::optional<bool> ParseTypedParameter<bool>(absl::string_view str);
template <>
absl::optional<double> ParseTypedParameter<double>(absl::string_view str);
template <>
absl::optional<int> ParseTypedParameter<int>(absl::string_view str);
template <>
absl::optional<unsigned> ParseTypedParameter<unsigned>(absl::string_view str);
template <>
absl::optional<std::string> ParseTypedParameter<std::string>(
    absl::string_view str);

template <>
absl::optional<absl::optional<bool>> ParseTypedParameter<absl::optional<bool>>(
    absl::string_view str);
template <>
absl::optional<absl::optional<int>> ParseTypedParameter<absl::optional<int>>(
    absl::string_view str);
template <>
absl::optional<absl::optional<unsigned>>
ParseTypedParameter<absl::optional<unsigned>>(absl::string_view str);
template <>
absl::optional<absl::optional<double>>
ParseTypedParameter<absl::optional<double>>(absl::string_view str);

// Accepts true, false, else parsed with sscanf %i, true if != 0.
extern template class FieldTrialParameter<bool>;
// Interpreted using sscanf %lf.
extern template class FieldTrialParameter<double>;
// Interpreted using sscanf %i.
extern template class FieldTrialParameter<int>;
// Interpreted using sscanf %u.
extern template class FieldTrialParameter<unsigned>;
// Using the given value as is.
extern template class FieldTrialParameter<std::string>;

extern template class FieldTrialConstrained<double>;
extern template class FieldTrialConstrained<int>;
extern template class FieldTrialConstrained<unsigned>;

extern template class FieldTrialOptional<double>;
extern template class FieldTrialOptional<int>;
extern template class FieldTrialOptional<unsigned>;
extern template class FieldTrialOptional<bool>;
extern template class FieldTrialOptional<std::string>;

}  // namespace webrtc

#endif  // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_
