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

import java.time.ZonedDateTime;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;

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.SeriesSpecs;
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.Value;

public class WindowMaker {

	public static Map<Address, Trend> computeWindows(Map<Address, Trend> data, SeriesSpecs s) {
		List<ZonedDateTime> timestamps = TimestampGenerator.generateTimestamps(s);
		
		Map<Address, Trend> r = new LinkedHashMap<Address, Trend>(data.size());
		for(Address a : data.keySet()) {
			Trend ot = data.get(a);
			Trend rt = new Trend(getWindows(ot.getNavigableData(), timestamps, s));
			r.put(a, rt);
		}
		
		return data;
	}

	private static NavigableMap<ZonedDateTime, Value> getWindows(NavigableMap<ZonedDateTime, Value> d,
			List<ZonedDateTime> t, SeriesSpecs s) {
		if(d == null || d.isEmpty()) {
			return errorSamples(t, s.getWindowAggregation());
		}
		
		NavigableMap<ZonedDateTime, Value> r = new TreeMap<ZonedDateTime, Value>();
		Iterator<Map.Entry<ZonedDateTime, Value>> it = d.entrySet().iterator();
		NavigableMap<ZonedDateTime, Value> window;
		AggregatedValue v;
		int i = 0;
		while(i < t.size() - 1) {
			ZonedDateTime windowStart = t.get(i);
			ZonedDateTime windowEnd = t.get(i+1);

			window = getWindow(it, windowStart, windowEnd);
			v = Aggregator.computeTemporalAggregation(window, s.getWindowAggregation(), windowStart, windowEnd);
			r.put(windowStart, v);
			i++;
			
		}
		// the last window
		window = getWindow(it, t.get(i), s.getEnd());
		v = Aggregator.computeTemporalAggregation(window, s.getWindowAggregation(), t.get(i), s.getEnd());
		r.put(t.get(i), v);
		
		return r;
	}
	
	// Custom implementation of NavigableMap.getSubmas, so the search for the boundaries does not have
	// to be performed over and over again 
	private static NavigableMap<ZonedDateTime, Value> getWindow(Iterator<Entry<ZonedDateTime, Value>> i,
			ZonedDateTime windowStart, ZonedDateTime windowEnd) {
		NavigableMap<ZonedDateTime, Value> w = new TreeMap<ZonedDateTime, Value>();
		if(!i.hasNext()) {
			return w;
		}
		
		Map.Entry<ZonedDateTime, Value> e = i.next();
		// moving to the start of a window - should not be called
		while(i.hasNext() && e.getKey().compareTo(windowStart) <= 0) {
			e = i.next();
		}
		// adding values to the window
		while(i.hasNext() && e.getKey().compareTo(windowEnd) < 0) {
			w.put(e.getKey(), e.getValue());
			e = i.next();
		}
		
		
		
		return w;
	}

	private static NavigableMap<ZonedDateTime, Value> errorSamples(List<ZonedDateTime> t, AggregationFunction a) {
		NavigableMap<ZonedDateTime, Value> r = new TreeMap<ZonedDateTime, Value>();
		for(ZonedDateTime dt : t) {
			r.put(dt, AggregatedValue.getAggregationError(a, "No data to use for window aggregation"));
		}
		return r;
	}

}
