package org.drools.compiler.integrationTests;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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.KieRepository;
import org.kie.api.builder.Message;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.Results;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.definition.KiePackage;
import org.kie.api.definition.rule.Rule;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.EntryPoint;
import org.kie.internal.builder.IncrementalResults;
import org.kie.internal.builder.InternalKieBuilder;

import com.ibm.jvm.Dump;
import com.ibm.jvm.InvalidDumpOptionException;


public class NotRemovingRules {
	
	final String artifactId = "vsc-rule-engine";
	final String defaultBaseName = "defaultKBase";
	final String pkgName = "org.drools.compiler.integrationTests";
	final KieServices ks = KieServices.Factory.get();
	final KieRepository kr = ks.getRepository();
	final String pathSep = System.getProperty("file.separator");
	final String pathToRule108 = "src/main/resources/rules/1081.drl";
	final String rulePath = "src/main/resources/rules/";
	static final int numOfRuns = 5;
	static int internalRuleID = Integer.MAX_VALUE - 5000;
	final String pathToInternalRule = rulePath+internalRuleID + ".drl";
	
	int ver=0;
	String updateTo = " ";
	KieSession kSession = null;
	ReleaseId prevReleaseId = null;
	KieFileSystem kfs = null;
	KieBuilder kb = null;
	KieContainer kContainer = null;
	KiePackage testPkg = null;
	KieModuleModel defaultModuleModel = null;
	KieBaseModel defaultBaseModel = null;
	KieBase kBase = null;
	EntryPoint currStream = null;
	static int counter = 1;
    
