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.util.Map;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
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.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
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.TrendsRequest;
import cz.muni.fi.lasaris.sbms.data.api.request.AddressRequest;
import cz.muni.fi.lasaris.sbms.data.api.response.AggregateResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.SeriesOfSlicesResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.SetOfAggregatesResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.SetOfTrendsResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.SliceResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.TrendResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.ValueResponse;
import cz.muni.fi.lasaris.sbms.data.entities.Address;
import cz.muni.fi.lasaris.sbms.data.entities.AddressGroup;
import cz.muni.fi.lasaris.sbms.data.entities.AggregationFunction;
import cz.muni.fi.lasaris.sbms.data.entities.AggregationWindow;
import cz.muni.fi.lasaris.sbms.data.entities.Interpolation;
import cz.muni.fi.lasaris.sbms.data.entities.SeriesSpecs;
import cz.muni.fi.lasaris.sbms.data.entities.containers.Aggregation;
import cz.muni.fi.lasaris.sbms.data.entities.containers.Series;
import cz.muni.fi.lasaris.sbms.data.entities.containers.Slice;
import cz.muni.fi.lasaris.sbms.data.entities.containers.Trend;
import cz.muni.fi.lasaris.sbms.data.entities.values.AggregatedValue;
import cz.muni.fi.lasaris.sbms.data.entities.values.InterpolatedValue;
import cz.muni.fi.lasaris.sbms.data.logic.TrendsDataCollector;

public class TrendsEndpoint {
	final static Logger logger = Logger.getLogger(TrendsEndpoint.class);

	private TrendsDataCollector dc;

	private ObjectMapper m;


	public TrendsEndpoint() {
		dc = new TrendsDataCollector();
		m = new ObjectMapper();
		m.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
	}

	// http://stackoverflow.com/questions/14202257/design-restful-query-api-with-a-long-list-of-query-parameters

