package org.aksw.simba.benchmark.startup;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.openrdf.query.BooleanQuery;
import org.openrdf.query.GraphQuery;
import org.openrdf.query.GraphQueryResult;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.sparql.SPARQLRepository;

import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.Syntax;

/**
 * Use this class to benchmark different triple stores based on the benchmark queries generated by FEASIBLE
 * @author Saleem
 *
 */
public class QueryEvaluation {
	public static RepositoryConnection con = null;
	public static void main(String[] args) throws RepositoryException, IOException, MalformedQueryException {
		String endpoint = "http://localhost:8890/sparql";
		String inputDir = "D:/workspace/FEASIBLE/Benchmarks_Evaluation/mix-benchmarks/dbpedia351/"; //dont forget last /
		int numberOfExecution =1;  // Number of rounds
		List<String> queries =  getFeasibleQueries(inputDir);
		String timeOutQueriesFile = "D:/QueryLogs/Results/Timeout Queries/Virtuoso.txt";
		List<String> timeOutQueries = getTimeOuts(timeOutQueriesFile);
		System.out.println("Total Queries: "+ queries.size());
		int timeOut = 180;  // in seconds
		Map<String, List<Long>> queryToRuntimes = runQueries(queries,endpoint,numberOfExecution,timeOut,timeOutQueries);
		printAverageRunTimes(queryToRuntimes,queries);
	}
	/**
	 * If you already have a file containing a list of queries which were timeout so no need to wait for those again.
	 * @param timeOutQueriesFile 
	 * @return List of time out queries
	 * @throws IOException 
	 */
	private static List<String> getTimeOuts(String timeOutQueriesFile) throws IOException {
		List<String> queries = new ArrayList<String> ();
		BufferedReader br = new BufferedReader(new FileReader(timeOutQueriesFile));
		String line ="";
		String query = br.readLine();
		while ((line = br.readLine()) != null)
		{	
			query=line;
			while((line = br.readLine())!=null && !line.startsWith("--------------"))
				query = query+ "\n"+ line ;
				queries.add(query);

		}
		br.close();
	
	return queries;
	
	}
	/**
	 * Print the average runtimes against individual benchark query and also print QpS and QMpH 
	 * @param queryToRuntimes A map storing query as key the corresponding runtime values as list 
	 * @param queries List of queries. We pass them in order to keep the order of the queries in final results
	 * @throws UnsupportedEncodingException
	 */
	private static void printAverageRunTimes(Map<String, List<Long>> queryToRuntimes, List<String> queries) throws UnsupportedEncodingException {
		double avgRunTimeSum = 0d;
		System.out.println("----------Runtimes------------");
		for(String query:queries)
		{
			String lineQuery = java.net.URLDecoder.decode(query, "UTF-8");
			lineQuery = lineQuery.replaceAll("\n", " ").replace("\r", " " );
			List<Long> runTimes = queryToRuntimes.get(query);
			double meanRunTime = getMean(runTimes);
			avgRunTimeSum = avgRunTimeSum + meanRunTime;
			//System.out.println("RunTimes"+ runTimes);
			System.out.println(meanRunTime);
			//System.out.println("\t" + lineQuery);
		}
		
		//System.out.println("Total avg time for benchmark of size "+queryToRuntimes.keySet().size()+" = "+avgRunTimeSum + " (ms)");
		System.out.println("QpS:"+ (queryToRuntimes.keySet().size()/avgRunTimeSum)*1000);	
		System.out.println("QMpH:"+(queryToRuntimes.keySet().size()/avgRunTimeSum)*3600000);
	}

	/**
	 * Get the mean of the runtimes values
	 * @param vector Vector
	 * @return Mean
	 */
	public static double getMean(List<Long> runTimes) {
		double sum = 0;
		for(int i=0;i<runTimes.size();i++)
		{
			sum = sum + runTimes.get(i);
		}
		return (sum/runTimes.size());
	}
	/**
	 * Run all the queries given in list 
	 * @param queries List of queries
	 * @param endpoint SPARQL endpoint
	 * @param numberOfExecution Number of rounds
	 * @param timeOut Timeout in seconds
	 * @param timeOutQueries List of time out queries for the given endpoint
	 * @return key to list of runtimes map
	 * @throws RepositoryException
	 * @throws MalformedQueryException
	 * @throws UnsupportedEncodingException
	 */
	private static Map<String, List<Long>> runQueries(List<String> queries, String endpoint,int numberOfExecution, int timeOut, List<String> timeOutQueries) throws RepositoryException, MalformedQueryException, UnsupportedEncodingException {
		initializeRepoConnection(endpoint);
		Map<String, List<Long>> queryToRuntimes = new ConcurrentHashMap<String, List<Long>>();
		List<Long> runTimes = new ArrayList<Long>();
		long runTime ;
		for(int i =0;i<numberOfExecution;i++)
		{
			System.out.println((i+1)+ " :Round started...");
			int queryNo= 1;
			for(String query:queries)
			{
				//System.out.println("------------\n"+java.net.URLDecoder.decode(query, "UTF-8"));
				System.out.println("\t"+queryNo+ " : query started...");
				String queryType = getQueryType(query);
				String curQuery = java.net.URLDecoder.decode(query, "UTF-8");
				//System.out.println(curQuery);
				if(timeOutQueries.contains(curQuery))
					{
					runTime = 180000;
					System.out.println("Recorded Time out:\n");
					System.out.println(curQuery+"\n\t");
					}
				else{
				long startTime = System.currentTimeMillis();
				long resultSize = executeQuery(query,queryType,timeOut);
				runTime = System.currentTimeMillis()-startTime;
				System.out.println("ResultSize: "+ resultSize);
				if(runTime>(timeOut*1000))
				{
					runTime = 180000;
					System.out.println("Time out:");
					System.out.println(curQuery);
				}
				}
			
				if(queryToRuntimes.containsKey(query))
				{
					runTimes = queryToRuntimes.get(query);
					synchronized (runTimes)	{
						runTimes.add(runTime);
					}
				}	
				else
				{
					ArrayList<Long> runTimes1 = new ArrayList<Long>();
					runTimes1.add(runTime);
					queryToRuntimes.put(query, runTimes1);
				}
				queryNo++;
 
			}
		}
		return queryToRuntimes;

	}

