package org.aksw.simba.sparqlbench.centroid;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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.sparqlbench.featurevectors.FeatureVector;
import org.aksw.simba.sparqlbench.rdf.PrintBenchmark;
import org.apache.commons.math3.ml.clustering.CentroidCluster;
import org.apache.commons.math3.ml.clustering.Cluster;
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.BindingSet;
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.http.HTTPRepository;
import org.openrdf.rio.RDFHandlerException;

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

public class KmeansPlusPlus {

	static public Map<String, Double[]> normalizedVectors;	
   // public static Map<String, Double[]> plainVectors= new ConcurrentHashMap<String,Double[]>();	
	static int index = 0;

	public static void main(String[] args) throws IOException, RepositoryException, MalformedQueryException, QueryEvaluationException, RDFHandlerException {
		int noQueries = 15;  //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 = "../SPARQLBench-Generator/personalized-query.txt";
		String outputFile = "KMeansPlusPlus-swdf-Sup15-benchmark.ttl";
        runKMeansPlusPlus(noQueries,maxIterations,noTrialRun,endpointURL,queryFile,outputFile);
		}

	public static void runKMeansPlusPlus(int noQueries, int maxIterations, int noTrialRun, String endpointURL,
			String queryFile, String outputFile) throws RepositoryException, MalformedQueryException, QueryEvaluationException, IOException, RDFHandlerException {
		long curTime = System.currentTimeMillis();
		MultiKMeansPlusPlusClusterer<DoublePoint> transformer =
				new MultiKMeansPlusPlusClusterer<DoublePoint>(new KMeansPlusPlusClusterer<DoublePoint>(noQueries, maxIterations), noTrialRun);  	
		System.out.println("Loading queries from endpoint...");
		normalizedVectors  = FeatureVector.getNormalizedFeaturesVectors(endpointURL,queryFile);
		//Double [] v = plainVectors.get("aea3f3e3");
		System.out.println("Total queries to be considered: "+normalizedVectors.keySet().size());
		System.out.println("Total queries to be selected: "+noQueries);
		System.out.println("Clustering method: KMeansPlusPlus");
		System.out.println("Benchmark query selection in progress...");
		DoublePoint[] data = loadDataPoints(); 
		List<CentroidCluster<DoublePoint>> clusters = transformer.cluster(Arrays.asList(data));
		Set<String> queryIds = new HashSet<String>();  
		for(CentroidCluster<DoublePoint> cluster:clusters){
			queryIds.add(getCentroidQueryId(cluster)); }
		//System.out.println(queryIds);
		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 = "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 (sec): "+exeTime);
		
	}

	public 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 ;
	}

	public static String getCentroidQueryId(Cluster<DoublePoint> cluster) {
		//first get middle point
		List<DoublePoint> points = cluster.getPoints();
		//for(DoublePoint point:points)
		//point.getPointLabel();
		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 = 	 getClosestPointLabel(cluster,middle);

		return centroid;
	}

	public static String getClosestPointLabel(Cluster<DoublePoint> cluster, Double[] middle) {
		List<DoublePoint> points = cluster.getPoints();		
		double min = Double.MAX_VALUE, d;
		String result = null;
		for (DoublePoint point:points) {
			d = getDistance(point.getPoint(), middle);
			if (d < min) {
				result = point.getPointLabel();
				min = d;
			}
		}
		return result;
	}

	public static double getDistance(double [] a, Double[] b) {

		if (a.length != b.length) {
			return Double.MAX_VALUE;
		}
		double d = 0d;
		for (int i = 0; i < a.length; i++) {
			d = d + Math.pow(a[i] - b[i], 2);
		}
		return Math.sqrt(d);

	}
	//		return 0;
	//	}

	
	
	

	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;
	}

}
