package eu.dnetlib.data.collective.transformation.core.xsl;


import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import eu.dnetlib.data.collective.transformation.rulelanguage.Rules;

/**
 * @author jochen
 *
 */
public class XslConstructor {
	private static final Log log = LogFactory.getLog(XslConstructor.class);
	
	public String writeOutVariableRule(Rules rule){
		XslElement xsltVariable = new XslElement(XsltConstants.param);
		xsltVariable.addAttribute("name", rule.getUniqueName().substring(1));
		if (rule.getXpath().length() > 0){
			xsltVariable.addAttribute("select", rule.getXpath());
		}else if (rule.getFunctionCall() != null){
			// TODO - set the value if rule is static
			if (rule.getFunctionCall().doPreprocess()){
				xsltVariable.addAttribute("select", rule.getFunctionCall().getXSLpreparatedFunctionCall());
			}else{
				xsltVariable.addAttribute("select", rule.getFunctionCall().getXSLdirectFunctionCall(null));				
			}
		}else{
			if (rule.getConstant().length() > 0){
				xsltVariable.addAttribute("select", rule.getConstant());
			}
		}
		return xsltVariable.asXml();
	}
	
	/**
	 * @param rule
	 * @return
	 */
	public String writeOutConditionalChooseComplex(Rules rule){
		XslElement choose = new XslElement(XsltConstants.choose);
		XslElement when = new XslElement(XsltConstants.when);
		log.debug("XslConstructor: conditionExpresssion: " + rule.getCondition().getConditionExpression());
		when.addAttribute("test", rule.getCondition().getConditionExpression());
		when.addEnclosedElements(this.writeOutRuleComplex(rule.getCondition().getPrimaryRule(), rule.getCondition().getPrimaryRule().getUniqueName()));
		choose.addEnclosedElements(when.asXml());
		XslElement otherwise = new XslElement(XsltConstants.otherwise);
		otherwise.addEnclosedElements(this.writeOutRuleComplex(rule.getCondition().getSecondaryRule(), rule.getCondition().getSecondaryRule().getUniqueName()));
		choose.addEnclosedElements(otherwise.asXml());
		return choose.asXml();
	}

