/*
 *  Copyright (C) 2013-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "Setting.h"

#include "ServiceBroker.h"
#include "SettingDefinitions.h"
#include "SettingsManager.h"
#include "utils/StringUtils.h"
#include "utils/XBMCTinyXML.h"
#include "utils/XMLUtils.h"
#include "utils/log.h"

#include <algorithm>
#include <mutex>
#include <shared_mutex>
#include <sstream>
#include <utility>

template<typename TKey, typename TValue>
bool CheckSettingOptionsValidity(const TValue& value, const std::vector<std::pair<TKey, TValue>>& options)
{
  return std::ranges::any_of(options,
                             [&value](const auto& option) { return option.second == value; });
}

template<typename TKey, typename TValue>
bool CheckSettingOptionsValidity(const TValue& value, const std::vector<TKey>& options)
{
  return std::ranges::any_of(options,
                             [&value](const auto& option) { return option.value == value; });
}

bool DeserializeOptionsSort(const TiXmlElement* optionsElement, SettingOptionsSort& optionsSort)
{
  using enum SettingOptionsSort;

  optionsSort = NoSorting;

  std::string sort;
  if (optionsElement->QueryStringAttribute("sort", &sort) != TIXML_SUCCESS)
    return true;

  if (StringUtils::EqualsNoCase(sort, "false") || StringUtils::EqualsNoCase(sort, "off") ||
    StringUtils::EqualsNoCase(sort, "no") || StringUtils::EqualsNoCase(sort, "disabled"))
    optionsSort = NoSorting;
  else if (StringUtils::EqualsNoCase(sort, "asc") || StringUtils::EqualsNoCase(sort, "ascending") ||
    StringUtils::EqualsNoCase(sort, "true") || StringUtils::EqualsNoCase(sort, "on") ||
    StringUtils::EqualsNoCase(sort, "yes") || StringUtils::EqualsNoCase(sort, "enabled"))
    optionsSort = Ascending;
  else if (StringUtils::EqualsNoCase(sort, "desc") || StringUtils::EqualsNoCase(sort, "descending"))
    optionsSort = Descending;
  else
    return false;

  return true;
}

Logger CSetting::s_logger;

CSetting::CSetting(std::string_view id, CSettingsManager* settingsManager /* = nullptr */)
  : ISetting(id, settingsManager)
{
  if (!s_logger)
    s_logger = CServiceBroker::GetLogging().GetLogger("CSetting");
}

CSetting::CSetting(std::string_view id, const CSetting& setting)
  : CSetting(id, setting.m_settingsManager)
{
  Copy(setting);
}

void CSetting::MergeBasics(const CSetting& other)
{
  // ISetting
  SetVisible(other.GetVisible());
  SetLabel(other.GetLabel());
  SetHelp(other.GetHelp());
  SetRequirementsMet(other.MeetsRequirements());
  // CSetting
  SetEnabled(other.GetEnabled());
  SetParent(other.GetParent());
  SetLevel(other.GetLevel());
  SetControl(other.GetControl());
  SetDependencies(other.GetDependencies());
}

