
// Transmogrify License
// 
// Copyright (c) 2001, ThoughtWorks, 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:
// - Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// - 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.
// Neither the name of the ThoughtWorks, Inc. nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "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 COPYRIGHT OWNER OR
// CONTRIBUTORS 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.

package com.puppycrawl.tools.checkstyle.checks.usage.transmogrify;

import java.io.File;

import antlr.collections.AST;

import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;



/**
 * an extension of <code>antlr.CommonAST</code> that includes
 * extra information about the AST's location.  This information
 * is the file and line number where the AST was created.
 *
 * To use this AST node in your tree structure, assuming your
 * antlr.TreeParser is called parser, use
 *
 * parser.setASTNOdeCLass(SymTabAST.class.getName());
 *
 * make sure you also call setTokenObjectClass for the lexer as well
 *
 *
 * @see SymTabToken
 */
//TODO: Should be an adapter of DetailAST

public class SymTabAST
    extends antlr.CommonASTWithHiddenTokens
{
    private Scope _scope;
    private IDefinition _definition = null;
    private boolean _isMeaningful = true;

    private File _file;
    private int _line;
    private int _column;

//  parent is not used by Checkstyle
//    private SymTabAST parent;

    private Span _span;

    /** original syntax tree node */
    private DetailAST detailNode;
    
//    /**
//     * gets parent of this node
//     * @return <code>SymTabAST</code>
//     */
//    public SymTabAST getParent() {
//        return parent;
//    }

//    /**
//     * gets previous sibling of this node
//     * @return <code>SymTabAST</code>
//     */
//    public SymTabAST getPreviousSibling() {
//        return prevSibling;
//    }

    /**
     * sets parent of this node
     * @param parent
     * @return <code>void</code>
     */
    public void setParent(SymTabAST parent) {
//parent is not used by Checkstyle
//        this.parent = parent;
    }

    /**
     * gets the scope of this node
     * @return <code>Scope</code>
     */
    public Scope getScope() {
        return _scope;
    }

    /**
     * sets the scope of this node
     * @param scope
     * @return <code>void</code>
     */
    public void setScope(Scope scope) {
        _scope = scope;
    }

    /**
     * sets <code>Definition</code> for this node
     * @param definition
     * @param scope
     * @return <code>void</code>
     * @see #setDefinition(IDefinition, Scope, boolean)
     */
    public void setDefinition(IDefinition definition, Scope scope) {
        setDefinition(definition, scope, true);
    }

    /**
     * sets <code>Definition</code> for this node and adds <code>Reference</code>
     * to the <code>_definition</code> and <code>scope</code>
     * @param definition
     * @param scope
     * @param createReference
     * @return <code>void</code>
     * @see net.sourceforge.transmogrify.symtab.Reference
     */
    public void setDefinition(
        IDefinition definition,
        Scope scope,
        boolean createReference) {
        _definition = definition;
        Reference reference = new Reference(this);
        if (scope != null) {
            scope.addReferenceInScope(reference);
        }

        if (definition.isSourced() && createReference) {
            _definition.addReference(reference);
        }
    }

    /**
     * gets <code>_definitin</code>
     * @return <code>IDefinition</code>
     */
    public IDefinition getDefinition() {
        return _definition;
    }

    /**
     * tests if this node is meaningful or should be ignored
     * @return <code>boolean</code>
     */
    public boolean isMeaningful() {
        return _isMeaningful;
    }

    /**
     * sets <code>_isMeaningful</code> member
     * @param isMeaningful
     * @return <code>void</code>
     */
    public void setMeaningfulness(boolean isMeaningful) {
        _isMeaningful = isMeaningful;
    }

    /**
     * sets meaningfulness for this node and its children
     * @return <code>void</code>
     * @see #setMeaningfulness(boolean)
     */
    public void ignoreChildren() {
        if (getType() == TokenTypes.IDENT) {
            setMeaningfulness(false);
        }
        SymTabAST child = (SymTabAST) getFirstChild();
        while (child != null) {
            child.ignoreChildren();
            child = (SymTabAST) child.getNextSibling();
        }
    }

    /**
     * sets file where this node belong to
     * @param file
     * @return <code>void</code>
     */
    public void setFile(File file) {
        _file = file;
    }

    /**
     * finishes process for adding node to its parent
     * @param file file where this node belongs to
     * @param parent parent of this node
     * @param previousSibling previous sibling of this node
     * @return <code>Span</code> the span of this node
     * @see #setFile(File)
     * @see #setParent(SymTabAST)
     * @see #setPreviousSibling(SymTabAST)
     * @see #finishChildren(File)
     * @see #setSpan(Span)
     */
    public Span finishDefinition(
        File file,
        SymTabAST parent) {
        setFile(file);
        setParent(parent);

        Span result = finishChildren(file);

        if (getLineNo() != 0) {
            result.compose(
                new Span(
                    getLineNo(),
                    getColumnNo(),
                    getLineNo(),
                    getColumnNo()
                        + ((getText() == null) ? 0 : getText().length() - 1)));
        }

        setSpan(result);
        return result;
    }

    /**
     * finishes children of this node definition process
     * @param file file where this node belongs to
     * @return <code>Span</code>
     * @see #finishDefinition(File, SymTabAST, SymTabAST)
     */
    public Span finishChildren(File file) {
        Span result = null;
        SymTabAST previousSibling = null;
        SymTabAST current = (SymTabAST) getFirstChild();

        if (current == null) {
            result = getSpan();
        }
        else {
            while (current != null) {
                Span childSpan =
                    current.finishDefinition(file, this);

                if (childSpan != null) {
                    if (result == null) {
                        result = new Span(childSpan);
                    }
                    else {
                        result.compose(childSpan);
                    }
                }

                SymTabAST temp = current;
                current = (SymTabAST) current.getNextSibling();
                previousSibling = temp;
            }
        }

        return result;
    }

    /**
     * gets file where this node belongs to
     * @return <code>File</code>
     */
    public File getFile() {
        return _file;
    }

    /**
     * sets the line where this node reside
     * @return <code>void</code>
     */
    public void setLine(int line) {
        _line = line;
    }

    /**
     * gets the line where this node reside
     * @return <code>int</code>
     */
    public int getLineNo() {
        return _line;
    }

    /**
     * sets the column where this node reside
     * @param column
     */
    public void setColumn(int column) {
        _column = column;
    }

    /**
     * gets the column where this node reside
     * @return <code>int</code>
     */
    public int getColumnNo() {
        return _column;
    }

    /**
     * gets the definition name of this node
     * @return <code>String</code>
     * @see net.sourceforge.transmogrify.symtab.IDefinition
     */
    public String getName() {
        String result = null;
        if (_definition != null) {
            result = _definition.getName();
        }

        return result;
    }

    /**
     * makes a new copy of the current <code>SymTabAST</code>.  Uses the
     * initialize(AST t) method to copy properties for each cloned node.
     *
     * @return <code>SymTabAST</code> a full tree copy of the current tree
     */
    private SymTabAST treeCopy()
    {
        SymTabAST copyOfThis = SymTabASTFactory.create(getType(), getText());
        copyOfThis.initialize(this);

        if (getFirstChild() != null) {
            copyOfThis.setFirstChild(((SymTabAST) getFirstChild()).treeCopy());
        }

        if (getNextSibling() != null) {
            copyOfThis.setNextSibling(
                ((SymTabAST) getNextSibling()).treeCopy());
        }

        return copyOfThis;
    }

    /**
     * prints the line, column and file for this node for debugging purpose
     * @return <code>String</code>
     */
    public String toString() {
        //StringBuffer resultBuffer = new StringBuffer(super.toString());
        StringBuffer resultBuffer = new StringBuffer(prefixString(true));
        resultBuffer.append("[" + getLineNo() + "," + getColumnNo() + "]");
        //if ( getSpan() != null ) {
        //  resultBuffer.append( " spans " + getSpan() );
        //}
        resultBuffer.append(" in " + getFile());
        //resultBuffer.append(" type: " + getType());
        return resultBuffer.toString();
    }

    public String prefixString(boolean verboseStringConversion) {
        StringBuffer b = new StringBuffer();

        try {
            final String name = TokenTypes.getTokenName(getType());
            // if verbose and type name not same as text (keyword probably)
            if (verboseStringConversion && !getText().equalsIgnoreCase(name)) {
                b.append('[');
                b.append(getText());
                b.append(",<");
                b.append(name);
                b.append(">]");
                return b.toString();
            }
        }
        catch (Exception ex) {
            ;
        }
        return getText();
    }

    /**
     * gets <code>Span</code> of this node
     * @return <code>Span</code>
     */
    public Span getSpan() {
        if ((_span == null)) {
            int endColumn = getColumnNo() + 1;
            final String text = getText();
            if (text != null) {
                endColumn += text.length() - 1;
            }
            _span = new Span(getLineNo(), getColumnNo() + 1, getLineNo(), endColumn);
        }
        return _span;
    }

    /**
     * sets <code>Span</code> for this node
     * @param span
     * @return <code>void</code>
     */
    public void setSpan(Span span) {
        _span = span;
    }

// not used by Checkstyle
//    /**
//     * tests if this node is inside the span
//     * @param line
//     * @param column
//     * @return <code>boolean</code> <code>true</code> if this node is within the span
//     *                              <code>false</code> otherwise
//     */
//    public boolean contains(int line, int column) {
//        return getSpan().contains(line, column);
//    }

    /**
     * gets enclosing node for this node based on line and column
     * @param line
     * @param column
     * @return <code>SymTabAST</code>
     * @see #getSpan()
     */
    public SymTabAST getEnclosingNode(int line, int column) {
        SymTabAST result = null;

        if ((getSpan() != null) && (getSpan().contains(line, column))) {
            SymTabAST child = (SymTabAST) getFirstChild();
            while (child != null && result == null) {
                result = child.getEnclosingNode(line, column);
                child = (SymTabAST) child.getNextSibling();
            }

            // if none of the children contain it, I'm the best node
            if (result == null) {
                result = this;
            }

        }
        return result;
    }

    public AST getFirstChild()
    {
        if (super.getFirstChild() == null) {
            DetailAST childDetailAST = null;
            final DetailAST detailAST = getDetailNode();
            if (detailAST != null) {
                childDetailAST = (DetailAST) detailAST.getFirstChild();
                if (childDetailAST != null) {
                    final SymTabAST child =
                        SymTabASTFactory.create(childDetailAST);
                    setFirstChild(child);
                    child.setParent(this);
                    child.setFile(getFile());
                }
            }
        }
        return super.getFirstChild(); 
    }
    
    public AST getNextSibling()
    {
        if (super.getNextSibling() == null) {
            DetailAST siblingDetailAST = null;
            final DetailAST detailAST = getDetailNode();           
            if (detailAST != null) {
                siblingDetailAST = (DetailAST) detailAST.getNextSibling();
                if (siblingDetailAST != null) {
                    final SymTabAST sibling =
                    SymTabASTFactory.create(siblingDetailAST);
                    setNextSibling(sibling);
//                    sibling.setParent(this.getParent());
                    sibling.setFile(getFile());
                }
            }
        }
        return super.getNextSibling(); 
    }

    
    /**
     * initialized this node with input node
     * @param aAST the node to initialize from. Must be a
     * <code>DetailAST</code> object.
     */
    public void initialize(AST aAST)
    {
        if (aAST != null) {
            super.initialize(aAST);
            final DetailAST detailAST = (DetailAST) aAST;
            setDetailNode(detailAST);
            _column = detailAST.getColumnNo() + 1;
            _line = detailAST.getLineNo();
        } 
    }
        
    /**
     * Gets first occurence of the child node with a certain type
     * @param type
     * @return <code>SymTabAST</code>
     * @see #getType()
     */
    public SymTabAST findFirstToken(int type) {
        SymTabAST result = null;

        AST sibling = getFirstChild();
        while (sibling != null) {
            if (sibling.getType() == type) {
                result = (SymTabAST) sibling;
                break;
            }
            sibling = sibling.getNextSibling();
        }

        return result;
    }

//  not used by Checkstyle
//    /**
//     * adds a node to the last position of this node children
//     * @param child
//     * @return <code>void</code>
//     */
//    public void addChild(SymTabAST child)
//    {
//        SymTabAST lastChild = (SymTabAST) getFirstChild();
//        if (lastChild == null) {
//            setFirstChild(child);
//            child.setParent(this);
//            child.setNextSibling(null);
//        }
//        else {
//            while (lastChild.getNextSibling() != null) {
//                lastChild = (SymTabAST) lastChild.getNextSibling();
//            }
//            lastChild.setNextSibling(child);
//            child.setNextSibling(null);
//            child.setParent(this);
//        }
//    }

    /**
     * Gets Iterator for this node
     * @return <code>SymTabASTIterator</code>
     */
    public SymTabASTIterator getChildren()
    {
        return new SymTabASTIterator(this);
    }

    /**
     * Returns the DetailAST associated with this node.
     * @return the DetailAST associated with this node.
     */
    public DetailAST getDetailNode()
    {
        return detailNode;
    }

    /**
     * Sets the DetailAST associated with this node.
     * @param aDetailAST the DetailAST associated with this node.
     */
    public void setDetailNode(DetailAST aDetailAST)
    {
        detailNode = aDetailAST;
        ASTManager.getInstance().put(aDetailAST, this);
    }

}