package cz.muni.fi.lasaris.sbms.data.api;

import java.io.IOException;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.apache.log4j.Logger;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import cz.muni.fi.lasaris.sbms.data.api.request.GroupedAddressRequest;
import cz.muni.fi.lasaris.sbms.data.api.request.AddressRequest;
import cz.muni.fi.lasaris.sbms.data.api.response.AggregationResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.SnapshotResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.SnapshotResponse;
import cz.muni.fi.lasaris.sbms.data.entities.Address;
import cz.muni.fi.lasaris.sbms.data.entities.AggregationFunction;
import cz.muni.fi.lasaris.sbms.data.entities.Interpolation;
import cz.muni.fi.lasaris.sbms.data.entities.Sampling;
import cz.muni.fi.lasaris.sbms.data.entities.SeriesSpecs;
import cz.muni.fi.lasaris.sbms.data.entities.containers.Trend;
import cz.muni.fi.lasaris.sbms.data.entities.values.RawValue;
import cz.muni.fi.lasaris.sbms.data.logic.DataCollector;
import cz.muni.fi.lasaris.sbms.data.logic.ProviderManager;
import cz.muni.fi.lasaris.sbms.data.util.Aggregator;

public class TrendsEndpoint {
	final static Logger logger = Logger.getLogger(TrendsEndpoint.class);
	
	private DataCollector dc;
	
	private ObjectMapper m;
	
	
	public TrendsEndpoint() {
		dc = new DataCollector();
		m = new ObjectMapper();
		m.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
	}
	
	// TODO http://stackoverflow.com/questions/14202257/design-restful-query-api-with-a-long-list-of-query-parameters
	
	@RolesAllowed({"user","admin"})
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public SnapshotResponse getTrends(
			@QueryParam("ids") String json,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@DefaultValue("NONE") @QueryParam("interpolation") Interpolation interpolation
			) {
		try {
			logger.debug("Processing request...");
			logger.debug(json);
			TrendsRequest readSpecs = getReadSpecs(json, start, end, samplingCron, samplingDur, interpolation, null);
			logger.debug(readSpecs);
			SnapshotResponse result = dc.getTrends(readSpecs);
			logger.debug(result);
			return result;
		} catch (IOException e) {
			return SnapshotResponse.getErrorResponse("Unable to parse query:" + e);
		}
		
		
	}
	
	
	@RolesAllowed({"user","admin"})
	@GET
	@Path("/aggregated")
	@Produces(MediaType.APPLICATION_JSON)
	public AggregationResponse getAggregatedDataPoints(
			@QueryParam("json") String json,
			@DefaultValue("NONE") @QueryParam("aggregation") AggregationFunction agg
			) {
		ObjectMapper m = new ObjectMapper();
		try {
			logger.debug("Processing request...");
			logger.debug(json);
			AddressRequest readSpecs = m.readValue(json, AddressRequest.class);
			logger.debug(readSpecs);
			return dc.getDataPointsAggregation(readSpecs, agg);
		} catch (IOException e) {
			return AggregationResponse.getErrorResponse("Unable to parse query:" + e);
		}
	}


	@RolesAllowed({"user","admin"})
	@GET
	@Path("/grouped")
	@Produces(MediaType.APPLICATION_JSON)
	public SnapshotResponse getGroupedDataPoints(
			@QueryParam("json") String json
			) {
		ObjectMapper m = new ObjectMapper();
		try {
			logger.debug("Processing request...");
			logger.debug(json);
			GroupedAddressRequest readSpecs = m.readValue(json,GroupedAddressRequest.class);
			logger.debug(readSpecs);
			return dc.getDataPointGroupedValues(readSpecs);
		} catch (IOException e) {
			return SnapshotResponse.getErrorResponse("Unable to parse query:" + e);
		}
	}
	
	@RolesAllowed({"user","admin"})
	@GET
	@Path("/group-aggregated")
	@Produces(MediaType.APPLICATION_JSON)
	public AggregationResponse getGroupedAggregatedDataPoints(
			@QueryParam("json") String json,
			@DefaultValue("NONE") @QueryParam("aggregation") AggregationFunction agg
			) {
		ObjectMapper m = new ObjectMapper();
		try {
			logger.debug("Processing request...");
			logger.debug(json);
			GroupedAddressRequest readSpecs = m.readValue(json, GroupedAddressRequest.class);
			logger.debug(readSpecs);
			AggregationResponse result =  dc.getDataPointGroupAggregations(readSpecs, agg);
			logger.debug(result);
			return result;
		} catch (IOException e) {
			return AggregationResponse.getErrorResponse("Unable to parse query:" + e);
		}
	}
	
	@RolesAllowed({"user","admin"})
	@GET
	@Path("/{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public SnapshotResponse getTrend(
			@QueryParam("id") String id,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@DefaultValue("NONE") @QueryParam("interpolation") Interpolation interpolation
			) {
		Address a = new Address(id);
		Trend v = ProviderManager.getTrendsProvider(a.getProtocol()).getTrend(a, getSeriesSpecs(start, end, samplingCron, samplingDur, interpolation));
		return new SnapshotResponse(a, v);
	}
	
	private TrendsRequest getReadSpecs(String json, String start, String end, String samplingCron, 
			Long samplingDur, Interpolation interpolation, AggregationFunction aggregation) {	
		TrendsRequest readSpecs = new TrendsRequest();
		readSpecs.setAddressesSpecs(m.readValue(json, AddressRequest.class));
		SeriesSpecs s = getSeriesSpecs(start, end, samplingCron, samplingDur, interpolation);
		
		readSpecs.setSeriesSpecs(s);
		readSpecs.setAggregation(aggregation);
		return readSpecs;
	}
	
	private SeriesSpecs getSeriesSpecs(String start, String end, String samplingCron, 
			Long samplingDur, Interpolation interpolation) {
		SeriesSpecs s = null;
		if(samplingCron != null) {
			s = new SeriesSpecs(getDateTime(start), getDateTime(end), samplingCron, interpolation);
		} else if (samplingDur != null) {
			s = new SeriesSpecs(getDateTime(start), getDateTime(end), Duration.of(samplingDur, ChronoUnit.SECONDS), interpolation);  
		} else {
			s = new SeriesSpecs(getDateTime(start), getDateTime(end));
		}
		return s;
	}
	
	private ZonedDateTime getDateTime(String s) {
		return ZonedDateTime.now();
	}
}
