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

* 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.engine;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.stp.sc.xmlvalidator.XmlValidatorPlugin;
import org.eclipse.stp.sc.xmlvalidator.XmlValidatorResources;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleDef;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleInstance;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleSet;
import org.eclipse.stp.sc.xmlvalidator.rule.model.VRuleDef.SEVERITY;
import org.eclipse.stp.sc.xmlvalidator.rule.parser.RuleXmlParser;
import org.osgi.service.prefs.Preferences;

/**
 * @author jma
 * manage the LFC of rule set during run time.
 * This class need to improve as follwoing:
 * It is singleton manager
 * <Li> moniter all rule paths. add/delete rules once the rule file has been added/removed from that path
 * <Li> once the rule model has version attribute, automatic update rule with latest version.
 */
public class VRuleManager {
	
	private static final LoggingProxy LOG = LoggingProxy.getlogger(VRuleManager.class);
	
	public static String DEFAULT_RULE_PATH_PROPERTY = "rule.path";
	private static String PATH_SEPERATOR = System.getProperty("os.name").contains("Windows")?";" : ":";
	LinkedHashMap<String, VRuleInstance> ruleTable;
	Hashtable<String, VRuleSet> ruleSetTable;
    HashSet<String> pathSet;
    RuleXmlParser parser;
    
    static VRuleManager instance = null;
    
    public static VRuleManager getInstance() {
    	if (instance == null) {
    		instance = new VRuleManager();
    	}
    	return instance;
    }
    
    protected VRuleManager() {
    	ruleTable = new LinkedHashMap<String, VRuleInstance>();
    	ruleSetTable = new Hashtable<String, VRuleSet>();
    	pathSet = new HashSet<String>();
    	parser = new RuleXmlParser();
    	initRulePath();
    }
    
    private void initRulePath() {
    	String paths = System.getProperty(DEFAULT_RULE_PATH_PROPERTY);
    	if (paths == null || paths.equals("")) {
    		return;
    	}
    	StringTokenizer st = new StringTokenizer(paths, PATH_SEPERATOR);
    	while (st.hasMoreTokens()) {
    		String path = st.nextToken();
    		pathSet.add(path);
    	}
    }
    
