#ifndef SETTINGS_H
#define SETTINGS_H

#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

class SettingValue {
 public:
  virtual ~SettingValue() {}
  virtual std::string ValueToString() const = 0;
  virtual void SetFromString(const std::string& valueStr) = 0;
  virtual std::unique_ptr<SettingValue> Create() const = 0;
};

class StringSetting final : public SettingValue {
 public:
  StringSetting() {}
  StringSetting(const std::string& value) { _value = value; }
  std::string& Value() { return _value; }
  const std::string& Value() const { return _value; }
  std::string ValueToString() const override { return _value; }
  void SetFromString(const std::string& valueStr) override {
    _value = valueStr;
  }
  std::unique_ptr<SettingValue> Create() const override {
    return std::unique_ptr<SettingValue>(new StringSetting(_value));
  }

 private:
  std::string _value;
};

class SettingItem {
 public:
  explicit SettingItem(std::unique_ptr<SettingValue> defaultValue)
      : _defaultValue(std::move(defaultValue)) {}
  bool HasValue() const { return _value != nullptr; }
  SettingValue& Value() { return *_value; }
  void SetValue(std::unique_ptr<SettingValue> value) {
    _value = std::move(value);
  }
  const SettingValue& DefaultValue() const { return *_defaultValue; }
  const SettingValue& ValueOrDefault() const {
    return _value == nullptr ? *_defaultValue : *_value;
  }
  void MakeDefault() { _value.reset(); }

 private:
  std::unique_ptr<SettingValue> _value, _defaultValue;
};

class Settings {
 public:
  Settings();

  static std::string GetConfigDir();

  std::string GetStrategyFilename() const;

  void Load();
  void Save() const;

  void InitializeWorkStrategy();

  std::vector<std::string> RecentFiles() const {
    return getStrArr("recent-files", 10);
  }
  void SetRecentFiles(const std::vector<std::string>& recentFiles) {
    return setStrArr("recent-files", recentFiles);
  }

  std::vector<std::string> RecentStrategies() const {
    return getStrArr("recent-strategies", 10);
  }
  void SetRecentStrategies(const std::vector<std::string>& recentStrategies) {
    return setStrArr("recent-strategies", recentStrategies);
  }

 private:
  static std::string getConfigFilename();

  void initStr(const std::string& key, const std::string& defaultStr) {
    _settings.emplace(
        key, std::unique_ptr<SettingValue>(new StringSetting(defaultStr)));
  }

  void initStrArray(const std::string& key,
                    const std::vector<std::string>& defaultVal) {
    for (size_t i = 0; i != defaultVal.size(); ++i)
      _settings.emplace(
          key + '[' + std::to_string(i) + ']',
          std::unique_ptr<SettingValue>(new StringSetting(defaultVal[i])));
  }

  void set(const std::string& key, const std::string& value) {
    auto iter = _settings.find(key);
    if (iter == _settings.end())
      throw std::runtime_error("Unknown setting in settings file: " + key);
    if (!iter->second.HasValue())
      iter->second.SetValue(iter->second.DefaultValue().Create());
    iter->second.Value().SetFromString(value);
  }

  std::string getStr(const std::string& key) const {
    return static_cast<const StringSetting&>(
               _settings.find(key)->second.ValueOrDefault())
        .Value();
  }

  void setStr(const std::string& key, const std::string& value) {
    SettingItem& item = _settings.find(key)->second;
    if (static_cast<const StringSetting&>(item.DefaultValue()).Value() ==
        value) {
      item.MakeDefault();
    } else {
      if (!item.HasValue()) item.SetValue(item.DefaultValue().Create());
      static_cast<StringSetting&>(item.Value()).Value() = value;
    }
  }

  std::vector<std::string> getStrArr(const std::string& key, size_t n) const {
    std::vector<std::string> arr(n);
    for (size_t i = 0; i != n; ++i)
      arr[i] = getStr(key + '[' + std::to_string(i) + ']');
    return arr;
  }

  void setStrArr(const std::string& key,
                 const std::vector<std::string>& values) {
    for (size_t i = 0; i != values.size(); ++i)
      setStr(key + '[' + std::to_string(i) + ']', values[i]);
  }

  std::map<std::string, SettingItem> _settings;
};

#endif