bool CSetting::Deserialize(const TiXmlNode *node, bool update /* = false */)
{
  // handle <visible> conditions
  if (!ISetting::Deserialize(node, update))
    return false;

  auto element = node->ToElement();
  if (!element)
    return false;

  auto parentSetting = element->Attribute(SETTING_XML_ATTR_PARENT);
  if (parentSetting)
    m_parentSetting = parentSetting;

  // get <enable>
  bool value;
  if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_ENABLED, value))
    m_enabled = value;

  // get the <level>
  int level = -1;
  if (XMLUtils::GetInt(node, SETTING_XML_ELM_LEVEL, level))
    m_level = static_cast<SettingLevel>(level);

  if (m_level < SettingLevel::Basic || m_level > SettingLevel::Internal)
    m_level = SettingLevel::Standard;

  auto dependencies = node->FirstChild(SETTING_XML_ELM_DEPENDENCIES);
  if (dependencies)
  {
    auto dependencyNode = dependencies->FirstChild(SETTING_XML_ELM_DEPENDENCY);
    while (dependencyNode)
    {
      CSettingDependency dependency(m_settingsManager);
      if (dependency.Deserialize(dependencyNode))
        m_dependencies.push_back(dependency);
      else
        s_logger->warn("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_DEPENDENCY, m_id);

      dependencyNode = dependencyNode->NextSibling(SETTING_XML_ELM_DEPENDENCY);
    }
  }

  auto control = node->FirstChildElement(SETTING_XML_ELM_CONTROL);
  if (control)
  {
    auto controlType = control->Attribute(SETTING_XML_ATTR_TYPE);
    if (!controlType)
    {
      s_logger->error("error reading \"{}\" attribute of <control> tag of \"{}\"",
                      SETTING_XML_ATTR_TYPE, m_id);
      return false;
    }

    m_control = m_settingsManager->CreateControl(controlType);
    if (!m_control || !m_control->Deserialize(control, update))
    {
      s_logger->error("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_CONTROL, m_id);
      return false;
    }
  }
  else if (!update && m_level < SettingLevel::Internal && !IsReference())
  {
    s_logger->error("missing <{}> tag of \"{}\"", SETTING_XML_ELM_CONTROL, m_id);
    return false;
  }

  auto updates = node->FirstChild(SETTING_XML_ELM_UPDATES);
  if (updates)
  {
    auto updateElem = updates->FirstChildElement(SETTING_XML_ELM_UPDATE);
    while (updateElem)
    {
      CSettingUpdate settingUpdate;
      if (settingUpdate.Deserialize(updateElem))
      {
        if (!m_updates.insert(settingUpdate).second)
          s_logger->warn("duplicate <{}> definition for \"{}\"", SETTING_XML_ELM_UPDATE, m_id);
      }
      else
        s_logger->warn("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_UPDATE, m_id);

      updateElem = updateElem->NextSiblingElement(SETTING_XML_ELM_UPDATE);
    }
  }

  return true;
}

bool CSetting::IsEnabled() const
{
  if (m_dependencies.empty() && m_parentSetting.empty())
    return m_enabled;

  // if the setting has a parent setting and that parent setting is disabled
  // the setting should automatically also be disabled
  if (!m_parentSetting.empty())
  {
    SettingPtr parentSetting = m_settingsManager->GetSetting(m_parentSetting);
    if (parentSetting && !parentSetting->IsEnabled())
      return false;
  }

  bool enabled = m_enabled;
  for (const auto& dep : m_dependencies)
  {
    if (dep.GetType() != SettingDependencyType::Enable)
      continue;

    if (!dep.Check())
    {
      enabled = false;
      break;
    }
  }

  return enabled;
}

void CSetting::SetEnabled(bool enabled)
{
  if (!m_dependencies.empty() || m_enabled == enabled)
    return;

  m_enabled = enabled;
  OnSettingPropertyChanged(shared_from_this(), "enabled");
}

void CSetting::MakeReference(const std::string& referencedId /* = "" */)
{
  auto tmpReferencedId = referencedId;
  if (referencedId.empty())
    tmpReferencedId = m_id;

  m_id = StringUtils::Format("#{}[{}]", tmpReferencedId, StringUtils::CreateUUID());
  m_referencedId = tmpReferencedId;
}

bool CSetting::IsVisible() const
{
  if (!ISetting::IsVisible())
    return false;

  bool visible = true;
  for (const auto& dep : m_dependencies)
  {
    if (dep.GetType() != SettingDependencyType::Visible)
      continue;

    if (!dep.Check())
    {
      visible = false;
      break;
    }
  }

  return visible;
}

bool CSetting::OnSettingChanging(const std::shared_ptr<const CSetting>& setting)
{
  if (!m_callback)
    return true;

  return m_callback->OnSettingChanging(setting);
}

void CSetting::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
{
  if (!m_callback)
    return;

  m_callback->OnSettingChanged(setting);
}

void CSetting::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
{
  if (!m_callback)
    return;

  m_callback->OnSettingAction(setting);
}

bool CSetting::DeserializeIdentification(const TiXmlNode* node,
                                         std::string& identification,
                                         bool& isReference)
{
  isReference = false;

  // first check if we can simply retrieve the setting's identifier
  if (ISetting::DeserializeIdentification(node, identification))
    return true;

  // otherwise try to retrieve a reference to another setting's identifier
  if (!DeserializeIdentificationFromAttribute(node, SETTING_XML_ATTR_REFERENCE, identification))
    return false;

  isReference = true;
  return true;
}

bool CSetting::OnSettingUpdate(const std::shared_ptr<CSetting>& setting,
                               const char* oldSettingId,
                               const TiXmlNode* oldSettingNode)
{
  if (!m_callback)
    return false;

  return m_callback->OnSettingUpdate(setting, oldSettingId, oldSettingNode);
}

void CSetting::OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting,
                                        const char* propertyName)
{
  if (!m_callback)
    return;

  m_callback->OnSettingPropertyChanged(setting, propertyName);
}

void CSetting::Copy(const CSetting &setting)
{
  SetVisible(setting.GetVisible());
  SetLabel(setting.GetLabel());
  SetHelp(setting.GetHelp());
  SetRequirementsMet(setting.MeetsRequirements());
  m_callback = setting.m_callback;
  m_level = setting.m_level;

  if (setting.m_control)
  {
    m_control = m_settingsManager->CreateControl(setting.m_control->GetType());
    *m_control = *setting.m_control;
  }
  else
    m_control = nullptr;

  m_dependencies = setting.m_dependencies;
  m_updates = setting.m_updates;
  m_changed = setting.m_changed;
}

Logger CSettingList::s_logger;

CSettingList::CSettingList(std::string_view id,
                           std::shared_ptr<CSetting> settingDefinition,
                           CSettingsManager* settingsManager /* = nullptr */)
  : CSetting(id, settingsManager),
    m_definition(std::move(settingDefinition))
{
  if (!s_logger)
    s_logger = CServiceBroker::GetLogging().GetLogger("CSettingList");
}

CSettingList::CSettingList(std::string_view id,
                           std::shared_ptr<CSetting> settingDefinition,
                           int label,
                           CSettingsManager* settingsManager /* = nullptr */)
  : CSettingList(id, std::move(settingDefinition), settingsManager)
{
  SetLabel(label);
}

CSettingList::CSettingList(std::string_view id, const CSettingList& setting) : CSetting(id, setting)
{
  copy(setting);
}

SettingPtr CSettingList::Clone(std::string_view id) const
{
  if (!m_definition)
    return nullptr;

  return std::make_shared<CSettingList>(id, *this);
}

void CSettingList::MergeDetails(const CSetting& other)
{
  if (other.GetType() != SettingType::List)
    return;

  const auto& listSetting = static_cast<const CSettingList&>(other);
  if (!m_definition && listSetting.m_definition)
    m_definition = listSetting.m_definition;
  if (m_defaults.empty() && !listSetting.m_defaults.empty())
    m_defaults = listSetting.m_defaults;
  if (m_values.empty() && !listSetting.m_values.empty())
    m_values = listSetting.m_values;
  if (m_delimiter == "|" && listSetting.m_delimiter != "|")
    m_delimiter = listSetting.m_delimiter;
  if (m_minimumItems == 0 && listSetting.m_minimumItems != 0)
    m_minimumItems = listSetting.m_minimumItems;
  if (m_maximumItems == -1 && listSetting.m_maximumItems != -1)
    m_maximumItems = listSetting.m_maximumItems;
}

bool CSettingList::Deserialize(const TiXmlNode *node, bool update /* = false */)
{
  std::unique_lock lock(m_critical);

  if (!m_definition)
    return false;

  if (!CSetting::Deserialize(node, update))
    return false;

  auto element = node->ToElement();
  if (!element)
  {
    s_logger->warn("unable to read type of list setting of {}", m_id);
    return false;
  }

  // deserialize the setting definition in update mode because we don't care
  // about an invalid <default> value (which is never used)
  if (!m_definition->Deserialize(node, true))
    return false;

  auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS);
  if (constraints)
  {
    // read the delimiter
    std::string delimiter;
    if (XMLUtils::GetString(constraints, SETTING_XML_ELM_DELIMITER, delimiter) && !delimiter.empty())
      m_delimiter = delimiter;

    XMLUtils::GetInt(constraints, SETTING_XML_ELM_MINIMUM_ITEMS, m_minimumItems);
    if (m_minimumItems < 0)
      m_minimumItems = 0;
    XMLUtils::GetInt(constraints, SETTING_XML_ELM_MAXIMUM_ITEMS, m_maximumItems);
    if (m_maximumItems <= 0)
      m_maximumItems = -1;
    else if (m_maximumItems < m_minimumItems)
    {
      s_logger->warn("invalid <{}> ({}) and/or <{}> ({}) of {}", SETTING_XML_ELM_MINIMUM_ITEMS,
                     m_minimumItems, SETTING_XML_ELM_MAXIMUM_ITEMS, m_maximumItems, m_id);
      return false;
    }
  }

  // read the default and initial values
  std::string values;
  if (XMLUtils::GetString(node, SETTING_XML_ELM_DEFAULT, values))
  {
    if (!fromString(values, m_defaults))
    {
      s_logger->warn("invalid <{}> definition \"{}\" of {}", SETTING_XML_ELM_DEFAULT, values, m_id);
      return false;
    }
    Reset();
  }

  return true;
}

