using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Vellum;
namespace Ariadne.Vellum
{
///
/// Saves every arc class as a value, so an arc can be a field of any saved object.
///
///
/// This is a converter family, so a game needs no converter of its own for an arc.
///
///
/// A concrete class with the constructor Ariadne.Generator writes to load it.
///
/// Whatever each tick hands the arc.
public class ArcConverter : SaveConverter
where ArcType : Arc
{
private readonly ConstructorInfo loader;
private readonly ArcPartList parts = new ArcPartList();
private readonly bool listsItsParts;
private const string ListPartsName = "ListParts";
///
/// The class has no constructor to load it from a save.
///
public ArcConverter()
{
loader = ArcLoader.FindConstructor(typeof(ArcType));
listsItsParts = ArcLoader.TryListParts(typeof(ArcType), parts);
}
// A malformed table is left to Read, which reports it.
private static bool TryGetTable(SaveNode node, string key, out SaveNode table)
{
if (!node.Entries.TryGetValue(key, out table))
{
return true;
}
return table.Kind == SaveNode.Kinds.Table;
}
public override bool TryListChildren(SaveNode node, ChildList children)
{
return TryListChildren(node, children, converters: null);
}
public override bool TryListChildren(SaveNode node, ChildList children, SaveConverters converters)
{
if (!listsItsParts || node.Kind != SaveNode.Kinds.Table)
{
return false;
}
SaveNode fields;
SaveNode locals;
if (!TryGetTable(node, ArcRecord.FieldsKey, out fields) ||
!TryGetTable(node, ArcRecord.LocalsKey, out locals))
{
return false;
}
AddChildren(parts, fields, locals, children);
var pending = new Stack>();
if (!PushScopes(parts, locals, pending, converters))
{
return false;
}
// A call's locals are a table inside its caller's, which the list's scopes mirror a level at a time.
while (pending.Count > 0)
{
KeyValuePair level = pending.Pop();
AddChildren(level.Key, fields: null, locals: level.Value, children: children);
if (!PushScopes(level.Key, level.Value, pending, converters))
{
return false;
}
}
return true;
}
private static void AddChildren(ArcPartList list, SaveNode fields, SaveNode locals, ChildList children)
{
for (int i = 0; i < list.Count; ++i)
{
SaveNode table = fields;
if (list.IsLocal[i])
{
table = locals;
}
SaveNode child;
if (table != null && table.Entries.TryGetValue(list.Keys[i], out child))
{
children.Add(child, list.Types[i]);
}
}
}
// The save names the types a polymorphic call's frame is built from, so its locals are listed by the closed
// frame class. Returns null if the save's names can't be used, which is left to Read to report.
private static ArcPartList ListPolymorphic(Type definition, SaveNode scope, SaveConverters converters)
{
SaveNode names;
if (converters == null
|| !scope.Entries.TryGetValue(ArcRecord.TypesKey, out names)
|| names.Kind != SaveNode.Kinds.List)
{
return null;
}
IReadOnlyList items = names.RequireItems();
var arguments = new Type[items.Count];
try
{
for (int i = 0; i < arguments.Length; ++i)
{
arguments[i] = converters.ResolveName(items[i].RequireText());
}
Type closed = definition.MakeGenericType(arguments);
var returnMe = new ArcPartList();
closed.GetMethod(ListPartsName, BindingFlags.Public | BindingFlags.Static)
.Invoke(null, new object[] { returnMe });
return returnMe;
}
catch (Exception exception) when (exception is ArgumentException || exception is InvalidDataException)
{
return null;
}
}
// Returns false if a call's entry isn't a table, or a polymorphic call's types can't be used. Read reports
// either.
private static bool PushScopes(ArcPartList list, SaveNode locals,
Stack> pending, SaveConverters converters)
{
for (int i = 0; i < list.Scopes.Count; ++i)
{
SaveNode scope;
if (locals != null && locals.Entries.TryGetValue(list.ScopeNames[i], out scope))
{
if (scope.Kind != SaveNode.Kinds.Table)
{
return false;
}
pending.Push(new KeyValuePair(list.Scopes[i], scope));
}
}
for (int i = 0; i < list.PolymorphicNames.Count; ++i)
{
SaveNode scope;
if (locals != null && locals.Entries.TryGetValue(list.PolymorphicNames[i], out scope))
{
if (scope.Kind != SaveNode.Kinds.Table)
{
return false;
}
ArcPartList listed = ListPolymorphic(list.PolymorphicDefinitions[i], scope, converters);
if (listed == null)
{
return false;
}
pending.Push(new KeyValuePair(listed, scope));
}
}
return true;
}
public override SaveNode Write(ArcType value, SaveSession session)
{
var record = new ArcRecord(session);
value.Save(record);
return record.ToNode();
}
///
/// The node isn't one Write could have made, or nests calls deeper than the stack can load.
///
/// The save's locals don't match the script's.
public override ArcType Read(SaveNode node, SaveSession session)
{
ArcRecord record = ArcRecord.FromNode(node, session);
// Loading follows nested calls down the stack, as running does, so a save past the limit was edited or was
// made on a bigger stack.
try
{
var returnMe = (ArcType)ArcLoader.Create(loader, record);
returnMe.VerifyLoaded(record);
return returnMe;
}
catch (InsufficientExecutionStackException exception)
{
throw new InvalidDataException("The arc's calls are nested deeper than the stack can load.", exception);
}
}
}
}