package at.ac.tuwien.lsdc.sched;

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

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

import at.ac.tuwien.lsdc.exception.ActiveApplicationsException;
import at.ac.tuwien.lsdc.exception.VMsRunningException;
import at.ac.tuwien.lsdc.types.Application;
import at.ac.tuwien.lsdc.types.ApplicationResourceComparator;
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;

public class SchedulerA extends AbstractScheduler {

	private static final Logger log = LoggerFactory.getLogger(SchedulerA.class);

	public SchedulerA(int numPMs, int numCloudPartners, File schedulerLog, ScenarioType scenario)
			throws IOException {
		super(numPMs, numCloudPartners, schedulerLog, scenario);

		this.vmType = VMType.NonResizable;
	}

	@Override
	protected void handleEndEvents(LinkedList<SchedulerEvent> events) {
		for (SchedulerEvent evt : events) {
			VirtualMachine vm = evt.getApp().getRunningOn();
			vm.stopApplication(evt.getApp());
			PhysicalMachine pm = vm.getRunningOn();
			try {
				pm.stopVirtualMachine(vm);
				if (pm.countCurrentlyRunningVMs() == 0) {
					try {
						manager.stopPhysicalMachine(pm.getId());
					} catch (VMsRunningException e) {
						log.warn("PM " + pm.getId() + " could not be stopped, " + e.getMessage());
					}
				}
				numStopped ++;
//				log.info("application stopped at timestamp " + currTime + ", " + "vm "
//						+ vm.getPositionOnPM() + ", pm " + pm.getId());
			} catch (ActiveApplicationsException e) {
				log.warn("VM " + vm.getId() + "could not be stopped, " + e.getMessage());
			}
		}
	}

	@Override
	protected void handleStartEvents(LinkedList<SchedulerEvent> events) {
		// sorting applications by amount of resources (descending)
//		List<Application> sortedApps = sortApps(events);
		List<Application> apps = getApplications(events);
		
		for (Application app : apps) {
			boolean appDeployed = false;

			if (manager.getPMs().size() == 0) {
				PhysicalMachine pm = manager.startPhysicalMachine();

				if (pm.checkVM(app.getSize(), app.getRam(), app.getCpu())) 
					appDeployed = startApp(pm, app);
				
			} else {
				// sorting physical machines by resource utilization (descending)
				List<PhysicalMachine> revSortedPMs = manager.getRevSortedPMs();

				for (PhysicalMachine pm : revSortedPMs) {
					if (pm.checkVM(app.getSize(), app.getRam(), app.getCpu())) {
						appDeployed = startApp(pm, app);
						break;
					}
				}
				if (!appDeployed && (manager.getPMs().size() < manager.getMaxPMs())) {
					PhysicalMachine pm = manager.startPhysicalMachine();
					if (pm.checkVM(app.getSize(), app.getRam(), app.getCpu()))
						appDeployed = startApp(pm, app);
				}
			}
			if (!appDeployed) {
				if (federation.askToOutsource(app)) {
					log.debug("OUTSOURCE !!!!!!!!!!!");
					insertOutsourcedStartEvent(currTime + 1, app);
					appDeployed = true;
				} else
					delayedApps.add(app);
			}
			if(appDeployed) {
				numStarted ++;
				if(manager.countCurrentlyRunningPMs() < manager.getMaxPMs()) {
					Application application = federation.askToInsource();
					if(application != null) {
						insertInsourcedStartEvent(currTime + 1, application);
					}
				}
			}
		}
	}
	
	private boolean startApp(PhysicalMachine pm, Application app) {
		VirtualMachine vm = pm.startVirtualMachine(app.getSize(), app.getRam(),
				app.getCpu(), vmType);
		vm.startApplication(app);
		insertStopEvent(currTime + app.getDuration(), app);
		return true;
	}

	// sorting applications by amount of resources (descending)
	private List<Application> sortApps(LinkedList<SchedulerEvent> events) {
		List<Application> sortedApps = new LinkedList<Application>();
		for (SchedulerEvent evt : events) {
			sortedApps.add(evt.getApp());
		}
		Collections.sort(sortedApps, new ApplicationResourceComparator());
		Collections.reverse(sortedApps);
		return sortedApps;
	}
	
	public List<Application> getApplications(LinkedList<SchedulerEvent> events) {
		List<Application> apps = new LinkedList<Application>();
		for(SchedulerEvent evt: events) {
			apps.add(evt.getApp());
		}
		return apps;
	}

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

}
