

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.BeforeClass;
import org.junit.Test;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.builder.Message.Level;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieSessionModel.KieSessionType;
import org.kie.api.conf.DeclarativeAgendaOption;
import org.kie.api.conf.EqualityBehaviorOption;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.event.rule.DebugAgendaEventListener;
import org.kie.api.event.rule.DebugRuleRuntimeEventListener;
import org.kie.api.io.Resource;
import org.kie.api.marshalling.Marshaller;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.KieSessionConfiguration;
import org.kie.api.runtime.conf.ClockTypeOption;
import org.kie.api.runtime.conf.TimedRuleExectionOption;

import com.milla.rules.common.Constants;
import com.milla.util.Utils;

public class DroolsTimerTest {
	static final String SESSION_FILE_NAME = "tmp.kSession";
	static final String BASE_FILE_NAME = "tmp.kBase";

	static KieServices ks = KieServices.Factory.get();
	static ReleaseId rid = ks.newReleaseId("co", "rules", "1.0");
	KieBase kb;
	Marshaller marshaller;

	@BeforeClass
	public static void setUpForAllTests() {
		try {
			Utils.setEnvironmentVariable(Constants.REDIS_HOST_ENV, "localhost");
		} catch (NoSuchFieldException | IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	@Test
	/**
	 * runs the timeout test.
	 * There is a timer on a rule which should run when the time expires.
	 * However if the session is saved and restored, the timer does not run
	 */
	public void test() throws IOException, ClassNotFoundException {
		System.err.println("Running test "+this.getClass().getSimpleName());
		String trigger = "ATrigger";
		int timeout = 3;
		
		String drl = "rule \"Rule A Timeout\"\n" +
		"when\n" +
		"    String( this == \""+trigger+"\" )\n" +
		"then\n" +
		"   insert (new String( \"A-Timer\") );\n" +
		"System.out.println(\"+++++++Got ATrigger, started A-Timer with "+timeout+"s timeout\");\n" +
		"end\n" +
		"\n" +
		"rule \"Timer For rule A Timeout\"\n" +
		"    timer ( int: "+timeout+"s )\n" +
		"when\n" +
		"   String( this == \"A-Timer\")\n" +
		"then\n" +
		"   delete ( \"A-Timer\" );\n" +
		"   delete ( \"ATrigger\" );\n" +
		"System.out.println(\"******* Rule A based on timer fired\");\n" +
		"end\n";

		String baseName = "KieBase";
		String sessionName = "ASess";

		createInMemoryKieSystem(drl, baseName, sessionName);
		KieContainer kc = ks.newKieContainer(rid);
		kb = kc.getKieBase(baseName);
		marshaller = ks.getMarshallers().newMarshaller(kb);

		KieSessionConfiguration ksConf = ks.newKieSessionConfiguration();
		ksConf.setOption(TimedRuleExectionOption.YES);
		ksConf.setOption(ClockTypeOption.get("realtime"));
		KieSession kSession = kc.newKieSession(sessionName, ksConf);
		int duration = 5 ;

		kSession.addEventListener(new DebugAgendaEventListener());
		kSession.addEventListener(new DebugRuleRuntimeEventListener());
		testTimerRule(trigger, kSession, duration);
		
		saveSessionAndBase(kSession);
		kSession.dispose();


		kSession = restoreSession(baseName, sessionName);
		kSession.addEventListener(new DebugAgendaEventListener());
		kSession.addEventListener(new DebugRuleRuntimeEventListener());
		
		testTimerRule(trigger, kSession, duration);
		
		saveSessionAndBase(kSession);
		kSession.dispose();
		System.out.println("Test complete");
	}

	public void testTimerRule(String trigger, KieSession kSession, int duration) {
		kSession.insert(trigger);
		System.out.println("Before fileAll, system contains "+kSession.getFactCount()+" facts");
		int nRules = kSession.fireAllRules();
		System.out.println("After firing "+nRules+" rules, system contains "+kSession.getFactCount()+" facts");
		try {
			System.out.println("Sleeping for "+duration+" secs, rule should fire");
			Thread.sleep(duration*1000); // ms
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("After waiting "+duration+"s, system contains "+kSession.getFactCount()+" facts");
	}

	/**
	 * Creates an in-memory drools session
	 * @param drl
	 * @return
	 */
	private void createInMemoryKieSystem(String drl, String baseName, String sessionName) {
		KieFileSystem kfs = ks.newKieFileSystem();
		System.out.println("Creating new session ");
		KieModuleModel kModuleModel = ks.newKieModuleModel();
		KieBaseModel kbm = kModuleModel.newKieBaseModel(baseName)
				                       .setEqualsBehavior(EqualityBehaviorOption.EQUALITY)
				                       .setDeclarativeAgenda(DeclarativeAgendaOption.ENABLED)
				                       .setEventProcessingMode(EventProcessingOption.STREAM);

		kbm.newKieSessionModel(sessionName).setType(KieSessionType.STATEFUL).setDefault(true);
		// theLog.info("Created the xml: "+kModuleModel.toXML());
		kfs.writeKModuleXML(kModuleModel.toXML());

		kfs.generateAndWritePomXML(rid);

		kfs.write("src/main/resources/rules/testDrl.drl",drl);

		KieBuilder kBldr = ks.newKieBuilder(kfs);

		kBldr.buildAll(); // kieModule is automatically deployed to
							// KieRepository if successfully built.
		if (kBldr.getResults().hasMessages(Level.ERROR)) {
			throw new RuntimeException("Build Errors:\n" + kBldr.getResults().toString());
		} else {
			System.out.println("All rules built OK");
		}
	}

	private void saveSessionAndBase(KieSession session) throws IOException {
//		System.out.println("Saving KieBase");
//		InternalKieModule kieModule = (InternalKieModule) ks.getRepository().getKieModule(rid);
//
//		byte[] theModulesBytes = kieModule.getBytes();
//
//		Files.write( Paths.get(BASE_FILE_NAME), theModulesBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING );
//		System.out.println("Saved KieBase");

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		marshaller.marshall(baos, session);
		Files.write( Paths.get(SESSION_FILE_NAME), baos.toByteArray(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING );
	}
	
	private KieSession restoreSession(String baseName, String sessionName) throws IOException, ClassNotFoundException {
		long start = System.currentTimeMillis();
//		Files.readAllBytes(Paths.get("tmp.kieSession"));
//		KieBase kb = restoreKieBase(baseName);
		
		ByteArrayInputStream baos = new ByteArrayInputStream(Files.readAllBytes( Paths.get(SESSION_FILE_NAME)));
		KieSession kSession = marshaller.unmarshall(baos);
		baos.close();
		System.out.println(" retrieving session took " + (System.currentTimeMillis() - start) + "ms");
		return kSession;
	}

	private KieBase restoreKieBase(String baseName) throws IOException {
		KieModule km = null;

		byte[] theBytes = Files.readAllBytes(Paths.get(BASE_FILE_NAME));
		Resource jarRes = ks.getResources().newByteArrayResource(theBytes);
		km = ks.getRepository().addKieModule(jarRes);
		if (km == null) {
			throw new IOException("Unable to find the kie base in the repository!");
		}
		KieContainer kc = ks.newKieContainer(km.getReleaseId());
		return kc.getKieBase(baseName);
	}
}