    public void loadRule(InputStream ruleStream) {
    	try {
    	    loadOneRuleFile(ruleStream, "");
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
    
    /**
     * load all rules from the rule paths
     * @return the number of rules loaded
     */
    public int loadRules() {
    	//clear first
    	ruleTable.clear();
    	ruleSetTable.clear();
    	
    	int count = 0;
    	for (String path : pathSet) {
    		count += loadRulesInPath(path);
    	}
    	return count;
    }
    
    /**
     * remove one rule set by name
     * this will let to remove all rules defined in that rule set
     * @param ruleSetName
     * @return
     */
    public int removeRuleSet(String ruleSetName) {
    	VRuleSet ruleSet = ruleSetTable.remove(ruleSetName);
    	if (ruleSet == null) {
    		return 0;
    	}
    	int ruleCount = ruleSet.getRules().size();
    	for (VRuleDef ruleDef : ruleSet.getRules()) {
    		ruleTable.remove(ruleDef.getId());
    	}
    	ruleSet.getRules().clear();
    	ruleSet = null;
    	return ruleCount;
    }
    
    public int loadRulesInPath(String path) {
    	LOG.debug("load rules from path:" + path);
		int ruleCount = 0;
		try {
			File dir = new File(path);
			if (!dir.isDirectory()) {
				return 0;
			}
			File[] files = dir.listFiles();
			for (File file : files) {
				if (file.isDirectory()) {
					ruleCount += loadRulesInPath(file.getAbsolutePath());
					continue;
				}
				if (!file.getName().endsWith(".xml")) {
					continue;
				}
				ruleCount += loadOneRuleFile(file.getAbsolutePath());	
			}

		} catch (Exception e) {
			LOG.error("error during load rule files from path:" + path);
			e.printStackTrace();
		}
		return ruleCount;
	}
    
    
    public int loadOneRuleFile(String filePath) {
    	try {
    	    FileInputStream fis = new FileInputStream(filePath);
    	    return loadOneRuleFile(fis, filePath);
    	} catch (Exception e) {
    		LOG.error("error during load one rule file." + filePath);
    	}
    	return 0;
    }
    
    
    private int loadOneRuleFile(InputStream is, String path) throws Exception {
    	int count = 0;
    	Hashtable<String, VRuleDef> ruleDefTables = new Hashtable<String, VRuleDef>();
    	
    		VRuleSet ruleSet = parser.loadRuleFile(is, ruleDefTables);
    		ruleSet.setLocation(path);
    		ruleSetTable.put(ruleSet.getName(), ruleSet);
    		
    		for (String ruleID : ruleDefTables.keySet()) {
    			VRuleDef def = ruleDefTables.get(ruleID);
    			VRuleInstance rule = new VRuleInstance(def);
    			ruleTable.put(ruleID, rule);
    			count ++;
    		}
    	
    	return count;
    }

    public boolean removeOneRule(String ruleID) {
        VRuleInstance rule = ruleTable.remove(ruleID);
        if (rule != null) {
            rule = null;
            return true;
        }
        return false;
    }
    
    /**
     * set the severity of one rule
     * @param ruleID
     * @param severity
     */
    public void setRuleSeverity(String ruleID, String severityStr) {
    	LOG.debug("change rule:" + ruleID + " severity to :" + severityStr);
    	VRuleInstance rule = ruleTable.get(ruleID);
    	VRuleDef ruleDef = rule.getRuleDef();
    	
    	if (severityStr.equals(XmlValidatorResources.getString("marker.severity.error"))) {
    		ruleDef.setSeverity(VRuleDef.SEVERITY.ERROR);
    	} else if (severityStr.equals(XmlValidatorResources.getString("marker.severity.warning"))) {
    		ruleDef.setSeverity(VRuleDef.SEVERITY.WARNING);
    	} else {
    		ruleDef.setSeverity(VRuleDef.SEVERITY.INFO);
    	}
    }
    
    public void setRuleEnabled(String ruleID, boolean enable) {
    	LOG.debug("set rule:" + ruleID + " enable to :" + enable);
    	VRuleInstance rule = ruleTable.get(ruleID);
    	rule.getRuleDef().setEnabled(enable);
    }
    
    public Hashtable<String, VRuleSet> getRuleSetTable(){
    	return ruleSetTable;
    }
    
    /**
     * Save the rule properties to preference store.
     */
    public void saveToPreference(){
    	if(ruleSetTable != null && ruleSetTable.size() > 0){
        	Preferences instanceNode = new InstanceScope().getNode(XmlValidatorPlugin.PLUGIN_ID);
        	Preferences rulesRootNode = instanceNode.node("validation_rules");
        	Iterator<VRuleSet> values = ruleSetTable.values().iterator();
        	while(values.hasNext()){
        		VRuleSet ruleSet = values.next();
        		List<VRuleDef> rules = ruleSet.getRules();
        		if(rules != null && rules.size() > 0){
            		Preferences rulelSetNode = rulesRootNode.node(ruleSet.getName());
            		for(VRuleDef rule : rules){
            			Preferences ruleNode = rulelSetNode.node(rule.getId());
            			ruleNode.putBoolean("enable", rule.getEnabled());
            			ruleNode.put("severity", getSeverityStr(rule.getSeverity()));
            		}
        		}
        	}
    	}
    }
    
    /**
     * Load rule properties from preference store
     */
    public void loadFromPreference(){
    	if(ruleSetTable != null && ruleSetTable.size() > 0){
        	Preferences instanceNode = new InstanceScope().getNode(XmlValidatorPlugin.PLUGIN_ID);
        	Preferences rulesRootNode = instanceNode.node("validation_rules");
        	Iterator<VRuleSet> values = ruleSetTable.values().iterator();
        	while(values.hasNext()){
        		VRuleSet ruleSet = values.next();
        		List<VRuleDef> rules = ruleSet.getRules();
        		if(rules != null && rules.size() > 0){
            		Preferences ruleSetNode = rulesRootNode.node(ruleSet.getName());
            		for(VRuleDef rule : rules){
            			Preferences ruleNode = ruleSetNode.node(rule.getId());
            			boolean isEnable = ruleNode.getBoolean("enable", true);
            			rule.setEnabled(isEnable);
            			String severityStr = ruleNode.get("severity", XmlValidatorResources.getString("marker.severity.error"));
            			rule.setSeverity(getSeverity(severityStr));
            		}
        		}
        	}
    	}    	
    }
    
    public static SEVERITY getSeverity(String severity){
       	if (severity.equals(XmlValidatorResources.getString("marker.severity.error"))) {
    		return VRuleDef.SEVERITY.ERROR;
    	} else if (severity.equals(XmlValidatorResources.getString("marker.severity.warning"))) {
    		return VRuleDef.SEVERITY.WARNING;
    	} else {
    		return VRuleDef.SEVERITY.INFO;
    	}
    }
    
    public static String getSeverityStr(SEVERITY severity){
       	if (severity.equals(SEVERITY.ERROR)) {
    		return XmlValidatorResources.getString("marker.severity.error");
    	} else if (severity.equals(VRuleDef.SEVERITY.WARNING)) {
    		return XmlValidatorResources.getString("marker.severity.warning");
    	} else {
    		return XmlValidatorResources.getString("marker.severity.info");
    	}
    	
    }
}
