package at.ac.tuwien.lsdc.sched;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import at.ac.tuwien.lsdc.exception.OutOfPMsException;
import at.ac.tuwien.lsdc.exception.VMResizeException;
import at.ac.tuwien.lsdc.types.Application;
import at.ac.tuwien.lsdc.types.PhysicalMachine;
import at.ac.tuwien.lsdc.types.ScenarioType;
import at.ac.tuwien.lsdc.types.SchedulerEvent;
import at.ac.tuwien.lsdc.types.SchedulerType;
import at.ac.tuwien.lsdc.types.VirtualMachine;
import at.ac.tuwien.lsdc.types.VirtualMachine.VMType;

/**
 * Scheduler B.
 *
 * Initial State: All PMs switched off. If an application arrives, try to modify size, CPU and RAM
 * of an existing VM to run the application. If no VM is running, create a new one (start a new PM
 * if necessary). If the application has finished decrease the size, CPU and RAM of the VM. If no
 * applications are running on a VM, shut down the VM. If no VM is running on a PM, shut down the
 * PM. Try to get a maximum of utilization on every PM. Migration: Try to move applications from VMs
 * to other VMs to get a better utilization and to use less PMs.
 *
 * @author jan
 *
 */
public class SchedulerB extends AbstractSchedulerWithMigration {
	/**
	 * Logger.
	 */
	private static final Logger LOG = LoggerFactory.getLogger(SchedulerB.class);

	public SchedulerB(int numPMs, int numCloudPartners, File schedulerLog, ScenarioType scenario)
			throws IOException {
		super(numPMs, numCloudPartners, schedulerLog, scenario);
		vmType = VMType.Resizable;
	}

	@Override
	protected boolean deployApp(VirtualMachine vm, Application app) {
		VirtualMachine current = app.getRunningOn();
		boolean deployed = false;
		if (!vm.enoughResources(app)) {
			try {
				vm.resizeUp(app);
			} catch (VMResizeException ex) {
				return false;
			}
		}
		deployed = vm.startApplication(app);
		if (deployed) {
			app.setRunningOn(vm);

			if (current != null) {
				current.stopApplication(app);
				current.resizeDown(app);
			}
		}
		return deployed;
	}

	/**
	 * Cleanup completed apps. Downsize VMs and if an VM becomes empty, shut down VM + PM.
	 *
	 * @param events list of all events that happened in this timeslot.
	 */
	@Override
	protected void handleEndEvents(LinkedList<SchedulerEvent> events) {
//		LOG.debug("stopApps():" + events);
		for (SchedulerEvent evt : events) {
			VirtualMachine vm = evt.getApp().getRunningOn();
			vm.stopApplication(evt.getApp());
			vm.resizeDown(evt.getApp());

			numStopped ++;

			if (vm.getApplications().size() == 0) {
				PhysicalMachine pm = vm.getRunningOn();
				pm.stopVirtualMachine(vm);
				manager.stopPhysicalMachine(pm.getId());
			}
		}
	}

	/**
	 * Try to start all Apps. Upsize the VM, if not possible start another PM, if not possible,
	 * delay start.
	 *
	 * @param events list of all events that happened in this timeslot.
	 */
	@Override
	protected void handleStartEvents(LinkedList<SchedulerEvent> events) {
//		LOG.debug("startApps():" + events);
		boolean deployed = false;
		for (SchedulerEvent evt : events) {
			deployed = false;
			VirtualMachine vm = null;
			List<PhysicalMachine> sortedPMs = manager.getRevSortedPMs();

			for (PhysicalMachine pm : sortedPMs) {
				if (pm.isRunning() && (pm.countCurrentlyRunningVMs() > 0)) {
					vm = pm.getVirtualMachines().values().iterator().next();
					deployed = deployApp(vm, evt.getApp());
					if (deployed)
						break;
				}
			}
			if (!deployed) {
				try {
					vm = manager.startPhysicalMachine().startVirtualMachine(evt.getApp().getSize(),
							evt.getApp().getRam(), evt.getApp().getCpu(), vmType);
					deployed = deployApp(vm, evt.getApp());
				} catch (OutOfPMsException e) {
					if (federation.askToOutsource(evt.getApp())) {
						insertOutsourcedStartEvent(currTime + 1, evt.getApp());
					} else {
//						LOG.info("delaying the start of:" + evt.getApp());
						delayedApps.add(evt.getApp());
					}
				}
			}
			if (deployed) {
				insertStopEvent(currTime + evt.getApp().getDuration(), evt.getApp());

				numStarted ++;

				if(manager.countCurrentlyRunningPMs() < manager.getMaxPMs()) {
					Application app = federation.askToInsource();
					if(app != null) {
						insertInsourcedStartEvent(currTime + 1, evt.getApp());
					}
				}
			}
		}
	}

	@Override
	protected String getSchedulerType() {
		return SchedulerType.B.toString();
	}

}
