package at.ac.tuwien.lsdc.types;

import java.util.ArrayList;
import java.util.HashMap;

import at.ac.tuwien.lsdc.exception.VMResizeException;
import at.ac.tuwien.lsdc.management.MachineManager;

public class VirtualMachine {

	public enum VMType {
		Resizable, NonResizable
	}

	private static int count = 0;
	private int posOnPM;

	private PhysicalMachine runningOn;

	private HashMap<Integer, Application> applications = new HashMap<Integer, Application>();

	private int id;

	public static final int initialSize = 100;
	public static final int initialRAM = 50;
	public static final int initialCPU = 150;

	private int reservedSize;
	private int reservedRAM;
	private int reservedCPU;

	private VMType type;

	private int totalResizeCalls = 0;

	public VirtualMachine(int size, int RAM, int CPU, PhysicalMachine pm, VMType type) {
		id = count;
		count++;
		reservedSize = size + initialSize;
		reservedRAM = RAM + initialRAM;
		reservedCPU = CPU + initialCPU;
		runningOn = pm;
		posOnPM = pm.countCurrentlyRunningVMs();
		this.type = type;
	}

	public boolean startApplication(Application app) {
		if (enoughResources(app)) {
			applications.put(app.getID(), app);
			app.setRunningOn(this);
			return true;
		} else
			return false;
	}

	/**
	 * Set the VM size to the given new values.
	 *
	 * @param newSize
	 *          the new size of the VM, excluding the initial consumption.
	 * @param newRAM
	 *          the new RAM of the VM, excluding the initial consumption.
	 * @param newCPU
	 *          the new CPU of the VM, excluding the initial consumption.
	 * @return
	 */
//	public boolean resizeVM(int newSize, int newRAM, int newCPU) {
//		if (type == VMType.Resizable
//				&& runningOn.checkExtendVM(newSize - reservedSize, newRAM - reservedRAM, newCPU
//						- reservedCPU)) {
//			// Resize VM
//			reservedSize = initialSize + newSize;
//			reservedRAM = initialRAM + newRAM;
//			reservedCPU = initialCPU + newCPU;
//			return true;
//		} else
//			return false;
////			throw new VMResizeException("Could not resize VM!", this);
//	}

	public boolean resizeUp(Application app) throws VMResizeException {

		int diffSize = (app.getSize() - availableSize());
		int diffRAM = (app.getRam() - availableRAM());
		int diffCPU = (app.getCpu() - availableCPU());

		if(!(type == VMType.Resizable) || !runningOn.checkExtendVM(diffSize, diffRAM, diffCPU)) {
			throw new VMResizeException("Could not resize VM!", this);
		}

		runningOn.resizeUp(diffSize, diffRAM, diffCPU);

		reservedSize = reservedSize + diffSize;
		reservedRAM = reservedRAM + diffRAM;
		reservedCPU = reservedCPU + diffCPU;
//		totalResizeCalls++;
		MachineManager.addResizeCall();
		return true;
	}

	public boolean resizeDown(Application app) {
		runningOn.resizeDown(app);

		reservedSize = reservedSize - app.getSize();
		reservedRAM = reservedRAM - app.getRam();
		reservedCPU = reservedCPU - app.getCpu();
//		totalResizeCalls++;
		MachineManager.addResizeCall();
		return true;
	}

	public boolean stopApplication(Application app) {
		if (applications.containsKey(app.getID())) {
			applications.remove(app.getID());
			return true;
		} else
			return false;
	}

	public boolean enoughResources(Application app) {
		return (app.getSize() <= availableSize()) && (app.getRam() <= availableRAM()) && (app.getCpu() <= availableCPU());
	}

	public int availableSize() {
		return reservedSize - getSize();
	}

	public int availableRAM() {
		return reservedRAM - getRAM();
	}

	public int availableCPU() {
		return reservedCPU - getCPU();
	}

	public int getId() {
		return id;
	}

	public PhysicalMachine getRunningOn() {
		return runningOn;
	}

	public int getPositionOnPM() {
		return posOnPM;
	}

	public ArrayList<Application> getApplications() {
		return new ArrayList<Application>(applications.values());
	}

	public int getSize() {
		int usedSize = initialSize;
		for (Application a : applications.values())
			usedSize += a.getSize();
		return usedSize;
	}

	public int getRAM() {
		int usedRAM = initialRAM;
		for (Application a : applications.values())
			usedRAM += a.getRam();
		return usedRAM;
	}

	public int getCPU() {
		int usedCPU = initialCPU;
		for (Application a : applications.values())
			usedCPU += a.getCpu();
		return usedCPU;
	}

	public int getReservedSize() {
		return reservedSize;
	}

	public int getReservedRAM() {
		return reservedRAM;
	}

	public int getReservedCPU() {
		return reservedCPU;
	}

	public int getTotalResizeCalls() {
		return totalResizeCalls;
	}

	@Override
	public String toString() {
		return "VirtualMachine [posOnPM=" + posOnPM + ", runningOn=" + runningOn + ", id=" + id + ", reservedSize="
				+ reservedSize + ", reservedRAM=" + reservedRAM + ", reservedCPU=" + reservedCPU + ", type=" + type + "]";
	}

}
