package cz.muni.fi.lasaris.sbms.semantics.logic;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.jena.query.Query;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.sparql.syntax.ElementGroup;
import org.apache.log4j.Logger;

import cz.muni.fi.lasaris.sbms.semantics.api.entities.Address;
import cz.muni.fi.lasaris.sbms.semantics.api.entities.Algorithm;
import cz.muni.fi.lasaris.sbms.semantics.api.request.AddressRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.AlgorithmRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.DataPointsRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.GroupableRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.Request;
import cz.muni.fi.lasaris.sbms.semantics.api.request.TrendsRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.response.AddressResponse;
import cz.muni.fi.lasaris.sbms.semantics.api.response.GroupedResponse;
import cz.muni.fi.lasaris.sbms.semantics.api.response.InfluenceResponse;

public class QueryParser {
	
	final static Logger logger = Logger.getLogger(QueryParser.class);
	
	public QueryParser() {
	}
	
	public static GroupedResponse getDataPointResponse(DataPointsRequest request) {
		ElementGroup body = BodyBuilders.getQueryBody(request);
		GroupedResponse response = new GroupedResponse();
		
		for (QuerySolution qs : getQueryResult(request, body))
		{
			logger.debug(qs.toString());
			response.addResult(ObjectBuilders.createDataPoint(qs, request.getResponseFields(), request.getGrouping()));
		}
		logger.debug("Query returned.");
		return response;
	}
	
	public static AddressResponse getInfluencingResponse(AddressRequest request) {
		ElementGroup body = BodyBuilders.getQueryBody(request);
		AddressResponse response = new AddressResponse();
		
		HashMap<String,Address> ads = new HashMap<String, Address>();
		
		for (QuerySolution qs : getQueryResult(request, body))
		{
			logger.debug(qs.toString());
			Address a = ObjectBuilders.createAddress(qs);
			if(ads.containsKey(a.getBmsId())) {
				Address a2 = ads.get(a.getBmsId());
				if(!a2.getInfluenceList().contains(a.getInfluenced())) {
					a2.getInfluenceList().add(a.getInfluenced());
				}
			} else {
				ads.put(a.getBmsId(), a);
			}
		}
		
		for(Address a : ads.values()) {
			response.addResult(a);
		}
		
		return response;
	}
	
	public static InfluenceResponse getInfluencedResponse(AddressRequest request) {
		ElementGroup body = BodyBuilders.getQueryBody(request);
		InfluenceResponse response = new InfluenceResponse();
		for (QuerySolution qs : getQueryResult(request, body))
		{
			logger.debug(qs.toString());
			response.addResult(ObjectBuilders.createInfluence(qs));
		}
		return response;
	}

	public static AddressResponse getUsedResponse(AlgorithmRequest request) {
		ElementGroup body = BodyBuilders.getQueryBody(request);
		AddressResponse response = new AddressResponse();
		for (QuerySolution qs : getQueryResult(request, body))
		{
			logger.debug(qs.toString());
			Algorithm a = ObjectBuilders.createAlgorithm(qs);
			response.addResult(a.getUsedAddress());
		}
		return response;
	}

	public static AddressResponse getUsingResponse(AlgorithmRequest request) {
		ElementGroup body = BodyBuilders.getQueryBody(request);
		AddressResponse response = new AddressResponse();
		for (QuerySolution qs : getQueryResult(request, body))
		{
			logger.debug(qs.toString());
			response.addResult(ObjectBuilders.createAlgorithm(qs));
		}
		return response;
	}
	
	public static GroupedResponse getTrendsResponse(TrendsRequest request) {
		
		ElementGroup body = BodyBuilders.getQueryBody(request);
		
		GroupedResponse response = new GroupedResponse();
		
		for (QuerySolution qs : getQueryResult(request, body))
		{
			logger.debug(qs.toString());
			response.addResult(ObjectBuilders.createTrend(
					ObjectBuilders.createDataPoint(qs, request.getResponseFields(), request.getGrouping()),
					qs, request.getResponseFields(), request.getGrouping()));
		}
		return response;
	}
	
	private static List<QuerySolution> getQueryResult(GroupableRequest request, ElementGroup body) {
		

		Query q = createQuery(request, body);
		
		if(request.getGrouping() != null && !request.getGrouping().equals(Fields.DEFAULT_GROUPING)) {
			q.addResultVar("group");
		}
		
		logger.debug(q);
		return TdbConnector.executeSPARQLResults(q);
	}
	
	private static List<QuerySolution> getQueryResult(Request request, ElementGroup body) {
		Query q = createQuery(request, body);
		
		logger.debug(q);
		return TdbConnector.executeSPARQLResults(q);
	}
	
	private static Query createQuery(Request request, ElementGroup body) {
		// https://jena.apache.org/documentation/query/manipulating_sparql_using_arq.html
		Query q = QueryFactory.make();
		q.setQueryPattern(body);                               // Set the body of the query to our group
		q.setQuerySelectType();								   // Make it a select query
		
		for(String field : request.getResponseFields()) {
			q.addResultVar(gsv(field));
		}
		
		if(!q.getResultVars().contains(gsv(request.getIdentityField()))) {
			q.addResultVar(gsv(request.getIdentityField()));
		}
		
		return q;
	}
	
	private static String gsv(String f) {
		return getSPARQLvar(f);
	}
	
	private static Map<String, String> sparqlVars;

	public static String getSPARQLvar(String field) {
		if(sparqlVars == null) {
			sparqlVars = new HashMap<String,String>();
			for(String f : Fields.DATAPOINT_DATA_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
			for(String f : Fields.DATAPOINT_FILTER_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
			for(String f : Fields.TREND_DATA_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
			for(String f : Fields.TREND_FILTER_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
			sparqlVars.put("trend.bmsId", "trendbmsId");
			for(String f : Fields.INFLUENCE_DATA_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
			for(String f : Fields.INFLUENCED_FILTER_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
			for(String f : Fields.INFLUENCING_FILTER_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
			for(String f : Fields.USED_FIELDS) {
				sparqlVars.put(f, f.replace(".", ""));
			}
		}
		return sparqlVars.get(field);
	}
	
}