	public String writeOutConditionalIfComplex(Rules rule){
		XslElement ifCondition = new XslElement(XsltConstants.ifCondition);
		if (rule.getCondition().isPrimary(rule)){
			ifCondition.addAttribute("test", rule.getCondition().getConditionExpression());
			ifCondition.addEnclosedElements(this.writeOutRuleComplex(rule.getCondition().getPrimaryRule(), rule.getCondition().getPrimaryRule().getUniqueName()));
//			ifCondition.addEnclosedElements(this.writeOutRule(rule, rule.getTargetField()));			
		}else{
			ifCondition.addAttribute("test", "not(" + rule.getCondition().getConditionExpression() + ")");
			ifCondition.addEnclosedElements(this.writeOutRuleComplex(rule.getCondition().getPrimaryRule(), rule.getCondition().getPrimaryRule().getUniqueName()));
//			ifCondition.addEnclosedElements(this.writeOutRule(rule, rule.getTargetField()));			
		}		
		return ifCondition.asXml();

	}
	/**
	 * @param rule
	 * @param targetFieldName
	 * @return
	 */
	public String writeOutRuleComplex(Rules rule, String targetFieldName){
		// TODO a lot
		XslElement targetField = new XslElement(targetFieldName);
		if (rule.hasSet()){
			for (Rules attrRule: rule.getRulesSet().getPendingRules()){
				if (attrRule.getConstant().length() > 0){
					targetField.addAttribute(attrRule.getAttribute(), attrRule.getConstant()); // TODO: check for value type (constant, function, xpath etc.)					
				}else if (attrRule.getAssignmentVariable().length() > 0){
					XslElement attr = new XslElement(XsltConstants.attribute);
					attr.addAttribute("name", attrRule.getAttribute());
					
					XslElement valueOf = new XslElement(XsltConstants.valueOf, "select", attrRule.getAssignmentVariable());
					attr.addEnclosedElements(valueOf.asXml());
					targetField.addEnclosedElements(attr.asXml());
				}else if (attrRule.getXpath().length() > 0){
					XslElement attr = new XslElement(XsltConstants.attribute);
					attr.addAttribute("name", attrRule.getAttribute());
					XslElement valueOf = new XslElement(XsltConstants.valueOf, "select", attrRule.getXpath());
					attr.addEnclosedElements(valueOf.asXml());
					targetField.addEnclosedElements(attr.asXml());					
				}else{
					XslElement valueOf;
					log.debug("name of the rule in writeOutRuleComplex: " + rule.getUniqueName() + " " + attrRule.getAttribute());
					if (attrRule.getFunctionCall().doPreprocess()){
						valueOf = new XslElement(XsltConstants.valueOf, "select", attrRule.getFunctionCall().getXSLpreparatedFunctionCall());				
					}else{
						valueOf = new XslElement(XsltConstants.valueOf, "select", attrRule.getFunctionCall().getXSLdirectFunctionCall(null));				
					}
					XslElement attr = new XslElement(XsltConstants.attribute);
					attr.addAttribute("name", attrRule.getAttribute());
					attr.addEnclosedElements(valueOf.asXml());
					targetField.addEnclosedElements(attr.asXml());
				}
			}
			log.debug(targetField.asXml());

		}
		log.debug("unique name of rule: " + rule.getUniqueName());

		if (rule.getXpath().length() > 0){
			// TODO for-each
//			throw new IllegalStateException("complex rule with Constant Assignment not yet implemented");			
			XslElement valueOf = new XslElement(XsltConstants.valueOf, "select", ".");
//			// valueOf.setBoundPrefix(currentRule.getNamespace()); // needed here?
			XslElement forEach = new XslElement(XsltConstants.forEach);
			forEach.addAttribute("select", rule.getXpath());
//			
//			XslElement targetField = new XslElement(targetFieldName);
			targetField.addEnclosedElements(valueOf.asXml());				
			forEach.addEnclosedElements(targetField.asXml());
//			
			return forEach.asXml();

		}else if (rule.getAssignmentVariable().length() > 0){
			log.debug("assignmentVar: " + rule.getAssignmentVariable());
			XslElement valueOf = new XslElement(XsltConstants.valueOf, "select", rule.getAssignmentVariable());
			targetField.addEnclosedElements(valueOf.asXml());
			log.debug(targetField.asXml());
		}else if (rule.getConstant().length() > 0){
			throw new IllegalStateException("complex rule with Constant Assignment not yet implemented");
		}
		
		return targetField.asXml();
		
//		if (rule.getConstant().length() > 0){
//			if (rule.getAttribute().length() > 0){
//				XslElement targetField = new XslElement(targetFieldName, rule.getAttribute(), rule.getConstant());
//				System.out.println("XslConstructor: " + rule.getUniqueName());
//				System.out.println("XslConstructor: " + rule.hasSet());
//				System.out.println("XslConstructor: defines attribute: " + rule.definesAttribute());
//				System.out.println("XslConstructor:  attribute: " + rule.getAttribute());
//
//				Iterator<Rules> rulesIterator = rule.getRulesSet().getPendingRules().iterator();
//				while (rulesIterator.hasNext()){
//					Rules pendingRule = rulesIterator.next();
//					targetField.addAttribute(pendingRule.getAttribute(), pendingRule.getConstant());
//				}
//				return targetField.asXml();
//			}
//		}
//		return "";
	}
	
	public String writeOutRuleCopy(Rules rule, String targetFieldName){
		XslElement targetField = new XslElement(targetFieldName);
		XslElement copy = new XslElement(XsltConstants.copyOf, "select", rule.getProperties().getProperty("copySelectExpression"));
		targetField.addEnclosedElements(copy.asXml(true));
		return targetField.asXml();
	}
	
