package learn.struct.algs;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import learn.struct.scoring.GraphScorable;
import learn.struct.scoring.bjp.BJP;
import learn.struct.scoring.ibmap.IBScore;
import learn.struct.scoring.mpl.MPL;
import learn.struct.scoring.search.HillClimbing;
import rep.UndirectedGraph;
import util.files.UndirectedGraphParser;
import util.math.UndirectedGraphsQualityMeasures;
import dataset.Dataset;
import dataset.DatasetType;
import dataset.FileHandler;
import dataset.adtree.ADNode;

/**
 * This class runs the HillClimbing algorithm. It can be used with three
 * different scoring functions: MPL @see learn.struct.scoring.mpl.MPL , IBMAP @see
 * learn.struct.scoring.ibmap.IBScore , and BJP @see
 * learn.struct.scoring.bjp.BJP Markov network structure learning
 * 
 */
public class TestHillClimbing {
	
	public static String[] scoringFunctions;
	private static String dir;
	private static String datasetName;
	private static String resultsDir;
	private static int domainSize = 2;
	private static Dataset dataset;
	private static String testSufix;
	private static String trainSufix;
	private static int D;
	private static int numberOfTestsForAccuracy = 2000;
	private static boolean useADTree;
	private static String output;

	/**
	 * Runs the experiment
	 * @param args
	 * @throws IOException
	 * @throws ParseException 
	 */
	public static void main(String[] args) throws IOException, ParseException {
		if (args.length == 0) {
			/* Default arguments used */
			String arg = "-sf mpl ibscore bjp "
					+ "-dir data "
					+ "-dataset msnbc " + "-numTests 2000 "
					+ "-resultsDir results "
					+ "-testSufix .test.data " + "-trainSufix .ts.data "
					+ "-D 100 " + "-useADTree true";
			runExperiment(arg.split(" "));
		} else {
			/* External arguments used */
			runExperiment(args);
		}

	}

	/**
	 * Runs the experiment using the specified arguments in @arg args.
	 * @param args Arguments: 	 * -dir : Path to datasets folder .
	 * -sf : Scoring function used. Available values are: {bjp, ibscore, mpl} . 
	 * -D: Number of data points to read from data .
	 * -datasetName: Name of dataset without sufixes.  
	 * -trainSufix: Sufix of train dataset. It will learn the structure from the file in $dir/$datasetName$trainSufix .  
	 * -testSufix: Sufix of test dataset. It will evaluate the structure from the file in $dir/$datasetName$testSufix . 
	 * -resultsDir: Folder where the results file will be stored .
	 * -useADTree: boolean value specifying if ADTree cache is used to get better performance for contingency tables construction.   
	 * @throws IOException
	 * @throws ParseException
	 */
	private static void runExperiment(String[] args) throws IOException, ParseException {
		/* Arguments parsing */
		parseParameters(args);

		/* Running the experiment for each algorithm in param -alg */
		for (String sf : scoringFunctions) {
			
			/* Read data set */
			DatasetType type = DatasetType.CSV;
			File fileDs = new java.io.File(dir + File.separator + datasetName
					+ trainSufix);
			dataset = FileHandler.readDataset(fileDs, D, type);

			/* Set binary cardinalities for the example data set */
			int n = dataset.getDomainVariables().size();
			int[] card = new int[n];
			for (int i = 0; i < n; ++i)
				card[i] = 2;
			dataset.setCard(card);

			/* ADTree cache use if -useADTree parameter is true */
			ADNode adTree = null;
			if (useADTree)
				adTree = ADNode.makeADNode(dataset);

			/* Runtime */
			long runtime = new GregorianCalendar().getTimeInMillis();

			GraphScorable scoringFunction = null;

			/* Scoring functions that can be used: 
			 * bjp (Blankets Joint posterior),
			 * ibscore (IBMAP)
			 * mpl (Marginal Pseudo-likelihood)
			 * */
			if (sf.equals("bjp"))
				scoringFunction = new BJP();
			else if (sf.equals("ibscore"))
				scoringFunction = new IBScore();
			else if (sf.equals("mpl"))
				scoringFunction = new MPL();

			/* Hill climbing search*/
			HillClimbing hc = new HillClimbing(n, dataset, scoringFunction,
					true, useADTree, adTree);
			hc.setIncremental(true);
			hc.run(new UndirectedGraph(n));
			runtime = new GregorianCalendar().getTimeInMillis() - runtime;

			/* Saving results */
			UndirectedGraph outputGraph = hc.getOutputNetwork();
			String resultsFile = resultsDir + File.separator
					+ "rawResults_generatingModel_" + datasetName + "_sf_"
					+ sf;
			
			/* Saving libra file for parameters learning with Libra toolkit */
			UndirectedGraphParser.writeGraphInLibraFormat(outputGraph,
					domainSize, resultsFile + ".mn");
			
			/* Saving .graph file for storing tha adjacency matrix of the graph learned */
			UndirectedGraphParser.writeGraphInAdjacencyMatrixFormat(
					outputGraph, resultsFile + ".graph");

			/* Computing accuracy of the learned structure in the test data set */
			double accuracy = UndirectedGraphsQualityMeasures.accuracy(
					outputGraph,
					dir + File.separator + datasetName + testSufix, D,
					numberOfTestsForAccuracy, 123456);
			
			/* Creating results file */
			FileWriter fstream;
			BufferedWriter out;

			fstream = new FileWriter(resultsFile + ".csv", true);
			out = new BufferedWriter(fstream);
			
			SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
			output = sf + " " + runtime + " " + accuracy + " "
					+ D + " " + datasetName + " " + dt.format(new Date()) ;
			out.write(output);
			out.write("\n");
			out.close();

			System.out.println(output);
		}
	}

