package ca.pfv.spmf.algorithms.frequentpatterns.estDec;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Hashtable;

/**
 * This is an implementation of the estDec algorithm (J. Chang, W.S. Lee 2006).
 * <br/><br/>
 * 
 * This implementation was made by Azadeh Soltani
 * <br/><br/>
 * 
 * Copyright (c) 2008-2014 Azadeh Soltani, Philippe Fournier-Viger
 * <br/><br/>
 * 
 * This file is part of the SPMF DATA MINING SOFTWARE
 * (http://www.philippe-fournier-viger.com/spmf).
 * <br/><br/>
 * 
 * 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.
 * <br/><br/>
 * 
 * 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.
 * <br/><br/>
 * 
 * You should have received a copy of the GNU General Public License along with
 * SPMF. If not, see <http://www.gnu.org/licenses/>.
 * @see  estNode
 * @see estTree
 * @author Azadeh Soltani
 */
public class Algo_estDec {
	// the "monitoring lattice" tree
	estTree tree;
	
	// for stats
	private long miningTime = 0; 
	double sumTransactionInsertionTime = 0; // sum of time for inserting transactions
	
	private double maxMemory = 0;

	/**
	 * Constructor
	 * @param mins minimum support
	 * @param minSigValue the minSig parameter
	 */
	public Algo_estDec(double mins, double minSigValue) {
		// create the "Monitoring Lattice" tree
		tree = new estTree(mins, minSigValue);
	}

	
	/**
	 * Run the algorithm by loading the transactions from an input file.
	 * @param input   the input file path
	 * @param output  the output file path for saving the result
	 * @param mins    the minsup threshold as a double value in [0, 1]
	 * @throws FileNotFoundException  if error opening the input file
	 * @throws IOException if error reading/writing files
	 */
	public void processTransactionFromFile(String input)
			throws FileNotFoundException, IOException {

		// read the input file
		BufferedReader reader = new BufferedReader(new FileReader(input));
		String line;
//		int n=0;
		// for each line (transaction)
		while (((line = reader.readLine()) != null)) { 
			String[] lineSplited = line.split(" ");
			
			// convert the array of strings to an array of integers (items)
			int[] transaction = new int[lineSplited.length];
			for (int i = 0; i < lineSplited.length; i++) {
				transaction[i] = Integer.parseInt(lineSplited[i]);
			} 
//			if (transaction.length<80)
			  processTransaction(transaction);
//			else
//				n++;

		}// while
		reader.close();
//		System.out.println(n);
	}
	
	/**
	 * Run the algorithm by loading the transactions from an input file.
	 * @param input   the input file path
	 * @param output  the output file path for saving the result
	 * @param mins    the minsup threshold as a double value in [0, 1]
	 * @param lineCount the number of lines to be read
	 * @throws FileNotFoundException  if error opening the input file
	 * @throws IOException if error reading/writing files
	 */
	public void processTransactionFromFile(String input, int lineCount)
			throws FileNotFoundException, IOException {

		// read the input file
		BufferedReader reader = new BufferedReader(new FileReader(input));
		String line;
		int n=0;
		// for each line (transaction)
		while (((line = reader.readLine()) != null) && n < lineCount) { 
			String[] lineSplited = line.split(" ");
			
			// convert the array of strings to an array of integers (items)
			int[] transaction = new int[lineSplited.length];
			for (int i = 0; i < lineSplited.length; i++) {
				transaction[i] = Integer.parseInt(lineSplited[i]);
			} 
			processTransaction(transaction);
			
			n++;

		}// while
		reader.close();
//		System.out.println("Transaction read from file:" + n);
	}


	/**
	 * Mine recent frequent itemsets from the current tree and 
	 * save the result to a file
	 * @throws IOException
	 * @param outputPath the output file path
	 */
	public void performMining_saveResultToFile(String outputPath) throws IOException {
		// Perform mining
		long startMiningTimeStamp = System.currentTimeMillis();
		
		tree.patternMining_saveToFile(outputPath);
	
		miningTime = System.currentTimeMillis() - startMiningTimeStamp;
		System.gc();
		checkMemory();
	}
	
	/**
	 * Mine recent frequent itemsets from the current tree and 
	 * save the result to memory
	 * @throws IOException
	 * @param outputPath the output file path
	 * @return 
	 */
	public Hashtable<int[], Double> performMining_saveResultToMemory() throws IOException {
		// Perform mining
		long startMiningTimeStamp = System.currentTimeMillis();
		
		Hashtable<int[], Double> patterns = tree.patternMining_saveToMemory();
		
		checkMemory();
		miningTime = System.currentTimeMillis() - startMiningTimeStamp;
		
		return patterns;
	}

	/**
	 * Process a transaction (add it to the tree and update itemsets
	 * @param transaction an array of integers
	 */
	public void processTransaction(int[] transaction) {
		double startCTimestamp = System.currentTimeMillis();
		// process the transaction
		tree.updateParams(transaction);
		tree.insertItemset(transaction);
		
		// force pruning every 1000 transactions
		if (tree.getK() % 10000 == 0)
			tree.forcePruning(tree.root);
		
		sumTransactionInsertionTime += (System.currentTimeMillis() - startCTimestamp);
	}

	/**
	 * Check the current memory consumption to record the maximum memory usage.
	 */
	private void checkMemory() {
		// Runtime.getRuntime().gc();
		double currentMemory = (Runtime.getRuntime().totalMemory() -  Runtime.getRuntime().freeMemory())
				/ 1024d / 1024d;
		if (currentMemory > maxMemory) {
			maxMemory = currentMemory;
		}
	}
	
	/**
	 * Set the decay rate
	 * @param b  decay base 
	 * @param h decay-base life
	 */
	public void setDecayRate(double b, double h) {
		tree.setDecayRate(b,h);
	}

	/**
	 * Print statistics about the algorithm execution to the console.
	 */
	public void printStats() {
		System.out.println("=============  ESTDEC - STATS =============");
		System.out.println(" Number of nodes : " + tree.nodeCount(tree.root));
		System.out.println(" Frequent itemsets count : " + tree.patternCount);
		System.out.println(" Maximum memory usage : " + maxMemory + " mb");
		System.out.println(" Number of transactions: " + tree.getK());
		System.out.println(" Total insertion time ~ " + sumTransactionInsertionTime + " ms");
		System.out.println(" Insertion time per transaction ~ "  + sumTransactionInsertionTime / tree.getK() + " ms");
		System.out.println(" Mining time ~ " + miningTime + " ms");
		System.out.println("===================================================");
	}
}
