package com.redhat.ceylon.compiler.typechecker.tree;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import org.antlr.runtime.Token;

import com.redhat.ceylon.compiler.typechecker.analyzer.AnalysisError;
import com.redhat.ceylon.compiler.typechecker.analyzer.AnalysisWarning;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.Unit;
import com.redhat.ceylon.compiler.typechecker.parser.LexError;
import com.redhat.ceylon.compiler.typechecker.parser.ParseError;
import com.redhat.ceylon.compiler.typechecker.util.PrintVisitor;

public abstract class Node {
    
    private String text;
    private Token token;
    private Scope scope;
    private Unit unit;
    private List<Message> errors = new ArrayList<Message>();
    private List<Node> children = new ArrayList<Node>();
    private Node parent;
    
    protected Node(Token token) {
        this.token = token;
        if (token==null) {
            text = "";
        }
        else {
            text = token.getText();
        }
    }
    
    /**
     * The scope within which the node occurs. 
     */
    public Scope getScope() {
        return scope;
    }

    public void setScope(Scope scope) {
        this.scope = scope;
    }
    
    /**
     * The compilation unit in which the node
     * occurs.
     */
    public Unit getUnit() {
        return unit;
    }
    
    public void setUnit(Unit unit) {
        this.unit = unit;
    }
    
    /**
     * The text of the corresponding ANTLR node.
     */
    public String getText() {
        return text;
    }
    
    public void setText(String text) {
        this.text = text;
    }
    
    /**
     * The text of the corresponding ANTLR tree node. Never null, 
     * since the two trees are isomorphic.
     */
    public Token getToken() {
    	Token ct = getChildToken();
    	if (ct!=null) return ct;
    	Token pt = getParentToken();
    	if (pt!=null) return pt;
    	return null;
    }
    
    private Token getParentToken() {
    	if (token!=null)
    		return token;
    	else if (parent!=null)
    		return parent.getParentToken();
    	else
    		return null;
    }
    
    private Token getChildToken() {
    	if (token!=null)
    		return token;
    	else if (!children.isEmpty())
    		return children.get(0).getChildToken();
    	else
    		return null;
    }
    
    /**
     * The compilation errors belonging to this node.
     */
    public List<Message> getErrors() {
        return errors;
    }
    
    public void addError(String message) {
        errors.add( new AnalysisError(this, message) );
    }
    
    public void addUnexpectedError(String message) {
        errors.add( new UnexpectedError(this, message) );
    }
    
    public void addWarning(String message) {
        errors.add( new AnalysisWarning(this, message) );
    }
    
    public void addParseError(ParseError error) {
        errors.add(error);
    }
    
    public void addLexError(LexError error) {
        errors.add(error);
    }
    
    public abstract void visit(Visitor visitor);
    
    public abstract void visitChildren(Visitor visitor);
    
    @Override
    public String toString() {
        StringWriter w = new StringWriter();
        PrintVisitor pv = new PrintVisitor(w);
        pv.visitAny(this);
        return w.toString();
        //return getClass().getSimpleName() + "(" + text + ")"; 
    }

    public String getNodeType() {
        return getClass().getSimpleName();
    }
    
    public void handleException(Exception e, Visitor visitor) {
	    addError(getMessage(e, visitor));
    }

    public String getMessage(Exception e, Visitor visitor) {
		return visitor.getClass().getSimpleName() +
	            " caused an exception visiting " + getNodeType() + 
	            " node: " + e + " at " + getLocationInfo(e);
	}

	private String getLocationInfo(Exception e) {
		return e.getStackTrace().length>0 ? 
				e.getStackTrace()[0].toString() : "unknown";
	}
	
	public void connect(Node child) {
		if (child!=null) {
			children.add(child);
			child.parent=this;
		}
	}

}

@@ -1875,24 +1875,29 @@
 ken ct = get
+First
 ChildToken()
@@ -2071,32 +2071,34 @@
 if (token!=null)
+ %7B
 %0A    %09%09return to
@@ -2094,32 +2094,39 @@
 %09%09return token;%0A
+    %09%7D%0A
     %09else if (pa
@@ -2132,24 +2132,26 @@
 arent!=null)
+ %7B
 %0A    %09%09retur
@@ -2169,32 +2169,39 @@
 tParentToken();%0A
+    %09%7D%0A
     %09else%0A    %09%09
@@ -2185,32 +2185,34 @@
     %09%7D%0A    %09else
+ %7B
 %0A    %09%09return nu
@@ -2207,32 +2207,39 @@
  %09%09return null;%0A
+    %09%7D%0A
     %7D%0A    %0A    p
@@ -2254,16 +2254,21 @@
 oken get
+First
 ChildTok
@@ -2295,16 +2295,18 @@
 n!=null)
+ %7B
 %0A    %09%09r
@@ -2327,69 +2327,113 @@
    %09
-else if (!children.isEmpty())%0A    %09%09return
+%7D%0A    %09else %7B%0A    %09%09Token token=null;%0A    %09%09for (Node child: children) %7B%0A    %09%09%09Token tok =
  child
-ren
 .get
-(0).ge
+Firs
 tChi
@@ -2452,31 +2452,160 @@
    %09
-else%0A    %09%09return null;
+%09%09if (tok!=null && ( token==null %7C%7C %0A    %09%09%09%09%09tok.getTokenIndex()%3Ctoken.getTokenIndex() )) %7B%0A    %09%09%09%09token=tok;%0A    %09%09%09%7D%0A    %09%09%7D%0A%09%09%09return token;%0A    %09%7D
 %0A   

compute "first child" more robustly