using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json.Linq;
using NpgsqlTypes;
using Pf2e.Database.Model.Common.I18n;
namespace Pf2e.Scrapper.Processors;
///
/// Класс для работы с JSON-объектом.
///
/// Название объекта, ассоциированного с вводом.
/// JSON-объект, который будет использоваться для доступа к данным.
/// [DI] Набор интерфейсов, разрешающих значения перечислений.
internal class ProcessorInput(string name, JObject input, IEnumerable enumSolvers)
{
///
/// Название объекта, ассоциированного с вводом.
///
public string Name { get; } = name;
///
/// Входящий JSON-объект, который содержит данные для обработки.
///
public JObject Input { get; } = input;
///
/// Интерфейсы, разрешающие значения перечислений.
///
public IEnumerable EnumSolvers { get; } = enumSolvers;
///
/// Значение из JSON по заданному пути, которое обязательно должно существовать.
///
/// Путь в JSON.
/// Объект типа `T` или `null`, если не удалось найти значение.
/// Если значение не найдено.
public virtual T GetRequired(string path)
{
JToken token =
Input.SelectToken(path) ?? throw new NullReferenceException($"'{path}' is null");
return token.ToObject() ?? throw new NullReferenceException($"'{path}' is null");
}
///
/// Значение из JSON по заданному пути, которое может не существовать.
///
/// Путь в JSON
/// Объект типа `T` или `null`, если значение не найдено
public virtual T? GetOptional(string path)
{
JToken? token = Input.SelectToken(path) ?? null;
return (token is null || token.Type == JTokenType.Null)
? default!
: token.ToObject() ?? default;
}
///
/// Значение из JSON по заданному пути с возможностью предоставить значение по умолчанию.
///
/// Путь в JSON.
/// Значение, которое возвращается, если значение не найдено.
/// Объект типа `T` или `defaultValue`, если значение не найдено.
public virtual T GetOptional(string path, T defaultValue)
{
JToken? token = Input.SelectToken(path) ?? null;
if (token is null || token.Type == JTokenType.Null)
{
return defaultValue;
}
return token.ToObject() ?? defaultValue;
}
///
/// Преобразование значения из JSON по заданному пути в перечисление с использованием `PgName` атрибута.
///
/// Путь в JSON.
/// Перечисление T или выбрасывает исключение, если значение не соответствует жодному варианту.
public virtual T EnumRequired(string path)
where T : Enum
{
string value =
GetOptional(path)
?? throw new NullReferenceException($"Value in path {path} null or not exists");
return Enum(value);
}
///
/// Преобразование значения из JSON по заданному пути в перечисление с возможностью предоставить значение по умолчанию.
///
/// Путь в JSON
/// Значение, которое возвращается, если значение не найдено.
/// Значение перечисления T или defaultValue
public virtual T EnumOptional(string path, T defaultValue)
where T : Enum
{
string? value = GetOptional(path);
if (value is null)
{
return defaultValue;
}
try
{
return Enum(value);
}
catch (InvalidEnumStringException)
{
return defaultValue;
}
}
///
/// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации.
///
/// Путь в JSON.
/// Тип локализации.
/// Объект с добавленным значением для заданного типа.
public virtual LocaleString GetRequiredLocale(string path, LocaleType type)
{
return new() { [type] = GetRequired(path) };
}
///
/// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации.
///
/// Путь в JSON.
/// Тип локализации.
/// Объект или null, если значение не найдено.
public virtual LocaleString? GetOptionalLocale(string path, LocaleType type)
{
string? value = GetOptional(path);
if (value is null)
{
return null;
}
return new() { [type] = value };
}
///
/// Преобразование значения из JSON в перечисление.
///
/// Значение, которое нужно преобразовать
/// Объект типа `T`, если преобразование успешно, иначе выбрасывает исключение
public virtual T Enum(string value)
where T : Enum
{
IEnumerable> solvers = EnumSolvers
.Where(solver => solver.GetType().IsAssignableTo(typeof(EnumSolver)))
.Cast>();
foreach (EnumSolver solver in solvers)
{
T? result = solver.SolveEnum(value);
if (result is not null)
{
return result;
}
}
foreach (System.Reflection.FieldInfo field in typeof(T).GetFields())
{
List variants = [field.Name];
if (
Attribute.GetCustomAttribute(field, typeof(PgNameAttribute))
is PgNameAttribute pgAttribute
)
{
variants.Add(pgAttribute.PgName);
}
if (variants.Contains(value))
{
return (T)field.GetValue(null)!;
}
}
throw new InvalidEnumStringException(typeof(T), value);
}
///
/// Создание контрольной суммы объекта.
///
/// Использовать шестнадцатеричные цифры в нижнем регистре.
/// Контрольная сумма объекта.
public string Md5(bool isLowercase = false)
{
byte[] byteHash = MD5.HashData(Encoding.UTF8.GetBytes(Input.ToString()));
string hash = Convert.ToHexString(byteHash).Replace("-", "");
return isLowercase ? hash.ToLower() : hash;
}
///
/// Исключение при неверном значении для перечисленния.
///
/// Тип перечисленния.
/// Некорректное значение.
public class InvalidEnumStringException(Type type, string value)
: Exception($"Invalid value '{value}' for enum type {type.FullName}.") { }
}
///
/// Интерфейс для решения значений перечислений.
///
internal interface IEnumSolver { }
///
/// Абстрактный класс для разрешения значений перечислений.
///
/// Тип перечисления, который будет разрешаться.
public abstract class EnumSolver : IEnumSolver
where TEnum : Enum
{
///
/// Словарь, содержащий данные для разрешения значений перечислений.
///
public abstract Dictionary Data { get; }
///
/// Метод для разрешения значения перечисления по строковому вводу.
///
/// Строка, представляющая значение перечисления.
/// Элемент перечисления , если найден; иначе null.
public TEnum? SolveEnum(string input)
{
return Data.GetValueOrDefault(input);
}
}