using System;
using System.Collections.Generic;
using System.IO;
using Vellum;
namespace Amphora.Vellum
{
///
/// Saves a BinaryDecisionDiagram as the table of its decision nodes, in position order, and the position it starts
/// at.
///
///
/// The outcomes are positions 0 (never) and 1 (always), so the list starts at position 2.
/// Loading rebuilds through a builder, so a hand-edited save that isn't reduced loads as the reduced diagram.
///
public sealed class BinaryDecisionDiagramConverter : SaveConverter
{
public const string NodesKey = "nodes";
public const string RootKey = "root";
public const string VariableKey = "variable";
public const string IfFalseKey = "ifFalse";
public const string IfTrueKey = "ifTrue";
// Both outcomes come before the first decision node.
private const int FirstDecisionPosition = 2;
public override SaveNode Write(BinaryDecisionDiagram value, SaveSession session)
{
SaveNode nodes = SaveNode.NewList();
for (int position = FirstDecisionPosition; position < value.NodeCount + FirstDecisionPosition; ++position)
{
SaveNode node = SaveNode.NewTable();
node.Entries[VariableKey] = session.ToNode(value.VariableAt(position));
node.Entries[IfFalseKey] = session.ToNode(value.IfFalseAt(position));
node.Entries[IfTrueKey] = session.ToNode(value.IfTrueAt(position));
nodes.Items.Add(node);
}
SaveNode returnMe = SaveNode.NewTable();
returnMe.Entries[NodesKey] = nodes;
returnMe.Entries[RootKey] = session.ToNode(value.RootPosition);
return returnMe;
}
private static int ReadPosition(SaveReader reader, string key, int limit)
{
int position = reader.Require(key);
if (position < 0 || position >= limit)
{
throw new InvalidDataException("A saved decision diagram has the position " + position + ", which is " +
"not one of the " + limit + " before it.");
}
return position;
}
public override BinaryDecisionDiagram Read(SaveNode node, SaveSession session)
{
IReadOnlyList saved = node.RequireEntry(NodesKey).RequireItems();
var builder = new BinaryDecisionDiagramBuilder();
// Where each saved position ended up in the builder, which merges equal nodes.
var positions = new int[saved.Count + FirstDecisionPosition];
positions[BinaryDecisionDiagramBuilder.NeverPosition] = BinaryDecisionDiagramBuilder.NeverPosition;
positions[BinaryDecisionDiagramBuilder.AlwaysPosition] = BinaryDecisionDiagramBuilder.AlwaysPosition;
for (int i = 0; i < saved.Count; ++i)
{
int own = i + FirstDecisionPosition;
var decision = new SaveReader(saved[i], session);
int variable = decision.Require(VariableKey);
int ifFalse = ReadPosition(decision, IfFalseKey, own);
int ifTrue = ReadPosition(decision, IfTrueKey, own);
try
{
positions[own] = builder.Make(variable, positions[ifFalse], positions[ifTrue]);
}
catch (ArgumentException exception)
{
throw new InvalidDataException("A saved decision diagram has a node that asks about the variable " +
variable + " before the nodes it goes to.", exception);
}
}
var rootReader = new SaveReader(node, session);
rootReader.Skip(NodesKey);
int root = ReadPosition(rootReader, RootKey, positions.Length);
return new BinaryDecisionDiagram(builder, positions[root]);
}
public override bool TryListChildren(SaveNode node, ChildList children)
{
// Everything it reads is built-in, and so left out.
SaveNode nodes;
return node.TryGetEntry(NodesKey, out nodes);
}
}
}