package org.aksw.simba.qaldbench.hybrid;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.aksw.simba.benchmark.Similarity;
import org.aksw.simba.benchmark.comparisons.AvgStats;
import org.aksw.simba.benchmark.log.operations.CleanQueryReader;
import org.aksw.simba.qaldbench.centroid.KmeansPlusPlus;
import org.aksw.simba.qaldbench.featurevectors.FeatureVector;
import org.aksw.simba.qaldbench.rdf.PrintBenchmark;
import org.apache.commons.math3.ml.clustering.Cluster;
import org.apache.commons.math3.ml.clustering.DBSCANClusterer;
import org.apache.commons.math3.ml.clustering.DoublePoint;
import org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer;
import org.apache.commons.math3.ml.clustering.MultiKMeansPlusPlusClusterer;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
import org.openrdf.rio.RDFHandlerException;

public class DbscanAndKMeansPluPlus {
	static Map<String, Double[]> normalizedVectors;	
	static int index = 0;
	public static void main(String[] args) throws IOException, RepositoryException, MalformedQueryException, QueryEvaluationException, RDFHandlerException {
		int radius = 1;
		int minPts = 1;
		int noQueries = 2;  //same as number of clusters. we need to pick one query from each cluster
		int maxIterations = 10; 
		int noTrialRun = 10; 	
		String endpointURL = "http://localhost:8890/sparql";	 //The endpoint URL containing the containment RDF data
		String queryFile = "../QALDBench-Generator/personalized-query.txt";
		String outputFile = "DBSCANPlusKMeansPlusPlus-QAGen15-Benchmark.ttl";
		runDBPlusKmeanPlusPlus(radius,minPts,noQueries,maxIterations,noTrialRun,endpointURL, queryFile,outputFile);
	}

	public static void runDBPlusKmeanPlusPlus(int radius, int minPts, int noQueries, int maxIterations,
			int noTrialRun, String endpointURL, String queryFile, String outputFile) throws RepositoryException, MalformedQueryException, QueryEvaluationException, IOException, RDFHandlerException {
		long curTime = System.currentTimeMillis();
			 normalizedVectors  = FeatureVector.getNormalizedFeaturesVectors(endpointURL,queryFile);
		 System.out.println("Total questions to be considered: "+normalizedVectors.keySet().size());
			System.out.println("Total questions to be selected: "+noQueries);
			System.out.println("Clustering method: DBSCAN+KMeansPlusPlus");
		 List<Cluster<DoublePoint>> dbscanClusters = performDBSCAN(radius, minPts);
		System.out.println("DBScan succeffuly performed. Outliers removed");
		performKMeansPlusPlus(dbscanClusters, noQueries,maxIterations,noTrialRun, endpointURL, curTime,outputFile);
		
		
	}

	private static List<Cluster<DoublePoint>> performDBSCAN(int radius, int minPts ) throws IOException {
		final DBSCANClusterer<DoublePoint> clusterer = new DBSCANClusterer<DoublePoint>(radius, minPts);
		DoublePoint[] data =  loadDataPoints(); 
		List<Cluster<DoublePoint>> clusters = clusterer.cluster(Arrays.asList(data));

		return clusters;
	}
	private static DoublePoint[] loadDataPoints() {
		DoublePoint[] data = new DoublePoint[normalizedVectors.keySet().size()];
		int i = 0; 
		for(String key:normalizedVectors.keySet()) {
			data[i] = new DoublePoint(getPoints(key),key);
			i++;
		} 
		return data ;
	}
	private static double[] getPoints(String key) {
		int length = normalizedVectors.get(key).length;
		double[] points = new double[length];
		Double[] temp = normalizedVectors.get(key);
		for(int i=0; i<length; i++) {
			points[i] = temp[i];
		}		
		index = index + 1;
		return points;
	}
	private static void performKMeansPlusPlus(List<Cluster<DoublePoint>> dbscanClusters, int noQueries, int maxIterations, int noTrialRun,String endpointURL, Long curTime, String outputFile) throws IOException, RepositoryException, MalformedQueryException, QueryEvaluationException, RDFHandlerException {
		int dpSize =  0 ; //data points size
		for(Cluster<DoublePoint> cluster:dbscanClusters){
			dpSize = dpSize+cluster.getPoints().size();
		}
		int e =0;
		DoublePoint[] data = new DoublePoint[dpSize];
		for(Cluster<DoublePoint> cluster:dbscanClusters){
			List<DoublePoint> points = cluster.getPoints();
			for(DoublePoint point:points){
				data[e] = point;   		
				e++;
			}
		}
		System.out.println("Total queries to be considered after outliers removal by DBSCAN: "+data.length);  
		MultiKMeansPlusPlusClusterer<DoublePoint> transformer =
				new MultiKMeansPlusPlusClusterer<DoublePoint>(new KMeansPlusPlusClusterer<DoublePoint>(noQueries, maxIterations), noTrialRun);  	
		List<? extends Cluster<DoublePoint>> clusters = transformer.cluster(Arrays.asList(data));
		Set<String> queryIds = new HashSet<String>();  
		for(Cluster<DoublePoint> cluster:clusters)
			queryIds.add(getCentroidQueryId(cluster)); 
		Set<String> benchmarkQueriesIds = new HashSet<String>(queryIds); //we need to copy the query ids here/ later being used for similarity calculation. The next function will clear the queriesIds. So we have to copy before
		Double similarityScore = Similarity.getSimilarityScore(normalizedVectors,benchmarkQueriesIds);
		Double diversityScore = Similarity.getDiversityScore(normalizedVectors,benchmarkQueriesIds);
		String method = "DBSCAN+KMeansPlusPlus";
		long exeTime = (System.currentTimeMillis()-curTime)/1000;
		PrintBenchmark.getRDF(queryIds, outputFile, endpointURL,method,similarityScore,exeTime,normalizedVectors, diversityScore);
		System.out.println("Composite Error: " + similarityScore);
		System.out.println("Diversity Score: " + diversityScore);
		System.out.println("Benchmark Generation time: "+exeTime);
	}


	public static String getCentroidQueryId(Cluster<DoublePoint> cluster) {
		//first get middle point
		List<DoublePoint> points = cluster.getPoints();
		int dimensions = points.get(0).getPoint().length;
		Double[] middle = new Double[dimensions];
		//  Map<String, Double[]> clusterVectors = new HashMap<String,Double[]>();
		String centroid;
		for (int i = 0; i < dimensions; i++) {
			middle[i] = 0d;
		}

		double[] v;
		for (DoublePoint point:points) {
			v = point.getPoint();

			// System.out.println(dimensions);
			for (int i = 0; i < dimensions; i++) {
				//System.out.println(middle[i]);
				middle[i] = middle[i] + v[i];
			}
		}

		for (int i = 0; i < dimensions; i++) {
			middle[i] = middle[i] / points.size();
		}
		//now we compute the actual query i.e. the centroid that is closet to the middle. Note middle may not represent any query
		centroid = 	 KmeansPlusPlus.getClosestPointLabel(cluster,middle);

		return centroid;
	}
}