	public String writeOutRule(Rules rule, String targetFieldName){
		if (rule.getXpath().length() > 0){
			// TODO consider the namespace
			// TODO consider the number of occurrences of the source element - limited or unlimited, currently unlimited
			XslElement valueOf = new XslElement(XsltConstants.valueOf, "select", ".");
			// valueOf.setBoundPrefix(currentRule.getNamespace()); // needed here?
			XslElement forEach = new XslElement(XsltConstants.forEach);
			forEach.addAttribute("select", rule.getXpath());
			
			XslElement targetField = new XslElement(targetFieldName);
			targetField.addEnclosedElements(valueOf.asXml());				
			forEach.addEnclosedElements(targetField.asXml());
			
			return forEach.asXml();

		}else if (rule.getConstant().length() > 0){
			
			// TODO case-distinction needed
			// (1) it's a constant
			// (2) it's an external function call
			// (2.1) the external function call's argument may contain an xpath-expression, which has to be determined
			
			// do not create 'for-each', just process the rule-function
			XslElement xslText = new XslElement(XsltConstants.text);
			xslText.addEnclosedElements(rule.getConstant());
			
			XslElement targetField = new XslElement(targetFieldName);
			targetField.addEnclosedElements(xslText.asXml());
			return targetField.asXml();
		}else if (rule.getAssignmentVariable().length() > 0){
			// hardcoded workaround
			XslElement targetField = new XslElement(targetFieldName);
//			if (rule.getAssignmentVariable().equals("varId")){
//				XslElement copyOf = new XslElement(XsltConstants.copyOf, "select", rule.getAssignmentVariable());
//				targetField.addEnclosedElements(copyOf.asXml());
//			}else{
				XslElement valueOf = new XslElement(XsltConstants.valueOf, "select", rule.getAssignmentVariable());
				targetField.addEnclosedElements(valueOf.asXml());				
//			}
			return targetField.asXml();
		}else if (rule.isEmpty()){
			XslElement targetField = new XslElement(targetFieldName);
			return targetField.asXml();
		}else if (rule.isSkip()){
			// TODO
			XslElement callTemplate = new XslElement(XsltConstants.callTemplate, "name", "terminate");
			//XslElement msgField = new XslElement(XsltConstants.message, "terminate", "yes");
			//XslElement xslText = new XslElement(XsltConstants.text);
			//xslText.addEnclosedElements("some default exception message");
			//msgField.addEnclosedElements(xslText.asXml());
			return callTemplate.asXml();
		}else{
			XslElement valueOf;
			if (rule.getFunctionCall().doPreprocess()){
				valueOf = new XslElement(XsltConstants.valueOf, "select", rule.getFunctionCall().getXSLpreparatedFunctionCall());				
			}else{
				valueOf = new XslElement(XsltConstants.valueOf, "select", rule.getFunctionCall().getXSLdirectFunctionCall(null));				
			}
			//valueOf.setBoundPrefix(ns_dnetExt);
			
			XslElement targetField = new XslElement(targetFieldName);
			targetField.addEnclosedElements(valueOf.asXml());				
			
			return targetField.asXml();			
		}		
	}
	
	public String writeOutConditionalChoose(Rules rule){
		XslElement choose = new XslElement(XsltConstants.choose);
		XslElement when = new XslElement(XsltConstants.when);
		when.addAttribute("test", rule.getCondition().getConditionExpression());
		when.addEnclosedElements(this.writeOutRule(rule.getCondition().getPrimaryRule(), rule.getCondition().getPrimaryRule().getUniqueName()));
		choose.addEnclosedElements(when.asXml());
		XslElement otherwise = new XslElement(XsltConstants.otherwise);
		otherwise.addEnclosedElements(this.writeOutRule(rule.getCondition().getSecondaryRule(), rule.getCondition().getSecondaryRule().getUniqueName()));
		choose.addEnclosedElements(otherwise.asXml());
		return choose.asXml();
	}
	
	public String writeOutConditionalIf(Rules rule){
		XslElement ifCondition = new XslElement(XsltConstants.ifCondition);
		if (rule.getCondition().isPrimary(rule)){
			ifCondition.addAttribute("test", rule.getCondition().getConditionExpression());
			ifCondition.addEnclosedElements(this.writeOutRule(rule, rule.getTargetField()));			
		}else{
			ifCondition.addAttribute("test", "not(" + rule.getCondition().getConditionExpression() + ")");
			ifCondition.addEnclosedElements(this.writeOutRule(rule, rule.getTargetField()));			
		}		
		return ifCondition.asXml();
	}
	