    final static String genRulePart1 =
    		"package org.drools.compiler.integrationTests \n" +
    		"declare Entity\n" +
    		"@role(event)\n" +
    		"	end\n" +
    		"rule \""; 
    final static String genRulePart2 = "\"\n" +
    		"no-loop true\n" + 
    		"when \n" +
			" then \n" +
			"end \n";

    	
	public static void main(String[] args) {
		System.out.println("current working dir is: " + System.getProperty("user.dir"));
		NotRemovingRules st = new NotRemovingRules();
		st.firstBuildOfRulesAndContainer();
		
		for (counter=1; counter < numOfRuns+1; counter++) {
			// change up the kfs
			Map<String,String> rulesToAdd = new LinkedHashMap<String,String>();
			List<String> rulesToRemove = new ArrayList<String>();
			if (counter == 1) {
				rulesToRemove.add(st.rulePath + "108" + counter + ".drl");	
			} else {
				rulesToRemove.add(st.rulePath + "120" + (counter -1) + ".drl");
			}
			
			String ruleName = "120" + counter;
			String path = st.rulePath + ruleName + ".drl";
			rulesToAdd.put(path, genRulePart1 + ruleName + genRulePart2);

			rulesToRemove.add(st.rulePath + internalRuleID + ".drl");			
			internalRuleID = internalRuleID + 1;
			path = st.rulePath +  internalRuleID + ".drl"; //internalRule
			ruleName = "Internal rule" + internalRuleID;
			rulesToAdd.put(path, genRulePart1 + ruleName + genRulePart2 );
			st.updateRuleRepository(rulesToAdd, rulesToRemove,-1);
			
			// change up the kfs
			rulesToRemove = new ArrayList<String>();
			rulesToAdd = new LinkedHashMap<String,String>();
			rulesToRemove.add(st.rulePath + internalRuleID + ".drl");
			
			internalRuleID = internalRuleID + 1;
			path = st.rulePath +  internalRuleID + ".drl"; //internalRule
			ruleName = "Internal rule" + internalRuleID;
			rulesToAdd.put(path, genRulePart1 + ruleName + genRulePart2 );

			st.updateRuleRepository(rulesToAdd, rulesToRemove,counter);
			
		}
		st.printRules("after all updates");
		st.makeDump("ckpoint3.dmp");
        
		st.done(null);
	}

	
	void makeDump(String fileName) {
		System.gc();
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e1) {
			done(e1);
		}
		String fName = "c:" + pathSep + "Users" + pathSep + "Administrator" + pathSep +"Desktop" + pathSep + fileName;
		Path pName = Paths.get(fName);
		try {
			Files.deleteIfExists(pName);
			Dump.systemDumpToFile(fName);
		} catch (IOException e) {
			done(e);
		} catch (InvalidDumpOptionException e) {
			done(e);
		}
	}
	
	
	void done(Exception e) {
		System.out.println("done...");
		if (e != null) {
			e.printStackTrace();
			System.exit(1);
		} else {
			System.exit(0);
		}
	}

	boolean firstBuildOfRulesAndContainer(){
		updateTo = "1.0." + ver++;
		prevReleaseId = ks.newReleaseId(pkgName, artifactId, updateTo);
		kfs = ks.newKieFileSystem();
		kfs.generateAndWritePomXML(prevReleaseId);
		defaultModuleModel = ks.newKieModuleModel();
		defaultBaseModel = defaultModuleModel.newKieBaseModel(defaultBaseName);
		defaultBaseModel.setEventProcessingMode(EventProcessingOption.STREAM).setDefault(true);
		defaultBaseModel.newKieSessionModel("defaultKSession").setDefault(true);
		kfs.writeKModuleXML(defaultModuleModel.toXML());

		byte[] baseRuleBytes = null;
		try {
			String ruleName = "108" + counter;
			String temp = genRulePart1 + ruleName + genRulePart2; 
			baseRuleBytes = temp.getBytes("US-ASCII");
			if (baseRuleBytes != null && baseRuleBytes.length > 0) 
				kfs.write(pathToRule108, baseRuleBytes); 
		} catch (UnsupportedEncodingException e) {
			done(e);
		};
		kb = ks.newKieBuilder(kfs);
		kb.buildAll();
		if (kb.getResults().hasMessages(org.kie.api.builder.Message.Level.ERROR)) {
			System.out.println(kb.getResults().toString());
			return false;
		}
		kContainer = ks.newKieContainer(prevReleaseId);
		makeDump("ckpoint1.dmp");
		kSession = kContainer.newKieSession();
		makeDump("ckpoint2.dmp");
		currStream = kSession.getEntryPoint( "currStream" );
		kBase = kContainer.getKieBase(defaultBaseName);

		printRules("firstBuildOfRulesAndContainer");
		return true;
	}


	void printRules(String caller) {
		kBase = kContainer.getKieBase(defaultBaseName);
		testPkg = kBase.getKiePackage(pkgName);
		int ruleCount = 0;
		System.out.println("Caller: " + caller);
		if (testPkg == null) {
			System.out.println("Rule package not found: " + pkgName);
		}
		else {
			System.out.println(testPkg.getName() + ":");
			for( Rule oneRule : testPkg.getRules()){
				System.out.println("\t" + oneRule.getName());
				++ruleCount;
			}
		}	
		System.out.println("Rule count:" + ruleCount);
	}
	
	public boolean updateRuleRepository(Map<String,String> rulesToAdd, List<String> rulesToRemove, int timeThru) {
		
		KieModule activeModule = kb.getKieModule();
		ReleaseId activeReleaseId = activeModule.getReleaseId();
		
		String updateTo = "1.0." + ver++;
		ReleaseId newReleaseId = ks.newReleaseId(pkgName, artifactId, updateTo);
		
		System.out.println("======>  New Kie Module Version: " + updateTo);

		kfs.generateAndWritePomXML( newReleaseId );

		boolean newRulesOK = true;
		long begin = System.currentTimeMillis();
		Results r = null;
		Map<String, String> prevRules = new LinkedHashMap<String, String>();
		// remove deleted/changed rules from the kie file system
		if (rulesToRemove != null) {
			for (String path : rulesToRemove) {
				System.out.println( "remove/delete rule at path: " + path);
				byte[] ruleBytes = kfs.read(path);
				if (ruleBytes != null)
					prevRules.put(path, new String(ruleBytes));
				kfs.delete(path);
			}			
		}

		// add new rules to the kie file system
		if (rulesToAdd != null) {
			java.util.Set<String> addPaths = rulesToAdd.keySet();
			for (String path : addPaths) {
				System.out.println( "adding rule at path: " + path);
				byte[] ruleBytes = kfs.read(path);
				if (ruleBytes != null)
					prevRules.put(path, new String(ruleBytes));
				kfs.write(path, rulesToAdd.get(path));
			}
		}
		if (timeThru == 1 || timeThru == numOfRuns)
			makeDump("updrule" + timeThru +"_1.dmp");
		IncrementalResults results = ( (InternalKieBuilder) kb ).incrementalBuild();
		if (timeThru == 1 || timeThru == numOfRuns)
			makeDump("updrule" + timeThru +"_2.dmp");
		if (results.getAddedMessages().size() > 0 ) {
			System.out.println( "*** Messages from incrementalBuild ***");
			for (Message ln : results.getAddedMessages()) {
				System.out.println("Issue on line: " + ln.getLine() + " is " + ln.getText());
			}
			newRulesOK = false;
		} else {
			// update container
			r = kContainer.updateToVersion(newReleaseId);
			if (timeThru == 1 || timeThru == numOfRuns)
				makeDump("updrule" + timeThru +"_3.dmp");
			if (r.hasMessages(org.kie.api.builder.Message.Level.ERROR)) {
				newRulesOK = false;
				System.out.println( "*** Messages from updateToVersion call ***");
				for (org.kie.api.builder.Message message : r.getMessages()) {
					System.out.println( message.getText());
				}
			} else {
				// Finally remove entries in caches for removed rules
				//not relevant removeRulefromCaches(rulesToRemove);
				// Remove the release that was previously active
				kr.removeKieModule( activeReleaseId );
				if (timeThru == 1 || timeThru == numOfRuns)
					makeDump("updrule" + timeThru +"_4.dmp");
			}
		}
		long end = System.currentTimeMillis();
		double d = end - begin;
		int removeCnt = rulesToRemove != null ? rulesToRemove.size() : 0;
		int addCnt = rulesToAdd != null ? rulesToAdd.size() : 0;
		System.out.println( "added " + addCnt + " rules");
		System.out.println( "removed " + removeCnt + " rules");
		System.out.println( "updated " + (removeCnt + addCnt) + " rules, duration: " + (d / 1000) + " seconds.");

		return newRulesOK;
	}
	
}
