package ca.pfv.spmf.algorithms.episodes.standardepisoderules;

import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

import ca.pfv.spmf.algorithms.episodes.emma.AlgoEMMA;
import ca.pfv.spmf.algorithms.episodes.emma.EpisodeEMMA;
import ca.pfv.spmf.algorithms.episodes.general.AbstractEpisode;
import ca.pfv.spmf.algorithms.episodes.general.FrequentEpisodes;
import ca.pfv.spmf.algorithms.episodes.general.Level;
import ca.pfv.spmf.tools.MemoryLogger;

/*
 * This file is part of the SPMF DATA MINING SOFTWARE *
 * (http://www.philippe-fournier-viger.com/spmf).
 *
 * SPMF is free software: you can redistribute it and/or modify it under the *
 * terms of the GNU General Public License as published by the Free Software *
 * Foundation, either version 3 of the License, or (at your option) any later *
 * version. SPMF is distributed in the hope that it will be useful, but WITHOUT
 * ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * SPMF. If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright Chen, Yangming, 2021
 */
/**
 * Algorithm to generate episode rules from frequent episodes generated by EMMA
 * 
 * @author Chen, Yangming
 * @see AlgoEMMA
 */
public class AlgoGenerateEpisodeRules {

	/** the list of rules that has been found */
	private List<EpisodeRule> ruleList;

	/** maximum memory usage of last execution */
	private double maxMemory = 0;

	/** runtime of last execution */
	private long runtime = 0;

	/** Constructor */
	public AlgoGenerateEpisodeRules() {
		ruleList = new ArrayList<EpisodeRule>();
	}

	/**
	 * Run the algorithm to generate rules from EMMA's or MINEPI+'s episodes
	 * 
	 * @param frequentEpisodes the frequent episodes as a priority queue
	 * @param support          the minimum support
	 * @param conf             the minimum confidence
	 * @param predictItemNum   the maximum consequent size
	 */
	public List<EpisodeRule> runAlgorithm(FrequentEpisodes frequentEpisodes, int support, double conf,
			int predictItemNum) {
		MemoryLogger.getInstance().reset();
		runtime = System.currentTimeMillis();

		List<Level> levelList = frequentEpisodes.getLevels();
		// List<EpisodeRule> ruleList = new ArrayList<EpisodeRule>();
		for (int numLevel = 2; numLevel < levelList.size(); ++numLevel) {
			Level l = levelList.get(numLevel);
			for (AbstractEpisode episode : l.getKFrequentEpisodes()) {
				// System.out.println(episode.toString());
				List<int[]> events = episode.getEvents();
				List<int[]> events2 = new ArrayList<>();
				int totalCount = episode.getSupport();
				for (int antiItem = 1; antiItem < numLevel; ++antiItem) {
					// System.out.println("antiItem:" + antiItem);
					events2.add(events.get(antiItem - 1));
					if (numLevel - antiItem > predictItemNum) {
						continue;
					}
					Level pre = levelList.get(antiItem);
					for (AbstractEpisode episode2 : pre.getKFrequentEpisodes()) {
						if ((totalCount / (double) episode2.getSupport()) <= conf || episode2.getSupport() <= support) {
							continue;
						}
						// System.out.println(episode2.toString());
						if (episode2.equal(events2)) {
							List<int[]> antiEvents = new ArrayList<>();
							antiEvents.addAll(events2);
							List<int[]> conseEvents = new ArrayList<>();
							for (int conseItem = antiItem; conseItem < numLevel; ++conseItem) {
								conseEvents.add(events.get(conseItem));
							}
							EpisodeRule rule = new EpisodeRule(antiEvents, conseEvents, totalCount, episode2.getSupport());
							ruleList.add(rule);
						}
					}
				}
			}
		}

		MemoryLogger.getInstance().checkMemory();
		maxMemory = MemoryLogger.getInstance().getMaxMemory();
		runtime = System.currentTimeMillis() - runtime;
		return ruleList;
	}

	/**
	 * Run the algorithm to generate rules from TKE's Episodes
	 * 
	 * @param frequentEpisodes the frequent episodes as a priority queue
	 * @param support          the minimum support
	 * @param conf             the minimum confidence
	 * @param predictItemNum   the maximum consequent size
	 * @return
	 */
	public List<EpisodeRule> runAlgorithm(PriorityQueue<EpisodeEMMA> frequentEpisodes, int support, double conf,
			int predictItemNum) {
		FrequentEpisodes episodesByLevel = new FrequentEpisodes();
		for (EpisodeEMMA episode : frequentEpisodes) {
			episodesByLevel.addFrequentEpisode(episode, episode.size());
		}

		return runAlgorithm(episodesByLevel, support, conf, predictItemNum);
	}

	/**
	 * Get the list of rules that has been found
	 * 
	 * @return the list of rules
	 */
	public List<EpisodeRule> getRuleList() {
		return ruleList;
	}

	/** Print statistics about the algorithm's last execution */
	public void printStats() {
		System.out.println("=============  Episode Rule Mining v.2.46 - STATS =============");
		System.out.println(" Episode Rule count : " + ruleList.size());
		System.out.println(" Maximum memory usage : " + maxMemory + " mb");
		System.out.println(" Total time ~ : " + runtime + " ms");
		System.out.println("===================================================");
	}


	
	/**
	 * Print the rules to the console
	 */
	public void printRules() {
		System.out.println(rulesAsString());
	}
	
	/**
	 * Write the rules to a file
	 * 
	 * @param outputPath an output file path
	 */
	public void writeRulesToFileSPMFFormat(String outputPath) {
		try {
			PrintWriter writer = new PrintWriter(outputPath, "UTF-8");
			writer.write(rulesAsString());
			writer.close();
		} catch (FileNotFoundException | UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Get a string representation of this set of rules (for printing or writing to
	 * file)
	 * @return a string
	 */
	private String rulesAsString() {
		DecimalFormat formater = new DecimalFormat("#.###");
		StringBuilder buffer = new StringBuilder();
		
		// For each rule
		for (int z = 0; z < ruleList.size(); z++) {
			EpisodeRule rule = ruleList.get(z);
			
			// write Antecedent
			List<int[]> antecedent = rule.getAntiEvents();
			for (int j = 0; j < antecedent.size(); j++) {
				int[] eventSet = antecedent.get(j);
				buffer.append('{');
				for (int i = 0; i < eventSet.length; i++) {
					buffer.append(eventSet[i]);
					if (i != eventSet.length - 1) {
						buffer.append(',');
					}else {
						buffer.append('}');
					}
				}
			}

			buffer.append(" ==> ");
			
			// write consequent
			List<int[]> consequent = rule.getConseEvents();
			for (int j = 0; j < consequent.size(); j++) {
				int[] eventSet = consequent.get(j);
				buffer.append('{');
				for (int i = 0; i < eventSet.length; i++) {
					buffer.append(eventSet[i]);
					if (i != eventSet.length - 1) {
						buffer.append(',');
					}else {
						buffer.append('}');
					}
				}
			}

			// write the support an confidence
			buffer.append(" #SUP: ");
			buffer.append(rule.getTotalCount());
			buffer.append(" #CONF: ");
			double confidence = rule.getTotalCount() / (double) rule.getAntiCount();
			buffer.append(formater.format(confidence));
			buffer.append(System.lineSeparator());
		}
		return buffer.toString();
	}

}
