package Rules.fixture;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.rules.ConfigurationException;
import javax.rules.RuleRuntime;
import javax.rules.RuleServiceProvider;
import javax.rules.RuleServiceProviderManager;
import javax.rules.StatelessRuleSession;
import javax.rules.admin.LocalRuleExecutionSetProvider;
import javax.rules.admin.RuleAdministrator;
import javax.rules.admin.RuleExecutionSet;

import fit.Parse;

/**
 * This fixture uses the JSR94 API to feed objects to a rule engine and execute
 * the rules (stateless engine instance only).
 * 
 * It makes the assumption that the rule engine is making changes on the objects
 * asserted. So this may not be suitable for all rule engine types.
 * Its very simple at this stage, as it is all I needed.
 * 
 * @author Michael Neale
 */
public class Engine extends AbstractRulesTestingFixture {

	private RuleAdministrator ruleAdministrator;

	private LocalRuleExecutionSetProvider ruleSetProvider;

	List objects = new ArrayList();

	private String uri;

	private RuleServiceProvider ruleServiceProvider;

	void processRow(Parse cell) {
		if (cell.text().equalsIgnoreCase("Provider")) {
			setupRuleEngine(cell.more.text(), cell.more.more.text());
		} else if (cell.text().equalsIgnoreCase("Execute")) {
			executeRulesEngine();
		} else if (cell.text().equalsIgnoreCase("Assert")
				|| cell.text().equalsIgnoreCase("Insert")) {
			addDomainObjects(cell.more.text());
		} else if (cell.text().equalsIgnoreCase("Ruleset")
				|| cell.text().equalsIgnoreCase("Package")) {
			try {
				loadRules(cell.more.text());
			} catch (Exception e) {
				super.exception(cell, e);
			}
		}
    	super.right(cell);
    	super.right(cell.more);
    	super.right(cell.more.more);			
	}

	private void setupRuleEngine(String engineURI, String clazz) {
		try {
			Class prov = Class.forName(clazz);
			RuleServiceProviderManager.registerRuleServiceProvider(engineURI, prov);

			ruleServiceProvider = RuleServiceProviderManager
					.getRuleServiceProvider(engineURI);

			ruleAdministrator = ruleServiceProvider.getRuleAdministrator();

			ruleSetProvider = ruleAdministrator
					.getLocalRuleExecutionSetProvider(null);
		} catch (ClassNotFoundException e) {
			throw new IllegalArgumentException(
					"Unable to load class for rule engine " + clazz);
		} catch (ConfigurationException e) {
			throw new IllegalArgumentException(
					"Unable to configure rule engine. " + e.getMessage());
		} catch (RemoteException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	private void loadRules(String rulesetURL) {
		
		this.uri = getURI(rulesetURL);
		InputStream stream = getStream(rulesetURL);

        //this is where I would schtick DSL config if needed...
		try {
		
	        RuleExecutionSet ruleExecutionSet =
	            ruleSetProvider.createRuleExecutionSet( new InputStreamReader( stream ), null );
	        ruleAdministrator.registerRuleExecutionSet(
	            uri, ruleExecutionSet, null );
	        
	        System.err.println("Regsitered ruleset");
		} catch (Exception e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	void addDomainObjects(String objName) {
		if (objName.equalsIgnoreCase("all")) {
			for (Iterator iter = getDomainObjects().all().iterator(); iter.hasNext();) {
				objects.add(iter.next());				
			} 
		} else {
			objects.add(getDomainObjects().getObject(objName));
		}
	}
	
	String getURI(String url) {
		return url.substring(url.lastIndexOf("/") + 1, url.length());
	}
	
	InputStream getStream(String rulesetUrl) {
		
		System.err.println("Loading ruleset: " + rulesetUrl);
		
		InputStream stream = tryClasspath(rulesetUrl);
		
		if (stream == null) {
			stream = tryFile(rulesetUrl);
		}
		if (stream == null) {
			stream = tryURL(rulesetUrl);
		}		
		if (stream == null) {
			throw new IllegalArgumentException("Unable to get stream for ruleset: " + rulesetUrl);
		}
		return stream;
	}

	private InputStream tryClasspath(String rulesetUrl) {
		System.err.println("trying classpath: " + rulesetUrl);
		return this.getClass().getResourceAsStream(rulesetUrl);
	}

	private InputStream tryURL(String rulesetUrl) {
		System.err.println("Trying URL: " + rulesetUrl);
		try {
			URL url = new URL(rulesetUrl);
			return url.openStream();
		} catch (IOException e) {
			throw new IllegalArgumentException("Unable to open URL to ruleset. " + e.getMessage());
		}
		
	}

	private InputStream tryFile(String rulesetUrl) {
		System.err.println("Trying file: " + rulesetUrl);
		File file = new File(rulesetUrl);
		if (file.exists()) {
			try {
				return new FileInputStream(file);
			} catch (FileNotFoundException e) {
				throw new IllegalStateException("Unable to open file. " + e.getMessage());
			}
		} 
		return null;
	}

	private void executeRulesEngine() {
		try {
			RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime( );

        	StatelessRuleSession session = ( StatelessRuleSession ) ruleRuntime.createRuleSession(
        					uri, null,
							RuleRuntime.STATELESS_SESSION_TYPE);
			session.executeRules(this.objects);
			ruleAdministrator.deregisterRuleExecutionSet(uri, null);
			System.err.println("Executed ruleset");
		} catch (Exception e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}
	
	

}
