/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 *
 * PeerfactSim.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.topology.movement;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

import de.tud.kom.p2psim.api.topology.movement.MovementModel;
import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator;
import de.tud.kom.p2psim.impl.topology.PositionVector;
import de.tudarmstadt.maki.simonstrator.api.Event;
import de.tudarmstadt.maki.simonstrator.api.EventHandler;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;

public class SmarterMovementModel implements MovementModel, EventHandler {
	private static final Location DEFAULT_LOCATION = new PositionVector(Double.NaN, Double.NaN);

	public static final int OFFSET_X = 0;
	public static final int OFFSET_Y = 0;
	
	public static final double SCALE_OVERVIEW = 0.3;
	public static final double SCALE_A = 2;
	public static final double SCALE_B = 4;
	public static final double SCALE_C = 4;
	public static final double SCALE = SCALE_OVERVIEW;

	private static SmarterMovementModel MOVEMENT;

	protected long timeBetweenMoveOperations = 1;
	
	private Set<SimLocationActuator> knowActuators = new HashSet<>();

	@XMLConfigurableConstructor({ "timeBetweenMoveOperations"})
	public SmarterMovementModel(long timeBetweenMoveOperations) {
		MOVEMENT = this;

		this.timeBetweenMoveOperations = timeBetweenMoveOperations;
	}

	/**
	 * Adding an additional component to be moved by this movement model
	 * @param comp The component to be added.
	 */
	@Override
	public final void addComponent(SimLocationActuator comp) {
		comp.updateCurrentLocation(DEFAULT_LOCATION);
		
		if (!knowActuators.contains(comp)) {
			knowActuators.add(comp);
			Event.scheduleImmediately(this, new HostTraceContainer(comp, (int) comp.getHost().getId().value()), 0);
		}
	}

	/**
	 * Returns the time between movement operations
	 * @return time between movement operations
	 */
	public long getTimeBetweenMoveOperations() {
		return timeBetweenMoveOperations;
	}

	/**
	 * Set the time between movement operations
	 * @param time the time between movement operations
	 */
	@Override
	public void setTimeBetweenMoveOperations(long time) {
		this.timeBetweenMoveOperations = time;
	}

	/**
	 * Place a component at the correct location
	 * @param actuator The component to be placed.
	 */
	@Override
	public void placeComponent(SimLocationActuator actuator) {
		// Initial placement
		if (!knowActuators.contains(actuator)) {
			knowActuators.add(actuator);
			Event.scheduleImmediately(this, new HostTraceContainer(actuator, (int) actuator.getHost().getId().value()), 0);
		}
	}

	/**
	 * Used for the periodical updates of the vehicle positions
	 * @param content not used in this case, should be null
	 * @param type not used in this case, should be 0
	 */
	@Override
	public void eventOccurred(Object content, int type) {
		HostTraceContainer container = (HostTraceContainer) content;
		
		Location location = container.getNextLocation();
		if (location != null) {
			container.getActuator().updateCurrentLocation(location);
		}

		if (container.tryNextStep()) {
			long sleepTime = container.getSleepTime();

			// Reschedule next step
			Event.scheduleWithDelay(sleepTime, this, container, 0);
		} else {
			container.getActuator().updateCurrentLocation(DEFAULT_LOCATION);
		}
	}

	private class HostTraceContainer {

		private SimLocationActuator actuator;
		private int currentIndex;
		private Location nextLocation;
		
		private Scanner scanner;
		private boolean init = false;
		private long _sleepTime;

		public HostTraceContainer(SimLocationActuator actuator,
				int currentIndex) {
			this.actuator = actuator;
			this.currentIndex = currentIndex;
		}
		
		public SimLocationActuator getActuator() {
			return actuator;
		}
		
		public Location getNextLocation() {
			return nextLocation;
		}
		
		public boolean tryNextStep() {
			if (scanner == null && !init) {
				init = true;
				try {
					File file = new File("smarter/trace/mobilityTraceFile-File-" + currentIndex + ".if");
					
					if (!file.exists()) {
						_sleepTime = -1;
						return false;
					}
					
					scanner = new Scanner(file);
				} catch (FileNotFoundException e) {
					_sleepTime = -1;
					return false;
				}
			}
			
			if (!scanner.hasNextLine()) {
				_sleepTime = -1;
				return false;
			}
			
			// 2419 2664.6969329 5214.21917358
			String line = scanner.nextLine();
			String[] split = line.split(" ");
			
			long nextTimestamp = Long.valueOf(split[0]);
			double x = Double.valueOf(split[1]) * SCALE + OFFSET_X;
			double y = Double.valueOf(split[2]) * SCALE + OFFSET_Y;
			
			nextLocation = new PositionVector(x, y);
			_sleepTime = nextTimestamp * timeBetweenMoveOperations - Time.getCurrentTime();
			
			return true;
		}
		
		public long getSleepTime() {
			return _sleepTime;
		}
		
	}
}
