package org.aksw.qa.commons.load;

import java.io.IOException;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.aksw.jena_sparql_api.http.QueryExecutionFactoryHttp;
import org.aksw.qa.commons.datastructure.IQuestion;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.ResultSet;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoadTest {
	private Logger log = LoggerFactory.getLogger(LoadTest.class);

	@Test
	/**
	 * Testing all datasets for loadability and validity of SPARQL-queries
	 */
	public void testAllDatasetsTowardsLoadibility() {
		Boolean queriesValid = true;
		for (Dataset d : Dataset.values()) {
			log.info("Try to load:" + d.name());
			try {
				List<IQuestion> questions = LoaderController.load(d);
				log.info("Dataset succesfully loaded:" + d.name());

				for (IQuestion q : questions) {
					// TODO enable that once the answer type of NLQ is fixed
					// Assert.assertNotNull(q.toString(), q.getAnswerType());

					// TODO enable this if datasets are clean
					// Assert.assertTrue((q.getPseudoSparqlQuery() != null) ||
					// (q.getSparqlQuery() != null) ||
					// d.equals(Dataset.Stanford_dev) ||
					// d.equals(Dataset.Stanford_train));

					if (q.getSparqlQuery() != null) {
						queriesValid = (execQueryWithoutResults(q) && queriesValid);
					}
					Assert.assertNotNull(q.getLanguageToQuestion());
					Assert.assertFalse(q.getLanguageToQuestion().values().isEmpty());
					Assert.assertNotNull(q.getLanguageToKeywords());
					Assert.assertTrue(q.toString(), q.getGoldenAnswers() != null);
					// FIXME sobald wir auf das eigentliche QALD repository
					// commiten können nimm hier und in QALD 5 den antworttyp
					// "uri" "list" raus
					// "list" und "uri" sollten vom loader auf URI gemappt
					// werden
					// "num" sollte auf number gemappt werden
					// Assert.assertTrue(q.toString(),
					// q.getAnswerType().matches("resource||uri||list||boolean||number||date||string"));
				}

			} catch (Exception e) {
				log.error("Dataset couldn't be loaded:" + d.name());
			}
		}
		// TODO fix this

		// Assert.assertTrue(queriesValid);
	}

	@Test
	public void loadStanfordDevTest() {
		List<IQuestion> load = LoaderController.load(Dataset.Stanford_dev);
		log.debug("Size of Dataset: " + load.size());
		Assert.assertTrue(load.size() == 10600);
		for (IQuestion q : load) {
			Assert.assertNotNull(q.getLanguageToQuestion());
			Assert.assertFalse(q.getLanguageToQuestion().values().isEmpty());
			Assert.assertTrue((q.getGoldenAnswers() != null) && (q.getGoldenAnswers().size() > 0));
		}
	}

	@Test
	public void loadQALD5Test() {
		List<IQuestion> load = LoaderController.load(Dataset.QALD5_Test_Hybrid);
		log.debug("Size of Dataset: " + load.size());
		Assert.assertTrue(load.size() == 10);
		for (IQuestion q : load) {
			Assert.assertNotNull(q.getAnswerType());
			Assert.assertTrue((q.getPseudoSparqlQuery() != null) || (q.getSparqlQuery() != null));
			Assert.assertNotNull(q.getLanguageToQuestion());
			Assert.assertFalse(q.getLanguageToQuestion().values().isEmpty());
			Assert.assertNotNull(q.getLanguageToKeywords());
			Assert.assertTrue((q.getGoldenAnswers() != null) && q.getAnswerType().matches("resource||boolean||number||date||string"));
		}
	}
	
	@Test
	public void missingAnswerQueryTest() {
		Dataset data = Dataset.QALD6_Test_Multilingual;
		int missingAnswers = 5;
		int missingQueries = 4;
		
		List<IQuestion> questions = LoaderController.load(data);
		if (questions == null) {
			log.debug("Dataset null" + data.toString());
		} else if (questions.size() == 0) {
			log.debug("Dataset empty" + data.toString());
		} else {
			Set<IQuestion> noanswers = new HashSet<>();
			Set<IQuestion> nosparql = new HashSet<>();
			for (IQuestion q : questions) {
				if (((q.getSparqlQuery() == null) || (q.getSparqlQuery().isEmpty())) && ((q.getPseudoSparqlQuery() == null) || q.getPseudoSparqlQuery().isEmpty())) {
					nosparql.add(q);
				}
				if (((q.getGoldenAnswers() == null) || q.getGoldenAnswers().isEmpty())) {
					noanswers.add(q);
				}
			}
			DecimalFormat df = new DecimalFormat("###.##");
			df.setRoundingMode(RoundingMode.CEILING);
			if (!noanswers.isEmpty()) {
				log.debug(((df.format(((double) noanswers.size() / questions.size()) * 100)) + "%") + " Missing answers on : " + data.toString() + ", " + noanswers.size() + " Question(s).");
				Assert.assertTrue(noanswers.size() == missingAnswers);
			}
			
			if (!nosparql.isEmpty()) {
				log.debug((df.format((((double) nosparql.size() / questions.size()) * 100)) + "%") + " Neither Sparql nor Pseudo : " + data.toString() + ", " + nosparql.size() + " Question(s).");
				Assert.assertTrue(nosparql.size() == missingQueries);
			}
			log.debug("Loaded successfully: " + data.toString());
		}
	}

	@Test
	public void loadQALD6TrainMultilingual() throws IOException {
		List<IQuestion> load = LoaderController.load(Dataset.QALD6_Train_Multilingual);
		List<Integer> expectedIncompletes = Arrays.asList(100, 118, 136, 137, 147, 152, 94, 95, 96, 97, 98, 99, 249, 250, 312, 342);
		log.debug("Number of Loaded Questions:" + load.size());
		log.debug("Incomplete:" + expectedIncompletes.size());

		ArrayList<Integer> foundIncompletes = new ArrayList<>();
		for (IQuestion q : load) {
			if ((q.getGoldenAnswers() == null) || q.getGoldenAnswers().isEmpty()) {
				foundIncompletes.add(Integer.parseInt(q.getId()));
			}

		}
		Assert.assertTrue(load.size() == 350);
		Assert.assertTrue(expectedIncompletes.size() == foundIncompletes.size());
		Assert.assertTrue(expectedIncompletes.containsAll(foundIncompletes) && foundIncompletes.containsAll(expectedIncompletes));

		for (IQuestion q : load) {
			Assert.assertNotNull(q.getAnswerType());
			Assert.assertTrue((q.getGoldenAnswers() != null) && q.getAnswerType().matches("resource||boolean||number||date||string"));
			Assert.assertNotNull(q.getLanguageToQuestion());
			Assert.assertFalse(q.getLanguageToQuestion().values().isEmpty());
			Assert.assertNotNull(q.getLanguageToKeywords());
			// skipping Answer on known incompletes:
			if (!foundIncompletes.contains(Integer.valueOf(q.getId()))) {
				Assert.assertTrue((q.getPseudoSparqlQuery() != null) || (q.getSparqlQuery() != null));
				// log.debug(q.getLanguageToQuestion().get("en") + "\t" +
				// "Answer:" + "\t" + q.getSparqlQuery());
			}  // else {
				// log.debug(q.getLanguageToQuestion().get("en") + "\t" +
				// "No Answer, known incomplete question.");
			// }
		}

	}

	private boolean execQuery(final IQuestion q, final boolean hurry) {

		Query query = new Query();

		// Execute the query and obtain results
		QueryExecutionFactoryHttp qef = new QueryExecutionFactoryHttp("http://139.18.2.164:3030/ds/sparql");
		Boolean queryValid = true;
		if (hurry) {
			log.debug("Testing query for parsability: " + q.getId() + ": " + q.getLanguageToQuestion().get("en"));
		} else {
			log.debug("Testing query for parsability and returned results: " + q.getId() + ": " + q.getLanguageToQuestion().get("en"));
		}
		try {
			query = QueryFactory.create(q.getSparqlQuery());

			// Execute the query and obtain results

			QueryExecution qe = qef.createQueryExecution(query.toString());

			if (!q.getGoldenAnswers().isEmpty()) {
				if (!hurry) {
					ResultSet results = qe.execSelect();

					if (results.toString().contains(q.getGoldenAnswers().toString())) {
						log.info("Question result doesn't contain golden answer!");
						log.info("Actual results: " + results.toString());
						log.info("Golden answers: " + q.getGoldenAnswers().toString());
					}
				}

				log.debug("Query valid for q" + q.getId() + " - " + q.getLanguageToQuestion().get("en"));
				// log.debug(query.toString());
				queryValid = (queryValid && true);
				qe.close();
			}
		} catch (Exception e) {
			// FIXME bereits hier eine Assertion mit Message einbauen sonst
			// sieht man oben nur das das flag false ist aber nicht warum und
			// wieso
			if (e.getClass() != org.apache.jena.sparql.resultset.ResultSetException.class) {

				log.debug(q.getSparqlQuery());
				log.info("Jena error: " + e.toString());
				log.info("!!!! Query invalid for q" + q.getId() + " - " + q.getLanguageToQuestion().get("en"));
				queryValid = false;
			} else {
				if (q.getGoldenAnswers().isEmpty()) {
					log.debug("Query delivers no results for q" + q.getId() + " (expecting: empty) - " + q.getLanguageToQuestion().get("en"));
					queryValid = (queryValid && true);
				} else {
					if (q.getGoldenAnswers().contains("true") || q.getGoldenAnswers().contains("false")) {
						log.debug("Query delivers no results for q" + q.getId() + " (expecting: boolean) - " + q.getLanguageToQuestion().get("en"));
						queryValid = (queryValid && true);
					}
					log.info("Golden answer not returned! Expecting:" + q.getGoldenAnswers().toString());
					queryValid = false;
				}

			} 
		} finally {
			qef.close();
		}
		return queryValid;
	}

//	private boolean execQuery(final IQuestion q) {
//		return execQuery(q, false);
//	}

	private boolean execQueryWithoutResults(final IQuestion q) {
		return execQuery(q, true);
	}
	
}