//------------------------------------------------------------------------------
//                                  Symbooglix
//
//
// Copyright 2014-2017 Daniel Liew
//
// This file is licensed under the MIT license.
// See LICENSE.txt for details.
//------------------------------------------------------------------------------
using System;
using System.IO;
using System.Collections.Generic;
using System.CodeDom.Compiler;
using System.Diagnostics;

namespace Symbooglix
{
    public class ExecutionTreePrinter
    {
        ExecutionTreeNode Root;
        int Indent;
        public ExecutionTreePrinter(ExecutionTreeNode root, int indent=2)
        {
            Debug.Assert(indent >= 0, "Indent cannot be negative");
            Debug.Assert(root != null, "root cannot be null");
            this.Root = root;
            this.Indent = indent;
        }

        protected virtual void PrintPreamble(TextWriter TW)
        {
            TW.WriteLine("/* Generated by Symbooglix */");
        }

        protected virtual void PrintStartGraph(TextWriter TW)
        {
            TW.WriteLine("digraph \"Execution Tree\"");
            TW.WriteLine("{");
            PushIndent(TW);
        }

        protected virtual void PrintCloseGraph(TextWriter TW)
        {
            PopIndent(TW);
            TW.WriteLine("}");
        }

        protected void PushIndent(TextWriter TW)
        {
            if (TW is IndentedTextWriter)
            {
                ( TW as IndentedTextWriter ).Indent += this.Indent;
            }
        }

        protected void PopIndent(TextWriter TW)
        {
            if (TW is IndentedTextWriter)
            {
                ( TW as IndentedTextWriter ).Indent -= this.Indent;
            }
        }

        protected virtual void PrintNode(TextWriter TW, ExecutionTreeNode node)
        {
            // Declare node
            TW.Write("{0} [", GetNodeID(node));

            // Write node attributes
            TW.Write(GetNodeAttributes(node));

            TW.WriteLine("];");

            if (node.ChildrenCount == 0)
                return;

            // Visit Children
            for (int index = 0; index < node.ChildrenCount; ++index)
                PrintNode(TW, node.GetChild(index));

            // Write edges. We can do this now because all child nodes have been declared
            TW.WriteLine("");
            for (int index = 0; index < node.ChildrenCount; ++index)
            {
                TW.WriteLine("{0} -> {1};", GetNodeID(node), GetNodeID(node.GetChild(index)));
            }
        }

        protected virtual string GetNodeID(ExecutionTreeNode node)
        {
            var id = string.Format("S{0}_{1}", (node.State.Id <0)?("m" + (node.State.Id*-1).ToString()):node.State.Id.ToString(), node.Depth);
            return id;
        }

        protected virtual string GetNodeAttributes(ExecutionTreeNode node)
        {
            var attr = string.Format("shape=record,label=\"State:{0}", node.State.Id);

            if (node.CreatedAt != null)
                attr += string.Format("\\nCreated at line {0}\\n{1}", node.CreatedAt.LineNumber, node.CreatedAt.ToString());

            bool terminated = ( node.ChildrenCount == 0 ) && node.State.TerminationType != null;

            if (terminated)
            {
                // FIXME: Fix trailing \n properly
                attr += "\\nTermination:" + EscapeLabelText(node.State.TerminationType.GetMessage().TrimEnd('\n'));
            }

            // Close label
            attr += "\"";

            if (terminated)
            {
                attr += ",style=filled, fillcolor=";
                if (node.State.TerminationType is TerminatedWithoutError)
                    attr += "\"green\"";
                else
                    attr += "\"red\"";
            }

            return attr;
        }

        protected string EscapeLabelText(string input)
        {
            var temp = input.Replace(">","\\>");
            temp = temp.Replace("<", "\\<");
            temp = temp.Replace("{", "\\{");
            temp = temp.Replace("}", "\\}");
            return temp;
        }

        protected virtual void PrintAdditionalEdges(TextWriter TW, ExecutionTreeNode root)
        {
            // For clients to override
        }
            
        public void Print(TextWriter TW)
        {
            TextWriter ITW = TW;
            if (this.Indent > 0)
            {
                ITW = new IndentedTextWriter(TW);
            }

            PrintPreamble(ITW);
            PrintStartGraph(ITW);

            PrintNode(ITW, this.Root);
            PrintNodeRankings(ITW, this.Root);
            PrintAdditionalEdges(ITW, this.Root);

            PrintCloseGraph(ITW);
        }

        protected virtual void PrintNodeRankings(TextWriter TW, ExecutionTreeNode root)
        {
            TW.WriteLine("/* Node rankings to enforce tree structure */");

            // Use BFS to group all nodes at the same depth together
            var queue = new Queue<ExecutionTreeNode>();
            int currentDepth = root.State.ExplicitBranchDepth -1; // This forces the first creation of a new level
            bool isFirst = true;
            queue.Enqueue(root);
            while (queue.Count > 0)
            {
                var node = queue.Dequeue();

                if (node.Depth > currentDepth)
                {
                    currentDepth = node.Depth;
                    if (!isFirst)
                    {
                        PopIndent(TW);
                        TW.WriteLine("}");
                    }

                    TW.WriteLine("{");
                    PushIndent(TW);
                    TW.WriteLine("rank=same;");
                    isFirst = false;
                }

                // Output this node
                TW.WriteLine(GetNodeID(node) + ";");

                // Add its children
                for (int index = 0; index < node.ChildrenCount; ++index)
                    queue.Enqueue(node.GetChild(index));

            }

            // emit last closing brace if we created at least one.
            if (!isFirst)
            {
                PopIndent(TW);
                TW.WriteLine("}");
            }
        }
    }
}

