package org.etsi.t3d.visitor;

import java.util.ArrayList;
import java.util.LinkedList;

import org.etsi.t3d.DependencyPrinter;
import org.etsi.t3d.exceptions.TTCN3BehaviorException;

import de.ugoe.cs.swe.trex.core.analyzer.rfparser.ASTUtil;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.LocationAST;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.TTCN3ParserTokenTypes;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.symboltable.EnumSymbol;
import de.ugoe.cs.swe.trex.core.analyzer.rfparser.symboltable.Symbol;

public class DependencyVisitor extends AbstractVisitor {
	
	private DependencyPrinter dependencyPrinter;

	public DependencyVisitor(DependencyPrinter dependencyPrinter) {
		this.dependencyPrinter = dependencyPrinter;
	}
	@Override
	public void finish() {
	}

	@Override
	public void init() {
	}
	
	
	/**
	 * Extracts identifiers related to a module definition identifier within the 
	 * corresponding module definition subtree
	 * TODO: filter out predefined functions
	 * @param moduleDefinitionIdentifierNode - A module definition identifier node
	 * @return - A list of referenced identifier nodes, excluding the identifier
	 *  nodes of the module definition itself; if node is not of type Identifier
	 *  an empty list is returned instead
	 */
	//TODO: this may need to be moved for reuse
	private static LinkedList<LocationAST> getRelatedIdentifierNodes(
			LocationAST identifierNode) {
		LinkedList<LocationAST> identifierNodes = new LinkedList<LocationAST>();
		if (identifierNode.getType() != TTCN3ParserTokenTypes.Identifier) {
			return identifierNodes;
		}
		LocationAST moduleDefinitionNode = LocationAST.resolveParentsUntilType(identifierNode,
				TTCN3ParserTokenTypes.ModuleDefinition);

		LocationAST typeNode = LocationAST.getModuleDefinitionTypeNode(moduleDefinitionNode);
		if (typeNode.getType() == TTCN3ParserTokenTypes.ModuleParDef
				&& typeNode.getFirstChild().getType() == TTCN3ParserTokenTypes.MultitypedModuleParList) {
			LocationAST sibling = identifierNode.getNextSibling();
			if (sibling != null
					&& sibling.getType() != TTCN3ParserTokenTypes.Identifier) {
				identifierNodes.addAll(ASTUtil.findTypeNodes(sibling,
						TTCN3ParserTokenTypes.Identifier));

			}
			LocationAST moduleParNode = LocationAST.resolveParentsUntilType(identifierNode,
					TTCN3ParserTokenTypes.ModulePar);
			LocationAST moduleParTypeNode = moduleParNode.getFirstChild();
			identifierNodes.addAll(ASTUtil.findTypeNodes(moduleParTypeNode,
					TTCN3ParserTokenTypes.Identifier));

		} else {
			identifierNodes = ASTUtil.findTypeNodes(moduleDefinitionNode,
					TTCN3ParserTokenTypes.Identifier);
			ArrayList<LocationAST> moduleDefinitionIdentifierNodes = LocationAST.getModuleDefinitionIdentifiersList(moduleDefinitionNode);
			identifierNodes.removeAll(moduleDefinitionIdentifierNodes);
		}
		// TODO: exclude predefined functions
		return identifierNodes;
	}	
	
	
	/**
	 * Extracts referenced identifiers from within a module definition subtree
	 * TODO: filter out predefined functions
	 * @param moduleDefinitionNode - A module definition node
	 * @return - A list of referenced identifier nodes, excluding the identifier nodes of the module definition itself; if node is not of type ModuleDefintion, an empty list is returned instead
	 */
	//TODO: This may have to be deprecated
	private static LinkedList<LocationAST> getReferencedIdentifierNodes(LocationAST moduleDefinitionNode){
		if (moduleDefinitionNode.getType()!=TTCN3ParserTokenTypes.ModuleDefinition) {
			return new LinkedList<LocationAST>();
		}
		LinkedList<LocationAST> identifierNodes = ASTUtil.findTypeNodes(moduleDefinitionNode, TTCN3ParserTokenTypes.Identifier);
		ArrayList<LocationAST> moduleDefinitionIdentifierNodes = LocationAST.getModuleDefinitionIdentifiersList(moduleDefinitionNode);
		identifierNodes.removeAll(moduleDefinitionIdentifierNodes);
		//TODO: exclude predefined functions
		return identifierNodes;
	}
	
	
	/**
	 * Transforms a list of <LocationAST> identifier nodes into a list of <String> reference IDs
	 * @param identifierNodes - the list of identifier nodes
	 * @return - A list of reference IDs 
	 */
	private LinkedList<String> getReferenceIds(LinkedList<LocationAST> identifierNodes){
		LinkedList<String> referencedIds = new LinkedList<String>();
		
		for (LocationAST identifierNode : identifierNodes) {
			LocationAST declarationNode = VisitorCommonFunctions.getDeclarationNodeFromIdentifier(identifierNode.getFirstChild());
			if (declarationNode != null) {
				String id = VisitorCommonFunctions.getIdFromIdentifier(declarationNode);
				// TODO: move predefined filtering up
				if (!id.equals("#") && !id.startsWith("trexPredefined")) {
					Symbol symbol = identifierNode.getFirstChild().getSymbol();
					if (symbol instanceof EnumSymbol) {
						//TODO: need to add handling in documentation and imports generators too probably
						declarationNode = symbol.getSurroundingScope().getScopeSymbol().getDeclarationNode();
						id = VisitorCommonFunctions.getIdFromIdentifier(declarationNode);
					}
					if (VisitorCommonFunctions.isValidCrossReference(declarationNode)) {
						if (!referencedIds.contains(id)) {
							referencedIds.add(id);
						}
					}
				} 
			} else {
				referencedIds.add("unresolvedReference---"
						+ VisitorCommonFunctions.getName(identifierNode.getFirstChild()));
			}
		}
		return referencedIds;
	}	
	
	
	private String getDependecies(LocationAST identifierNode) {
		//TODO: this should be a part of the printer
		LinkedList<String> referenceIds = getReferenceIds(getRelatedIdentifierNodes(identifierNode));
		//sanitize list for slipped-through enumerated values
		referenceIds.remove(VisitorCommonFunctions.getIdFromIdentifier(identifierNode));
		String refs = "\n\t\t<reflist>";
		for (String id : referenceIds)
			refs += "\n\t\t\t<ref id=\"" + id + "\"/>";
		refs += "\n\t\t</reflist>";
		return refs;
	}
	
