package org.etsi.t3d;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.LinkedList;

import org.etsi.t3d.visitor.VisitorCommonFunctions;

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 DependencyPrinter {
	private PrintStream stream;
	private String currentTTCN3File;
	
	public void finishXML(){
		writeStream("\n</dependencies>");
		
	}
	//TODO: unify

	/**
	 * 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;
	}	

	/**
	 * 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 void newElement(LocationAST node) {
		ArrayList<LocationAST> identifiersList = LocationAST.getModuleDefinitionIdentifiersList(node);
		LocationAST typeNode = LocationAST.getModuleDefinitionTypeNode(node);
		//TODO: refine further 
		String option = "";
		if (identifiersList.size() > 1) {
			option = " conflicted=\"true\"";
			//TODO: use proper logging interface
			System.out.println(currentTTCN3File.substring(currentTTCN3File.lastIndexOf("/") + 1)+": "+node.getLine()+": WARNING: Multiple identifiers declared in a single module defintion!");
		}
		LocationAST nextModuleDefinitionNode = node.getNextSibling();
		while (nextModuleDefinitionNode!=null && nextModuleDefinitionNode.getType()==TTCN3ParserTokenTypes.SemiColon){
			nextModuleDefinitionNode = nextModuleDefinitionNode.getNextSibling();
		}
		if (nextModuleDefinitionNode!=null && node.getLine()==nextModuleDefinitionNode.getLine()){
			option = " conflicted=\"true\"";
			//TODO: use proper logging interface
			System.out.println(currentTTCN3File.substring(currentTTCN3File.lastIndexOf("/") + 1)+": "+node.getLine()+": WARNING: Multiple module definitions on the same line");
		}
		for (LocationAST identifierNode : identifiersList) {
			writeStream("\n\t<element id=\""
					+ VisitorCommonFunctions.getIdFromIdentifier(identifierNode.getFirstChild())
					+ "\" name=\""
					+ VisitorCommonFunctions.getName(identifierNode)
					+ "\" type=\""
					+ LocationAST.getTTCN3ParserTokenTypeTypePrettyName(typeNode.getType())
					+ "\" startline=\""
					+ node.getLine()
					+ "\" endline=\""
					+ node.getEndLine()
					+ "\" module=\""
					+ VisitorCommonFunctions.getModuleName(identifierNode)
					+ "\" file=\""
					+ currentTTCN3File.substring(currentTTCN3File.lastIndexOf("/") + 1)
					+ "\""
					+ option
					+ ">");
			
			if (typeNode.getType()==TTCN3ParserTokenTypes.GroupDef) {
				writeStream(getElementList(identifierNode));
			}else {
				writeStream(getDependecies(identifierNode));
			}
			writeStream("\n\t</element>\n");
		}
	}

	public void setXMLPath(String xmlpath){
		FileOutputStream file;
		try {
			file = new FileOutputStream(xmlpath);
			stream = new PrintStream(file);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		writeStream("<?xml-stylesheet type=\"text/xsl\" href=\"../dependencies.xsl\"?>\n<dependencies>");
	}

	private void writeStream(String content){
		stream.print(content);
	}
	
	public void setCurrentTTCN3File(String currentTTCN3File) {
		this.currentTTCN3File = currentTTCN3File;
	}

	public String getCurrentTTCN3File() {
		return currentTTCN3File;
	}
}