SettingType CSettingList::GetElementType() const
{
  std::shared_lock<CSharedSection> lock(m_critical);

  if (!m_definition)
    return SettingType::Unknown;

  return m_definition->GetType();
}

bool CSettingList::FromString(const std::string &value)
{
  SettingList values;
  if (!fromString(value, values))
    return false;

  return SetValue(values);
}

std::string CSettingList::ToString() const
{
  return toString(m_values);
}

bool CSettingList::Equals(const std::string &value) const
{
  SettingList values;
  if (!fromString(value, values) || values.size() != m_values.size())
    return false;

  bool ret = true;
  for (size_t index = 0; index < values.size(); index++)
  {
    if (!m_values[index]->Equals(values[index]->ToString()))
    {
      ret = false;
      break;
    }
  }

  return ret;
}

bool CSettingList::CheckValidity(const std::string &value) const
{
  SettingList values;
  return fromString(value, values);
}

void CSettingList::Reset()
{
  std::unique_lock lock(m_critical);
  SettingList values;
  for (const auto& it : m_defaults)
    values.push_back(it->Clone(it->GetId()));

  SetValue(values);
}

bool CSettingList::FromString(const std::vector<std::string> &value)
{
  SettingList values;
  if (!fromValues(value, values))
    return false;

  return SetValue(values);
}

bool CSettingList::SetValue(const SettingList &values)
{
  std::unique_lock lock(m_critical);

  if (static_cast<int>(values.size()) < m_minimumItems ||
      (m_maximumItems > 0 && static_cast<int>(values.size()) > m_maximumItems))
    return false;

  bool equal = values.size() == m_values.size();
  for (size_t index = 0; index < values.size(); index++)
  {
    if (values[index]->GetType() != GetElementType())
      return false;

    if (equal &&
        !values[index]->Equals(m_values[index]->ToString()))
      equal = false;
  }

  if (equal)
    return true;

  SettingList oldValues = m_values;
  m_values.clear();
  m_values.insert(m_values.begin(), values.begin(), values.end());

  if (!OnSettingChanging(shared_from_base<CSettingList>()))
  {
    m_values = oldValues;

    // the setting couldn't be changed because one of the
    // callback handlers failed the OnSettingChanging()
    // callback so we need to let all the callback handlers
    // know that the setting hasn't changed
    OnSettingChanging(shared_from_base<CSettingList>());
    return false;
  }

  m_changed = toString(m_values) != toString(m_defaults);
  OnSettingChanged(shared_from_base<CSettingList>());
  return true;
}

void CSettingList::SetDefault(const SettingList &values)
{
  std::unique_lock lock(m_critical);

  m_defaults.clear();
  m_defaults.insert(m_defaults.begin(), values.begin(), values.end());

  if (!m_changed)
  {
    m_values.clear();
    for (const auto& it : m_defaults)
      m_values.push_back(it->Clone(it->GetId()));
  }
}

void CSettingList::copy(const CSettingList &setting)
{
  CSetting::Copy(setting);

  copy(setting.m_values, m_values);
  copy(setting.m_defaults, m_defaults);

  if (setting.m_definition)
  {
    auto definitionCopy = setting.m_definition->Clone(m_id + ".definition");
    if (definitionCopy)
      m_definition = definitionCopy;
  }

  m_delimiter = setting.m_delimiter;
  m_minimumItems = setting.m_minimumItems;
  m_maximumItems = setting.m_maximumItems;
}

void CSettingList::copy(const SettingList &srcValues, SettingList &dstValues)
{
  dstValues.clear();

  for (const auto& value : srcValues)
  {
    if (!value)
      continue;

    SettingPtr valueCopy = value->Clone(value->GetId());
    if (!valueCopy)
      continue;

    dstValues.emplace_back(valueCopy);
  }
}

bool CSettingList::fromString(const std::string &strValue, SettingList &values) const
{
  return fromValues(StringUtils::Split(strValue, m_delimiter), values);
}

bool CSettingList::fromValues(const std::vector<std::string> &strValues, SettingList &values) const
{
  if (static_cast<int>(strValues.size()) < m_minimumItems ||
      (m_maximumItems > 0 && static_cast<int>(strValues.size()) > m_maximumItems))
    return false;

  bool ret = true;
  int index = 0;
  for (const auto& value : strValues)
  {
    const std::shared_ptr<CSetting> settingValue =
        m_definition->Clone(StringUtils::Format("{}.{}", m_id, index));
    index++;
    if (!settingValue || !settingValue->FromString(value))
    {
      ret = false;
      break;
    }

    values.emplace_back(settingValue);
  }

  if (!ret)
    values.clear();

  return ret;
}

