package at.ac.tuwien.lsdc.management;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import at.ac.tuwien.lsdc.exception.OutOfPMsException;
import at.ac.tuwien.lsdc.exception.VMsRunningException;
import at.ac.tuwien.lsdc.types.PhysicalMachine;
import at.ac.tuwien.lsdc.util.NumberUtils;

/**
 * This class is responsible to start and stop PMs & VMs also it will be used to
 * put an application on a VM move an application and get utilization data
 *
 */
public class MachineManager {

	private HashMap<Integer, PhysicalMachine> PMs = new HashMap<Integer, PhysicalMachine>();

	private int numPMs;

	private int totalPMs = 0;
	private int totalVMs = 0;
	private static int totalResizeCalls = 0;

	public MachineManager(int numPMs) {
		this.numPMs = numPMs;
	}

	/**
	 * Start a physical machine
	 *
	 * @return the PM that was started, null if all machines already running
	 */
	public PhysicalMachine startPhysicalMachine() {
		if (PMs.size() >= numPMs)
			throw new OutOfPMsException("We cannot start another PM. All out PMs are already running!: " + numPMs);

		PhysicalMachine pm = new PhysicalMachine();
		PMs.put(pm.getId(), pm);
		pm.start();
		totalPMs++;
		return pm;
	}

	/**
	 * Stops a physical machine with the given id
	 *
	 * @param id
	 *          the id of the PM to stop
	 * @throws VMsRunningException
	 *           is thrown when there are still VMs running on this PM
	 */
	public void stopPhysicalMachine(int id) throws VMsRunningException {
		if (PMs.containsKey(id)) {
			PhysicalMachine pm = PMs.get(id);
			totalVMs += pm.getTotalVMs();
			pm.stop();
//			totalResizeCalls += pm.getTotalResizeCalls();
			PMs.remove(id);
		}
	}

	/**
	 * Returns all running physical machines
	 *
	 * @return the currently active PMs
	 */
	public Collection<PhysicalMachine> getPMs() {
		return PMs.values();
	}

	/**
	 * Returns the maximum number of available physical machines
	 *
	 * @return the maximum number of PMs
	 */
	public int getMaxPMs() {
		return numPMs;
	}

	/**
	 * Get the total size on all PMs
	 *
	 * @return the total size on all PMs
	 */
	public int getTotalSize() {
		int totalSize = 0;
		for (PhysicalMachine pm : PMs.values()) {
			if (pm.isRunning())
				totalSize += pm.getCurrentSize();
		}
		return totalSize;
	}

	/**
	 * Get the total Ram usage of all PMs
	 *
	 * @return the total usage of Ram
	 */
	public int getTotalRam() {
		int totalRam = 0;
		for (PhysicalMachine pm : PMs.values()) {
			if (pm.isRunning())
				totalRam += pm.getCurrentRam();
		}
		return totalRam;
	}

	/**
	 * Get the total Cpu usage of all PMs
	 *
	 * @return the total usage of Cpu power
	 */
	public int getTotalCpu() {
		int totalCpu = 0;
		for (PhysicalMachine pm : PMs.values()) {
			if (pm.isRunning())
				totalCpu += pm.getCurrentCPU();
		}
		return totalCpu;
	}

	/**
	 * Gets the current power consumption summed up from each PM
	 *
	 * @return the current power consumption
	 */
	public double getCurrentConsumption() {
		double consumption = 0;
		for (PhysicalMachine pm : PMs.values()) {
			if (pm.isRunning())
				consumption += pm.getConsumption();
		}
		consumption = NumberUtils.roundDouble(consumption);
		return consumption;
	}

	/**
	 * Gets the number of currently running PMs
	 *
	 * @return the number of currently running PMs
	 */
	public int countCurrentlyRunningPMs() {
		int running = 0;
		for (PhysicalMachine pm : PMs.values()) {
			if (pm.isRunning())
				running++;
		}
		return running;
	}

	/**
	 * Gets the number of currently running VMs
	 *
	 * @return the number of currently running VMs
	 */
	public int countCurrentlyRunningVMs() {
		int running = 0;
		for (PhysicalMachine pm : PMs.values()) {
			if (pm.isRunning()) {
				running += pm.countCurrentlyRunningVMs();
			}
		}
		return running;
	}

	/**
	 * sorting physical machines by resource utilization
	 *
	 * @return List of PMs sorted by resource utilization
	 */
	public List<PhysicalMachine> getSortedPMs() {
		List<PhysicalMachine> sortedPMs = new ArrayList<PhysicalMachine>(PMs.values());
		Collections.sort(sortedPMs);
		return sortedPMs;
	}
	
	/**
	 * sorting physical machines by resource utilization
	 *
	 * @return List of PMs sorted by resource utilization
	 */
	public List<PhysicalMachine> getRevSortedPMs() {
		List<PhysicalMachine> revSortedPMs = getSortedPMs();
		Collections.reverse(revSortedPMs);
		return revSortedPMs;
	}

	public int getTotalPMs() {
		return totalPMs;
	}

	public int getTotalVMs() {
		return totalVMs;
	}

	public static int getTotalResizeCalls() {
		return totalResizeCalls;
	}
	
	public static void addResizeCall() {
		totalResizeCalls ++;
	}

}
