/*******************************************************************************

* Copyright (c) 2006 IONA Technologies PLC

* All rights reserved. This program and the accompanying materials

* are made available under the terms of the Eclipse Public License v1.0

* which accompanies this distribution, and is available at

* http://www.eclipse.org/legal/epl-v10.html

* 

* Contributors:

*     IONA Technologies PLC - initial API and implementation

*******************************************************************************/
package org.eclipse.stp.sc.xmlvalidator.rule.parser;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Hashtable;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleAssert;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleDef;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleSet;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * @author jma
 * This class is used to parsing the input rule xml file.
 * generate rule description from it
 * we will validting rules against rule schema during parser rule file
 * We will also try to compile each xpath express using xpath engine to verify those expresss are correct
 * 
 */
public class RuleXmlParser {
	
	private static final LoggingProxy LOG = LoggingProxy.getlogger(RuleXmlParser.class);
	
	public final static String TAG_RULESET = "ruleset";
	public final static String TAG_RULESET_NAME = "name";
	public final static String TAG_RULE = "rule";
	public final static String TAG_ID = "id";
	public final static String TAG_DESC = "description";
	public final static String TAG_CONTEXT = "context";
	public final static String TAG_EXPRESSION = "expression";
	public final static String TAG_ERRORMSG = "errormsg";
	public final static String TAG_ASSERTTRUE = "asserttrue";
	public final static String TAG_ASSERTFALSE = "assertfalse";
	
	public static String XPATH_RESULT_SEPERATOR = "-";
	
	XPathFactory factory;
	XPath xpath;
	
	public RuleXmlParser() {
		factory = XPathFactory.newInstance();
        xpath = factory.newXPath();
	}
	
	public VRuleSet loadRuleFile(String filePath, Hashtable<String, VRuleDef> ruleTable) {
		FileInputStream is = null;
		try{
		    is = new FileInputStream(filePath);
		} catch (Exception e) {
			
			LOG.error("error during read rule file", e);
			return null;
		}	
		return loadRuleFile(is, ruleTable);
	}
	
	/**
	 * load rules from input xml file, put into rule tabl
	 * @param inputRuleFile
	 * @param ruleTable
	 */
	public VRuleSet loadRuleFile(InputStream inputRuleFile, Hashtable<String, VRuleDef> ruleTable) {
	    try {
		    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		    //todo, revisit here to validate the rule file
		    Document doc = builder.parse(inputRuleFile);
		    doc.normalize();
		    //parse rule set
		    NodeList ruleSetList = doc.getElementsByTagName(TAG_RULESET);
		    if (ruleSetList.getLength() == 0) {
		    	LOG.debug("the input is not rule xml file");
		    	return null;
		    }
		    Element ruleSetElem = (Element)ruleSetList.item(0);
		    String name = ruleSetElem.getAttribute(TAG_RULESET_NAME);
		    String desc = ruleSetElem.getAttribute(TAG_DESC);
		    
		    VRuleSet ruleSet = new VRuleSet(name, desc);
		    
		    NodeList list = doc.getElementsByTagName(TAG_RULE);
		    if (list.getLength() == 0) {
		    	LOG.debug("the input is not rule xml file");
		    	return null;
		    }
		    
		    
		    for (int i = 0 ; i < list.getLength(); i++) {
		    	Element ruleElem = (Element)list.item(i);
                VRuleDef ruleDef = loadRuleElement(ruleElem, ruleTable);
                if (ruleDef != null) {
		    	    ruleTable.put(ruleDef.getId(), ruleDef);
		    	    ruleSet.addRule(ruleDef);
                }
		    }
		    return ruleSet;
	    } catch (Exception e) {
	        LOG.error("error parse rule file", e);
	        return null;
	    }
	} 
	
