package org.rki.readclassifier;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.LinkedList;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;



public class Classifier {
	
	public enum Cutoff {NONE, LOWER, HIGHER}
	
//	public static Logger logger = Logger.getLogger(Classifier.class.getName());
	public static KmerDistribution[] kmersOrg1;
	public static KmerDistribution[] kmersOrg2;
	public static int kmersize;
	public static long startTimeMillis;
	
	public static boolean showTime = false;
	public static int threads = 4;
	public static int inChunksize = 500;
	
	public static int normalizeTo = -1;
	
	public static double cutoffLower = 0.0;
	public static double cutoffHigher = 0.0;
	
	public static Cutoff useCutoff = Cutoff.NONE; 
	
	public static FileInputStream in1 = null;
	public static FileInputStream in2 = null;

	public static FileInputStream trainin = null;

	public static FileOutputStream out1 = null;
	public static FileOutputStream out2 = null;
	
	public static ReadClassifier[] classifiers = {new DistClassifier(), new MMClassifier()};
	public static ReadClassifier selectedClassifier = null;
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			init();
			parseOptions(args);
//			parseKmers();
			process();
			finish();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void finish() {
		if(showTime) {
			long currentTime = System.currentTimeMillis();
			System.out.println("Time taken: " + (currentTime - startTimeMillis) + " ms");
		}
	}
	
	public static void process() throws Exception {
		selectedClassifier.readData(trainin);
		Reader reader = new Reader(in1, in2, inChunksize, 10000, threads);
		Writer writer = new Writer(out1, out2, 100000);
		LinkedList<ClassifierThread> threadList = new LinkedList<ClassifierThread>();
                reader.start();
                writer.start();
		while(threadList.size() < threads) {
			threadList.add(new ClassifierThread(reader, writer, selectedClassifier));
		}
		for(ClassifierThread c : threadList) {
			c.start();
		}
        reader.join();
		for(ClassifierThread c : threadList) {
			c.join();
		}
                writer.join();
	}
		
	public static void init() throws Exception {
		startTimeMillis = System.currentTimeMillis();
//		FileAppender appender = new FileAppender(new SimpleLayout(), "classifier.log", false);
//		logger.addAppender(appender);
//		logger.setLevel(Level.ALL);		
	}
	
	public static void printClassifiers() {
		for(ReadClassifier classifier : classifiers) {
			System.out.println("  " + classifier.getName() + ": " + classifier.getDescription());
		}
	}
	
	public static void parseOptions(String[] args) throws ParseException, FileNotFoundException {
		Options options = new Options();
		options.addOption("1", "in_1", true, "Input fastq/fasta file containing the first read");
		options.addOption("2", "in_2", true, "Input fastq/fasta file containing the second read");
		options.addOption("c", "classifier", true, "Name of classifier to use");
		options.addOption("o1", "out_1", true, "Output fastq file for the first read");
		options.addOption("o2", "out_2", true, "Output fastq file for the second read");
		options.addOption("cl", "cutoff_lower", true, "Output only reads with a score lower than or equal to this value, use m1 for -1");
		options.addOption("ch", "cutoff_higher", true, "Output only reads with a score higher than or equal to this value, use m1 for -1");
		options.addOption("n", "normalize_scores", true, "Normalize scores to this length (default: Do not normalize)");
		options.addOption("d", "datafile", true, "File containing data from training");
		options.addOption("t", "threads", true, "Number of threads");
		options.addOption("C", "inchunksize", true, "Input chunk size (don't touch this unless you know what you are doing)");
		options.addOption("T", "show_time", false, "Show processing time");
		options.addOption("l", "list_classifiers", true, "List available classifiers");
		options.addOption("h", "help", false, "Print usage instructions");
		
		CommandLineParser parser = new GnuParser();
		CommandLine cmd = parser.parse(options, args);
		
		if(cmd.hasOption("h")) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("java -jar classifier.jar", options);
			System.exit(0);
		}
		if(cmd.hasOption("l")) {
			printClassifiers();
			System.exit(0);
		}
		
		if(!cmd.hasOption("c")) {
			System.out.println("Please provide a classifier to use. Available classifiers:");
			printClassifiers();
			System.exit(1);
		}
		else {
			for(ReadClassifier classifier : classifiers) {
				if(classifier.getName().equals(cmd.getOptionValue("c"))) {
					selectedClassifier = classifier;
					break;
				}
			}
			if(selectedClassifier == null) {
				System.out.println("Invalid classifier selected. Available classifiers:");
				printClassifiers();
			}
		}
		if(!cmd.hasOption("d")) {
			System.out.println("Data from training has to be provided.");
			System.exit(1);
		}
		if(!cmd.hasOption("1")) {
			System.out.println("Input fastq for the first read must be provided.");
			System.exit(1);
		}
		if(!cmd.hasOption("o1")) {
			System.out.println("Output fastq for the first read must be provided.");
			System.exit(1);
		}
		if((cmd.hasOption("o2") && !cmd.hasOption("2"))) {
			System.out.println("If output fastq for the second read is provided, input fastq for the second read must also be provided.");
			System.exit(1);
		}
		if((!cmd.hasOption("o2") && cmd.hasOption("2"))) {
			System.out.println("If input fastq for the second read is provided, output fastq for the second read must also be provided.");
			System.exit(1);
		}
		trainin = new FileInputStream(new File(cmd.getOptionValue("d")));
		in1 = new FileInputStream(new File(cmd.getOptionValue("1")));
		out1 = new FileOutputStream(new File(cmd.getOptionValue("o1")));
		if(cmd.hasOption("2")) {
			in2 = new FileInputStream(new File(cmd.getOptionValue("2")));
			out2 = new FileOutputStream(new File(cmd.getOptionValue("o2")));			
		}
		if(cmd.hasOption("t")) {
			threads = Integer.parseInt(cmd.getOptionValue("t"));
		}
		if(cmd.hasOption("C")) {
			inChunksize = Integer.parseInt(cmd.getOptionValue("C"));
		}
		if(cmd.hasOption("cl")) {
			String val = cmd.getOptionValue("cl");
			if(val.charAt(0) == 'm') 
				cutoffLower = -1*Double.parseDouble(val.substring(1));				
			else
				cutoffLower = Double.parseDouble(val);				
			useCutoff = Cutoff.LOWER;
		}
		if(cmd.hasOption("ch")) {
			String val = cmd.getOptionValue("ch");
			if(val.charAt(0) == 'm') 
				cutoffHigher = -1*Double.parseDouble(val.substring(1));				
			else
				cutoffHigher = Double.parseDouble(val);				
			useCutoff = Cutoff.HIGHER;
		}
		if(cmd.hasOption("n")) {
			normalizeTo = Integer.parseInt(cmd.getOptionValue("n"));
			if(normalizeTo <= 0) {
				System.out.println("Length for normalization must be an integer > 0.");
				System.exit(1);
			}
		}
		showTime = cmd.hasOption("T");
	}
		
	public static int calculateVal(char[] kmer) {
		int res = 0;
		for(char base : kmer) {
			res = res << 2;
			switch (base) {
			case 'A':
				break;
			case 'C':
				res |= 1;
				break;
			case 'G':
				res |= 2;
				break;
			case 'T':
				res |= 3;
				break;
			default:
				break;
			}
		}
		return res;
	}
}