std::string CSettingList::toString(const SettingList &values) const
{
  std::vector<std::string> strValues;
  for (const auto& value : values)
  {
    if (value)
      strValues.push_back(value->ToString());
  }

  return StringUtils::Join(strValues, m_delimiter);
}

Logger CSettingBool::s_logger;

CSettingBool::CSettingBool(std::string_view id, CSettingsManager* settingsManager /* = nullptr */)
  : CSettingBool(id, DefaultLabel, DefaultValue, settingsManager)
{
}

CSettingBool::CSettingBool(std::string_view id, const CSettingBool& setting)
  : CSettingBool(id, setting.m_settingsManager)
{
  copy(setting);
}

CSettingBool::CSettingBool(std::string_view id,
                           int label,
                           bool value,
                           CSettingsManager* settingsManager /* = nullptr */)
  : CTraitedSetting(id, settingsManager),
    m_value(value),
    m_default(value)
{
  SetLabel(label);

  if (!s_logger)
    s_logger = CServiceBroker::GetLogging().GetLogger("CSettingBool");
}

SettingPtr CSettingBool::Clone(std::string_view id) const
{
  return std::make_shared<CSettingBool>(id, *this);
}

void CSettingBool::MergeDetails(const CSetting& other)
{
  if (other.GetType() != SettingType::Boolean)
    return;

  const auto& boolSetting = static_cast<const CSettingBool&>(other);
  if (m_default == false && boolSetting.m_default == true)
    m_default = boolSetting.m_default;
  if (m_value == m_default && boolSetting.m_value != m_default)
    m_value = boolSetting.m_value;
}

bool CSettingBool::Deserialize(const TiXmlNode *node, bool update /* = false */)
{
  std::unique_lock lock(m_critical);

  if (!CSetting::Deserialize(node, update))
    return false;

  // get the default value
  bool value;
  if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_DEFAULT, value))
    m_value = m_default = value;
  else if (!update)
  {
    s_logger->error("error reading the default value of \"{}\"", m_id);
    return false;
  }

  return true;
}

bool CSettingBool::FromString(const std::string &value)
{
  bool bValue;
  if (!fromString(value, bValue))
    return false;

  return SetValue(bValue);
}

std::string CSettingBool::ToString() const
{
  return m_value ? "true" : "false";
}

bool CSettingBool::Equals(const std::string &value) const
{
  bool bValue;
  return (fromString(value, bValue) && m_value == bValue);
}

bool CSettingBool::CheckValidity(const std::string &value) const
{
  bool bValue;
  return fromString(value, bValue);
}

bool CSettingBool::SetValue(bool value)
{
  std::unique_lock lock(m_critical);

  if (value == m_value)
    return true;

  bool oldValue = m_value;
  m_value = value;

  if (!OnSettingChanging(shared_from_base<CSettingBool>()))
  {
    m_value = oldValue;

    // the setting couldn't be changed because one of the
    // callback handlers failed the OnSettingChanging()
    // callback so we need to let all the callback handlers
    // know that the setting hasn't changed
    OnSettingChanging(shared_from_base<CSettingBool>());
    return false;
  }

  m_changed = m_value != m_default;
  OnSettingChanged(shared_from_base<CSettingBool>());
  return true;
}

void CSettingBool::SetDefault(bool value)
{
  std::unique_lock lock(m_critical);

  m_default = value;
  if (!m_changed)
    m_value = m_default;
}

void CSettingBool::copy(const CSettingBool &setting)
{
  CSetting::Copy(setting);

  m_value = setting.m_value;
  m_default = setting.m_default;
}

bool CSettingBool::fromString(const std::string &strValue, bool &value) const
{
  if (StringUtils::EqualsNoCase(strValue, "true"))
  {
    value = true;
    return true;
  }
  if (StringUtils::EqualsNoCase(strValue, "false"))
  {
    value = false;
    return true;
  }

  return false;
}

Logger CSettingInt::s_logger;

CSettingInt::CSettingInt(std::string_view id, CSettingsManager* settingsManager /* = nullptr */)
  : CSettingInt(id, DefaultLabel, DefaultValue, settingsManager)
{
}

CSettingInt::CSettingInt(std::string_view id, const CSettingInt& setting)
  : CSettingInt(id, setting.m_settingsManager)
{
  copy(setting);
}

CSettingInt::CSettingInt(std::string_view id,
                         int label,
                         int value,
                         CSettingsManager* settingsManager /* = nullptr */)
  : CSettingInt(id, label, value, DefaultMin, DefaultStep, DefaultMax, settingsManager)
{
  SetLabel(label);
}

CSettingInt::CSettingInt(std::string_view id,
                         int label,
                         int value,
                         int minimum,
                         int step,
                         int maximum,
                         CSettingsManager* settingsManager /* = nullptr */)
  : CTraitedSetting(id, settingsManager),
    m_value(value),
    m_default(value),
    m_min(minimum),
    m_step(step),
    m_max(maximum)
{
  SetLabel(label);

  if (!s_logger)
    s_logger = CServiceBroker::GetLogging().GetLogger("CSettingInt");
}

CSettingInt::CSettingInt(std::string_view id,
                         int label,
                         int value,
                         const TranslatableIntegerSettingOptions& options,
                         CSettingsManager* settingsManager /* = nullptr */)
  : CSettingInt(id, label, value, settingsManager)
{
  SetTranslatableOptions(options);
}

SettingPtr CSettingInt::Clone(std::string_view id) const
{
  return std::make_shared<CSettingInt>(id, *this);
}