	public String writeOutApplyTemplates(String selectValue){
		XslElement applyTemplates = new XslElement(XsltConstants.applyTemplates, "select", selectValue);
		return applyTemplates.asXml(true);
	}
	
	public String writeOutCallTemplate(String aTemplateName){
		XslElement callTemplate = new XslElement(XsltConstants.callTemplate, "name", aTemplateName);
		return callTemplate.asXml();
	}
	
	public String writeOutApplyConditionalTemplateChoose(Rules rule){
		XslElement forEach = new XslElement(XsltConstants.forEach);
		forEach.addAttribute("select", rule.getCondition().getApplyExpression());
		XslElement choose = new XslElement(XsltConstants.choose);
		XslElement when = new XslElement(XsltConstants.when);
		when.addAttribute("test", rule.getCondition().getConditionExpression());
		XslElement targetField = new XslElement(rule.getCondition().getPrimaryRule().getTargetField());
		// TODO check type of rule -> xpath, constant, function
		XslElement valueOf = null;
		Rules pRule = rule.getCondition().getPrimaryRule();
		if (pRule.getFunctionCall() != null){
			if (pRule.getFunctionCall().doPreprocess()){
				valueOf = new XslElement(XsltConstants.valueOf, "select", pRule.getFunctionCall().getXSLpreparatedFunctionCall());				
			}else{
				valueOf = new XslElement(XsltConstants.valueOf, "select", pRule.getFunctionCall().getXSLdirectFunctionCall(null));				
			}
		}else{
			valueOf = new XslElement(XsltConstants.valueOf, "select", pRule.getXpath());			
		}
		targetField.addEnclosedElements(valueOf.asXml());				
		when.addEnclosedElements(targetField.asXml());
		choose.addEnclosedElements(when.asXml());
		XslElement otherwise = new XslElement(XsltConstants.otherwise);
		targetField = new XslElement(rule.getCondition().getSecondaryRule().getTargetField());
		// TODO check type of rule -> xpath, constant, function
		Rules sRule = rule.getCondition().getSecondaryRule();
		if (sRule.getFunctionCall() != null){
			if (sRule.getFunctionCall().doPreprocess()){
				valueOf = new XslElement(XsltConstants.valueOf, "select", sRule.getFunctionCall().getXSLpreparatedFunctionCall());							
			}else{
				valueOf = new XslElement(XsltConstants.valueOf, "select", sRule.getFunctionCall().getXSLdirectFunctionCall(null));				
			}
		}else{
			valueOf = new XslElement(XsltConstants.valueOf, "select", sRule.getXpath());
		}
		targetField.addEnclosedElements(valueOf.asXml());		
		otherwise.addEnclosedElements(targetField.asXml());
		choose.addEnclosedElements(otherwise.asXml());
		return forEach.asXml();
	}
	
