using System;
using System.Collections.Generic;
using UnityEngine;
namespace DanroJump.Settings
{
///
/// Runtime-сервис операторских настроек: хранит значения, валидирует их по базе и сохраняет во внешний файл.
///
public sealed class ServiceSettingsService : IServiceSettingsService
{
private readonly ServiceSettingsDatabase database;
private readonly IServiceSettingsRepository repository;
private readonly IServiceSettingApplier applier;
private readonly Dictionary values = new(StringComparer.Ordinal);
///
/// Создает сервис поверх базы описаний, репозитория сохранения и опционального applier.
///
public ServiceSettingsService(
ServiceSettingsDatabase database,
IServiceSettingsRepository repository,
IServiceSettingApplier applier = null)
{
this.database = database;
this.repository = repository;
this.applier = applier ?? new NullServiceSettingApplier();
}
///
/// База описаний настроек, используемая сервисом.
///
public ServiceSettingsDatabase Database => database;
public event Action SettingsLoaded;
public event Action SettingsSaved;
public event Action SettingChanged;
///
/// Загружает сохраненные значения, дополняет дефолтами и применяет их.
///
public void Load()
{
values.Clear();
if (repository != null && repository.TryLoad(out var loadedValues))
{
foreach (var pair in loadedValues)
{
if (TryGetDefinition(pair.Key, out var definition) && IsStoredValueDefinition(definition))
{
values[pair.Key] = definition.Sanitize(pair.Value);
}
}
}
EnsureDefaults();
ApplyAll();
SettingsLoaded?.Invoke();
}
///
/// Сохраняет текущий snapshot настроек в репозиторий.
///
public void Save()
{
if (repository == null || !repository.Save(values))
{
return;
}
SettingsSaved?.Invoke();
}
///
/// Перечитывает только значения, разрешенные для внешних изменений.
///
public void ReloadExternalChanges()
{
if (repository == null || !repository.TryLoad(out var loadedValues))
{
return;
}
foreach (var pair in loadedValues)
{
SetExternal(pair.Key, pair.Value);
}
}
///
/// Повторно применяет все значения через applier.
///
public void ApplyAll()
{
if (database == null)
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition))
{
continue;
}
applier.Apply(definition, Get(definition.Key));
}
}
///
/// Сбрасывает все настройки к дефолтам из базы и сохраняет результат.
///
public void ResetAllToDefaults()
{
if (database == null)
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition))
{
continue;
}
Set(definition.Key, definition.DefaultValue);
}
Save();
}
///
/// Сбрасывает одну группу настроек к дефолтам.
///
public void ResetGroupToDefaults(string groupName)
{
if (database == null || string.IsNullOrWhiteSpace(groupName))
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition) ||
!string.Equals(definition.Group, groupName, StringComparison.OrdinalIgnoreCase))
{
continue;
}
Set(definition.Key, definition.DefaultValue);
}
Save();
}
///
/// Устанавливает значение настройки после проверки и sanitization.
///
public bool Set(string key, ServiceSettingValue value)
{
if (!TryGetDefinition(key, out var definition))
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Unknown setting key '{key}'.");
return false;
}
if (definition.ValueType == ServiceSettingValueType.Button)
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Button setting '{key}' does not store a value.");
return false;
}
var sanitized = definition.Sanitize(value);
if (values.TryGetValue(key, out var current) && current.Equals(sanitized))
{
return true;
}
// Значение применяем сразу, а Save отвечает только за запись на диск.
values[key] = sanitized;
applier.Apply(definition, sanitized);
SettingChanged?.Invoke(key, sanitized);
return true;
}
///
/// Устанавливает значение из внешнего файла только для разрешенных настроек.
///
public bool SetExternal(string key, ServiceSettingValue value)
{
if (!TryGetDefinition(key, out var definition))
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Unknown external setting key '{key}'.");
return false;
}
if (!definition.ExposedForExternalChanges)
{
Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Setting '{key}' is not exposed for external changes.");
return false;
}
return Set(key, value);
}
///
/// Устанавливает bool-настройку по ключу.
///
public bool SetBool(string key, bool value)
{
return Set(key, ServiceSettingValue.Bool(value));
}
///
/// Устанавливает int-настройку по ключу.
///
public bool SetInt(string key, int value)
{
return Set(key, ServiceSettingValue.Int(value));
}
///
/// Устанавливает float-настройку по ключу.
///
public bool SetFloat(string key, float value)
{
return Set(key, ServiceSettingValue.Float(value));
}
///
/// Устанавливает string-настройку по ключу.
///
public bool SetString(string key, string value)
{
return Set(key, ServiceSettingValue.String(value));
}
///
/// Устанавливает option-настройку по индексу варианта.
///
public bool SetOptionIndex(string key, int optionIndex)
{
return Set(key, ServiceSettingValue.Option(optionIndex));
}
///
/// Пытается найти описание настройки по ключу.
///
public bool TryGetDefinition(string key, out ServiceSettingDefinition definition)
{
definition = null;
return !string.IsNullOrWhiteSpace(key) &&
database != null &&
database.TryFind(key, out definition);
}
///
/// Возвращает копию текущих значений для безопасного чтения снаружи.
///
public IReadOnlyDictionary Snapshot()
{
return new Dictionary(values, StringComparer.Ordinal);
}
///
/// Возвращает текущее значение или дефолт из базы.
///
public ServiceSettingValue Get(string key)
{
if (values.TryGetValue(key, out var value))
{
return value;
}
return TryGetDefinition(key, out var definition) ? definition.DefaultValue : default;
}
///
/// Возвращает bool-значение настройки.
///
public bool GetBool(string key)
{
return Get(key).boolValue;
}
///
/// Возвращает int-значение настройки.
///
public int GetInt(string key)
{
return Get(key).intValue;
}
///
/// Возвращает float-значение настройки.
///
public float GetFloat(string key)
{
return Get(key).floatValue;
}
///
/// Возвращает string-значение настройки.
///
public string GetString(string key)
{
return Get(key).stringValue ?? string.Empty;
}
///
/// Возвращает индекс выбранного option-варианта.
///
public int GetOptionIndex(string key)
{
return Get(key).intValue;
}
///
/// Заполняет отсутствующие значения дефолтами и заново sanitizes загруженные данные.
///
private void EnsureDefaults()
{
if (database == null)
{
return;
}
foreach (var definition in database.Entries)
{
if (!IsStoredValueDefinition(definition))
{
continue;
}
values[definition.Key] = values.TryGetValue(definition.Key, out var existingValue)
? definition.Sanitize(existingValue)
: definition.Sanitize(definition.DefaultValue);
}
}
///
/// Проверяет, что описание настройки пригодно для работы сервиса.
///
private static bool IsValidDefinition(ServiceSettingDefinition definition)
{
return definition != null && !string.IsNullOrWhiteSpace(definition.Key);
}
///
/// Проверяет, что настройка хранит значение. Кнопки являются действиями и не попадают в JSON.
///
private static bool IsStoredValueDefinition(ServiceSettingDefinition definition)
{
return IsValidDefinition(definition) && definition.ValueType != ServiceSettingValueType.Button;
}
}
}