package org.etsi.t3q.visitor;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.etsi.common.MiscTools;
import org.etsi.common.exceptions.TerminationException;
import org.etsi.common.logging.LoggingInterface.MessageClass;
import org.etsi.t3q.T3Q;

import antlr.collections.AST;

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.Symbol;

public class NamingConventionsChecker {

	private QualityChecker checker = null;

	public NamingConventionsChecker(QualityChecker checker) {
		this.checker = checker;
	}

	//TODO: remove coupling with quality checker and add custom logger, potentially the whole logging process shall be moved to a separate hierarchy
	
	private boolean regExpMatch(String regExp, String subject) {
		boolean matches = false;
		Pattern pattern = Pattern.compile(regExp);
		Matcher matcher = pattern.matcher(subject);
		// System.out.println(regExp + " : " + subject);
		if (matcher.matches()) {
			matches = true;
		}
		return matches;
	}

	private void checkIdentifierForNamingConventionCompliance(LocationAST node,
			String regExp, String type) {
		if (regExp == null){
			try {
				throw new TerminationException("ERROR: Naming conventions rule for \""+type+"\" is not set! Configuration profile may be outdated or corrupted!");
			} catch (TerminationException e) {
			}
		}
		
		if (regExp.equals("")) {
			return;
		}
		LocationAST identifierNode = (LocationAST) ASTUtil.findChild(node,
				TTCN3ParserTokenTypes.Identifier);
		if (identifierNode == null) {
			if (ASTUtil.findChild(node, TTCN3ParserTokenTypes.AddressKeyword) == null) {
				checker.getLoggingInterface().logFix(node.getLine(), node.getEndLine(), MessageClass.NAMING, "Could not determine identifier or address leaf node under: " + node + " while analyzing for type  \"" + type + "\"", "2.1, "+MiscTools.getMethodName());
			}
			return;
		}
		String identifier = identifierNode.getFirstChild().getText();

		if (!regExpMatch(regExp, identifier)) {
			checker
					.getLoggingInterface()
					.logWarning(
							node.getLine(),
							node.getEndLine(),
							MessageClass.NAMING,
							"\""
									+ identifier
									+ "\" does not comply to the naming conventions for \""
									+ type + "\"!" 
									, "2.1, " + regExp);
			
		}
	}