void CSettingInt::MergeDetails(const CSetting& other)
{
  if (other.GetType() != SettingType::Integer)
    return;

  const auto& intSetting = static_cast<const CSettingInt&>(other);
  if (m_default == 0.0 && intSetting.m_default != 0.0)
    m_default = intSetting.m_default;
  if (m_value == m_default && intSetting.m_value != m_default)
    m_value = intSetting.m_value;
  if (m_min == 0.0 && intSetting.m_min != 0.0)
    m_min = intSetting.m_min;
  if (m_step == 1.0 && intSetting.m_step != 1.0)
    m_step = intSetting.m_step;
  if (m_max == 0.0 && intSetting.m_max != 0.0)
    m_max = intSetting.m_max;
  if (m_translatableOptions.empty() && !intSetting.m_translatableOptions.empty())
    m_translatableOptions = intSetting.m_translatableOptions;
  if (m_options.empty() && !intSetting.m_options.empty())
    m_options = intSetting.m_options;
  if (m_optionsFillerName.empty() && !intSetting.m_optionsFillerName.empty())
    m_optionsFillerName = intSetting.m_optionsFillerName;
  if (!m_optionsFiller && intSetting.m_optionsFiller)
    m_optionsFiller = intSetting.m_optionsFiller;
  if (m_dynamicOptions.empty() && !intSetting.m_dynamicOptions.empty())
    m_dynamicOptions = intSetting.m_dynamicOptions;
  if (m_optionsSort == SettingOptionsSort::NoSorting &&
      intSetting.m_optionsSort != SettingOptionsSort::NoSorting)
    m_optionsSort = intSetting.m_optionsSort;
}

bool CSettingInt::Deserialize(const TiXmlNode *node, bool update /* = false */)
{
  std::unique_lock lock(m_critical);

  if (!CSetting::Deserialize(node, update))
    return false;

  // get the default value
  int value;
  if (XMLUtils::GetInt(node, SETTING_XML_ELM_DEFAULT, value))
    m_value = m_default = value;
  else if (!update)
  {
    s_logger->error("error reading the default value of \"{}\"", m_id);
    return false;
  }

  auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS);
  if (constraints)
  {
    // get the entries
    auto options = constraints->FirstChildElement(SETTING_XML_ELM_OPTIONS);
    if (options && options->FirstChild())
    {
      if (!DeserializeOptionsSort(options, m_optionsSort))
        s_logger->warn(StringUtils::Format(R"(invalid "sort" attribute of <{}> for "{}")",
                                           SETTING_XML_ELM_OPTIONS, m_id));

      if (options->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT)
      {
        m_optionsFillerName = options->FirstChild()->ValueStr();
        if (!m_optionsFillerName.empty())
        {
          m_optionsFiller =
              m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingInt>()).intFiller;
        }
      }
      else
      {
        m_translatableOptions.clear();
        auto optionElement = options->FirstChildElement(SETTING_XML_ELM_OPTION);
        while (optionElement)
        {
          TranslatableIntegerSettingOption entry;
          if (optionElement->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &entry.label) ==
                  TIXML_SUCCESS &&
              entry.label > 0)
          {
            entry.value =
                static_cast<int>(std::strtol(optionElement->FirstChild()->Value(), nullptr, 10));
            m_translatableOptions.push_back(entry);
          }
          else
          {
            std::string label;
            if (optionElement->QueryStringAttribute(SETTING_XML_ATTR_LABEL, &label) ==
                TIXML_SUCCESS)
            {
              const auto val =
                  static_cast<int>(std::strtol(optionElement->FirstChild()->Value(), nullptr, 10));
              m_options.emplace_back(label, val);
            }
          }

          optionElement = optionElement->NextSiblingElement(SETTING_XML_ELM_OPTION);
        }
      }
    }

    // get minimum
    XMLUtils::GetInt(constraints, SETTING_XML_ELM_MINIMUM, m_min);
    // get step
    XMLUtils::GetInt(constraints, SETTING_XML_ELM_STEP, m_step);
    // get maximum
    XMLUtils::GetInt(constraints, SETTING_XML_ELM_MAXIMUM, m_max);
  }

  return true;
}

bool CSettingInt::FromString(const std::string &value)
{
  int iValue;
  if (!fromString(value, iValue))
    return false;

  return SetValue(iValue);
}

std::string CSettingInt::ToString() const
{
  std::ostringstream oss;
  oss << m_value;

  return oss.str();
}

bool CSettingInt::Equals(const std::string &value) const
{
  int iValue;
  return (fromString(value, iValue) && m_value == iValue);
}

bool CSettingInt::CheckValidity(const std::string &value) const
{
  int iValue;
  if (!fromString(value, iValue))
    return false;

  return CheckValidity(iValue);
}

bool CSettingInt::CheckValidity(int value) const
{
  if (!m_translatableOptions.empty())
  {
    if (!CheckSettingOptionsValidity(value, m_translatableOptions))
      return false;
  }
  else if (!m_options.empty())
  {
    if (!CheckSettingOptionsValidity(value, m_options))
      return false;
  }
  else if (m_optionsFillerName.empty() && !m_optionsFiller && m_min != m_max &&
           (value < m_min || value > m_max))
    return false;

  return true;
}

bool CSettingInt::SetValue(int value)
{
  std::unique_lock lock(m_critical);

  if (value == m_value)
    return true;

  if (!CheckValidity(value))
    return false;

  int oldValue = m_value;
  m_value = value;

  if (!OnSettingChanging(shared_from_base<CSettingInt>()))
  {
    m_value = oldValue;

    // the setting couldn't be changed because one of the
    // callback handlers failed the OnSettingChanging()
    // callback so we need to let all the callback handlers
    // know that the setting hasn't changed
    OnSettingChanging(shared_from_base<CSettingInt>());
    return false;
  }

  m_changed = m_value != m_default;
  OnSettingChanged(shared_from_base<CSettingInt>());
  return true;
}

void CSettingInt::SetDefault(int value)
{
  std::unique_lock lock(m_critical);

  m_default = value;
  if (!m_changed)
    m_value = m_default;
}

SettingOptionsType CSettingInt::GetOptionsType() const
{
  std::shared_lock<CSharedSection> lock(m_critical);
  if (!m_translatableOptions.empty())
    return SettingOptionsType::StaticTranslatable;
  if (!m_options.empty())
    return SettingOptionsType::Static;
  if (!m_optionsFillerName.empty() || m_optionsFiller)
    return SettingOptionsType::Dynamic;

  return SettingOptionsType::Unknown;
}