	public String writeOutApplyConditionalTemplateIf(Rules rule, boolean isComplexStructure){
		// TODO check primary, alternative rule -> if, choose
		XslElement forEach = new XslElement(XsltConstants.forEach);
		forEach.addAttribute("select", rule.getCondition().getApplyExpression());
		
		// store position in variable
		XslElement posVar = new XslElement(XsltConstants.variable);
		posVar.addAttribute("name", "posVar");
		posVar.addAttribute("select", "position()");
		forEach.addEnclosedElements(posVar.asXml());
		// if case
		XslElement ifCondition = new XslElement(XsltConstants.ifCondition);
		if (rule.getCondition().isPrimary(rule)){
			ifCondition.addAttribute("test", rule.getCondition().getConditionExpression());
			Rules pRule = rule.getCondition().getPrimaryRule();
			if (isComplexStructure){
				ifCondition.addEnclosedElements(writeOutRuleComplex(pRule, pRule.getUniqueName()));				
			}else{
				XslElement targetField = new XslElement(pRule.getTargetField());
				// TODO check type of rule -> xpath, constant, function
				XslElement valueOf = null;
				if (pRule.getFunctionCall() != null){
					if (pRule.getFunctionCall().doPreprocess()){
						valueOf = new XslElement(XsltConstants.valueOf, "select", pRule.getFunctionCall().getXSLpositionFunctionCall());					
					}else{
						valueOf = new XslElement(XsltConstants.valueOf, "select", pRule.getFunctionCall().getXSLdirectFunctionCall(null));					
					}
				}else{
					valueOf = new XslElement(XsltConstants.valueOf, "select", pRule.getXpath());			
				}
				targetField.addEnclosedElements(valueOf.asXml());
				ifCondition.addEnclosedElements(targetField.asXml());				
			}
			forEach.addEnclosedElements(ifCondition.asXml());
		}else{
			ifCondition.addAttribute("test", "not(" + rule.getCondition().getConditionExpression() + ")");
			Rules sRule = rule.getCondition().getSecondaryRule();
			if (isComplexStructure){
				ifCondition.addEnclosedElements(writeOutRuleComplex(sRule, sRule.getUniqueName()));
			}else{
				XslElement targetField = new XslElement(sRule.getTargetField());
				// TODO check type of rule -> xpath, constant, function
				XslElement valueOf = null;
				
				if (sRule.getFunctionCall() != null){
					if (sRule.getFunctionCall().doPreprocess()){
						valueOf = new XslElement(XsltConstants.valueOf, "select", sRule.getFunctionCall().getXSLpositionFunctionCall());								
					}else{
						valueOf = new XslElement(XsltConstants.valueOf, "select", sRule.getFunctionCall().getXSLdirectFunctionCall(null));					
					}
				}else{
					valueOf = new XslElement(XsltConstants.valueOf, "select", sRule.getXpath());
				}
				targetField.addEnclosedElements(valueOf.asXml());
				ifCondition.addEnclosedElements(targetField.asXml());				
			}
			forEach.addEnclosedElements(ifCondition.asXml());			
		}
		return forEach.asXml();
	}

	public XslElement writeOutRecursiveTemplate(Rules rule, String templateName, Map<String, String> nsDeclarations) {
		XslElement subTemplate = new XslElement(XsltConstants.template, "name", templateName);
		XslElement param = new XslElement(XsltConstants.param);
		param.addAttribute("name", templateName + "param");
		param.addAttribute("select", rule.getFunctionCall().getXSLdirectFunctionCall(templateName)); // TODO functionCall
		subTemplate.addEnclosedElements(param.asXml());
		
		XslElement forEach = new XslElement(XsltConstants.forEach);
		forEach.addAttribute("select", "$" + templateName + "param");
		XslElement element = new XslElement(XsltConstants.element);
		// split ns element name into element name and namespace
		String targetElementName = rule.getFunctionCall().getParameters().get("elementName");
		String namespace = "";
		if (targetElementName.contains(":")){
			String[] nameSplitting = targetElementName.split(":");
			targetElementName = nameSplitting[1];
			namespace = nsDeclarations.get(nameSplitting[0]);			
		}
		element.addAttribute("name", targetElementName);
		element.addAttribute("namespace", namespace);
		element.addAttribute("inherit-namespaces", "yes");

		XslElement valueOf = new XslElement(XsltConstants.valueOf, "select", ".");								

		element.addEnclosedElements(valueOf.asXml());
		forEach.addEnclosedElements(element.asXml());		
		subTemplate.addEnclosedElements(forEach.asXml());
		return subTemplate;
	}

	public String writeOutApplyAbout() {
		//XslElement about = new XslElement("about");
		XslElement copy = new XslElement(XsltConstants.copyOf, "select", "@*|//*[local-name()='about']");
//		XslElement applyTemplatesSelect = new XslElement(XsltConstants.applyTemplates, "select", "@*|node()");
//		copy.addEnclosedElements(applyTemplatesSelect.asXml());
		//about.addEnclosedElements(copy.asXml());
		return copy.asXml();
	}
	
}
