package org.etsi.t3d.visitor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringEscapeUtils;

import antlr.RecognitionException;

import de.ugoe.cs.swe.trex.core.analyzer.astutil.ReferenceFinder;
import de.ugoe.cs.swe.trex.core.analyzer.astutil.ReferenceWithContext;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.ASTUtil;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.KeywordExtractor;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.LocationAST;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3FormatterTreeParser;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3Lexer;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3ParserTokenTypes;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.symboltable.Scope;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.symboltable.Symbol;
import de.ugoe.cs.swe.trex.core.formatter.TTCN3FormatterParameters;

public class VisitorCommonFunctions {

	
	public static String addSyntaxHighlighting(String element){
		String[] TTCN3_KEYWORDS = KeywordExtractor.getInstance().getLiterals().toArray(new String[0]);
		element = " " + element;
		for(String keyword : TTCN3_KEYWORDS)
			element = element.replaceAll(" " + keyword + " ", " <keyword>" + keyword + "</keyword> ").replaceAll("\\n" + keyword + " ", "\n<keyword>" + keyword + "</keyword> ").replaceAll(">" + keyword + " ", "><keyword>" + keyword + "</keyword> ");
		return element.substring(1);
	}	
	
	static class TTCN3Reference{
		public TTCN3Reference(String name, String location) {
			this.name = name;
			this.location = location;
		}
		public String name;
		public String location;
	}
	//returns a list of all references of a node in the TTCN3 project
	public static LinkedList<TTCN3Reference> getReferences(LocationAST node){		
		LinkedList<TTCN3Reference> refvector = new LinkedList<TTCN3Reference>();
		LinkedList<LocationAST> refNodeList = ASTUtil.findTypeNodes(node, TTCN3ParserTokenTypes.IDENTIFIER);
		for(LocationAST IdNode : refNodeList){
			String name = IdNode.getText();
			String location = getDecLocationFromIdentifier(IdNode);
			if(!location.equals("#") && !location.startsWith("trexPredefined")
					&& isValidCrossReference(IdNode)){
					refvector.add(new TTCN3Reference(name, location));
			}
		}
		return refvector;
	}
	//returns true if refNode is a valid cross-reference
	//TODO: REVIEW, seems rather pointless 
	public static boolean isValidCrossReference(LocationAST identifierNode){
		LocationAST declarationNode = getDeclarationNodeFromIdentifier(identifierNode);
		LocationAST declarationModuleDefinitionNode = LocationAST.resolveParentsUntilType(declarationNode, TTCN3ParserTokenTypes.ModuleDefinition);

		if(declarationModuleDefinitionNode == null) {
			return true;
		}
		
		LinkedList<String> identifierNames = new LinkedList<String>();
		for(LocationAST node : LocationAST.getModuleDefinitionIdentifiersList(declarationModuleDefinitionNode)) {
			identifierNames.add(node.getFirstChild().getText());
		}
//		if(modDefNode.getFirstChild().getType() == TTCN3ParserTokenTypes.ExtFunctionDef || modDefNode.getFirstChild().getType() == TTCN3ParserTokenTypes.ExtConstDef)
//			return false;
//		else {
			return (identifierNames.contains(identifierNode.getText()));
//		}
	}
	
	public static String replaceReferences(String b, LinkedList<TTCN3Reference> reflist){
		for(TTCN3Reference r : reflist){
			b = b.replaceAll("( |\\t)" + r.name + " ", " <link loc=\"" + r.location + "\">" + r.name + "</link> ");
			b = b.replaceAll("( |\\t)" + r.name + ";", " <link loc=\"" + r.location + "\">" + r.name + "</link>;");
			b = b.replaceAll("( |\\t)" + r.name + ",", " <link loc=\"" + r.location + "\">" + r.name + "</link>,");
			b = b.replaceAll("( |\\t)" + r.name + "\n", " <link loc=\"" + r.location + "\">" + r.name + "</link>\n");
		}
		return b;
	}
	
	//returns the XML representation(<behaviour>) of a ModuleDefinition node
	public static String getBehaviour(LocationAST node, int tabs, boolean syntaxHighlighting, boolean showContructBody){
		String tabString = "";
		for(int i=0; i < tabs; i++)
			tabString += "<tab/>";
		LinkedList<TTCN3Reference> reflist = getReferences(node);
		String b = getWholeElement(node, showContructBody).replaceAll("\\r", "");
		b = StringEscapeUtils.escapeXml(b);
		b = replaceReferences(b, reflist);
		String constrEndString = "";
		if(b.contains("{")){
			b = b.replaceFirst("\\{", "<constructbody id=\"id" + node.getOffset() + "\">{");
			constrEndString = "</constructbody>";
		}
		if(syntaxHighlighting)
			b = addSyntaxHighlighting(b);
		
		if(b.charAt(b.length()-2) == '}' && b.charAt(b.length()-3) != ';'){
			return tabString + b.substring(0, b.length()-3).replaceAll("\n", "\n" + tabString + "<tab/>") + "\n" + tabString + "}" + constrEndString + "\n";
		}else{
			
			String beh = tabString + b.replaceAll("\n", "\n" + tabString + "<tab/>") + ";" + constrEndString + "\n";
			return beh.replaceAll("(\\s|\\n|<tab/>)*;", ";").replaceFirst("<tab/>\\};</constructbody>", "};</constructbody>");
		}
	}
	