IntegerSettingOptions CSettingInt::UpdateDynamicOptions()
{
  std::unique_lock lock(m_critical);
  IntegerSettingOptions options;
  if (!m_optionsFiller && (m_optionsFillerName.empty() || !m_settingsManager))
    return options;

  if (!m_optionsFiller)
  {
    m_optionsFiller =
        m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingInt>()).intFiller;

    if (!m_optionsFiller)
    {
      s_logger->warn("unknown options filler \"{}\" of \"{}\"", m_optionsFillerName, m_id);
      return options;
    }
  }

  int bestMatchingValue = m_value;
  m_optionsFiller(shared_from_base<CSettingInt>(), options, bestMatchingValue);

  if (bestMatchingValue != m_value)
    SetValue(bestMatchingValue);

  bool changed = m_dynamicOptions.size() != options.size();
  if (!changed)
  {
    for (size_t index = 0; index < options.size(); index++)
    {
      if (options[index].label.compare(m_dynamicOptions[index].label) != 0 ||
          options[index].value != m_dynamicOptions[index].value)
      {
        changed = true;
        break;
      }
    }
  }

  if (changed)
  {
    m_dynamicOptions = options;
    OnSettingPropertyChanged(shared_from_base<CSettingInt>(), "options");
  }

  return options;
}

void CSettingInt::copy(const CSettingInt &setting)
{
  CSetting::Copy(setting);

  std::unique_lock lock(m_critical);

  m_value = setting.m_value;
  m_default = setting.m_default;
  m_min = setting.m_min;
  m_step = setting.m_step;
  m_max = setting.m_max;
  m_translatableOptions = setting.m_translatableOptions;
  m_options = setting.m_options;
  m_optionsFillerName = setting.m_optionsFillerName;
  m_optionsFiller = setting.m_optionsFiller;
  m_dynamicOptions = setting.m_dynamicOptions;
}

bool CSettingInt::fromString(const std::string &strValue, int &value)
{
  if (strValue.empty())
    return false;

  char *end = nullptr;
  value = static_cast<int>(std::strtol(strValue.c_str(), &end, 10));
  if (end && *end != '\0')
    return false;

  return true;
}

Logger CSettingNumber::s_logger;

CSettingNumber::CSettingNumber(std::string_view id,
                               CSettingsManager* settingsManager /* = nullptr */)
  : CSettingNumber(id, DefaultLabel, DefaultValue, settingsManager)
{
}

CSettingNumber::CSettingNumber(std::string_view id, const CSettingNumber& setting)
  : CSettingNumber(id, setting.m_settingsManager)
{
  copy(setting);
}

CSettingNumber::CSettingNumber(std::string_view id,
                               int label,
                               float value,
                               CSettingsManager* settingsManager /* = nullptr */)
  : CSettingNumber(id, label, value, DefaultMin, DefaultStep, DefaultMax, settingsManager)
{
}

CSettingNumber::CSettingNumber(std::string_view id,
                               int label,
                               float value,
                               float minimum,
                               float step,
                               float maximum,
                               CSettingsManager* settingsManager /* = nullptr */)
  : CTraitedSetting(id, settingsManager),
    m_value(static_cast<double>(value)),
    m_default(static_cast<double>(value)),
    m_min(static_cast<double>(minimum)),
    m_step(static_cast<double>(step)),
    m_max(static_cast<double>(maximum))
{
  SetLabel(label);

  if (!s_logger)
    s_logger = CServiceBroker::GetLogging().GetLogger("CSettingNumber");
}

SettingPtr CSettingNumber::Clone(std::string_view id) const
{
  return std::make_shared<CSettingNumber>(id, *this);
}

void CSettingNumber::MergeDetails(const CSetting& other)
{
  if (other.GetType() != SettingType::Number)
    return;

  const auto& numberSetting = static_cast<const CSettingNumber&>(other);
  if (m_default == 0.0 && numberSetting.m_default != 0.0)
    m_default = numberSetting.m_default;
  if (m_value == m_default && numberSetting.m_value != m_default)
    m_value = numberSetting.m_value;
  if (m_min == 0.0 && numberSetting.m_min != 0.0)
    m_min = numberSetting.m_min;
  if (m_step == 1.0 && numberSetting.m_step != 1.0)
    m_step = numberSetting.m_step;
  if (m_max == 0.0 && numberSetting.m_max != 0.0)
    m_max = numberSetting.m_max;
}

bool CSettingNumber::Deserialize(const TiXmlNode *node, bool update /* = false */)
{
  std::unique_lock lock(m_critical);

  if (!CSetting::Deserialize(node, update))
    return false;

  // get the default value
  double value;
  if (XMLUtils::GetDouble(node, SETTING_XML_ELM_DEFAULT, value))
    m_value = m_default = value;
  else if (!update)
  {
    s_logger->error("error reading the default value of \"{}\"", m_id);
    return false;
  }

  auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS);
  if (constraints)
  {
    // get the minimum value
    XMLUtils::GetDouble(constraints, SETTING_XML_ELM_MINIMUM, m_min);
    // get the step value
    XMLUtils::GetDouble(constraints, SETTING_XML_ELM_STEP, m_step);
    // get the maximum value
    XMLUtils::GetDouble(constraints, SETTING_XML_ELM_MAXIMUM, m_max);
  }

  return true;
}

bool CSettingNumber::FromString(const std::string &value)
{
  double dValue;
  if (!fromString(value, dValue))
    return false;

  return SetValue(dValue);
}

std::string CSettingNumber::ToString() const
{
  std::ostringstream oss;
  oss << m_value;

  return oss.str();
}

bool CSettingNumber::Equals(const std::string &value) const
{
  double dValue;
  std::shared_lock<CSharedSection> lock(m_critical);
  return (fromString(value, dValue) && m_value == dValue);
}

bool CSettingNumber::CheckValidity(const std::string &value) const
{
  double dValue;
  if (!fromString(value, dValue))
    return false;

  return CheckValidity(dValue);
}

