/*
 * [The "BSD licence"]
 * Copyright (c) 2011 Terence Parr
 * All rights reserved.
 *
 * Conversion to C#:
 * Copyright (c) 2011 Sam Harwell, Pixel Mine, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Antlr.Runtime.Tree
{
    using System.Collections.Generic;

    using ArgumentNullException = System.ArgumentNullException;
    using Exception = System.Exception;
    using IDictionary = System.Collections.IDictionary;
    using NotSupportedException = System.NotSupportedException;

    /** <summary>A TreeAdaptor that works with any Tree implementation.</summary> */
    public abstract class BaseTreeAdaptor : ITreeAdaptor
    {
        /** <summary>
         *  System.identityHashCode() is not always unique; we have to
         *  track ourselves.  That's ok, it's only for debugging, though it's
         *  expensive: we have to create a hashtable with all tree nodes in it.
         *  </summary>
         */
        protected IDictionary<object, int> treeToUniqueIDMap;
        protected int uniqueNodeID = 1;

        public virtual object Nil()
        {
            return Create( null );
        }

        /** <summary>
         *  Create tree node that holds the start and stop tokens associated
         *  with an error.
         *  </summary>
         *
         *  <remarks>
         *  If you specify your own kind of tree nodes, you will likely have to
         *  override this method. CommonTree returns Token.INVALID_TOKEN_TYPE
         *  if no token payload but you might have to set token type for diff
         *  node type.
         *
         *  You don't have to subclass CommonErrorNode; you will likely need to
         *  subclass your own tree node class to avoid class cast exception.
         *  </remarks>
         */
        public virtual object ErrorNode( ITokenStream input, IToken start, IToken stop,
                                RecognitionException e )
        {
            CommonErrorNode t = new CommonErrorNode( input, start, stop, e );
            //System.out.println("returning error node '"+t+"' @index="+input.index());
            return t;
        }

        public virtual bool IsNil( object tree )
        {
            return ( (ITree)tree ).IsNil;
        }

        public virtual object DupNode(int type, object treeNode)
        {
            object t = DupNode(treeNode);
            SetType(t, type);
            return t;
        }

        public virtual object DupNode(object treeNode, string text)
        {
            object t = DupNode(treeNode);
            SetText(t, text);
            return t;
        }

        public virtual object DupNode(int type, object treeNode, string text)
        {
            object t = DupNode(treeNode);
            SetType(t, type);
            SetText(t, text);
            return t;
        }

        public virtual object DupTree( object tree )
        {
            return DupTree( tree, null );
        }

        /** <summary>
         *  This is generic in the sense that it will work with any kind of
         *  tree (not just ITree interface).  It invokes the adaptor routines
         *  not the tree node routines to do the construction.  
         *  </summary>
         */
        public virtual object DupTree( object t, object parent )
        {
            if ( t == null )
            {
                return null;
            }
            object newTree = DupNode( t );
            // ensure new subtree root has parent/child index set
            SetChildIndex( newTree, GetChildIndex( t ) ); // same index in new tree
            SetParent( newTree, parent );
            int n = GetChildCount( t );
            for ( int i = 0; i < n; i++ )
            {
                object child = GetChild( t, i );
                object newSubTree = DupTree( child, t );
                AddChild( newTree, newSubTree );
            }
            return newTree;
        }

        /** <summary>
         *  Add a child to the tree t.  If child is a flat tree (a list), make all
         *  in list children of t.  Warning: if t has no children, but child does
         *  and child isNil then you can decide it is ok to move children to t via
         *  t.children = child.children; i.e., without copying the array.  Just
         *  make sure that this is consistent with have the user will build
         *  ASTs.
         *  </summary>
         */
        public virtual void AddChild( object t, object child )
        {
            if ( t != null && child != null )
            {
                ( (ITree)t ).AddChild( (ITree)child );
            }
        }

        /** <summary>
         *  If oldRoot is a nil root, just copy or move the children to newRoot.
         *  If not a nil root, make oldRoot a child of newRoot.
         *  </summary>
         *
         *  <remarks>
         *    old=^(nil a b c), new=r yields ^(r a b c)
         *    old=^(a b c), new=r yields ^(r ^(a b c))
         *
         *  If newRoot is a nil-rooted single child tree, use the single
         *  child as the new root node.
         *
         *    old=^(nil a b c), new=^(nil r) yields ^(r a b c)
         *    old=^(a b c), new=^(nil r) yields ^(r ^(a b c))
         *
         *  If oldRoot was null, it's ok, just return newRoot (even if isNil).
         *
         *    old=null, new=r yields r
         *    old=null, new=^(nil r) yields ^(nil r)
         *
         *  Return newRoot.  Throw an exception if newRoot is not a
         *  simple node or nil root with a single child node--it must be a root
         *  node.  If newRoot is ^(nil x) return x as newRoot.
         *
         *  Be advised that it's ok for newRoot to point at oldRoot's
         *  children; i.e., you don't have to copy the list.  We are
         *  constructing these nodes so we should have this control for
         *  efficiency.
         *  </remarks>
         */
        public virtual object BecomeRoot( object newRoot, object oldRoot )
        {
            //System.out.println("becomeroot new "+newRoot.toString()+" old "+oldRoot);
            ITree newRootTree = (ITree)newRoot;
            ITree oldRootTree = (ITree)oldRoot;
            if ( oldRoot == null )
            {
                return newRoot;
            }
            // handle ^(nil real-node)
            if ( newRootTree.IsNil )
            {
                int nc = newRootTree.ChildCount;
                if ( nc == 1 )
                    newRootTree = (ITree)newRootTree.GetChild( 0 );
                else if ( nc > 1 )
                {
                    // TODO: make tree run time exceptions hierarchy
                    throw new Exception( "more than one node as root (TODO: make exception hierarchy)" );
                }
            }
            // add oldRoot to newRoot; addChild takes care of case where oldRoot
            // is a flat list (i.e., nil-rooted tree).  All children of oldRoot
            // are added to newRoot.
            newRootTree.AddChild( oldRootTree );
            return newRootTree;
        }

        /** <summary>Transform ^(nil x) to x and nil to null</summary> */
        public virtual object RulePostProcessing( object root )
        {
            //System.out.println("rulePostProcessing: "+((Tree)root).toStringTree());
            ITree r = (ITree)root;
            if ( r != null && r.IsNil )
            {
                if ( r.ChildCount == 0 )
                {
                    r = null;
                }
                else if ( r.ChildCount == 1 )
                {
                    r = (ITree)r.GetChild( 0 );
                    // whoever invokes rule will set parent and child index
                    r.Parent = null;
                    r.ChildIndex = -1;
                }
            }
            return r;
        }

        public virtual object BecomeRoot( IToken newRoot, object oldRoot )
        {
            return BecomeRoot( Create( newRoot ), oldRoot );
        }

        public virtual object Create( int tokenType, IToken fromToken )
        {
            fromToken = CreateToken( fromToken );
            fromToken.Type = tokenType;
            object t = Create( fromToken );
            return t;
        }

        public virtual object Create( int tokenType, IToken fromToken, string text )
        {
            if ( fromToken == null )
                return Create( tokenType, text );

            fromToken = CreateToken( fromToken );
            fromToken.Type = tokenType;
            fromToken.Text = text;
            object result = Create(fromToken);
            return result;
        }

        public virtual object Create(IToken fromToken, string text)
        {
            if (fromToken == null)
                throw new ArgumentNullException("fromToken");

            fromToken = CreateToken(fromToken);
            fromToken.Text = text;
            object result = Create(fromToken);
            return result;
        }

        public virtual object Create( int tokenType, string text )
        {
            IToken fromToken = CreateToken( tokenType, text );
            object t = Create( fromToken );
            return t;
        }

        public virtual int GetType( object t )
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return TokenTypes.Invalid;

            return tree.Type;
        }

        public virtual void SetType( object t, int type )
        {
            throw new NotSupportedException( "don't know enough about Tree node" );
        }

        public virtual string GetText( object t )
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return null;

            return tree.Text;
        }

        public virtual void SetText( object t, string text )
        {
            throw new NotSupportedException( "don't know enough about Tree node" );
        }

        public virtual object GetChild( object t, int i )
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return null;

            return tree.GetChild(i);
        }

        public virtual void SetChild( object t, int i, object child )
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return;

            ITree childTree = GetTree(child);
            tree.SetChild(i, childTree);
        }

        public virtual object DeleteChild( object t, int i )
        {
            return ( (ITree)t ).DeleteChild( i );
        }

        public virtual int GetChildCount( object t )
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return 0;

            return tree.ChildCount;
        }

        public virtual int GetUniqueID( object node )
        {
            if ( treeToUniqueIDMap == null )
            {
                treeToUniqueIDMap = new Dictionary<object, int>();
            }
            int id;
            if ( treeToUniqueIDMap.TryGetValue( node, out id ) )
                return id;

            id = uniqueNodeID;
            treeToUniqueIDMap[node] = id;
            uniqueNodeID++;
            return id;
            // GC makes these nonunique:
            // return System.identityHashCode(node);
        }

        /** <summary>
         *  Tell me how to create a token for use with imaginary token nodes.
         *  For example, there is probably no input symbol associated with imaginary
         *  token DECL, but you need to create it as a payload or whatever for
         *  the DECL node as in ^(DECL type ID).
         *  </summary>
         *
         *  <remarks>
         *  If you care what the token payload objects' type is, you should
         *  override this method and any other createToken variant.
         *  </remarks>
         */
        public abstract IToken CreateToken( int tokenType, string text );

        /** <summary>
         *  Tell me how to create a token for use with imaginary token nodes.
         *  For example, there is probably no input symbol associated with imaginary
         *  token DECL, but you need to create it as a payload or whatever for
         *  the DECL node as in ^(DECL type ID).
         *  </summary>
         *
         *  <remarks>
         *  This is a variant of createToken where the new token is derived from
         *  an actual real input token.  Typically this is for converting '{'
         *  tokens to BLOCK etc...  You'll see
         *
         *    r : lc='{' ID+ '}' -> ^(BLOCK[$lc] ID+) ;
         *
         *  If you care what the token payload objects' type is, you should
         *  override this method and any other createToken variant.
         *  </remarks>
         */
        public abstract IToken CreateToken( IToken fromToken );

        public abstract object Create( IToken payload );

        /** <summary>
         *  Duplicate a node.  This is part of the factory;
         *  override if you want another kind of node to be built.
         *  </summary>
         *
         *  <remarks>
         *  I could use reflection to prevent having to override this
         *  but reflection is slow.
         *  </remarks>
         */
        public virtual object DupNode(object treeNode)
        {
            ITree tree = GetTree(treeNode);
            if (tree == null)
                return null;

            return tree.DupNode();
        }

        public abstract IToken GetToken( object t );

        /** <summary>
         *  Track start/stop token for subtree root created for a rule.
         *  Only works with Tree nodes.  For rules that match nothing,
         *  seems like this will yield start=i and stop=i-1 in a nil node.
         *  Might be useful info so I'll not force to be i..i.
         *  </summary>
         */
        public virtual void SetTokenBoundaries(object t, IToken startToken, IToken stopToken)
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return;

            int start = 0;
            int stop = 0;

            if (startToken != null)
                start = startToken.TokenIndex;
            if (stopToken != null)
                stop = stopToken.TokenIndex;

            tree.TokenStartIndex = start;
            tree.TokenStopIndex = stop;
        }

        public virtual int GetTokenStartIndex(object t)
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return -1;

            return tree.TokenStartIndex;
        }

        public virtual int GetTokenStopIndex(object t)
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return -1;

            return tree.TokenStopIndex;
        }

        public virtual object GetParent(object t)
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return null;

            return tree.Parent;
        }

        public virtual void SetParent(object t, object parent)
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return;

            ITree parentTree = GetTree(parent);
            tree.Parent = parentTree;
        }

        public virtual int GetChildIndex(object t)
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return 0;

            return tree.ChildIndex;
        }

        public virtual void SetChildIndex(object t, int index)
        {
            ITree tree = GetTree(t);
            if (tree == null)
                return;

            tree.ChildIndex = index;
        }

        public virtual void ReplaceChildren(object parent, int startChildIndex, int stopChildIndex, object t)
        {
            ITree tree = GetTree(parent);
            if (tree == null)
                return;

            tree.ReplaceChildren(startChildIndex, stopChildIndex, t);
        }

        protected virtual ITree GetTree(object t)
        {
            if (t == null)
                return null;

            ITree tree = t as ITree;
            if (tree == null)
                throw new NotSupportedException();

            return tree;
        }
    }
}