using System;
using System.Collections.Generic;
using System.Text;
#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 parameters provided in the definition.
///
/// Note: When no parenthesis are provided, is an empty array.
///
///
/// This gets called BEFORE *your* `Init` method.
///
/// 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 parameters passed to the feature definition, or null
/// if the feature is valid for the plugin, otherwise
public abstract bool Initialize(PluginMetadata meta, string[] parameters);
///
/// Evaluates the Feature for use in conditional meta-Features. This should be re-calculated on every call, unless it can be proven to not change.
///
/// This will be called on every feature that returns from
///
/// the truthiness of the Feature.
public virtual bool Evaluate() => true;
///
/// The message to be logged when the feature is not valid for a plugin.
/// This should also be set whenever either or returns false.
///
/// the message to show when the feature is marked invalid
public virtual string InvalidMessage { get; protected set; }
///
/// Called before a plugin is loaded. This should never throw an exception. An exception will abort the loading of the plugin with an error.
///
///
/// The assembly will still be loaded, but the plugin will not be constructed if this returns .
/// Any features it defines, for example, will still be loaded.
///
/// the plugin about to be loaded
/// whether or not the plugin should be loaded
public virtual bool BeforeLoad(PluginMetadata plugin) => true;
///
/// 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 bool BeforeInit(PluginMetadata plugin) => true;
///
/// 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) { }
///
/// Ensures a plugin's assembly is loaded. Do not use unless you need to.
///
/// the plugin to ensure is loaded.
protected void RequireLoaded(PluginMetadata plugin) => PluginLoader.Load(plugin);
///
/// Defines whether or not this feature will be accessible from the plugin metadata once loaded.
///
/// if this will be stored on the plugin metadata, otherwise
protected internal virtual bool StoreOnPlugin => true;
// TODO: rework features to take arguments as JSON objects
static Feature()
{
Reset();
}
internal static void Reset()
{
featureTypes = new Dictionary
{
{ "define-feature", typeof(DefineFeature) }
};
}
private static Dictionary featureTypes;
internal static bool HasFeature(string name) => featureTypes.ContainsKey(name);
internal static bool RegisterFeature(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;
featureTypes.Add(name, type);
return true;
}
internal struct FeatureParse
{
public readonly string Name;
public readonly string[] Parameters;
public FeatureParse(string name, string[] parameters)
{
Name = name;
Parameters = parameters;
}
}
// returns false with both outs null for no such feature
internal static bool TryParseFeature(string featureString, PluginMetadata plugin,
out Feature feature, out Exception failException, out bool featureValid, out FeatureParse parsed,
FeatureParse? preParsed = null)
{
failException = null;
feature = null;
featureValid = false;
if (preParsed == null)
{
var builder = new StringBuilder();
string name = null;
var parameters = new List();
bool escape = false;
int parens = 0;
bool removeWhitespace = true;
foreach (var chr in featureString)
{
if (escape)
{
builder.Append(chr);
escape = false;
}
else
{
switch (chr)
{
case '\\':
escape = true;
break;
case '(':
parens++;
if (parens != 1) goto default;
removeWhitespace = true;
name = builder.ToString();
builder.Clear();
break;
case ')':
parens--;
if (parens != 0) goto default;
goto case ',';
case ',':
if (parens > 1) goto default;
parameters.Add(builder.ToString());
builder.Clear();
removeWhitespace = true;
break;
default:
if (removeWhitespace && !char.IsWhiteSpace(chr))
removeWhitespace = false;
if (!removeWhitespace)
builder.Append(chr);
break;
}
}
}
if (name == null)
name = builder.ToString();
parsed = new FeatureParse(name, parameters.ToArray());
if (parens != 0)
{
failException = new Exception("Malformed feature definition");
return false;
}
}
else
parsed = preParsed.Value;
if (!featureTypes.TryGetValue(parsed.Name, out var featureType))
return false;
try
{
if (!(Activator.CreateInstance(featureType) is Feature aFeature))
{
failException = new InvalidCastException("Feature type not a subtype of Feature");
return false;
}
featureValid = aFeature.Initialize(plugin, parsed.Parameters);
feature = aFeature;
return true;
}
catch (Exception e)
{
failException = e;
return false;
}
}
}
}