package cluster;

import graph.APSP;
import graph.DistanceMeasure;
import graph.ITimeGraph;
import graph.NodeWeightedTimeGraph;
import graph.WeightedTimeGraph;
import ilog.cplex.cppimpl.doubleArray;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;

import cluster.Common.GDIST;
import misc.Common;
import misc.ExtToolsDriver;
import misc.IO;

public class Experiment {
	
	// Fixed Budget
	public static void getErrorDecreaseBudget(ITimeGraph tg, 
											  DistanceMeasure dm, String prefix,
											  int maxbudget, int numperm,
											  int numslices,
											  double[] sdcdf,
											  boolean probeall
											  ) {
		Random r = new Random();
		
		int[][] perms = getPerms(tg, numperm);
		
		// result
		SplitTreeNode tree = null;
		double[][] sse = new double[maxbudget][2]; 
		HashSet<Integer> sampled = new HashSet<Integer>();
		int t;
		// compute the split clustering for every slice 
		for (int tt = 0; tt < numslices; tt++) {
			do{
				t = Arrays.binarySearch(sdcdf, r.nextDouble());
				if (t < 0) t = t*(-1);
			} while (sampled.contains(t));
			
			// ACTUAL
			moveWeights(tg, t);

			System.err.print("Actual\n");
			StopCriteria sc = new StopCriteria(-1, -1);
			for (int b=0; b < maxbudget; b++) {
				// control the resolution
				sc.numSplits = b;
				if (b==0) {
					tree = TreeSplitClustering.scluster(tg, dm, sc,probeall);
				} else {
					TreeSplitClustering.updateSplits(tree, tg, dm, sc,probeall);
				}
			    sse[b][0] += tree.aggregateSSE()/numslices;
				System.err.print(b + "\t" + tree.aggregateSSE() + "\n");
			    //tree.visualize();
			}
			
			//tree.visualize();
			// clear
			tree.pruneSubtree();
			tree = null;
			
			System.err.print("Permutation\n");
			for (int p = 1; p < numperm+1; p++) {
				// choose permutation
				movePermutedWeights(tg, perms[r.nextInt(perms.length)], t);	
				for (int b=0; b < maxbudget; b++) {
					sc.numSplits = b;
					if (b==0) {
						tree = TreeSplitClustering.scluster(tg, dm, sc,probeall);
					} else {
						TreeSplitClustering.updateSplits(tree, tg, dm, sc,probeall);
					}
				    sse[b][1] += tree.aggregateSSE()/(numperm*numslices);
					System.err.print(b + "\t" + tree.aggregateSSE() + "\n");
				    //tree.visualize();
				}
				
			}	
			//tree.visualize();
			tree.pruneSubtree();
			tree = null;
		} 
		
		double norm = sse[0][0];
		for (int i = 0; i < sse.length; i++) {
			for (int j =0 ; j < sse[0].length; j++) {
				sse[i][j] = sse[i][j]/norm;
			}
		}
		
		String[] x = new String[maxbudget];
		for (int i=0; i < maxbudget; i++) x[i] = "" + i;
		String[] curve_names = {"Actual","Random"};
		
		ExtToolsDriver.plotCurves(x, curve_names, sse, "Budget", "%SSE", prefix + "_budgetsse");
	}
	
	
	// Fixed error
	public static void getNumParamFixedError(ITimeGraph tg, 
			  DistanceMeasure dm, String prefix,
			  int errlevels, int numperm,
			  int numslices,
			  double[] sdcdf,
			  boolean probeall
			  ) {
		Random r = new Random();
		// Precompute permutations of nodes
		int perms[][] = getPerms(tg, numperm);
		SplitTreeNode tree = null;

		// result
		double[][] nparam = new double[errlevels][2]; 
		int t;
		HashSet<Integer> sampled = new HashSet<Integer>();
		// compute the split clustering for every slice 
		for (int tt = 0; tt < numslices; tt++) {
			
			do{
				t = Arrays.binarySearch(sdcdf, r.nextDouble());
				if (t < 0) t = t*(-1);
			} while (sampled.contains(t));

			// ACTUAL
			moveWeights(tg, t);
			
			System.err.print("Actual\n");
			StopCriteria sc = new StopCriteria(Integer.MAX_VALUE, Double.NEGATIVE_INFINITY);
			for (int b=0; b < errlevels; b++) {
				sc.sseFraction = 1.0 - b*1.0/errlevels;
				if (b==0) {
					tree = TreeSplitClustering.scluster(tg, dm, sc,probeall);
				} else {
					TreeSplitClustering.updateSplits(tree, tg, dm, sc,probeall);
				}
				nparam[b][0] += tree.numSplitDecendants()*1.0/numslices;
				System.err.print(String.format("%f.1",(1.0 - b*1.0/errlevels)) + "\t" + tree.numSplitDecendants() + "\n");
				//tree.visualize();
			}

			//tree.visualize();
			tree.pruneSubtree();
			tree = null;

			System.err.print("Permutation\n");
			for (int p = 1; p < numperm+1; p++) {
				movePermutedWeights(tg, perms[r.nextInt(perms.length)], t);
				for (int b=0; b < errlevels; b++) {
					sc.sseFraction = 1.0 - b*1.0/errlevels;
					if (b==0) {
						tree = TreeSplitClustering.scluster(tg, dm, sc,probeall);
					} else {
						TreeSplitClustering.updateSplits(tree, tg, dm, sc,probeall);
					}
					nparam[b][1] += tree.numSplitDecendants()*1.0/(numslices*numperm*1.0);
					System.err.print(String.format("%f.1",(1.0 - b*1.0/errlevels)) + "\t" + tree.numSplitDecendants() + "\n");
					//tree.visualize();
				}

			}	
			//tree.visualize();
			tree.pruneSubtree();
			tree = null;
		} 
		
		String[] x = new String[errlevels];
		for (int i=0; i < errlevels; i++) x[i] = String.format("%.1f",(1.0 - i*1.0/errlevels));
		String[] curve_names = {"Actual","Random"};
		
		ExtToolsDriver.plotCurves(x, curve_names, nparam, "%SSE", "#Splits", prefix + "_fixedsse");
	}

// UTILS

