package dotnet54.tscore.threading;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import dotnet54.applications.tschief.TSChiefOptions;
import dotnet54.classifiers.tschief.results.TSChiefForestResult;
import dotnet54.classifiers.tschief.results.TSChiefTreeResult;
import dotnet54.classifiers.tschief.TSCheifForest;
import dotnet54.classifiers.tschief.TSCheifTree;
import dotnet54.tscore.data.Dataset;
import dotnet54.util.PrintUtilities;

public class MultiThreadedTasks {

	private ExecutorService executor;
	private int pool_size;
	private int verbosity;

	public MultiThreadedTasks(int numThreads, int verbosity) {
		this.verbosity = verbosity;

		if (numThreads == 0) { // auto
			numThreads = Runtime.getRuntime().availableProcessors();
		}

		setExecutor(Executors.newFixedThreadPool(numThreads));

		// this is important for slurm jobs because
		// Runtime.getRuntime().availableProcessors() does not equal SBATCH argument
		// cpus-per-task
		if (executor instanceof ThreadPoolExecutor) {
			pool_size = ((ThreadPoolExecutor) executor).getMaximumPoolSize();
		}

//		System.out.println("Using " + pool_size + " threads (CPUs=" + Runtime.getRuntime().availableProcessors() + ")");

	}

//	public ExecutorService createExecutor(int num_threads) {
//		return Executors.newFixedThreadPool(num_threads);
//	}

	public ExecutorService getExecutor() {
		return executor;
	}

	public void setExecutor(ExecutorService executor) {
		this.executor = executor;
	}

	public ThreadPoolExecutor getThreadPool() {

		if (executor instanceof ThreadPoolExecutor) {
			return ((ThreadPoolExecutor) executor);
		} else {
			return null;
		}
	}

	public class TrainingTask implements Callable<Integer> {
		TSCheifTree[] trees;
		Dataset train;
		int start;
		int end;

		public TrainingTask(TSCheifTree[] trees, int start, int end, Dataset train) {
			this.trees = trees;
			this.start = start;
			this.end = end;
			this.train = train;
		}

		@Override
		public Integer call() throws Exception {
			
			int indices[];
			for (int k = start; k < end; k++) {
				trees[k].fit(train);

				if (verbosity > 0) {
					System.out.print(k + ".");
					if (verbosity > 2) {
						PrintUtilities.printMemoryUsage(true);

						// TODO check an atomic integer
//						if ((i+1) % 20 == 0) {
//							System.out.println();
//						}
					}
				}
			}

			return null;
		}

	}
	
//	public class TestingTask implements Callable<Integer> {
//		ProximityForest forest;
//		TSDataset test_data;
//		int start;
//		int end;
//
//		TIntIntMap num_votes;
//		List<Integer> max_voted_classes;
//
//		public TestingTask(ProximityForest forest, int start, int end, TSDataset test) {
//			this.forest = forest;
//			this.start = start;
//			this.end = end;
//			this.test_data = test;
//		}
//
//		@Override
//		public Integer call() throws Exception {
//			int correct = 0;
//
//			num_votes = new TIntIntHashMap(AppContext.num_classes);
//			max_voted_classes = new ArrayList<Integer>();
//
//			int predicted_class;
//			int actual_class;
//			int size = test_data.size();
//
//			for (int i = start; i < end; i++) {
//				actual_class = test_data.get_class(i);
//				predicted_class = predict(test_data.get_series(i));
//				if (actual_class == predicted_class) {
//					correct++;
//				}
//
////				if (AppContext.verbosity > 0) {
////					if (i % AppContext.print_test_progress_for_each_instances == 0) {
//				System.out.print(".");
////					}				
////				}
//			}
//
//			return correct;
//		}
//
//		private Integer predict(TimeSeries query) throws Exception {
//			int label;
//			int max_vote_count = -1;
//			int temp_count = 0;
//
//			num_votes.clear(); // TODO
//			max_voted_classes.clear();
//
//			for (int i = 0; i < forest.getTrees().length; i++) {
//				label = forest.getTree(i).predict(query);
//
//				num_votes.put(label, num_votes.get(label) + 1);
//			}
//
////				System.out.println("vote counting using uni dist");
//
//			for (int key : num_votes.keys()) {
//				temp_count = num_votes.get(key);
//
//				if (temp_count > max_vote_count) {
//					max_vote_count = temp_count;
//					max_voted_classes.clear();
//					max_voted_classes.add(key);
//				} else if (temp_count == max_vote_count) {
//					max_voted_classes.add(key);
//				}
//			}
//
//			int r = AppConfig.getRand().nextInt(max_voted_classes.size());
//
//			// collecting some stats
//			if (max_voted_classes.size() > 1) {
////				this.result.majority_vote_match_count++;
//			}
//
//			return max_voted_classes.get(r);
//		}
//
//	}

	public class TestingPerModelTask implements Callable<Integer> {
		TSCheifForest forest;
		Dataset test_data;
		List<Integer> model_indices;
		TSChiefForestResult result;

		public TestingPerModelTask(TSCheifForest ensemble, List<Integer> base_model_indices, Dataset test,
								   TSChiefForestResult result) {
			this.forest = ensemble;
			this.model_indices = base_model_indices;
			this.test_data = test;
			this.result = result;
		}

		@Override
		public Integer call() throws Exception {
			TSChiefTreeResult result;

			for (int i = 0; i < model_indices.size(); i++) {
				int model_index = model_indices.get(i);
				result = (TSChiefTreeResult) forest.getTree(model_index).predict(test_data);
				this.result.addBaseModelResult(model_index, result);
				if (verbosity > 0) {
					System.out.print(".");
				}
			}

			return null;
		}

//		private Integer predict(TimeSeries query) throws Exception {
//			int label;
//			int max_vote_count = -1;
//			int temp_count = 0;
//			
//			num_votes.clear(); //TODO
//			max_voted_classes.clear();
//
//			for (int i = 0; i < forest.getTrees().length; i++) {
//				label = forest.getTree(i).predict(query);
//				
//				num_votes.put(label, num_votes.get(label) + 1);
//			}
//			
////				System.out.println("vote counting using uni dist");
//			
//			for (int key : num_votes.keys()) {
//				temp_count = num_votes.get(key);
//				
//				if (temp_count > max_vote_count) {
//					max_vote_count = temp_count;
//					max_voted_classes.clear();
//					max_voted_classes.add(key);
//				}else if (temp_count == max_vote_count) {
//					max_voted_classes.add(key);
//				}
//			}
//
//			
//			int r = AppConfig.getRand().nextInt(max_voted_classes.size());
//			
//			//collecting some stats
//			if (max_voted_classes.size() > 1) {
////				this.result.majority_vote_match_count++;
//			}
//			
//			return max_voted_classes.get(r);
//		}

	}

}
