Code/Core/Contexts/ExecutionContext/ExecutionContext.Evaluate.cs
namespace Nodebox;

public partial class ExecutionContext {
    public virtual EvaluateResult Evaluate() {
        InvalidOperationException.ThrowIf(Graph == null, $"{nameof(Graph)} is not set");

        var result = new EvaluateResult();
        var subgraphs = Graph.ConnectedSubGraphs();
        foreach (var graph in subgraphs) {
            result.Register(EvaluateImpl(graph));
        }

        return result;
    }

    protected virtual EvaluateResult EvaluateImpl(Graph graph) {
        var result = new EvaluateResult();
        result.Register(Check(graph));
        if (result.HasExceptions) {
            return result;
        }

        try {
            var toEvaluate = EvaluationTopologicalSort(graph);
            foreach (var node in toEvaluate) {
                Evaluate(node);
            }
        }
        catch (Exception e) {
            result.Register(e);
        }

        return result;
    }


    public virtual void Evaluate(Node node) {
        var nodeType = node.GetType();
        if (!Implementations.TryGetValue(nodeType, out var implentation)) {
            throw new MissingImplementationException(node);
        }

        implentation.Evaluate(this, node);

        foreach (var wireSet in node.OutputWires) {
            foreach (var wire in wireSet) {
                if (!Graph.Contains<Wire>(wire)) {
                    continue;
                }

                Store[wire.Destination].SetInput(wire.DestinationIndex, Store[wire.Source].GetOutput<object>(wire.SourceIndex));
            }
        }
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class ReaderAttribute : Attribute { }
    public virtual bool IsReader(Node node) => TypeLibrary.GetType(node.GetType()).HasAttribute<ReaderAttribute>();

    [AttributeUsage(AttributeTargets.Class)]
    public class WriterAttribute : Attribute { }
    public virtual bool IsWriter(Node node) => TypeLibrary.GetType(node.GetType()).HasAttribute<WriterAttribute>();

    public virtual List<Node> EvaluationTopologicalSort(Graph graph) {
        var list = new List<Node>();

        var evaluatedNodes = new HashSet<Node>();
        var queue = new Queue<Node>(graph.GetAll<Node>().Where(IsWriter));
        while (queue.TryDequeue(out var node)) {
            list.Add(node);
            foreach (var wireSet in node.InputWires) {
                foreach (var wire in wireSet) {
                    if (evaluatedNodes.Contains(wire.Source)) {
                        continue;
                    }

                    queue.Enqueue(wire.Source);
                    evaluatedNodes.Add(wire.Source);
                }
            }
        }

        list.Reverse();
        return list;
    }


    public class EvaluateResult {
        public bool HasExceptions => Exceptions.Count > 0;
        public List<Exception> Exceptions { get; set; } = [];

        public static EvaluateResult Error(IEnumerable<Exception> exceptions) => new() {
            Exceptions = [.. exceptions]
        };

        public EvaluateResult Register(EvaluateResult other) {
            Exceptions.AddRange(other.Exceptions);
            return this;
        }

        public EvaluateResult Register(Exception exception) {
            Exceptions.Add(exception);
            return this;
        }

        public EvaluateResult Register(IEnumerable<Exception> exceptions) {
            Exceptions.AddRange(exceptions);
            return this;
        }
    }
}