	private VRuleDef loadRuleElement(Element ruleElem, Hashtable<String, VRuleDef> ruleTable) throws Exception {
		String id = ruleElem.getAttribute(TAG_ID);
    	String desc = "";
        if (ruleElem.getElementsByTagName(TAG_DESC) != null) {
    	    desc = ruleElem.getElementsByTagName(TAG_DESC).item(0).getTextContent();
        }
        if (ruleElem.getElementsByTagName(TAG_CONTEXT) == null
                || ruleElem.getElementsByTagName(TAG_CONTEXT).getLength() == 0) {
            LOG.error("context node has not been defined");
            return null;
        }
    	String context = ruleElem.getElementsByTagName(TAG_CONTEXT).item(0).getTextContent();
    	VRuleDef ruleDef = new VRuleDef(id);
    	if(ruleTable.containsKey(ruleDef.getId())){
    		LOG.error("already contains rule with same id:" + id);
    		return null;
    	}
    	ruleDef.setContextNode(context);
    	ruleDef.setDescription(desc);
    	NodeList assertTruelist = ruleElem.getElementsByTagName(TAG_ASSERTTRUE);
    	loadRuleAssert(assertTruelist, ruleDef);
    	NodeList assertFalselist = ruleElem.getElementsByTagName(TAG_ASSERTFALSE);
    	loadRuleAssert(assertFalselist, ruleDef);
    	
        if (ruleDef.getAssertList().size() == 0) {
            LOG.error("can't load rule assert for rule:" + id);
            return null;
        }
    	LOG.debug("Loaded rule def -- " + ruleDef.toString());
    	return ruleDef;
	}
	
	private void loadRuleAssert(NodeList assertList, VRuleDef ruleDef) throws Exception {
		for (int i = 0; i < assertList.getLength(); i++) {
			Element assertElem = (Element) assertList.item(i);
			VRuleAssert ruleAssert = loadRuleAssert(assertElem, ruleDef.getContextNode());
			if (ruleAssert != null) {
				ruleDef.addRuleAssert(ruleAssert);
			}
		}
	}
	
	private String createExpString(String context, String inputExp) {
		String exp;
		String contextStr = "";
		String expStr = "";
		if (context != null || !context.equals("")) {
		    contextStr = "string(" + context + "/@linenumber)"; 
		}
		expStr = "string(" + inputExp + ")";
		exp = "concat(" + contextStr + ",\"" + XPATH_RESULT_SEPERATOR + "\"," + expStr +")";
		return exp;
	}
	
	private VRuleAssert loadRuleAssert(Element assertElem, String context) {
        if (assertElem.getElementsByTagName(TAG_EXPRESSION) == null
                || assertElem.getElementsByTagName(TAG_EXPRESSION).getLength() == 0) {
            return null;
        }
		String exp = assertElem.getElementsByTagName(TAG_EXPRESSION).item(0).getTextContent();
		XPathExpression xpathExp = null;
		try {
			//create the xpath exp.
			String vexp = createExpString(context, exp);
			LOG.debug("Generated xpath exp:" + vexp);
			//we try to compile it here.
			
			xpathExp = xpath.compile(vexp);
		} catch (Exception xpathe) {
			LOG.error("wrong xpath exp:" + exp);
			LOG.error("error stack", xpathe);
			return null;
		}
    	String errorMsg = assertElem.getElementsByTagName(TAG_ERRORMSG).item(0).getTextContent();
    	VRuleAssert.Type type = VRuleAssert.Type.FALSE;
    	if (assertElem.getNodeName().equals(TAG_ASSERTTRUE)) {
    		type = VRuleAssert.Type.TRUE; 
    	} else if (assertElem.getNodeName().equals(TAG_ASSERTFALSE)) {
    		type = VRuleAssert.Type.FALSE; 
    	}  
    	
    	VRuleAssert ruleAssert = new VRuleAssert(type, errorMsg, exp);
    	ruleAssert.setXpathExp(xpathExp);
    	
    	return ruleAssert;
	}

	public static void main(String[] args) {
		Hashtable<String, VRuleDef> ruleTable = new Hashtable<String, VRuleDef>();
		RuleXmlParser parser = new RuleXmlParser();
		String filePath = "C:\\Projects\\STP\\Workspaces\\STP\\XMLTest\\JAX_WS_VRules.xml";
		parser.loadRuleFile(filePath, ruleTable);
	}
}