bool CSettingNumber::CheckValidity(double value) const
{
  std::shared_lock<CSharedSection> lock(m_critical);
  if (m_min != m_max &&
     (value < m_min || value > m_max))
    return false;

  return true;
}

bool CSettingNumber::SetValue(double value)
{
  std::unique_lock lock(m_critical);

  if (value == m_value)
    return true;

  if (!CheckValidity(value))
    return false;

  double oldValue = m_value;
  m_value = value;

  if (!OnSettingChanging(shared_from_base<CSettingNumber>()))
  {
    m_value = oldValue;

    // the setting couldn't be changed because one of the
    // callback handlers failed the OnSettingChanging()
    // callback so we need to let all the callback handlers
    // know that the setting hasn't changed
    OnSettingChanging(shared_from_base<CSettingNumber>());
    return false;
  }

  m_changed = m_value != m_default;
  OnSettingChanged(shared_from_base<CSettingNumber>());
  return true;
}

void CSettingNumber::SetDefault(double value)
{
  std::unique_lock lock(m_critical);

  m_default = value;
  if (!m_changed)
    m_value = m_default;
}

void CSettingNumber::copy(const CSettingNumber &setting)
{
  CSetting::Copy(setting);
  std::unique_lock lock(m_critical);

  m_value = setting.m_value;
  m_default = setting.m_default;
  m_min = setting.m_min;
  m_step = setting.m_step;
  m_max = setting.m_max;
}

bool CSettingNumber::fromString(const std::string &strValue, double &value)
{
  if (strValue.empty())
    return false;

  char *end = nullptr;
  value = strtod(strValue.c_str(), &end);
  if (end && *end != '\0')
    return false;

  return true;
}

const CSettingString::Value CSettingString::DefaultValue;
Logger CSettingString::s_logger;

CSettingString::CSettingString(std::string_view id,
                               CSettingsManager* settingsManager /* = nullptr */)
  : CSettingString(id, DefaultLabel, DefaultValue, settingsManager)
{
}

CSettingString::CSettingString(std::string_view id, const CSettingString& setting)
  : CSettingString(id, setting.m_settingsManager)
{
  copy(setting);
}

CSettingString::CSettingString(std::string_view id,
                               int label,
                               const std::string& value,
                               CSettingsManager* settingsManager /* = nullptr */)
  : CTraitedSetting(id, settingsManager),
    m_value(value),
    m_default(value)
{
  SetLabel(label);

  if (!s_logger)
    s_logger = CServiceBroker::GetLogging().GetLogger("CSettingString");
}

SettingPtr CSettingString::Clone(std::string_view id) const
{
  return std::make_shared<CSettingString>(id, *this);
}

void CSettingString::MergeDetails(const CSetting& other)
{
  if (other.GetType() != SettingType::String)
    return;

  const auto& stringSetting = static_cast<const CSettingString&>(other);
  if (m_default.empty() && !stringSetting.m_default.empty())
    m_default = stringSetting.m_default;
  if (m_value == m_default && stringSetting.m_value != m_default)
    m_value = stringSetting.m_value;
  if (m_allowEmpty == false && stringSetting.m_allowEmpty == true)
    m_allowEmpty = stringSetting.m_allowEmpty;
  if (m_allowNewOption == false && stringSetting.m_allowNewOption == true)
    m_allowNewOption = stringSetting.m_allowNewOption;
  if (m_translatableOptions.empty() && !stringSetting.m_translatableOptions.empty())
    m_translatableOptions = stringSetting.m_translatableOptions;
  if (m_options.empty() && !stringSetting.m_options.empty())
    m_options = stringSetting.m_options;
  if (m_optionsFillerName.empty() && !stringSetting.m_optionsFillerName.empty())
    m_optionsFillerName = stringSetting.m_optionsFillerName;
  if (!m_optionsFiller && stringSetting.m_optionsFiller)
    m_optionsFiller = stringSetting.m_optionsFiller;
  if (m_dynamicOptions.empty() && !stringSetting.m_dynamicOptions.empty())
    m_dynamicOptions = stringSetting.m_dynamicOptions;
  if (m_optionsSort == SettingOptionsSort::NoSorting &&
      stringSetting.m_optionsSort != SettingOptionsSort::NoSorting)
    m_optionsSort = stringSetting.m_optionsSort;
}

bool CSettingString::Deserialize(const TiXmlNode *node, bool update /* = false */)
{
  std::unique_lock lock(m_critical);

  if (!CSetting::Deserialize(node, update))
    return false;

  auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS);
  if (constraints)
  {
    // get allowempty (needs to be parsed before parsing the default value)
    XMLUtils::GetBoolean(constraints, SETTING_XML_ELM_ALLOWEMPTY, m_allowEmpty);

    // Values other than those in options constraints allowed to be added
    XMLUtils::GetBoolean(constraints, SETTING_XML_ELM_ALLOWNEWOPTION, m_allowNewOption);

    // get the entries
    auto options = constraints->FirstChildElement(SETTING_XML_ELM_OPTIONS);
    if (options && options->FirstChild())
    {
      if (!DeserializeOptionsSort(options, m_optionsSort))
        s_logger->warn(StringUtils::Format(R"(invalid "sort" attribute of <{}> for "{}")",
                                           SETTING_XML_ELM_OPTIONS, m_id));

      if (options->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT)
      {
        m_optionsFillerName = options->FirstChild()->ValueStr();
        if (!m_optionsFillerName.empty())
        {
          m_optionsFiller =
              m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingString>())
                  .stringFiller;
        }
      }
      else
      {
        m_translatableOptions.clear();
        auto optionElement = options->FirstChildElement(SETTING_XML_ELM_OPTION);
        while (optionElement)
        {
          TranslatableStringSettingOption entry;
          if (optionElement->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &entry.first) == TIXML_SUCCESS && entry.first > 0)
          {
            entry.second = optionElement->FirstChild()->Value();
            m_translatableOptions.push_back(entry);
          }
          else
          {
            const std::string value = optionElement->FirstChild()->Value();
            // if a specific "label" attribute is present use it otherwise use the value as label
            std::string label = value;
            optionElement->QueryStringAttribute(SETTING_XML_ATTR_LABEL, &label);

            m_options.emplace_back(label, value);
          }

          optionElement = optionElement->NextSiblingElement(SETTING_XML_ELM_OPTION);
        }
      }
    }
  }

  // get the default value
  std::string value;
  if (XMLUtils::GetString(node, SETTING_XML_ELM_DEFAULT, value) &&
     (!value.empty() || m_allowEmpty))
    m_value = m_default = value;
  else if (!update && !m_allowEmpty)
  {
    s_logger->error("error reading the default value of \"{}\"", m_id);
    return false;
  }

  return true;
}