	/**
	 * Initialize repository for a SPARQL endpoint
	 * @param endpointUrl Endpoint Url
	 * @throws RepositoryException
	 */
	public static void initializeRepoConnection(String endpointUrl) throws RepositoryException {
		Repository repo = new SPARQLRepository(endpointUrl);
		repo.initialize();
		con = repo.getConnection();

	}

	/**
	 * Execute query 
	 * @param query Query String
	 * @param queryType Type of query {SELECT, CONSTRUCT, DESCRIBE, ASK}
	 * @param timeOut time out in secons
	 * @return resultSize after iterating over all results
	 * @throws RepositoryException
	 * @throws MalformedQueryException
	 * @throws UnsupportedEncodingException
	 */
	private static long executeQuery(String query, String queryType, int timeOut) throws RepositoryException, MalformedQueryException, UnsupportedEncodingException {
		long totalSize = 0;
		query = java.net.URLDecoder.decode(query, "UTF-8");
		query=  addNamedGraph(query, "http://dbpedia.org"); // this we need for fuseki
		if(queryType.equals("select") || queryType.equals("ask") )
		{
			try {
				if (queryType.equals("select"))
				{
					TupleQuery tupleQuery = con.prepareTupleQuery(QueryLanguage.SPARQL,query);
					tupleQuery.setMaxQueryTime(timeOut);
					TupleQueryResult res;
					res = tupleQuery.evaluate();
					totalSize = 0;
					while(res.hasNext())
					{
						res.next();
						totalSize++;
					}
				}
				else
				{
					BooleanQuery booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL,query );
					booleanQuery.setMaxQueryTime(timeOut);
					booleanQuery.evaluate();
					totalSize = 1;

				}

			} catch (QueryEvaluationException e) { //System.err.println(e.getMessage()); 
			}
		}
		else
		{
			try {
				GraphQuery gq = con.prepareGraphQuery(QueryLanguage.SPARQL, query);
				gq.setMaxQueryTime(timeOut);
				GraphQueryResult graphResult = gq.evaluate();
				totalSize = 0;
				while (graphResult.hasNext()) 
				{
					graphResult.next();
					totalSize++;
				}
			} catch (QueryEvaluationException e) {System.err.println(e.getMessage()); } 
			}
		return totalSize;
	}
	/**
	 * Add named graph to a query if one doesnt exist
	 * @param query query
	 * @param namedGraph NamedGraph
	 * @return New query with named graph
	 */
	public static String addNamedGraph(String query, String namedGraph) {
		Query jenaQuery = QueryFactory.create(query,Syntax.syntaxARQ);
		if (jenaQuery.getGraphURIs().isEmpty())
		{
			jenaQuery.addGraphURI(namedGraph);
			query = jenaQuery.toString();
		}
		return query;
		
	}

	/**
	 * Get query type
	 * @param query  query
	 * @return {ASK, SELECT, CONSTRUCT, DESCRIBE}
	 * @throws UnsupportedEncodingException
	 */
	private static String getQueryType(String query) throws UnsupportedEncodingException {
		query = java.net.URLDecoder.decode(query, "UTF-8");
		String queryType ="";
		try{
		Query jenaQuery = QueryFactory.create(query,Syntax.syntaxARQ);
		if(jenaQuery.isAskType())
			queryType = "ask";
		else if(jenaQuery.isConstructType())
			queryType ="construct";
		else if(jenaQuery.isDescribeType())
			queryType ="describe";
		else if (jenaQuery.isSelectType())
			queryType ="select";
		}
		catch(Exception e){ queryType = "select";}
		return queryType;
	}
	/**
	 *Get FEASIBLE benchmark queries from input directory of benchmarks
	 * @param inputDir Directory
	 * @return List of queries
	 * @throws IOException
	 */
	private static List<String> getFeasibleQueries(String inputDir) throws IOException {
		List<String> queries  = new ArrayList<String>();
		File dir = new File(inputDir);
		File[] listOfQueryLogs = dir.listFiles();
		for (File queryLogFile : listOfQueryLogs)
		{
			System.out.println(queryLogFile.getName()+ ": in progress...");
			BufferedReader br = new BufferedReader(new FileReader(inputDir+queryLogFile.getName()));
			String line ="";
			String query = br.readLine();
			while ((line = br.readLine()) != null)
			{	
				query=line;
				while((line = br.readLine())!=null && !line.equals("#-------------------------------------------------------"))
					query = query+ "\n "+ line ;
					queries.add(java.net.URLEncoder.encode(query, "UTF-8"));

			}
			br.close();
		}
		return queries;
	}

}