	public static void movePermutedWeights(ITimeGraph tg, int[] perms, int t) {
		// set node/edge weights
		if (TreeSplitClustering.isNodeW(tg)) { // node weighted
			for (int i = 0; i < tg.getn(); i++) 
				tg.getGraph().wn[perms[i]] = tg.getTimeseries()[t][i];
		} else if (TreeSplitClustering.isEdgeW(tg)) { // edge weighted
			for (int i = 0; i < tg.getn(); i++) {
				for (int j = tg.getGraph().ind(i); j < tg.getGraph().ind(i+1); j++) {
					if (i < tg.getGraph().endv(j)) { // permute the first direction of the edge
						tg.getGraph().we[perms[j]] = tg.getTimeseries()[t][i];
						tg.getGraph().we[tg.getGraph().getReverseEdgeIndex(perms[j])] =
							tg.getTimeseries()[t][i];
					}
				}
			}
		}
	}

	public static void moveWeights(ITimeGraph tg, int t) {
		// set node/edge weights
		if (TreeSplitClustering.isNodeW(tg)) {
			for (int i = 0; i < tg.getn(); i++) 
				tg.getGraph().wn[i] = tg.getTimeseries()[t][i];
		} else if (TreeSplitClustering.isEdgeW(tg)) {
			for (int i = 0; i < tg.getm(); i++) 
				tg.getGraph().we[i] = tg.getTimeseries()[t][i];
		}
	}

	public static int[][] getPerms(ITimeGraph tg, int numperm) {
		// Precompute permutations of nodes
		int perms[][] = null;
		if (TreeSplitClustering.isNodeW(tg)) 
			perms = new int[100*numperm][tg.getn()];
		else if (TreeSplitClustering.isEdgeW(tg)) {
			perms = new int[100*numperm][tg.getm()];
		}
		for (int i=0; i < numperm*100; i++) {
			cluster.Common.getIndexPermutation(perms[i]);
		}
		return perms;
	}
	
	public static double[] getTimeSDCDF(ITimeGraph tg) {
		double[] sdcdf = new double[tg.gett()];
		double mean, sd;
		for (int t =0; t < tg.gett(); t++) {
			mean = 0; sd = 0;
			for (int i = 0; i < tg.getTimeseries()[t].length; i++) {
				mean += tg.getTimeseries()[t][i]/tg.getTimeseries()[t].length;
			}
			for (int i = 0; i < tg.getTimeseries()[t].length; i++) {
				sd += Math.pow(mean - tg.getTimeseries()[t][i],2);
			}
			sd /= tg.getm();
			sdcdf[t] = Math.sqrt(sd);
			if (t > 0) sdcdf[t] += sdcdf[t-1];
		}
		for (int t=0; t < tg.gett(); t++) sdcdf[t] /= sdcdf[sdcdf.length-1];
		return sdcdf;
	}
	
