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

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;

import cz.muni.fi.lasaris.sbms.data.api.request.GroupedSnapshotRequest;
import cz.muni.fi.lasaris.sbms.data.api.request.SnapshotRequest;
import cz.muni.fi.lasaris.sbms.data.api.response.AggregationResponse;
import cz.muni.fi.lasaris.sbms.data.api.response.GroupedSnapshotResponse;
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.containers.Snapshot;
import cz.muni.fi.lasaris.sbms.data.entities.values.AggregatedValue;
import cz.muni.fi.lasaris.sbms.data.entities.values.RawValue;
import cz.muni.fi.lasaris.sbms.data.util.Aggregator;



public class DataCollector {
	
	final static Logger logger = Logger.getLogger(DataCollector.class);
	
	public SnapshotResponse getDataPointValues(SnapshotRequest readSpecs, boolean allowCache) {
		return new SnapshotResponse(getDataPointValuesSnapshot(readSpecs, allowCache));
	}
	
	private Snapshot getDataPointValuesSnapshot(SnapshotRequest readSpecs, boolean allowCache) {
NavigableMap<String, SnapshotRequest> protocols = readSpecs.getProtocols();
		
		if(protocols.size() == 1) {
			return ProviderManager.getDataPointsProvider(protocols.firstKey())
					.getDataPointValues(protocols.get(protocols.firstKey()).getAddresses(), allowCache);
		}
		
		AtomicInteger counter = new AtomicInteger(0);
		int count = protocols.keySet().size();
		
		List<Snapshot> results = Collections.synchronizedList(new LinkedList<Snapshot>());
		
		for(String protocol : protocols.keySet()) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					Snapshot rs = (ProviderManager.getDataPointsProvider(protocol)
							.getDataPointValues(protocols.get(protocol).getAddresses(), allowCache)); 
					synchronized(results) {
						results.add(rs);
						counter.incrementAndGet();
						results.notifyAll();
					}
					
				}}).start();
		}
		synchronized(results) {
			while(counter.get() < count) {
				try {
					results.wait();
				} catch (InterruptedException e) {
					logger.error("Exception while waiting for completion.", e);
				}
			}
		}
		
		Snapshot result = new Snapshot();
		
		
		synchronized(results) {
			for(Snapshot r : results) {
				result.addAll(r.getData());
			}
		}
		
		return result;
	}
	
	public AggregationResponse getDataPointsAggregation(SnapshotRequest readSpecs, AggregationFunction aggregation,
			boolean allowCache) {
		
		NavigableMap<String, SnapshotRequest> protocols = readSpecs.getProtocols();
		
		if(protocols.size() == 1) {
			return new AggregationResponse(ProviderManager.getDataPointsProvider(protocols.firstKey())
					.getDataPointsAggregation(protocols.get(protocols.firstKey()).getAddresses(), aggregation, allowCache));
		}
		
		Snapshot data = getDataPointValuesSnapshot(readSpecs, allowCache);
		
		return new AggregationResponse(computeAggregation(data.getData(), aggregation));
		
	}
	
	public GroupedSnapshotResponse getDataPointGroupedValues(GroupedSnapshotRequest readSpecs,
			boolean allowCache) {
		return new GroupedSnapshotResponse(getDataPointGroupedValuesMap(readSpecs, allowCache));
	}
	
	private Map<String, Snapshot> getDataPointGroupedValuesMap(GroupedSnapshotRequest readSpecs,
			boolean allowCache) {
		NavigableMap<String, GroupedSnapshotRequest> protocols = readSpecs.getProtocols();
		
		if(protocols.size() == 1) {
			return ProviderManager.getDataPointsProvider(protocols.firstKey())
					.getDataPointGroupedValues(protocols.get(protocols.firstKey()).getGroups(), allowCache);
		}
		
		AtomicInteger counter = new AtomicInteger(0);
		int count = protocols.keySet().size();
		
		List<GroupedSnapshotResponse> results = Collections.synchronizedList(new LinkedList<GroupedSnapshotResponse>());
		
		for(String protocol : protocols.keySet()) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					GroupedSnapshotResponse rs = new GroupedSnapshotResponse(ProviderManager.getDataPointsProvider(protocol).getDataPointGroupedValues(protocols.get(protocol).getGroups(), allowCache)); 
					synchronized(results) {
						results.add(rs);
						counter.incrementAndGet();
						results.notifyAll();
					}
					
				}}).start();
		}
		
		synchronized(results) {
			while(counter.get() < count) {
				try {
					results.wait();
				} catch (InterruptedException e) {
					logger.error("Exception while waiting for completion.", e);
				}
			}
		}
		
		Map<String, Snapshot> result = new LinkedHashMap<String, Snapshot>();
		for(String group : readSpecs.getGroups().keySet()) {
			result.put(group, new Snapshot());
		}
		
		synchronized(results) {
			for(GroupedSnapshotResponse r : results) {
				for(String group : r.getResults().keySet()) {
					result.get(group).addAll(r.getResults().get(group).getData());
				}
			}
		}
		
		return result;
	}

	public AggregationResponse getDataPointGroupAggregations(GroupedSnapshotRequest readSpecs,
			AggregationFunction aggregation, boolean allowCache) {
		
		NavigableMap<String, GroupedSnapshotRequest> protocols = readSpecs.getProtocols();
		
		if(protocols.size() == 1) {
			return new AggregationResponse(ProviderManager.getDataPointsProvider(protocols.firstKey())
					.getDataPointGroupAggregations(protocols.get(protocols.firstKey()).getGroups(), aggregation, allowCache));
		}
		
		Map<String, Snapshot> data = getDataPointGroupedValuesMap(readSpecs, allowCache);
		
		return new AggregationResponse(computeGroupAggregations(data, aggregation));
	}

	private Map<String, AggregatedValue> computeGroupAggregations(Map<String, Snapshot> data, AggregationFunction aggregation) {
		Map<String, AggregatedValue> result = new LinkedHashMap<String, AggregatedValue>();
		
		for(String group : data.keySet()) {
			AggregatedValue value = Aggregator.computeAggregation(data.get(group).getData(), aggregation);
			result.put(group, value);
		}
		return result;
	}
	
	private AggregatedValue computeAggregation(Map<Address, RawValue> data, AggregationFunction aggregation) {
		return Aggregator.computeAggregation(data, aggregation);
	}

}
