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.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);
		
		// adding the end of the series in oder to compute the last window
		timestamps.add(s.getEnd());
		
		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 errorWindows(t, s.getWindowAggregation());
		}
		if( t == null || t.isEmpty()) {
			return errorWindow(s.getStart(), s.getWindowAggregation());
		}
		
		
		// Custom implementation of NavigableMap.getSubmap, so the search for the boundaries does not have
		// to be performed over and over again 
		NavigableMap<ZonedDateTime, Value> r = new TreeMap<ZonedDateTime, Value>();
		Iterator<Map.Entry<ZonedDateTime, Value>> it = d.entrySet().iterator();
		NavigableMap<ZonedDateTime, Value> window;
		AggregatedValue v;
		
		Map.Entry<ZonedDateTime, Value> previous = null;
		ZonedDateTime windowStart;
		ZonedDateTime windowEnd;
		
		// move to the start of the first window
		Map.Entry<ZonedDateTime, Value> e = it.next();
		// moving to the start of a window - should not be called
		while(it.hasNext() && e.getKey().compareTo(t.get(0)) < 0) {
			previous = e;
			e = it.next();
		}
		
		// window creation
		
		int i = 0;
		while(i < t.size() - 1) {
			windowStart = t.get(i);
			windowEnd = t.get(i+1);

			window = new TreeMap<ZonedDateTime, Value>();
			// needed for a computation of the temporal avgs
			if(previous != null) {
				window.put(previous.getKey(), previous.getValue());
			}
			// adding values to the window
			while(it.hasNext() && e.getKey().compareTo(windowEnd) < 0) {
				window.put(e.getKey(), e.getValue());
				previous = e;
				e = it.next();
			}
			v = Aggregator.computeTemporalAggregation(window, s.getWindowAggregation(), windowStart, windowEnd);
			r.put(windowStart, v);
			i++;
			
		}
		
		return r;
	}

	private static NavigableMap<ZonedDateTime, Value> errorWindows(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;
	}
	
	private static NavigableMap<ZonedDateTime, Value> errorWindow(ZonedDateTime start, AggregationFunction a) {
		NavigableMap<ZonedDateTime, Value> r = new TreeMap<ZonedDateTime, Value>();
		r.put(start, AggregatedValue.getAggregationError(a, "No windows defined."));
		
		return r;
	}

}