bool CSettingString::CheckValidity(const std::string &value) const
{
  std::shared_lock<CSharedSection> lock(m_critical);
  if (!m_allowEmpty && value.empty())
    return false;

  if (!m_translatableOptions.empty())
  {
    if (!CheckSettingOptionsValidity(value, m_translatableOptions))
      return false;
  }
  else if (!m_options.empty() && !m_allowNewOption)
  {
    if (!CheckSettingOptionsValidity(value, m_options))
      return false;
  }

  return true;
}

bool CSettingString::SetValue(const std::string &value)
{
  std::unique_lock lock(m_critical);

  if (value == m_value)
    return true;

  if (!CheckValidity(value))
    return false;

  std::string oldValue = m_value;
  m_value = value;

  if (!OnSettingChanging(shared_from_base<CSettingString>()))
  {
    m_value = oldValue;

    // the setting couldn't be changed because one of the
    // callback handlers failed the OnSettingChanging()
    // callback so we need to let all the callback handlers
    // know that the setting hasn't changed
    OnSettingChanging(shared_from_base<CSettingString>());
    return false;
  }

  m_changed = m_value != m_default;
  OnSettingChanged(shared_from_base<CSettingString>());
  return true;
}

void CSettingString::SetDefault(const std::string &value)
{
  std::shared_lock<CSharedSection> lock(m_critical);

  m_default = value;
  if (!m_changed)
    m_value = m_default;
}

SettingOptionsType CSettingString::GetOptionsType() const
{
  std::shared_lock<CSharedSection> lock(m_critical);
  if (!m_translatableOptions.empty())
    return SettingOptionsType::StaticTranslatable;
  if (!m_options.empty())
    return SettingOptionsType::Static;
  if (!m_optionsFillerName.empty() || m_optionsFiller)
    return SettingOptionsType::Dynamic;

  return SettingOptionsType::Unknown;
}

StringSettingOptions CSettingString::UpdateDynamicOptions()
{
  std::unique_lock lock(m_critical);
  StringSettingOptions options;
  if (!m_optionsFiller && (m_optionsFillerName.empty() || !m_settingsManager))
    return options;

  if (!m_optionsFiller)
  {
    m_optionsFiller =
        m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingString>()).stringFiller;

    if (!m_optionsFiller)
    {
      s_logger->error("unknown options filler \"{}\" of \"{}\"", m_optionsFillerName, m_id);
      return options;
    }
  }

  std::string bestMatchingValue = m_value;
  m_optionsFiller(shared_from_base<CSettingString>(), options, bestMatchingValue);

  if (bestMatchingValue != m_value)
    SetValue(bestMatchingValue);

  // check if the list of items has changed
  bool changed = m_dynamicOptions.size() != options.size();
  if (!changed)
  {
    for (size_t index = 0; index < options.size(); index++)
    {
      if (options[index].label.compare(m_dynamicOptions[index].label) != 0 ||
          options[index].value.compare(m_dynamicOptions[index].value) != 0)
      {
        changed = true;
        break;
      }
    }
  }

  if (changed)
  {
    m_dynamicOptions = options;
    OnSettingPropertyChanged(shared_from_base<CSettingString>(), "options");
  }

  return options;
}

void CSettingString::copy(const CSettingString &setting)
{
  CSetting::Copy(setting);

  std::unique_lock lock(m_critical);
  m_value = setting.m_value;
  m_default = setting.m_default;
  m_allowEmpty = setting.m_allowEmpty;
  m_allowNewOption = setting.m_allowNewOption;
  m_translatableOptions = setting.m_translatableOptions;
  m_options = setting.m_options;
  m_optionsFillerName = setting.m_optionsFillerName;
  m_optionsFiller = setting.m_optionsFiller;
  m_dynamicOptions = setting.m_dynamicOptions;
}

Logger CSettingAction::s_logger;

CSettingAction::CSettingAction(std::string_view id,
                               CSettingsManager* settingsManager /* = nullptr */)
  : CSettingAction(id, DefaultLabel, settingsManager)
{
}

CSettingAction::CSettingAction(std::string_view id,
                               int label,
                               CSettingsManager* settingsManager /* = nullptr */)
  : CSetting(id, settingsManager)
{
  SetLabel(label);

  if (!s_logger)
    s_logger = CServiceBroker::GetLogging().GetLogger("CSettingAction");
}

CSettingAction::CSettingAction(std::string_view id, const CSettingAction& setting)
  : CSettingAction(id, setting.m_settingsManager)
{
  copy(setting);
}

SettingPtr CSettingAction::Clone(std::string_view id) const
{
  return std::make_shared<CSettingAction>(id, *this);
}

void CSettingAction::MergeDetails(const CSetting& other)
{
  if (other.GetType() != SettingType::Action)
    return;

  const auto& actionSetting = static_cast<const CSettingAction&>(other);
  if (!HasData() && actionSetting.HasData())
    SetData(actionSetting.GetData());
}

bool CSettingAction::Deserialize(const TiXmlNode *node, bool update /* = false */)
{
  std::shared_lock<CSharedSection> lock(m_critical);

  if (!CSetting::Deserialize(node, update))
    return false;

  m_data = XMLUtils::GetString(node, SETTING_XML_ELM_DATA);

  return true;
}

void CSettingAction::copy(const CSettingAction& setting)
{
  CSetting::Copy(setting);

  std::unique_lock lock(m_critical);
  m_data = setting.m_data;
}
