/** The initiating call should be to the root node of the tree.
         * It fills in an nxn (hash) table of the leftmost leaf for a
         * given node.  It also compiles an array of key roots. The
         * int values IDs must come from the post-ordering of the
         * nodes in the tree.
         */
        private void findHelperTables(TreeDefinition someTree, Dictionary<int, int> leftmostLeaves, List<int> keyroots, int aNodeID)
        {
            findHelperTablesRecurse(someTree, leftmostLeaves, keyroots, aNodeID);

            //add root to keyroots
            keyroots.Add(aNodeID);

            //add boundary nodes
            leftmostLeaves[0] = 0;
        }
        private void findHelperTablesRecurse(TreeDefinition someTree, Dictionary<int, int> leftmostLeaves, List<int> keyroots, int aNodeID)
        {

            //If this is a leaf, then it is the leftmost leaf
            if (someTree.isLeaf(aNodeID))
            {
                leftmostLeaves[aNodeID] = aNodeID;
            }
            else
            {
                bool seenLeftmost = false;
                foreach (int child in someTree.getChildrenIDs(aNodeID))
                {
                    findHelperTablesRecurse(someTree, leftmostLeaves, keyroots, child);
                    if (!seenLeftmost)
                    {
                        leftmostLeaves[aNodeID] = leftmostLeaves[child];
                        seenLeftmost = true;
                    }
                    else
                        keyroots.Add(child);
                }
            }
        }
 public Transformation getTransformation(TreeDefinition aTree, TreeDefinition bTree)
 {
     Transformation transform = new Transformation();            
     Transformation t1 = findDistance(aTree, bTree);
     transform.totalCost = distance[aTree.getNodeCount(), bTree.getNodeCount()];
     Transformation t2 = findDistance(bTree, aTree);
     
     transform.editScriptAtoB = t1.editScriptAtoB;
     transform.editScriptBtoA = t2.editScriptAtoB;
     transform.pdlA = t1.pdlA;
     transform.pdlB = t1.pdlB;
     transform.pdlMappingTreeB = t1.pdlMappingTreeB;
     transform.pdlMappingTreeA = t1.pdlMappingTreeA;
     return transform;
 }
        //Computes tree edits distance betwee aTree and bTree
        public Transformation findDistance(TreeDefinition aTree, TreeDefinition bTree)
        {
            distance = new double[aTree.getNodeCount() + 1, bTree.getNodeCount() + 1];
            distScript = new TreeEditScript[aTree.getNodeCount() + 1, bTree.getNodeCount() + 1];

            //Preliminaries
            //1. Find left-most leaf and key roots
            Dictionary<int, int> aLeftLeaf = new Dictionary<int, int>();
            Dictionary<int, int> bLeftLeaf = new Dictionary<int, int>();
            List<int> aTreeKeyRoots = new List<int>();
            List<int> bTreeKeyRoots = new List<int>();

            findHelperTables(aTree, aLeftLeaf, aTreeKeyRoots, aTree.getRootID());

            findHelperTables(bTree, bLeftLeaf, bTreeKeyRoots, bTree.getRootID());

            var a = bTree.getRootID();
            //Comparison
            foreach (int aKeyroot in aTreeKeyRoots)
            {
                foreach (int bKeyroot in bTreeKeyRoots)
                {
                    //Re-initialise forest distance tables
                    Dictionary<int, Dictionary<int, Double>> fD =
                        new Dictionary<int, Dictionary<int, Double>>();

                    //Re-initialise forest edit script distance tables
                    Dictionary<int, Dictionary<int, TreeEditScript>> fESD =
                        new Dictionary<int, Dictionary<int, TreeEditScript>>();

                    setForestDistance(aLeftLeaf[aKeyroot], bLeftLeaf[bKeyroot], 0.0d, fD);
                    //script is automatically null                 

                    //for all descendents of aKeyroot: i
                    for (int i = aLeftLeaf[aKeyroot]; i <= aKeyroot; i++)
                    {
                        var edit = new Delete(i,aTree);

                        setForestDistance(i,
                          bLeftLeaf[bKeyroot] - 1,
                          getForestDistance(i - 1, bLeftLeaf[bKeyroot] - 1, fD) +
                          edit.getCost(),
                          fD);

                        var scriptSuffix = new TreeEditScript(getForestScript(i - 1, bLeftLeaf[bKeyroot] - 1, fESD));
                        scriptSuffix.Insert(edit);

                        setForestScript(i, bLeftLeaf[bKeyroot] - 1, scriptSuffix, fESD);
                    }

                    //for all descendents of bKeyroot: j
                    for (int j = bLeftLeaf[bKeyroot]; j <= bKeyroot; j++)
                    {
                        var edit = new Insert(j, bTree);

                        setForestDistance(aLeftLeaf[aKeyroot] - 1, j,
                          getForestDistance(aLeftLeaf[aKeyroot] - 1, j - 1, fD) +
                          edit.getCost(),
                          fD);

                        var scriptSuffix = new TreeEditScript(getForestScript(aLeftLeaf[aKeyroot] - 1, j - 1, fESD));
                        scriptSuffix.Insert(edit);

                        setForestScript(aLeftLeaf[aKeyroot] - 1, j, scriptSuffix, fESD);
                    }

                    for (int i = aLeftLeaf[aKeyroot]; i <= aKeyroot; i++)
                    {
                        for (int j = bLeftLeaf[bKeyroot]; j <= bKeyroot; j++)
                        {
                            TreeEditScript tempScript = null;

                            EditOperation delEdit = new Delete(i, aTree); ;
                            EditOperation insEdit = new Insert(j, bTree);
                            double min;
                            double delCost = getForestDistance(i - 1, j, fD) + delEdit.getCost();
                            double insCost = getForestDistance(i, j - 1, fD) + insEdit.getCost();

                            //This min compares del vs ins
                            if (delCost <= insCost)
                            {
                                //Option 1: Delete node from aTree
                                tempScript = new TreeEditScript(getForestScript(i - 1, j, fESD));
                                tempScript.Insert(delEdit);
                                min = delCost;
                            }
                            else
                            {
                                //Option 2: Insert node into bTree
                                tempScript = new TreeEditScript(getForestScript(i, j - 1, fESD));
                                tempScript.Insert(insEdit);
                                min = insCost;
                            }

                            if (aLeftLeaf[i] == aLeftLeaf[aKeyroot] && bLeftLeaf[j] == bLeftLeaf[bKeyroot])
                            {
                                var renEdit = new Rename(i, j, aTree, bTree);
                                var dist = getForestDistance(i - 1, j - 1, fD) + renEdit.getCost();

                                distance[i, j] = Math.Min(min, dist);

                                if (min <= dist)
                                {
                                    tempScript = new TreeEditScript(tempScript);
                                    distScript[i, j] = tempScript;
                                }
                                else
                                {
                                    tempScript = new TreeEditScript(getForestScript(i - 1, j - 1, fESD));
                                    tempScript.Insert(renEdit);
                                    distScript[i, j] = tempScript;
                                }
                                setForestDistance(i, j, distance[i, j], fD);
                                setForestScript(i, j, new TreeEditScript(distScript[i, j]), fESD);

                            }
                            else
                            {
                                var value = getForestDistance(aLeftLeaf[i] - 1, bLeftLeaf[j] - 1, fD) + distance[i, j];
                                setForestDistance(i, j, Math.Min(min, value), fD);

                                if (min <= value)
                                    setForestScript(i, j, new TreeEditScript(tempScript), fESD);
                                else
                                {
                                    var tempList = getForestScript(aLeftLeaf[i] - 1, bLeftLeaf[j] - 1, fESD).script;
                                    tempList = distScript[i, j].script.Concat(tempList).ToList();

                                    setForestScript(i, j, new TreeEditScript(tempList), fESD);
                                }

                                setForestDistance(i, j, Math.Min(min, value), fD);
                                
                            }
                        }
                    }
                }
            }

            Transformation transform = new Transformation();
            transform.totalCost = distance[aTree.getNodeCount(), bTree.getNodeCount()];
            transform.editScriptAtoB = distScript[aTree.getNodeCount(), bTree.getNodeCount()];
            transform.pdlA = aTree.pdl;
            transform.pdlB = bTree.pdl;
            transform.pdlMappingTreeB = bTree.pdlNodeMapping;
            transform.pdlMappingTreeA = aTree.pdlNodeMapping;
            return transform;
        }
        public Rename(int aNodeId, int bNodeId, TreeDefinition aTree, TreeDefinition bTree)
        {
            labelA = aTree.getLabelForMatching(aNodeId);
            labelB = bTree.getLabelForMatching(bNodeId);

            pdlA = aTree.pdlNodeMapping[labelA];
            pdlB = bTree.pdlNodeMapping[labelB];

            int aDiv = labelA.LastIndexOf(":");
            if (aDiv == -1)
                throw new PDLException(string.Format("All nodes should have a : in the middle: {0}", labelA));
            int bDiv = labelB.LastIndexOf(":");
            if (bDiv == -1)
                throw new PDLException(string.Format("All nodes should have a : in the middle: {0}", labelB));

            labelA = labelA.Substring(0, aDiv);
            labelB = labelB.Substring(0, bDiv);
        }
        public Insert(int bNodeId, TreeDefinition bTree)
        {
            labelB = bTree.getLabelForMatching(bNodeId);

            pdlB = bTree.pdlNodeMapping[labelB];

            int div = labelB.LastIndexOf(":");
            if (div == -1)
                throw new PDLException(string.Format("All nodes should have a : in the middle: {0}", labelB));            

            labelB = labelB.Substring(0, div);
        }
        public Delete(int aNodeId, TreeDefinition aTree)
        {
            labelA = aTree.getLabelForMatching(aNodeId);

            pdlA = aTree.pdlNodeMapping[labelA];

            int div = labelA.LastIndexOf(":");
            if (div == -1)
                throw new PDLException(string.Format("All nodes should have a : in the middle: {0}", labelA));

            labelA = labelA.Substring(0, div);
        }