/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of Simonstrator.KOM.
 *
 * Simonstrator.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.vehicular.caching;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.tud.kom.p2psim.impl.simengine.Simulator;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException;
import de.tudarmstadt.maki.simonstrator.api.component.network.NetInterface;
import de.tudarmstadt.maki.simonstrator.api.component.network.NetworkComponent.NetInterfaceName;
import de.tudarmstadt.maki.simonstrator.api.component.transport.ConnectivityListener;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.VehicleInformationComponent;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.caching.CachingComponent;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.caching.decision.CacheDecisionStrategy;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.caching.invalidation.CacheInvalidationStrategy;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.caching.replacement.CacheReplacementStrategy;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.AvailableInformationAttributes;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.JamInformation;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.PointInformation;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetworkEdge;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetworkRoute;

public class CooperativeCachingComponent extends DefaultCachingComponent
		implements CachingComponent, ConnectivityListener {
	
	private double vehicleRate = 0.0; // vehicles/s (it gives the No. of vehicles for 1 second)

	public CooperativeCachingComponent(Host pHost,
			CacheInvalidationStrategy pInvalidationStrategy,
			CacheReplacementStrategy pReplacementStrategy,
			CacheDecisionStrategy pDecisionStrategy) {
		super(pHost, pInvalidationStrategy, pReplacementStrategy,
				pDecisionStrategy);
	}

	@Override
	public <T extends PointInformation> List<T> getCacheEntries(
			Class<T> pCacheEntryClass) {
		
		if (_cache.containsKey(pCacheEntryClass)) {
			List<? extends PointInformation> cacheEntries = _cache
					.get(pCacheEntryClass);
			List<T> results = new ArrayList<>();

			int invalidatedCount = 0;
			long maxTimestamp = -1;
			
			for (int i = 0; i < cacheEntries.size(); i++) {
				PointInformation object = cacheEntries.get(i);
				if (_invalidationStrategy.checkInformation(object)) {
					invalidatedCount++;
					if (maxTimestamp < object.getDetectionDate()) {
						maxTimestamp = object.getDetectionDate();
					}
				}
			}
			
			if (invalidatedCount == cacheEntries.size()) {
				cacheEntries.clear();
			} else if (invalidatedCount > 1) {
				for (int i = 0; i < cacheEntries.size(); i++) {
					PointInformation object = cacheEntries.get(i);
					if (_invalidationStrategy.checkInformation(object)) {
						if (maxTimestamp != object.getDetectionDate()) {
							cacheEntries.remove(i--);
						}
					}
				}
			}
			
			if (cacheEntries.size() > 0) {
				PointInformation object = cacheEntries.get(0);
				
				if (object.getAttribute(AvailableInformationAttributes.VEHICULAR_RATE) != null) {
					vehicleRate = (double) (object.getAttribute(AvailableInformationAttributes.VEHICULAR_RATE));
				} else {
					vehicleRate = 1 / (double)Time.SECOND; // USE A DEFAULT CONSTANT VEHICULAR RATE
				}
				
				if (vehicleRate > 0.0) {
					long timeDiff = ((long) (1 / vehicleRate));
					long minValidInformation = Math.max(Time.getCurrentTime() - (long)object.getAttribute(AvailableInformationAttributes.TTL), 0);
					for (long timestamp = minValidInformation + timeDiff; timestamp < object.getDetectionDate(); timestamp += timeDiff) {
						PointInformation pointInformation = object
								.createDefaultInformation();
						pointInformation.setDetectionTime(timestamp);
						results.add((T) pointInformation);
					}
				}
			}
			
			for (int i = 0; i < cacheEntries.size(); i++) {
				PointInformation object = cacheEntries.get(i);
				long nextUpdate = 0;
				PointInformation nextObj;

				if (i + 1 < cacheEntries.size()) {
					nextObj = cacheEntries.get(i + 1);
					nextUpdate = nextObj.getDetectionDate();
					if (nextObj.getAttribute(AvailableInformationAttributes.VEHICULAR_RATE) != null) {
						vehicleRate = (double) (nextObj.getAttribute(AvailableInformationAttributes.VEHICULAR_RATE));
					} else {
						vehicleRate = 1 / (double)Time.SECOND; // USE A DEFAULT CONSTANT VEHICULAR RATE
					}
				} else {
					nextUpdate = Time.getCurrentTime();
					if (object.getAttribute(AvailableInformationAttributes.VEHICULAR_RATE) != null) {
						if (i == 0) { // TO MEASURE VEHICLERATE FOR FIRST PUB FROM THE TIME SIMULATION STARTED (Simulator.getStartTime())
							long timeDiffOfFirstPub = (object.getDetectionDate());
							if (timeDiffOfFirstPub > 0) {
								vehicleRate = (double) object.getAttribute(AvailableInformationAttributes.VEHICULAR_RATE);
								vehicleRate = (vehicleRate * object.getDetectionDate()) / timeDiffOfFirstPub;
							}
						} else {
							vehicleRate = (double) (object.getAttribute(AvailableInformationAttributes.VEHICULAR_RATE));
						}
					} else {
						vehicleRate = 1 / (double)Time.SECOND; // USE A DEFAULT CONSTANT VEHICULAR RATE
						
					}
				}
				results.add((T) object);

				if (vehicleRate > 0.0) {
					long timeDiff = ((long) (1 / vehicleRate));
					for (long timestamp = object.getDetectionDate()
							+ timeDiff; timestamp < nextUpdate; timestamp += timeDiff) {
						PointInformation pointInformation = object
								.clonePointInformation();
						pointInformation.setDetectionTime(timestamp);
						results.add((T) pointInformation);
					}
				}
			}

			for (int i = 0; i < results.size(); i++) {
				PointInformation object = results.get(i);
				if (_invalidationStrategy.checkInformation(object)) {
					results.remove(i--);
					continue;
				}
			}
			
			return results;
		}
		return null;
	}

	// @Override
	public <T extends PointInformation> void storeCacheEntry(T pCacheEntry) {
		super.storeCacheEntry(pCacheEntry);
	}

}