	/**
	 * 
	 * Parsing external parameters.  
	 *  
	 * @param args Arguments: 	 * -dir : Path to datasets folder .
	 * -sf : Scoring function used. Available values are: {bjp, ibscore, mpl} . 
	 * -D: Number of data points to read from data .
	 * -datasetName: Name of dataset without sufixes.  
	 * -trainSufix: Sufix of train dataset. It will learn the structure from the file in $dir/$datasetName$trainSufix .  
	 * -testSufix: Sufix of test dataset. It will evaluate the structure from the file in $dir/$datasetName$testSufix . 
	 * -resultsDir: Folder where the results file will be stored .
	 * -useADTree: boolean value specifying if ADTree cache is used to get better performance for contingency tables construction.   
	 */
	private static void parseParameters(String[] args) {
		for (int i = 0; i < args.length; i++) {
			if (args[i].equals("-sf")) {
				List<String> aux = new ArrayList<String>();
				int dIndex = i + 1;
				while (!args[dIndex].contains("-") || dIndex == args.length - 1) {
					aux.add(args[dIndex]);
					++dIndex;
				}
				scoringFunctions = new String[aux.size()];
				scoringFunctions = aux.toArray(scoringFunctions);
			} else if (args[i].equals("-dir")) {
				dir = args[i + 1];
			} else if (args[i].equals("-D")) {
				D = new Integer(args[i + 1]);
			} else if (args[i].equals("-dataset")) {
				datasetName = args[i + 1];
			} else if (args[i].equals("-numTests")) {
				numberOfTestsForAccuracy = new Integer(args[i + 1]);
			} else if (args[i].equals("-testSufix")) {
				testSufix = args[i + 1];
			} else if (args[i].equals("-trainSufix")) {
				trainSufix = args[i + 1];
			} else if (args[i].equals("-resultsDir")) {
				resultsDir = args[i + 1];
			} else if (args[i].equals("-useADTree")) {
				useADTree = new Boolean(args[i + 1]);
			}
		}
	}

}
