using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Collections.Manager;
///
/// ModCollections can inherit from an arbitrary number of other collections.
/// This is transitive, so a collection A inheriting from B also inherits from everything B inherits.
/// Circular dependencies are resolved by distinctness.
///
public class InheritanceManager : IDisposable
{
public enum ValidInheritance
{
Valid,
/// Can not inherit from self
Self,
/// Can not inherit from the empty collection
Empty,
/// Already inherited from
Contained,
/// Inheritance would lead to a circle.
Circle,
}
private readonly CollectionStorage _storage;
private readonly CommunicatorService _communicator;
private readonly SaveService _saveService;
private readonly ModStorage _modStorage;
public InheritanceManager(CollectionStorage storage, SaveService saveService, CommunicatorService communicator, ModStorage modStorage)
{
_storage = storage;
_saveService = saveService;
_communicator = communicator;
_modStorage = modStorage;
ApplyInheritances();
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.InheritanceManager);
}
public void Dispose()
{
_communicator.CollectionChange.Unsubscribe(OnCollectionChange);
}
/// Check whether a collection can be inherited from.
public static ValidInheritance CheckValidInheritance(ModCollection potentialInheritor, ModCollection? potentialParent)
{
if (potentialParent == null || ReferenceEquals(potentialParent, ModCollection.Empty))
return ValidInheritance.Empty;
if (ReferenceEquals(potentialParent, potentialInheritor))
return ValidInheritance.Self;
if (potentialInheritor.DirectlyInheritsFrom.Contains(potentialParent))
return ValidInheritance.Contained;
if (ModCollection.InheritedCollections(potentialParent).Any(c => ReferenceEquals(c, potentialInheritor)))
return ValidInheritance.Circle;
return ValidInheritance.Valid;
}
///
/// Add a new collection to the inheritance list.
/// We do not check if this collection would be visited before,
/// only that it is unique in the list itself.
///
public bool AddInheritance(ModCollection inheritor, ModCollection parent)
=> AddInheritance(inheritor, parent, true);
/// Remove an existing inheritance from a collection.
public void RemoveInheritance(ModCollection inheritor, int idx)
{
var parent = inheritor.DirectlyInheritsFrom[idx];
((List)inheritor.DirectlyInheritsFrom).RemoveAt(idx);
((List)parent.DirectParentOf).Remove(inheritor);
_saveService.DelaySave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
Penumbra.Log.Debug($"Removed {parent.AnonymizedName} from {inheritor.AnonymizedName} inheritances.");
}
/// Order in the inheritance list is relevant.
public void MoveInheritance(ModCollection inheritor, int from, int to)
{
if (!((List)inheritor.DirectlyInheritsFrom).Move(from, to))
return;
_saveService.DelaySave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
Penumbra.Log.Debug($"Moved {inheritor.AnonymizedName}s inheritance {from} to {to}.");
}
///
private bool AddInheritance(ModCollection inheritor, ModCollection parent, bool invokeEvent)
{
if (CheckValidInheritance(inheritor, parent) != ValidInheritance.Valid)
return false;
((List)inheritor.DirectlyInheritsFrom).Add(parent);
((List)parent.DirectParentOf).Add(inheritor);
if (invokeEvent)
{
_saveService.DelaySave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
}
Penumbra.Log.Debug($"Added {parent.AnonymizedName} to {inheritor.AnonymizedName} inheritances.");
return true;
}
///
/// Inheritances can not be setup before all collections are read,
/// so this happens after reading the collections in the constructor, consuming the stored strings.
///
private void ApplyInheritances()
{
foreach (var (collection, directParents, changes) in _storage.ConsumeInheritanceNames())
{
var localChanges = changes;
foreach (var parent in directParents)
{
if (AddInheritance(collection, parent, false))
continue;
localChanges = true;
Penumbra.ChatService.NotificationMessage($"{collection.Name} can not inherit from {parent.Name}, removed.", "Warning",
NotificationType.Warning);
}
if (localChanges)
_saveService.ImmediateSave(new ModCollectionSave(_modStorage, collection));
}
}
private void OnCollectionChange(CollectionType collectionType, ModCollection? old, ModCollection? newCollection, string _3)
{
if (collectionType is not CollectionType.Inactive || old == null)
return;
foreach (var c in _storage)
{
var inheritedIdx = c.DirectlyInheritsFrom.IndexOf(old);
if (inheritedIdx >= 0)
RemoveInheritance(c, inheritedIdx);
((List)c.DirectParentOf).Remove(old);
}
}
private void RecurseInheritanceChanges(ModCollection newInheritor)
{
foreach (var inheritor in newInheritor.DirectParentOf)
{
_communicator.CollectionInheritanceChanged.Invoke(inheritor, true);
RecurseInheritanceChanges(inheritor);
}
}
}