using System;
using System.Collections.Generic;
using System.IO;
using Vellum;
namespace Ariadne.Vellum
{
///
/// Saves a wait on triggers or conditions, which a script keeps as a local while it is paused.
///
///
/// The wait and what it waits on refer to one another, so the wait is made empty first and filled in later. A wait
/// on conditions lists them under a key of their own, which is how a load knows what kind of wait it is.
///
public class TriggerWaitConverter : SaveConverter
{
private const string CapacityKey = "capacity";
private const string WaitKey = "wait";
private const string RequiresAllKey = "requiresAll";
private const string SeenKey = "seen";
private const string FiredKey = "fired";
private const string TriggersKey = "triggers";
private const string ConditionsKey = "conditions";
private const string LatchKey = "latch";
private const string InvertedKey = "inverted";
// A condition is saved as its latch and whether it is the latch's inverse, since an inverse has no data.
private static SaveNode WriteConditions(TriggerWait value, SaveSession session)
{
SaveNode returnMe = SaveNode.NewList(value.Count);
for (int i = 0; i < value.Count; ++i)
{
Condition condition = value.Conditions[i];
var inverse = condition as InvertedLatch;
var latch = condition as Latch;
if (inverse == null && latch == null)
{
throw new InvalidOperationException("Only a latch or its inverse can be saved as a condition a " +
"wait waits on, and this is a " + condition.GetType().Name + ".");
}
SaveNode entry = SaveNode.NewTable();
entry.Entries[LatchKey] = session.ToNode(inverse == null ? latch : inverse.Original);
entry.Entries[InvertedKey] = session.ToNode(inverse != null);
returnMe.Items.Add(entry);
}
return returnMe;
}
private static void FillConditions(TriggerWait blank, IReadOnlyList items, SaveSession session)
{
for (int i = 0; i < items.Count; ++i)
{
var latch = session.FromNode(items[i].RequireEntry(LatchKey));
bool inverted = session.FromNode(items[i].RequireEntry(InvertedKey));
if (inverted)
{
blank.Conditions[i] = latch.Inverse;
}
else
{
blank.Conditions[i] = latch;
}
}
}
public override SaveNode Write(TriggerWait value, SaveSession session)
{
SaveNode returnMe = SaveNode.NewTable();
returnMe.Entries[CapacityKey] = session.ToNode(value.Subscriptions.Length);
returnMe.Entries[WaitKey] = session.ToNode(value.Wait);
returnMe.Entries[RequiresAllKey] = session.ToNode(value.RequiresAll);
returnMe.Entries[SeenKey] = session.ToNode(value.Seen);
returnMe.Entries[FiredKey] = session.ToNode(value.Fired);
if (value.OnConditions)
{
returnMe.Entries[ConditionsKey] = WriteConditions(value, session);
return returnMe;
}
SaveNode triggers = SaveNode.NewList(value.Count);
for (int i = 0; i < value.Count; ++i)
{
triggers.Items.Add(session.ToNode(value.Triggers[i]));
}
returnMe.Entries[TriggersKey] = triggers;
return returnMe;
}
public override TriggerWait Allocate(SaveNode node, SaveSession session)
{
int capacity = session.FromNode(node.RequireEntry(CapacityKey));
if (capacity < 1 || capacity > TriggerWait.MaximumTriggers)
{
throw new InvalidDataException("A saved wait holds " + capacity + " triggers, and a wait holds " +
"between 1 and " + TriggerWait.MaximumTriggers + ".");
}
SaveNode ignored;
return new TriggerWait(capacity, onConditions: node.TryGetEntry(ConditionsKey, out ignored));
}
/// The saved wait is one no script could have paused in.
public override void Fill(TriggerWait blank, SaveNode node, SaveSession session)
{
IReadOnlyList items = node.RequireEntry(blank.OnConditions ? ConditionsKey : TriggersKey)
.RequireItems();
if (items.Count > blank.Subscriptions.Length)
{
throw new InvalidDataException("A saved wait of " + blank.Subscriptions.Length + " triggers lists " +
items.Count + ".");
}
int seen = session.FromNode(node.RequireEntry(SeenKey));
int fired = session.FromNode(node.RequireEntry(FiredKey));
if (fired < -1 || fired >= items.Count)
{
throw new InvalidDataException("A saved wait of " + items.Count + " triggers was ended by trigger " +
fired + ".");
}
if (items.Count < TriggerWait.MaximumTriggers && (unchecked((uint)seen) >> items.Count) != 0)
{
throw new InvalidDataException("A saved wait of " + items.Count + " triggers has raised " + seen + ".");
}
if (blank.OnConditions)
{
FillConditions(blank, items, session);
}
else
{
for (int i = 0; i < items.Count; ++i)
{
blank.Triggers[i] = session.FromNode(items[i]);
}
}
blank.Count = items.Count;
blank.Wait = session.FromNode(node.RequireEntry(WaitKey));
blank.RequiresAll = session.FromNode(node.RequireEntry(RequiresAllKey));
blank.Seen = seen;
blank.Fired = fired;
// A wait on conditions records nothing about what happened, so there is nothing to disagree with.
if (!blank.OnConditions && blank.Count > 0 && blank.Complete != (fired >= 0))
{
throw new InvalidDataException("A saved wait that is " + (blank.Complete ? "over" : "going on") +
" says trigger " + fired + " ended it.");
}
}
public override void Complete(TriggerWait blank, SaveNode node, SaveSession session)
{
}
public override bool TryListChildren(SaveNode node, ChildList children)
{
SaveNode conditions;
if (node.TryGetEntry(ConditionsKey, out conditions))
{
IReadOnlyList entries;
if (!conditions.TryGetItems(out entries))
{
return false;
}
foreach (SaveNode entry in entries)
{
SaveNode latch;
if (!entry.TryGetEntry(LatchKey, out latch))
{
return false;
}
children.Add(latch, typeof(Latch));
}
return true;
}
SaveNode triggers;
return node.TryGetEntry(TriggersKey, out triggers)
&& SavedItems.ListChildren(triggers, typeof(Trigger), children);
}
public override TriggerWait Read(SaveNode node, SaveSession session)
{
TriggerWait returnMe = Allocate(node, session);
session.Publish(node, returnMe);
Fill(returnMe, node, session);
return returnMe;
}
}
}