// Copyright (C) 2011-2012 CRS4.
//
// This file is part of Seal.
//
// Seal 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.
//
// Seal 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.
//
// You should have received a copy of the GNU General Public License along
// with Seal.  If not, see <http://www.gnu.org/licenses/>.


package tests.it.crs4.seal.recab;

import org.junit.*;
import static org.junit.Assert.*;

import it.crs4.seal.recab.VariantTable;
import it.crs4.seal.recab.ArrayVariantTable;
import it.crs4.seal.recab.ArrayListVariantTable;
import it.crs4.seal.recab.HashSetVariantTable;
import it.crs4.seal.recab.VariantRegion;
import it.crs4.seal.recab.VariantReader;
import it.crs4.seal.recab.VcfVariantReader;
import it.crs4.seal.common.FormatException;

import java.util.ArrayList;
import java.util.Random;
import java.util.Set;
import java.io.File;
import java.io.FileReader;
import java.io.PrintStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BenchmarkVariantTable
{
	public static class Run
	{
		private long startLoad = 0;
		private long endLoad = 0;
		private long startQuery = 0;
		private long endQuery = 0;
		private int nQueries = 0;
		private int tableSize = 0;
		private long memUsage = 0;

		public void setRun( long sLoad, long eLoad, long sQuery, long eQuery, int nQueries, int tableSize)
		{
			startLoad = sLoad;
			endLoad = eLoad;
			startQuery = sQuery;
			endQuery = eQuery;
			this.nQueries = nQueries;
			this.tableSize = tableSize;
		}

		public long getLoadTime() { return endLoad - startLoad; }
		public long getQueryTime() { return endQuery - startQuery; }
		public long getNQueries() { return nQueries; }
		public long getTableSize() { return tableSize; }

		public void report(String klas, PrintStream out)
		{
			out.println("Benchmark for " + klas);
			out.println("\ttable size: " + getTableSize());
			out.println("\tn queries: " + getNQueries());
			out.println("\tload time: " + String.format("%.3f", (getLoadTime() / 1e9)) );
			out.println("\tquery time: " + String.format("%.3f", (getQueryTime() / 1e9)) );
			out.println("\tloads / sec: " + String.format("%.3f", getTableSize() / (getLoadTime() / 1e9) ) );
			out.println("\tqueries / sec: " + String.format("%.3f", getNQueries() / (getQueryTime() / 1e9) ) );
			out.println("\tmemory usage: " + String.format("%.3f MB", memUsage / ((float)1024*1024)) );
		}

		public void snapMemoryUsage()
		{
			System.gc();
			System.gc();
			Runtime r = Runtime.getRuntime();
			memUsage = r.totalMemory() - r.freeMemory();
		}
	}

	public static class Benchmark
	{
		private VariantTable table;
		private VariantReader reader;
		private int nQueries;

		private static class QGenerator
		{
			private ArrayList<String> contigs;
			private int maxpos;
			private Random rnd;

			public QGenerator(Set<String> contigs, int maxpos)
			{
				this.maxpos = maxpos+1;

				this.contigs = new ArrayList<String>(contigs.size());
				for (String c: contigs)
					this.contigs.add(c);

				rnd = new Random();
			}

			public void next(VariantRegion snp)
			{
				snp.setContigName( contigs.get( rnd.nextInt(contigs.size()) ));
				snp.setPosition( rnd.nextInt(maxpos) );
			}
		}

		public Benchmark(VariantTable table, VariantReader reader)
		{
			this.table = table;
			this.reader = reader;
			nQueries = 100000000;
		}

		public void setNQueries(int n)
		{
			nQueries = n;
		}

		public Run run() throws Exception
		{
			System.gc();
			Run run = new Run();

			long loadStart = System.nanoTime();
			table.load(reader);
			long loadEnd = System.nanoTime();
			System.gc();
			run.snapMemoryUsage();

			QGenerator generator = new QGenerator(table.getContigs(), 250000000);
			VariantRegion snp = new VariantRegion();

			long queryStart = System.nanoTime();
			for (int i = nQueries; i > 0; --i)
			{
				generator.next(snp);
				table.isVariantLocation(snp.getContigName(), snp.getPosition());
			}
			long queryEnd = System.nanoTime();

			run.setRun(loadStart, loadEnd, queryStart, queryEnd, nQueries, table.size());

			return run;
		}
	}

	public static void main(String[] args) throws Exception
	{
		if (args.length != 1)
		{
			System.err.println("Usage: BenchmarkVariantTable <vcf file>");
			System.exit(1);
		}

		File vcf = new File(args[0]);
		VcfVariantReader vcfReader = new VcfVariantReader(new FileReader(vcf));

		Class[] tableClasses = new Class[] { HashSetVariantTable.class, ArrayListVariantTable.class, ArrayVariantTable.class };

		for (Class klas: tableClasses)
		{
			System.out.println("Trying " + klas);
			try
			{
				vcfReader = new VcfVariantReader(new FileReader(vcf));
				VariantTable table = (VariantTable)klas.newInstance();
				Benchmark bench = new Benchmark(table, vcfReader);
				System.out.println("Running bechmark");
				Run r = bench.run();
				System.out.println("finished\n\n");
				r.report(klas.toString(), System.out);
				Thread.sleep(5000);
			}
			catch (OutOfMemoryError e) {
				System.err.println("ran out of memory with class " + klas);
			}
			catch (Exception e) {
				System.err.println("Error with class " + klas);
			}
		}
	}
}
