#nullable enable
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
#if NET3
using Net3_Proxy;
#endif
namespace IPA.Loader.Features
{
///
/// The root interface for a mod Feature.
///
///
/// Avoid storing any data in any subclasses. If you do, it may result in a failure to load the feature.
///
public abstract class Feature
{
///
/// Initializes the feature with the data provided in the definition.
///
///
/// This gets called AFTER your Init method, but BEFORE the target's Init method. If it is applied to the defining plugin, BeforeInit is not called.
/// Returning does not prevent the plugin from being loaded. It simply prevents the feature from being used.
///
/// the metadata of the plugin that is being prepared
/// the data provided with the feature
/// if the feature is valid for the plugin, otherwise
protected abstract bool Initialize(PluginMetadata meta, JObject featureData);
///
/// The message to be logged when the feature is not valid for a plugin.
/// This should also be set whenever either returns false.
///
/// the message to show when the feature is marked invalid
public virtual string? InvalidMessage { get; protected set; }
///
/// Called before a plugin's `Init` method is called. This will not be called if there is no `Init` method. This should never throw an exception. An exception will abort the loading of the plugin with an error.
///
/// the plugin to be initialized
/// whether or not to call the Init method
public virtual void BeforeInit(PluginMetadata plugin) { }
///
/// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
///
/// the plugin that was just initialized
/// the instance of the plugin being initialized
public virtual void AfterInit(PluginMetadata plugin, object pluginInstance) => AfterInit(plugin);
///
/// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
///
/// the plugin that was just initialized
public virtual void AfterInit(PluginMetadata plugin) { }
///
/// Called after a plugin with this feature appplied is disabled.
///
/// the plugin that was disabled
public virtual void AfterDisable(PluginMetadata plugin) { }
// TODO: rework features to take arguments as JSON objects
[SuppressMessage("Nullability", "CS8618", Justification = "Reset sets those fields.")]
static Feature()
{
Reset();
}
internal static void Reset()
{
featureTypes = new()
{
{ "IPA.DefineFeature", typeof(DefineFeature) }
};
featureDelcarers = new()
{
{ "IPA.DefineFeature", null }
};
}
private static Dictionary featureTypes;
private static Dictionary featureDelcarers;
internal static bool HasFeature(string name) => featureTypes.ContainsKey(name);
internal static bool PreregisterFeature(PluginMetadata defining, string name)
{
if (featureDelcarers.ContainsKey(name)) return false;
featureDelcarers.Add(name, defining);
return true;
}
internal static bool RegisterFeature(PluginMetadata definingPlugin, string name, Type type)
{
if (!typeof(Feature).IsAssignableFrom(type))
throw new ArgumentException($"Feature type not subclass of {nameof(Feature)}", nameof(type));
if (featureTypes.ContainsKey(name)) return false;
if (featureDelcarers.TryGetValue(name, out var declarer))
{
if (definingPlugin != declarer)
return false;
}
else
{
featureDelcarers.Add(name, definingPlugin);
}
featureTypes.Add(name, type);
return true;
}
private class EmptyFeature : Feature
{
protected override bool Initialize(PluginMetadata meta, JObject featureData)
{
throw new NotImplementedException();
}
}
internal string FeatureName = null!;
internal class Instance
{
public readonly PluginMetadata AppliedTo;
public readonly string Name;
public readonly JObject Data;
public Instance(PluginMetadata appliedTo, string name, JObject data)
{
AppliedTo = appliedTo;
Name = name;
Data = data;
type = null;
}
private Type? type;
public bool TryGetDefiningPlugin(out PluginMetadata? plugin)
{
return featureDelcarers.TryGetValue(Name, out plugin);
}
// returns whether or not Initialize returned true, feature is always set when the thing exists
public bool TryCreate(out Feature feature)
{
if (type == null)
{
if (!featureTypes.TryGetValue(Name, out type))
{
feature = new EmptyFeature() { InvalidMessage = "No such feature type found", FeatureName = Name };
return false;
}
}
bool result;
try
{
feature = (Feature)Activator.CreateInstance(type);
feature.FeatureName = Name;
result = feature.Initialize(AppliedTo, Data);
}
catch (Exception e)
{
result = false;
feature = new EmptyFeature() { InvalidMessage = e.ToString(), FeatureName = Name };
}
return result;
}
}
}
}