	public static void runTraffic100DataCompressibility() throws IOException {
		
		NodeWeightedTimeGraph ntg = null;
		WeightedTimeGraph etg = null;
		String name = null;
		String act = "";
		DistanceMeasure dm;
		int maxb, nump, numsl;
		double[] sd;
		
		// ##################### TRAFFIC #############################################
		// 100 edges traffic

//		etg = IO.readEWGraph("/home/petko/data/seine/15_traffic/preprocessed/graphall", 
//		 "/home/petko/data/seine/15_traffic/preprocessed/ewall");
		// 100 nodes traffic
		ntg = IO.readNWGraph("/home/petko/data/seine/15_traffic/preprocessed/graphall", 
		 "/home/petko/data/seine/15_traffic/preprocessed/nwall");
		
//		ntg = IO.readNWGraph("/home/petko/data/seine/15_traffic/preprocessed/100.speeds.graph", 
//		 "/home/petko/data/seine/15_traffic/preprocessed/100.speeds.data");
//		for (int i = 0; i < ntg.getm(); i++) ntg.we[i] = 1;
		
		name = "traffic100";
		
		// Precompute ptp distance
		sd = getTimeSDCDF(ntg);
		dm = new APSP(ntg,true);
		maxb=15; nump=5; numsl = 30;
		getErrorDecreaseBudget(ntg,dm,name+act,maxb,nump,numsl,sd,false);
		getNumParamFixedError(ntg,dm,name + act ,10,5,10,sd,false);
	}
	
	public static void runWikiDataCompressibility() throws IOException {
		
		NodeWeightedTimeGraph ntg = null;
		WeightedTimeGraph etg = null;
		String name = null;
		String act = "";
		DistanceMeasure dm;
		int maxb, nump, numsl;
		double[] sd;
		
		// ##################### WIKIPEDIA #############################################
		// wiki Abortion
		act = "views08";
		//act = "edits";
		ntg = IO.readNWGraph("/home/petko/data/seine/03_wikipedia/preprocessed/gregress/graph.300.5", 
		  "/home/petko/data/seine/03_wikipedia/preprocessed/gregress/" + act+ ".300.5");
		
		// change all edges to 1 (or maybe 1/numlinks) ? 
		for (int i=0; i < ntg.getm(); i++) ntg.we[i] = 1;
		
		// take the log of views/edits
		for (int t = 0; t < ntg.gett(); t++) {
			for (int i=0; i < ntg.getn(); i++) { 
				if(ntg.wsl[t][i] > 1) 
					ntg.wsl[t][i] = (float)Math.log(ntg.wsl[t][i]);
				else ntg.wsl[t][i] = 0;
			}
		}
		name = "wikilog";
		
		// Precompute ptp distance
		sd = getTimeSDCDF(ntg);
		dm = new APSP(ntg,true);
		getErrorDecreaseBudget(ntg,dm,name + act ,20,5,10,sd,false);
		getNumParamFixedError(ntg,dm,name + act ,20,5,10,sd,false);
	}
	
	public static void runRandomDataCompressibility() throws IOException {
		
		NodeWeightedTimeGraph ntg = null;
		WeightedTimeGraph etg = null;
		String name = null;
		String act = "";
		DistanceMeasure dm;
		int maxb, nump, numsl;
		double[] sd;
		
		// ##################### RANDOM #############################################
		
		ntg = TreeSplitClustering.getSmallTestGraph(100);
		int t = 100, n = 100, m = 300, nclusters = 2;
		ntg = TreeSplitClustering.getRandomTestGraph(100,n,m,nclusters);
		name = "rand";
		
		// Precompute ptp distance
		sd = getTimeSDCDF(ntg);
		dm = new APSP(ntg,true);
		getErrorDecreaseBudget(ntg,dm,name + act ,20,5,10,sd,false);
		getNumParamFixedError(ntg,dm,name + act ,20,5,10,sd,false);
	}
	

	
	public static void main(String[] args) throws IOException{
		
		cluster.Common.DEBUG = true;
		misc.Common.RES_DIR = "/home/petko/Dropbox/slice_tree/paper/fig/";
		misc.Common.enablePlotting = true;
		
		NodeWeightedTimeGraph ntg = null;
		WeightedTimeGraph etg = null;
		String name = null;
		String act = "";
		DistanceMeasure dm;
		int maxb, nump, numsl;
		double[] sd;
		
//		runWikiDataCompressibility();
		runTraffic100DataCompressibility();
		runRandomDataCompressibility();
		
//		// ##################### FACEBOOK #############################################
//		// fb
//		name = "fb";
//		etg = IO.readEWGraph("/home/petko/data/seine/01_facebook/preprocessed/graph1000", 
//							 "/home/petko/data/seine/01_facebook/preprocessed/ew1000");
//		
//		sd = getTimeSDCDF(etg);
//		dm = new APSP(etg,true);
//		getErrorDecreaseBudget(etg,dm,name + act,10,5,10,sd);
//		getNumParamFixedError(etg,dm,name + act,10,5,10,sd);
		
	}
}