	private String getElementList(LocationAST node){
		//TODO: this should be a part of the printer
		String elementList = "\n\t\t<elementlist>";
		LinkedList <LocationAST> moduleDefinitionsList = ASTUtil.findTypeNodes(LocationAST.resolveParentsUntilType(node, TTCN3ParserTokenTypes.ModuleDefinition).getFirstChild(), TTCN3ParserTokenTypes.ModuleDefinition);
		for(LocationAST moduleDefinitionNode : moduleDefinitionsList){
			ArrayList<LocationAST> identifiersList = LocationAST.getModuleDefinitionIdentifiersList(moduleDefinitionNode);
			for(LocationAST identifierNode : identifiersList)
				elementList += "\n\t\t\t<ref id=\"" + VisitorCommonFunctions.getIdFromIdentifier(identifierNode) + "\"/>";
		}		
		return elementList + "\n\t\t</elementlist>";
	}

	public ContinueStatus visitModuleDefinition(LocationAST node) throws TTCN3BehaviorException {
		ArrayList<LocationAST> identifiersList = LocationAST.getModuleDefinitionIdentifiersList(node);
		LocationAST typeNode = LocationAST.getModuleDefinitionTypeNode(node);
		
		for (LocationAST identifierNode : identifiersList) {
			if (typeNode.getType()==TTCN3ParserTokenTypes.GroupDef) {
				dependencyPrinter.newGroup(VisitorCommonFunctions.getName(identifierNode),
						VisitorCommonFunctions.getIdFromIdentifier(identifierNode.getFirstChild()),
						typeNode.getType(),
						identifierNode.getLine(),
						VisitorCommonFunctions.getModuleName(identifierNode),
						getElementList(identifierNode));
			}else {
				dependencyPrinter.newElement(VisitorCommonFunctions.getName(identifierNode),
						VisitorCommonFunctions.getIdFromIdentifier(identifierNode.getFirstChild()),
						typeNode.getType(),
						identifierNode.getLine(),
						VisitorCommonFunctions.getModuleName(identifierNode),
						getDependecies(identifierNode));
			}
		}

		return ContinueStatus.getInstance(true,true);
	}


	
}
