package org.antlr.jetbrains.sample.psi;

import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.IncorrectOperationException;
import org.antlr.intellij.adaptor.lexer.RuleIElementType;
import org.antlr.intellij.adaptor.psi.ANTLRPsiLeafNode;
import org.antlr.intellij.adaptor.psi.Trees;
import org.antlr.jetbrains.sample.SampleLanguage;
import org.antlr.jetbrains.sample.SampleParserDefinition;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import static org.antlr.jetbrains.sample.parser.SampleLanguageParser.RULE_call_expr;
import static org.antlr.jetbrains.sample.parser.SampleLanguageParser.RULE_expr;
import static org.antlr.jetbrains.sample.parser.SampleLanguageParser.RULE_primary;
import static org.antlr.jetbrains.sample.parser.SampleLanguageParser.RULE_statement;

/** From doc: "Every element which can be renamed or referenced
 *             needs to implement com.intellij.psi.PsiNamedElement interface."
 *
 *  So, all leaf nodes that represent variables, functions, classes, or
 *  whatever in your plugin language must be instances of this not just
 *  LeafPsiElement.  Your ASTFactory should create this kind of object for
 *  ID tokens. This node is for references *and* definitions because you can
 *  highlight a function and say "jump to definition". Also we want defs
 *  to be included in "find usages." Besides, there is no context information
 *  in the AST factory with which you could decide whether this node
 *  is a definition or a reference.
 *
 *  PsiNameIdentifierOwner (vs PsiNamedElement) implementations are the
 *  corresponding subtree roots that define symbols.
 *
 *  You can click on an ID in the editor and ask for a rename for any node
 *  of this type.
 */
public class IdentifierPSINode extends ANTLRPsiLeafNode implements PsiNamedElement {
	public IdentifierPSINode(IElementType type, CharSequence text) {
		super(type, text);
	}

	@Override
	public String getName() {
		return getText();
	}

	/** Alter this node to have text specified by the argument. Do this by
	 *  creating a new node through parsing of an ID and then doing a
	 *  replace.
	 */
	@Override
	public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
		if ( getParent()==null ) return this; // weird but it happened once
		/*
		IElementType elType = getParent().getNode().getElementType();
		String kind = "??? ";
		if ( elType instanceof RuleIElementType ) {
			int ruleIndex = ((RuleIElementType) elType).getRuleIndex();
			if ( ruleIndex == RULE_call_expr ) {
				kind = "call ";
			}
			else if ( ruleIndex == RULE_statement ) {
				kind = "assign ";
			}
			else if ( ruleIndex == RULE_function ) {
				kind = "func def ";
			}
		}
		System.out.println("IdentifierPSINode.setName("+name+") on "+
			                   kind+this+" at "+Integer.toHexString(this.hashCode()));
		*/
		PsiElement newID = Trees.createLeafFromText(getProject(),
		                                            SampleLanguage.INSTANCE,
		                                            getContext(),
		                                            name,
		                                            SampleParserDefinition.ID);
		if ( newID!=null ) {
			return this.replace(newID); // use replace on leaves but replaceChild on ID nodes that are part of defs/decls.
		}
		return this;
	}

	/** Create and return a PsiReference object associated with this ID
	 *  node. The reference object will be asked to resolve this ref
	 *  by using the text of this node to identify the appropriate definition
	 *  site. The definition site is typically a subtree for a function
	 *  or variable definition whereas this reference is just to this ID
	 *  leaf node.
	 *
	 *  As the AST factory has no context and cannot create different kinds
	 *  of PsiNamedElement nodes according to context, every ID node
	 *  in the tree will be of this type. So, we distinguish references
	 *  from definitions or other uses by looking at context in this method
	 *  as we have parent (context) information.
	 */
	@Override
	public PsiReference getReference() {
		PsiElement parent = getParent();
		IElementType elType = parent.getNode().getElementType();
		// do not return a reference for the ID nodes in a definition
		if ( elType instanceof RuleIElementType ) {
			switch ( ((RuleIElementType) elType).getRuleIndex() ) {
				case RULE_statement :
				case RULE_expr :
				case RULE_primary :
					return new VariableRef(this);
				case RULE_call_expr :
					return new FunctionRef(this);
			}
		}
		return null;
	}
}