	//returns String representation of a ModuleDefinition node
	public static String getWholeElement(LocationAST node, boolean showContructBody){
		TTCN3FormatterTreeParser prettyPrinter = new TTCN3FormatterTreeParser();
		prettyPrinter.setFormatterParameters(new TTCN3FormatterParameters());
		try {
			String element;
			if(node.getType() == TTCN3ParserTokenTypes.ModuleControlPart)
				element = prettyPrinter.pr_ModuleControlPart(node).toString() + " ";
			else if(node.getType() == TTCN3ParserTokenTypes.ModulePar)
				element = "modulepar " + prettyPrinter.pr_ModulePar(node).toString() + " ";
			else
				element = prettyPrinter.pr_ModuleDefinition(node.getParent()).toString() + " ";
			if(!showContructBody && element.contains("{")){
				if(node.getType() == TTCN3ParserTokenTypes.TemplateDef && element.contains(":="))
					element= element.substring(0, element.indexOf(":="));
				else
					element= element.substring(0, element.indexOf('{'));				
			}
			
			return element.replaceAll("\\r","");
		} catch (RecognitionException e) {
			e.printStackTrace();
		}
		return "*** Parser error ***";
	}
	
	// returns a list of every node that references the given node
	public static LinkedList<LocationAST> getReferenceList(LocationAST node){
		LinkedList<LocationAST> linkedRefList = new LinkedList<LocationAST>();
		ReferenceFinder referenceFinder = new ReferenceFinder();
		Scope scope = node.getScope();
		if (scope == null)
			return null;
		Symbol symb = scope.resolve(node);
		Map<String, List<ReferenceWithContext>> result = referenceFinder.findReferences(symb);

		Iterator<List<ReferenceWithContext>> refIt = result.values().iterator();
		if(refIt != null)
			while(refIt.hasNext()){
				
				ArrayList<ReferenceWithContext> refList = (ArrayList<ReferenceWithContext>)refIt.next();
				for(ReferenceWithContext ref : refList){
					linkedRefList.add(ref.getReferenceNode());
				}
			}
		if(linkedRefList.isEmpty())
			return null;
		return linkedRefList;
	}
	
	
	//returns the location of an html file that would represent node
	public static String getLocationFromIdentifier(LocationAST node){
		return getModuleName(node) + "-" + node.getText() + "-" + node.getOffset() + ".html";
	}
	
	public static String getIdFromIdentifier(LocationAST node){
		return getModuleName(node) + "-" + VisitorCommonFunctions.getName(node);
	}
	//returns the name of the TTCN3 module node belongs to 
	public static String getModuleName(LocationAST node){
		LocationAST module = getModule(node);
		while(module.getFirstChild() != null)
			module = module.getFirstChild();
		return module.getText();
	}
	
	public static LocationAST getModule(LocationAST node){
		LocationAST module = node;
		while(module.getParent() != null){
			module = module.getParent();
		}
		return module;
	}
	
	//returns the name of a TTCN3 element
	public static String getName(LocationAST node){
		return getIdentifier(node).getText();
	}
	//returns the identifier of a TTCN3 element
	public static LocationAST getIdentifier(LocationAST node){
		if(node.getType() == TTCN3ParserTokenTypes.Identifier)
			return node.getFirstChild();
		LocationAST identifier = null;
		for(int i = 1; identifier == null && i < 1000; i++)
			identifier = searchIdentifier(node, i);
		
		if(identifier.getFirstChild() != null)
			identifier = identifier.getFirstChild();
		return identifier;
	}
	//returns the first identifier node found in a certain depth or null if none is found
	public static LocationAST searchIdentifier(LocationAST node, int depth){
		if(node.getType() == TTCN3ParserTokenTypes.Identifier)
			return node;
		if(node.getParent().getType() == TTCN3ParserTokenTypes.Identifier)
			return node;
		if(depth <= 0)
			return null;
		LocationAST currNode = node.getFirstChild();
		LocationAST identifier = null;
		while(currNode != null && identifier == null){
			identifier = searchIdentifier(currNode, depth-1);
			currNode = currNode.getNextSibling();
		}
		return identifier;
	}
	
	//returns getLocationFromIdentifier() of the declaration node of node
	public static String getDecLocationFromIdentifier(LocationAST node){
		LocationAST decNode = getDeclarationNodeFromIdentifier(node);
		if(decNode != null)			
			if(VisitorCommonFunctions.getModuleName(decNode).equals(node.getText()))
				return node.getText() + ".html";
			else 
				return VisitorCommonFunctions.getLocationFromIdentifier(decNode);
		else
			return "#";
	}
	
	public static LocationAST getDeclarationNodeFromIdentifier(LocationAST node){
		Scope identifierScope = node.getScope();
		Symbol decSymbol = null;
		if(identifierScope != null)
			decSymbol = identifierScope.resolve(node);
		else
			return null;
		//Symbol decSymbol = node.getSymbol();
		if(decSymbol != null){
			LocationAST decNode = decSymbol.getDeclarationNode();
			return decNode;
		}
		else
			return null;
	}
	
}