	public void checkFunction(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getFunctionRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp, "Function");
	}

	public void checkExtFunction(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getExtFunctionRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp,
				"ExtFunction");
	}

	public void checkConstant(LocationAST node) {
		String regExp = "";
		String message = "";
		if (node.getNthParent(2).getType() == TTCN3ParserTokenTypes.ModuleDefinition){
			regExp = T3Q.activeProfile.getNamingConventionsConfig()
					.getConstantRegExp();
			message = "Constant";
		} else {
			regExp = T3Q.activeProfile.getNamingConventionsConfig()
			.getLocalConstantRegExp();
			message = "Local Constant";
		}
		checkIdentifierForNamingConventionCompliance(node, regExp, message);

	}

	public void checkExtConstant(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getExtConstantRegExp();

		LocationAST identifierNode = node.getFirstChild().getNextSibling();
		do {
			checkIdentifierForNamingConventionCompliance(identifierNode,
					regExp, "External Constant");
		} while ((identifierNode = identifierNode.getNextSibling()) != null);
	}

	public void checkAltstep(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getAltstepRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp, "Altstep");
	}

	public void checkGroup(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getGroupRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp, "Group");
	}

	public void checkModule(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getModuleRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp, "Module");
	}

	public void checkModuleParameter(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getModuleParameterRegExp();
		LocationAST identifierNode = node.getFirstChild();
		do {
			if (identifierNode.getType() == TTCN3ParserTokenTypes.Identifier) {
				checkIdentifierForNamingConventionCompliance(identifierNode,
						regExp, "ModuleParameter");
			}
		} while ((identifierNode = identifierNode.getNextSibling()) != null);
	}

	public void checkPortInstance(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getPortInstanceRegExp();
		LocationAST portElementNode = node.getFirstChild().getNextSibling();
		do {
			checkIdentifierForNamingConventionCompliance(portElementNode,
					regExp, "PortInstance");
		} while ((portElementNode = portElementNode.getNextSibling()) != null);
	}

	public void checkTestcase(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getTestcaseRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp, "Testcase");
	}

	public void checkSignatureTemplate(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getSignatureTemplateRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp,
				"SignatureTemplate");
	}

	public void checkFormalParameter(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getFormalParameterRegExp();
		LocationAST identifierNode = (LocationAST) ASTUtil.findSibling(node
				.getFirstChild(), TTCN3ParserTokenTypes.Identifier);
		checkIdentifierForNamingConventionCompliance(identifierNode, regExp,
				"FormalParameter");
	}

	public void checkEnumeratedValue(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getEnumeratedValueRegExp();
		checkIdentifierForNamingConventionCompliance(node, regExp,
				"EnumeratedValue");
	}

	public void checkDataType(LocationAST node) {
		String regExp = T3Q.activeProfile.getNamingConventionsConfig()
				.getDataTypeRegExp();
		LocationAST targetNode = node;
		// adjust for differences in tree structure
		if ((node.getNthChild(2).getType() == TTCN3ParserTokenTypes.RecordOfDef)
				|| (node.getNthChild(2).getType() == TTCN3ParserTokenTypes.SetOfDef)) {
			if (node.getNthChild(3).getType() == TTCN3ParserTokenTypes.StringLength){
				targetNode = node.getNthChild(3).getNextSibling().getFirstChild().getNextSibling();
			} else {
				targetNode = node.getNthChild(4).getNextSibling();
			}
		} else if (node.getFirstChild().getType() == TTCN3ParserTokenTypes.SubTypeDef) {
			targetNode = node.getNthChild(2).getNextSibling();
		}
		checkIdentifierForNamingConventionCompliance(targetNode, regExp,
				"DataType");
	}

	public void checkSTF160Template(LocationAST node) {
		String regExp = "";
		String templateType = "";
		boolean isSendTemplate = false;
		boolean isAmbiguous = false;
		LocationAST identifierNode = null;
		boolean isDerived = false;
		if (ASTUtil.findDerivedDef(node)!= null){
			isDerived = true;
		}

		//duplicates checkMessageTemplate
		if (node.getFirstChild().getType() == TTCN3ParserTokenTypes.BaseTemplate) {
			identifierNode = node.getFirstChild().getFirstChild()
					.getNextSibling();
		} else {
			identifierNode = node.getFirstChild().getNextSibling()
					.getFirstChild().getNextSibling();
		}
		//end of duplication
		
		LocationAST templateRestrictionNode = (LocationAST) ASTUtil.findChild(node,
				TTCN3ParserTokenTypes.TemplateRestriction);
		if (templateRestrictionNode != null) {
			// check if it is not a present restriction
			if (templateRestrictionNode.getFirstChild().getType() != TTCN3ParserTokenTypes.PRESENT) {

				LinkedList<LocationAST> formalTemplateParameters = ASTUtil.findTypeNodes(node,
						TTCN3ParserTokenTypes.FormalTemplatePar);
				for (LocationAST formalTemplateParameter : formalTemplateParameters) {
					LinkedList<LocationAST> formalParameterTemplateRestrictionNodes = ASTUtil.findTypeNodes(formalTemplateParameter,
							TTCN3ParserTokenTypes.TemplateRestriction);
					if (formalParameterTemplateRestrictionNodes == null
							|| formalParameterTemplateRestrictionNodes.isEmpty()) {
						// problem occurred, inconsistent definition
						isAmbiguous=true;
					} else {
						LocationAST restrictionNode = formalParameterTemplateRestrictionNodes.get(0);
						if (restrictionNode.getFirstChild().getType() == TTCN3ParserTokenTypes.PRESENT) {
							// problem occurred, inconsistent definition
							isAmbiguous	= true;
						}
					}
					if (isAmbiguous) {
						checker
						.getLoggingInterface()
						.logInformation(
								node.getLine(),
								node.getEndLine(),
								MessageClass.NAMING,
								"The template definition for \""
										+ identifierNode.getFirstChild().getText()
										+ "\" is ambiguous. It cannot be determined whether it is a send or a receive template according to STF160's conventions. It will not be analyzed further for naming conventions compliance.",
								"2.1, " + MiscTools.getMethodName());
						return;
					}
				}
				//if no problems occurred this far check name for send template
				isSendTemplate = true;
			}
			//(else) check name for receive template
		}
		//(else) check name for receive template
		if (!isDerived) {
			if (isSendTemplate) {
				templateType = "STF160SendTemplate";
				regExp = T3Q.activeProfile	.getNamingConventionsConfig()
											.getStf160sendTemplateRegExp();
			} else {
				templateType = "STF160ReceiveTemplate";
				regExp = T3Q.activeProfile	.getNamingConventionsConfig()
											.getStf160receiveTemplateRegExp();
			}
		} else {
			if (isSendTemplate) {
				templateType = "DerivedSTF160SendTemplate";
				regExp = T3Q.activeProfile	.getNamingConventionsConfig()
											.getDerivedStf160sendTemplateRegExp();
			} else {
				templateType = "DerivedSTF160ReceiveTemplate";
				regExp = T3Q.activeProfile	.getNamingConventionsConfig()
											.getDerivedStf160receiveTemplateRegExp();
			}

		}
		checkIdentifierForNamingConventionCompliance(identifierNode, regExp,
				templateType);
	}
	
	
	
	
	public void checkMessageTemplate(LocationAST node) {
		// TODO: needs a deep analysis of contents (analyzing the referenced
		// template types if any) and also modified templates (which may be
		// difficult to impossible)
		String regExp = "";
		String templateType = "";
		boolean isDerived = false;
		if (ASTUtil.findDerivedDef(node)!= null){
			isDerived = true;
		}
		
		// basic shallow search
		LinkedList<LocationAST> matchingSymbolNodes = ASTUtil.findTypeNodes(
				node, TTCN3ParserTokenTypes.MatchingSymbol);
		// TODO: implement an advanced deep search

		
		if (!isDerived){
			if (matchingSymbolNodes.isEmpty()) {
				regExp = T3Q.activeProfile	.getNamingConventionsConfig()
											.getMessageTemplateRegExp();
				templateType = "MessageTemplate";
			} else {
				regExp = T3Q.activeProfile	.getNamingConventionsConfig()
											.getMessageTemplateWithWildcardsRegExp();
				templateType = "MessageTemplateWithMatchingExpression";
			}
		} else {
			if (matchingSymbolNodes.isEmpty()) {
				regExp = T3Q.activeProfile.getNamingConventionsConfig()
						.getDerivedMessageTemplateRegExp();
				templateType = "DerivedMessageTemplate";
			} else {
				regExp = T3Q.activeProfile.getNamingConventionsConfig()
						.getDerivedMessageTemplateWithWildcardsRegExp();
				templateType = "DerivedMessageTemplateWithMatchingExpression";
			}
			
		}
		LocationAST identifierNode = null;

		//duplicated in checkSTF160Template
		if (node.getFirstChild().getType() == TTCN3ParserTokenTypes.BaseTemplate) {
			identifierNode = node.getFirstChild().getFirstChild()
					.getNextSibling();
		} else {
			identifierNode = node.getFirstChild().getNextSibling()
					.getFirstChild().getNextSibling();
		}

		checkIdentifierForNamingConventionCompliance(identifierNode, regExp,
				templateType);
	}

	public void checkTimer(LocationAST node) {
		String regExp = "";
		String timerType = "";

		if (node.getParent().getType() == TTCN3ParserTokenTypes.ComponentElementDef) {
			regExp = T3Q.activeProfile.getNamingConventionsConfig()
					.getComponentTimerRegExp();
			timerType = "ComponentTimer";
		} else {
			regExp = T3Q.activeProfile.getNamingConventionsConfig()
					.getTimerRegExp();
			timerType = "Timer";
		}
		LocationAST singleTimerInstanceNode = node.getFirstChild();
		do {
			checkIdentifierForNamingConventionCompliance(
					singleTimerInstanceNode, regExp, timerType);
		} while ((singleTimerInstanceNode = singleTimerInstanceNode
				.getNextSibling()) != null);

	}

	public void checkVariable(LocationAST node) {
		String regExp = "";
		String variableType = "";

		if (node.getParent().getType() == TTCN3ParserTokenTypes.ComponentElementDef) {
			regExp = T3Q.activeProfile.getNamingConventionsConfig()
					.getComponentVariableRegExp();
			variableType = "ComponentVariable";
		} else {
			regExp = T3Q.activeProfile.getNamingConventionsConfig()
					.getVariableRegExp();
			variableType = "Variable";
		}

		LocationAST typeNode = node.getFirstChild();
		if (node.getFirstChild().getType() != TTCN3ParserTokenTypes.Type) {
			typeNode = typeNode.getNextSibling();
		}

		if (typeNode.getFirstChild().getType() == TTCN3ParserTokenTypes.ReferencedType) {
			LocationAST typeReferenceNode = typeNode.getNthChild(2);
			if (typeReferenceNode.getType() != TTCN3ParserTokenTypes.TypeReference) {
				typeReferenceNode = typeReferenceNode.getNextSibling();
			}
			LocationAST typeNodeIdentifier = typeReferenceNode.getNthChild(2);
			Symbol typeSymbol = typeNodeIdentifier.getSymbol();
			if (typeSymbol == null) {
				checker
						.getLoggingInterface()
						.logInformation(
								node.getLine(),
								node.getEndLine(),
								MessageClass.NAMING,
								"The type declaration for \""
										+ typeNodeIdentifier.getText()
										+ "\" could not be resolved. It cannot be determined whether the variable instances of this type are component instances. They will be treated as variable instances.",
								"2.1, " + MiscTools.getMethodName());
				
			} else {
				LocationAST declarationNode = typeSymbol.getDeclarationNode();
				if (declarationNode.getNthParent(2).getType() == TTCN3ParserTokenTypes.ComponentDef) {
					regExp = T3Q.activeProfile
							.getNamingConventionsConfig()
							.getComponentInstanceRegExp();
					variableType = "ComponentInstance";

				}
			}
		}

		LocationAST singleVarInstanceNode = typeNode.getNextSibling()
				.getFirstChild();
		do {
			checkIdentifierForNamingConventionCompliance(singleVarInstanceNode,
					regExp, variableType);
		} while ((singleVarInstanceNode = singleVarInstanceNode
				.getNextSibling()) != null);
	}

}