	// Method 4
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/si")
	@Produces(MediaType.APPLICATION_JSON)
	public TrendResponse getSI(
			@QueryParam("id") String id,
			@QueryParam("start") String start,
			@QueryParam("end") String end
			) {

		try {
			TrendsRequest r = getReadSpecsSingle(id, start, end, null, null, null, null, null);
			Trend v = dc.getTrend(r);
			return new  TrendResponse(v);
		} catch (Exception e) {
			return  TrendResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 5
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/sts")
	@Produces(MediaType.APPLICATION_JSON)
	public ValueResponse getSTS(
			@QueryParam("id") String id,
			@QueryParam("timestamp") String timestamp,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@QueryParam("sampling.interpolation") Interpolation interpolation
			) {

		try {
			TrendsRequest r = getReadSpecsSingle(id, timestamp, timestamp, samplingCron, samplingDur, interpolation, null, null);
			InterpolatedValue v = dc.getValue(r);
			return new ValueResponse(v);
		} catch (Exception e) {
			return ValueResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 6
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/sis")
	@Produces(MediaType.APPLICATION_JSON)
	public  TrendResponse getSIS(
			@QueryParam("id") String id,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@QueryParam("sampling.interpolation") Interpolation interpolation
			) {
		try {
			TrendsRequest r = getReadSpecsSingle(id, start, end, samplingCron, samplingDur, interpolation, null, null);
			Trend v = dc.getTrend(r);
			return new  TrendResponse(v);
		} catch (Exception e) {
			return  TrendResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 7
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/sas")
	@Produces(MediaType.APPLICATION_JSON)
	public ValueResponse getSAS(
			@QueryParam("id") String id,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("aggregation.function") AggregationFunction aggregation
			) {
		try {
			TrendsRequest r = getReadSpecsSingle(id, start, end, null, null, null, aggregation, null);
			AggregatedValue v = dc.getAggregate(r);
			return new ValueResponse(v);
		} catch (Exception e) {
			return ValueResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 8
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/siw")
	@Produces(MediaType.APPLICATION_JSON)
	public  TrendResponse getSIW(
			@QueryParam("id") String id,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("aggregation.function") AggregationFunction aggregation,
			@QueryParam("aggregation.window") AggregationWindow window,
			@QueryParam("aggregation.cron") String windowCron,
			@QueryParam("aggregation.duration") Long windowDur
			) {
		try {
			TrendsRequest r = getReadSpecsSingle(id, start, end, windowCron, windowDur, null, aggregation, window);
			Trend v = dc.getTrend(r);
			return new  TrendResponse(v);
		} catch (Exception e) {
			return  TrendResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 9
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/mt")
	@Produces(MediaType.APPLICATION_JSON)
	public SliceResponse getMT(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("timestamp") String timestamp
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, timestamp, timestamp, null, null, null, null, null);
			Slice v = dc.getSlice(r);
			return new SliceResponse(v);
		} catch (Exception e) {
			return SliceResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 10
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/mta")
	@Produces(MediaType.APPLICATION_JSON)
	public AggregateResponse getMTA(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("timestamp") String timestamp,
			@QueryParam("aggregation.function") AggregationFunction aggregation
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, timestamp, timestamp, null, null, null, null, null);
			if(grouping) {
				Aggregation<AddressGroup> v = dc.getGroupedAggregatesForSlice(r);
				return new AggregateResponse(v);
			} else {
				AggregatedValue v = dc.getAggregateForSlice(r);
				return new AggregateResponse(v);
			}

		} catch (Exception e) {
			return AggregateResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 11
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/mi")
	@Produces(MediaType.APPLICATION_JSON)
	public SetOfTrendsResponse getMI(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, null, null, null, null, null);
			if(grouping) {
				Map<String, Map<Address, Trend>> v = dc.getGroupedTrends(r);
				return new SetOfTrendsResponse(v, true);
			} else {
				Map<Address, Trend> v = dc.getTrends(r);
				return new SetOfTrendsResponse(v);
			}
		} catch (Exception e) {
			return SetOfTrendsResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 12
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/mia")
	@Produces(MediaType.APPLICATION_JSON)
	public SetOfAggregatesResponse getMIA(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("aggregation.function") AggregationFunction aggregation
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, null, null, null, aggregation, null);
			if(grouping) {
				Map<String, Aggregation<Address>> v = dc.getGroupedAggregates(r);
				return new SetOfAggregatesResponse(v);
			} else {
				Aggregation<Address> v = dc.getAggregates(r);
				return new SetOfAggregatesResponse(v);
			}
		} catch (Exception e) {
			return SetOfAggregatesResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 13
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/miaa")
	@Produces(MediaType.APPLICATION_JSON)
	public AggregateResponse getMIAA(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("aggregation.function") AggregationFunction aggregation
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, null, null, null, aggregation, null);
			if(grouping) {
				Aggregation<AddressGroup> v = dc.getGroupedTrendsAggregate(r);
				return new AggregateResponse(v);
			} else {
				AggregatedValue v = dc.getTrendsAggregate(r);
				return new AggregateResponse(v);
			}
		} catch (Exception e) {
			return AggregateResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 14
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/misss")
	@Produces(MediaType.APPLICATION_JSON)
	public SetOfTrendsResponse getMISSS(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@QueryParam("sampling.interpolation") Interpolation interpolation
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, samplingCron, samplingDur, interpolation, null, null);
			if(grouping) {
				Map<String, Map<Address, Trend>> v = dc.getGroupedTrends(r);
				return new SetOfTrendsResponse(v, true);
			} else {
				Map<Address, Trend> v = dc.getTrends(r);
				return new SetOfTrendsResponse(v);
			}
		} catch (Exception e) {
			return SetOfTrendsResponse.getErrorResponse(e.getMessage());
		}
	}

	// Method 15
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/missl")
	@Produces(MediaType.APPLICATION_JSON)
	public SeriesOfSlicesResponse getMISSL(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@QueryParam("sampling.interpolation") Interpolation interpolation
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, samplingCron, samplingDur, interpolation, null, null);
			if(grouping) {
				Map<String, Series<Slice>> v = dc.getGroupedSeriesOfSlices(r);
				return new SeriesOfSlicesResponse(v);
			} else {
				Series<Slice> v = dc.getSeriesOfSlices(r);
				return new SeriesOfSlicesResponse(v);
			}
		} catch (Exception e) {
			return SeriesOfSlicesResponse.getErrorResponse(e.getMessage());
		}
	}


	// Method 16
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/mias")
	@Produces(MediaType.APPLICATION_JSON)
	public TrendResponse getMIAS(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@QueryParam("sampling.interpolation") Interpolation interpolation,
			@QueryParam("aggregation.function") AggregationFunction aggregation
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, samplingCron, samplingDur, interpolation, aggregation, null);
			if(grouping) {
				Map<String, Trend> v = dc.getGroupedSeriesOfAggregates(r);
				return new TrendResponse(v);
			} else {
				Trend v = dc.getSeriesOfAggregates(r);
				return new TrendResponse(v);
			}
		} catch(Exception e) {
			return TrendResponse.getErrorResponse(e.getMessage());
		}
	}


	// Method 17
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/miw")
	@Produces(MediaType.APPLICATION_JSON)
	public SetOfTrendsResponse getMIW(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("aggregation.function") AggregationFunction aggregation,
			@QueryParam("aggregation.window") AggregationWindow window,
			@QueryParam("aggregation.cron") String windowCron,
			@QueryParam("aggregation.duration") Long windowDur
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, windowCron, windowDur, null, aggregation, window);
			if(grouping) {
				Map<String, Map<Address, Trend>> v = dc.getGroupedTrends(r);
				return new SetOfTrendsResponse(v, true);
			} else {
				Map<Address, Trend> v = dc.getTrends(r);
				return new SetOfTrendsResponse(v);
			}
		} catch(Exception e) {
			return SetOfTrendsResponse.getErrorResponse(e.getMessage());
		}
	}
	
	// Method 18
	@RolesAllowed({"user","admin"})
	@POST
	@Path("/miwa")
	@Produces(MediaType.APPLICATION_JSON)
	public TrendResponse getMIWA(
			@QueryParam("ids") String ids,
			@QueryParam("grouping") boolean grouping,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("aggregation.function") AggregationFunction aggregation,
			@QueryParam("aggregation.window") AggregationWindow window,
			@QueryParam("aggregation.cron") String windowCron,
			@QueryParam("aggregation.duration") Long windowDur
			) {
		try {
			TrendsRequest r = getReadSpecsMulti(ids, grouping, start, end, windowCron, windowDur, null, aggregation, window);
			if(grouping) {
				Map<String, Trend> v = dc.getGroupedWindowedTrendsAggregate(r);
				return new TrendResponse(v);
			} else {
				Trend v = dc.getWindowedTrendsAggregate(r);
				return new TrendResponse(v);
			}
		} catch(Exception e) {
			return TrendResponse.getErrorResponse(e.getMessage());
		}
	}



	/* TEMPLATE
	@RolesAllowed({"user","admin"})
	@GET
	@Path("/serie/{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public SeriesOfValuesResponse getFunc(
			@QueryParam("id") String id,
			@QueryParam("start") String start,
			@QueryParam("end") String end,
			@QueryParam("sampling.cron") String samplingCron,
			@QueryParam("sampling.duration") Long samplingDur,
			@QueryParam("sampling.interpolation") Interpolation interpolation,
			@QueryParam("aggregation.function") AggregationFunction aggregation,
			@QueryParam("aggregation.window") AggregationWindow window,
			@QueryParam("aggregation.cron") String windowCron,
			@QueryParam("aggregation.duration") Long windowDur
			) {
		Address a = new Address(id);
		Trend v = ProviderManager.getTrendsProvider(a.getProtocol()).getTrend(a, getSeriesSpecs(start, end, samplingCron, samplingDur, interpolation));
		return new SeriesOfValuesResponse(a, v);
	}
	 */


	private TrendsRequest getReadSpecsSingle(String id, String start, String end, String samplingCron, 
			Long samplingDur, Interpolation interpolation, AggregationFunction aggregation, AggregationWindow window) {

		TrendsRequest readSpecs = getReadSpecsBase(start, end, samplingCron, samplingDur, interpolation, aggregation, window);
		readSpecs.setAddressSpecs(new Address(id));
		return readSpecs;
	}

	private TrendsRequest getReadSpecsMulti(String json, boolean grouping, String start, String end, String samplingCron, 
			Long samplingDur, Interpolation interpolation, AggregationFunction aggregation, AggregationWindow window) throws JsonParseException, JsonMappingException, IOException {	
		TrendsRequest readSpecs = getReadSpecsBase(start, end, samplingCron, samplingDur, interpolation, aggregation, window);
		if(grouping) {
			readSpecs.setAddressSpecs(m.readValue(json, AddressRequest.class));
		} else {
			readSpecs.setAddressSpecs(m.readValue(json, GroupedAddressRequest.class));
		}
		return readSpecs;
	}


	private TrendsRequest getReadSpecsBase(String start, String end, String samplingCron, 
			Long samplingDur, Interpolation interpolation, AggregationFunction aggregation, AggregationWindow window) {
		TrendsRequest readSpecs = new TrendsRequest();
		SeriesSpecs s = getSeriesSpecs(start, end, samplingCron, samplingDur, interpolation, aggregation, window);
		readSpecs.setSeriesSpecs(s);
		readSpecs.setAggregation(aggregation, window);
		return readSpecs;
	}
	
	private SeriesSpecs getSeriesSpecs(String start, String end, String cron, 
			Long duration, Interpolation interpolation,  AggregationFunction aggregation, AggregationWindow window) {
		SeriesSpecs s = null;
		if(interpolation != null && !interpolation.equals(Interpolation.NONE)) {
			if(cron != null) {
				s = new SeriesSpecs(getDateTime(start), getDateTime(end), cron, interpolation);
			} else if (duration != null) {
				s = new SeriesSpecs(getDateTime(start), getDateTime(end), Duration.of(duration, ChronoUnit.SECONDS), interpolation);  
			} 
		} else if (window != null && !window.equals(AggregationWindow.NONE)) {
			if(cron != null) {
				s = new SeriesSpecs(getDateTime(start), getDateTime(end), cron, aggregation);
			} else if (duration != null) {
				s = new SeriesSpecs(getDateTime(start), getDateTime(end), Duration.of(duration, ChronoUnit.SECONDS), aggregation);  
			}
		} else {
			s = new SeriesSpecs(getDateTime(start), getDateTime(end));
		}
		return s;
	}

	private ZonedDateTime getDateTime(String s) {
		return ZonedDateTime.now();
	}
}
