using System;
using System.Collections.Generic;
using System.IO;
using Vellum;
namespace Ariadne.Vellum
{
///
/// Holds everything an Arc saves as SaveNodes, and writes it as one Lua table.
///
public class ArcRecord : IArcWriter, IArcReader
{
public const string CursorKey = "cursor";
public const string FinishedKey = "finished";
public const string FieldsKey = "fields";
public const string LocalsKey = "locals";
private readonly SaveSession session;
private readonly SaveReader fieldReader;
private readonly SaveReader localReader;
///
/// Gets the labels the arc is paused at, outermost passage first; empty before its first tick.
///
public List Cursor { get; } = new List();
IReadOnlyList IArcReader.Cursor
{
get
{
return Cursor;
}
}
public bool Finished { get; set; }
///
/// The saved fields, a table keyed by field name.
///
public readonly SaveNode Fields;
///
/// The saved locals and passage parameters keyed by "Passage.local", and a table for each call the arc was
/// paused in, keyed by "Passage.label/", holding that call's own.
///
public readonly SaveNode Locals;
///
/// Ends the key of a call's table, which no local's key does.
///
public const char ScopeMark = '/';
// The table the record was read from, which an arc is published against. Null for a record being written.
private readonly SaveNode node;
private ArcRecord(SaveSession session, SaveNode node, SaveNode fields, SaveNode locals)
{
this.session = session;
this.node = node;
Fields = fields;
Locals = locals;
fieldReader = new SaveReader(fields, session);
localReader = new SaveReader(locals, session);
}
/// Saves the types Vellum doesn't know, eg: those the arc's assembly names.
public ArcRecord(SaveSession session)
: this(session, node: null, fields: SaveNode.NewTable(), locals: SaveNode.NewTable())
{
}
///
/// Holds the key of the list of type arguments a call's table may have, which no local's key is, since those
/// all have a dot.
///
public const string TypesKey = "types";
private static IEnumerable KeysOf(SaveNode table, bool scopes)
{
foreach (string key in table.Entries.Keys)
{
if (key.EndsWith(ScopeMark.ToString(), StringComparison.Ordinal) == scopes
&& (scopes || key != TypesKey))
{
yield return key;
}
}
}
private void WriteTypes(SaveNode table, Type[] types)
{
SaveNode names = SaveNode.NewList();
foreach (Type type in types)
{
string name;
if (!session.TryNameOf(type, out name))
{
throw new NotSupportedException("A " + type.FullName + " can't be saved as a type argument, " +
"since it, or a type it's made from, isn't one a registered converter saves by name.");
}
names.Items.Add(SaveNode.OfText(name));
}
table.Entries[TypesKey] = names;
}
private bool TryReadTypes(SaveNode table, out Type[] types)
{
SaveNode names;
if (!table.Entries.TryGetValue(TypesKey, out names))
{
types = null;
return false;
}
session.NoteRead(table, TypesKey);
IReadOnlyList items = names.RequireItems();
types = new Type[items.Count];
for (int i = 0; i < items.Count; ++i)
{
types[i] = session.ResolveName(items[i].RequireText());
}
return true;
}
public IEnumerable LocalKeys
{
get
{
return KeysOf(Locals, scopes: false);
}
}
public IEnumerable ScopeNames
{
get
{
return KeysOf(Locals, scopes: true);
}
}
///
/// Holds one call's table of locals, which is written and read through as the arc itself is.
///
private sealed class LocalScope : IArcWriter, IArcReader
{
private readonly ArcRecord record;
private readonly SaveNode table;
private readonly SaveReader reader;
public LocalScope(ArcRecord record, SaveNode table)
{
this.record = record;
this.table = table;
reader = new SaveReader(table, record.session);
}
void IArcWriter.Cursor(IReadOnlyList cursor)
{
((IArcWriter)record).Cursor(cursor);
}
void IArcWriter.Finished(bool finished)
{
((IArcWriter)record).Finished(finished);
}
public void Field(string key, T value)
{
record.Field(key, value);
}
public void Local(string key, T value)
{
table.Entries.Add(key, record.session.ToNode(value));
}
public IArcWriter Scope(string name)
{
return record.NewScope(table, name);
}
public IReadOnlyList Cursor
{
get
{
return record.Cursor;
}
}
public bool Finished
{
get
{
return record.Finished;
}
}
public IEnumerable LocalKeys
{
get
{
return KeysOf(table, scopes: false);
}
}
public IEnumerable ScopeNames
{
get
{
return KeysOf(table, scopes: true);
}
}
public bool TryField(string key, out T value)
{
return record.TryField(key, out value);
}
public bool HasField(string key)
{
return record.HasField(key);
}
public bool TryLocal(string key, out T value)
{
return reader.TryRead(key, out value);
}
public bool TryScope(string name, out IArcReader scope)
{
return record.TryScopeIn(table, name, out scope);
}
public void TypeArguments(params Type[] types)
{
record.WriteTypes(table, types);
}
public bool TryTypeArguments(out Type[] types)
{
return record.TryReadTypes(table, out types);
}
public void Publish(object built)
{
record.Publish(built);
}
}
private IArcWriter NewScope(SaveNode parent, string name)
{
SaveNode table = SaveNode.NewTable();
parent.Entries.Add(name, table);
return new LocalScope(this, table);
}
private bool TryScopeIn(SaveNode parent, string name, out IArcReader scope)
{
SaveNode table;
if (parent.Entries.TryGetValue(name, out table) && table.Kind == SaveNode.Kinds.Table)
{
session.NoteRead(parent, name);
scope = new LocalScope(this, table);
return true;
}
scope = null;
return false;
}
/// The arc already wrote a call with this name.
public IArcWriter Scope(string name)
{
return NewScope(Locals, name);
}
public bool TryScope(string name, out IArcReader scope)
{
return TryScopeIn(Locals, name, out scope);
}
/// A type can't be named in a save.
public void TypeArguments(params Type[] types)
{
WriteTypes(Locals, types);
}
public bool TryTypeArguments(out Type[] types)
{
return TryReadTypes(Locals, out types);
}
void IArcWriter.Cursor(IReadOnlyList cursor)
{
Cursor.Clear();
Cursor.AddRange(cursor);
}
void IArcWriter.Finished(bool finished)
{
Finished = finished;
}
/// The arc already wrote a field with this key.
/// A can't be saved.
public void Field(string key, T value)
{
Fields.Entries.Add(key, session.ToNode(value));
}
/// The arc already wrote a local with this key.
/// A can't be saved.
public void Local(string key, T value)
{
Locals.Entries.Add(key, session.ToNode(value));
}
public bool TryField(string key, out T value)
{
return fieldReader.TryRead(key, out value);
}
public bool HasField(string key)
{
return Fields.Entries.ContainsKey(key);
}
public bool TryLocal(string key, out T value)
{
return localReader.TryRead(key, out value);
}
/// The record is being written, not read.
/// is null.
public void Publish(object built)
{
if (node == null)
{
throw new InvalidOperationException("A record being written has no saved arc to publish.");
}
session.Publish(node, built);
}
private SaveNode CursorNode()
{
SaveNode returnMe = SaveNode.NewList();
foreach (string label in Cursor)
{
returnMe.Items.Add(SaveNode.OfText(label));
}
return returnMe;
}
///
/// Returns the table Save writes, which holds this record's own Fields and Locals rather than copies.
///
/// A cursor label is null.
public SaveNode ToNode()
{
SaveNode returnMe = SaveNode.NewTable();
returnMe.Entries[CursorKey] = CursorNode();
returnMe.Entries[FinishedKey] = SavedValue.ToNode(Finished, typeof(bool));
returnMe.Entries[FieldsKey] = Fields;
returnMe.Entries[LocalsKey] = Locals;
return returnMe;
}
private static InvalidDataException Malformed(string message)
{
return new InvalidDataException("Malformed save: " + message);
}
private static SaveNode TableOrEmpty(SaveNode record, string key)
{
SaveNode returnMe;
if (!record.Entries.TryGetValue(key, out returnMe))
{
return SaveNode.NewTable();
}
if (returnMe.Kind != SaveNode.Kinds.Table)
{
throw Malformed(key + " must be a table.");
}
return returnMe;
}
///
/// Reads a record from the table ToNode made. Missing entries keep a fresh record's values.
///
/// An entry the record has is the wrong shape.
public static ArcRecord FromNode(SaveNode node, SaveSession session)
{
if (node.Kind != SaveNode.Kinds.Table)
{
throw Malformed("an arc must be a table.");
}
var returnMe = new ArcRecord(session, node, fields: TableOrEmpty(node, FieldsKey),
locals: TableOrEmpty(node, LocalsKey));
SaveNode cursor;
if (node.Entries.TryGetValue(CursorKey, out cursor))
{
foreach (SaveNode label in cursor.RequireItems())
{
returnMe.Cursor.Add(label.RequireText());
}
}
var reader = new SaveReader(node, session);
reader.Skip(CursorKey);
reader.Skip(FieldsKey);
reader.Skip(LocalsKey);
bool finished;
if (reader.TryRead(FinishedKey, out finished))
{
returnMe.Finished = finished;
}
return returnMe;
}
}
}