using System.ComponentModel;
using System.Reflection;
using Pf2eDataScrapper.Processors;
namespace Pf2eDataScrapper.App;
///
/// Класс с коллекцией обработчиков.
///
internal class ProcessorCollection
{
///
/// Коллекция всех доступных обработчиков.
///
public ICollection AllItems { get; } = [];
///
/// Коллекция обработчиков, которые будут обрабатываться.
/// Для изменения необходимо использовать .
///
public List Items { get; private set; } = [];
///
/// Конструктор класса .
///
public ProcessorCollection()
{
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(ProcessAttribute), true).Length <= 0)
{
continue;
}
AllItems.Add(CreateItem(type));
}
CheckDependencies();
FilterProcessors([]);
}
///
/// Создание элемента коллекции обработчиков.
///
/// Тип обработчика.
///
private ProcessorCollectionItem CreateItem(Type type)
{
if (!type.IsSubclassOf(typeof(IProcessor)))
{
throw new(
$"'ProcessAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not."
);
}
ProcessAttribute processAttribute = type.GetCustomAttribute()!;
DescriptionAttribute? descriptionAttribute =
type.GetCustomAttribute();
ProcessorGroupAttribute? groupAttribute =
type.GetCustomAttribute();
ProcessorDependenciesAttribute? dependenciesAttribute =
type.GetCustomAttribute();
ProcessorInnerTypeAttribute? innerTypeAttribute =
type.GetCustomAttribute();
bool isCollection = innerTypeAttribute is not null;
ConstructorInfo? constructor = isCollection
? type.GetConstructor([])
: type.GetConstructor([typeof(ProcessorInput)])!;
if (constructor is null)
{
throw new($"Не удаётся определить конструктор класса {type}");
}
return new()
{
Name = processAttribute.Name,
Type = type,
Description = descriptionAttribute?.Description ?? processAttribute.Name,
Group = groupAttribute?.Group ?? ProcessorGroupEnum.General,
Dependencies = dependenciesAttribute?.Dependencies ?? [],
IsCollectionProcessor = isCollection,
InnerType = innerTypeAttribute?.InnerType,
Constructor = constructor,
};
}
///
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
///
private void CheckDependencies()
{
foreach (ProcessorCollectionItem item in Items)
{
foreach (string dependency in item.Dependencies)
{
if (Items.All(i => i.Name != dependency))
{
throw new($"Dependency '{dependency}' for processor '{item.Name}' not found.");
}
}
}
}
///
/// Сортировать обработчики в соответсвии со всеми зависимостями.
///
public void SortItems()
{
Dictionary priorities = [];
while (priorities.Count < Items.Count)
{
foreach (ProcessorCollectionItem item in Items)
{
if (item.Priority > 0)
{
continue;
}
IEnumerable dependencyPriorities = item.Dependencies.Select(d =>
priorities.GetValueOrDefault(d, -1)
);
if (dependencyPriorities.Any(p => p == -1))
{
continue;
}
item.Priority = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1;
priorities.Add(item.Name, item.Priority);
}
}
Items.Sort((a, b) => a.Priority - b.Priority);
}
///
/// Ограничить список используемых обработчиков по названию.
/// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики.
///
/// Коллекция названиий требуемых обработчиков.
public void FilterProcessors(ICollection processorNames)
{
if (processorNames.Count == 0)
{
Items = [.. AllItems];
SortItems();
return;
}
Dictionary nameFlags = processorNames.ToDictionary(name => name, _ => false);
while (nameFlags.Values.Any(v => !v))
{
foreach (
KeyValuePair pair in nameFlags.Where(pair => !pair.Value).ToList()
)
{
ProcessorCollectionItem item = AllItems.First(item => item.Name == pair.Key);
foreach (string dependency in item.Dependencies)
{
nameFlags.TryAdd(dependency, false);
}
nameFlags[pair.Key] = true;
}
}
Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList();
}
}
///
/// Элемент коллекции обработчиков.
///
internal class ProcessorCollectionItem
{
///
/// Название обработчика.
///
public required string Name { get; init; }
///
/// Тип класса обработчика.
///
public required Type Type { get; init; }
///
/// Описание обработчика.
///
public string Description { get; init; } = "";
///
/// Группа обработчика.
///
public required ProcessorGroupEnum Group { get; init; }
///
/// Коллекция названий обработчиков, после которых должен выполняться обработчик.
///
public ICollection Dependencies { get; init; } = [];
///
/// Приоритет выполнения обработчика.
///
public int Priority { get; set; } = -1;
///
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
/// Внутренний тип элементов указывается в .
///
public required bool IsCollectionProcessor { get; init; }
///
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
///
public string? InnerType { get; init; }
///
/// Конструктор обработчика.
///
public required ConstructorInfo Constructor { get; init; }
}