/*
 * Title:        CloudSim Toolkit
 * Description:  CloudSim (Cloud Simulation) Toolkit for Modeling and Simulation of Clouds
 * Licence:      GPL - http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2009-2012, The University of Melbourne, Australia
 */

package org.cloudbus.cloudsim.network.datacenter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.core.CloudSimTags;
import org.cloudbus.cloudsim.core.SimEntity;
import org.cloudbus.cloudsim.core.SimEvent;
import org.cloudbus.cloudsim.core.predicates.PredicateType;
import org.cloudbus.cloudsim.lists.VmList;

/**
 * Represents a Network Switch.
 * @todo attributes should be private
 */
public class Switch extends SimEntity {

	/** The switch id */
	public int id;

        /**
         * The level (layer) of the switch in the network topology.
         */
	public int level;

        /**
         * The id of the datacenter where the switch is connected to.
         * @todo It doesn't appear to be used
         */
	public int datacenterid;

        /**
         * Map of packets sent to switches on the uplink,
         * where each key is a switch id and the corresponding
         * value is the packets sent to that switch.
         */
	public Map<Integer, List<NetworkPacket>> uplinkswitchpktlist;

        /**
         * Map of packets sent to switches on the downlink,
         * where each key is a switch id and the corresponding
         * value is the packets sent to that switch.
         */
	public Map<Integer, List<NetworkPacket>> downlinkswitchpktlist;

        /**
         * Map of hosts connected to the switch, where each key is the host ID
         * and the corresponding value is the host itself.
         */
	public Map<Integer, NetworkHost> hostlist;

        /**
         * List of uplink switches.
         */
	public List<Switch> uplinkswitches;

        /**
         * List of downlink switches.
         */
	public List<Switch> downlinkswitches;

        /**
         * Map of packets sent to hosts connected in the switch,
         * where each key is a host id and the corresponding
         * value is the packets sent to that host.
         */
	public Map<Integer, List<NetworkPacket>> packetTohost;

        /**
         * The switch type: edge switch or aggregation switch.
         * @todo should be an enum
         */
	int type;

        /**
         * Bandwitdh of uplink.
         */
	public double uplinkbandwidth;

        /**
         * Bandwitdh of downlink.
         */
	public double downlinkbandwidth;

        /**
         * The latency of the network where the switch is connected to.
         * @todo Its value is being defined by a constant, but not every subclass
         * is setting the attribute accordingly.
         * The constants should be used as default values, but the class
         * should have a setter for this attribute.
         */
	public double latency;

	public double numport;

        /**
         * The datacenter where the switch is connected to.
         * @todo It doesn't appear to be used
         */
	public NetworkDatacenter dc;

	/** Something is running on these hosts. 
         * @todo The attribute is only used at the TestExample class. */
	public SortedMap<Double, List<NetworkHost>> fintimelistHost = new TreeMap<Double, List<NetworkHost>>();

	/** Something is running on these hosts. 
         * @todo The attribute doesn't appear to be used */
	public SortedMap<Double, List<NetworkVm>> fintimelistVM = new TreeMap<Double, List<NetworkVm>>();

        /**
         * List of  received packets.
         */
	public ArrayList<NetworkPacket> pktlist = new ArrayList<NetworkPacket>();

	/** 
         * @todo The attribute doesn't appear to be used */
	public List<Vm> BagofTaskVm = new ArrayList<Vm>();

        /**
         * The time the switch spends to process a received packet.
         * This time is considered constant no matter how many packets 
         * the switch have to process.
         * 
         * @todo The value of this attribute is being defined by
         * constants such as {@link NetworkConstants#SwitchingDelayRoot},
         * but not all sub classes are setting a value to it.
         * The constants should be used as default values, but the class
         * should have a setter for this attribute.
         */
	public double switching_delay;

        /**
         * A map of VMs connected to this switch.
         * @todo The list doesn't appear to be updated (VMs added to it) anywhere. 
         */
	public Map<Integer, NetworkVm> Vmlist = new HashMap<Integer, NetworkVm>();

	public Switch(String name, int level, NetworkDatacenter dc) {
		super(name);
		this.level = level;
		this.dc = dc;
	}

	@Override
	public void startEntity() {
		Log.printConcatLine(getName(), " is starting...");
		schedule(getId(), 0, CloudSimTags.RESOURCE_CHARACTERISTICS_REQUEST);
	}

	@Override
	public void processEvent(SimEvent ev) {
		// Log.printLine(CloudSim.clock()+"[Broker]: event received:"+ev.getTag());
		switch (ev.getTag()) {
		// Resource characteristics request
			case CloudSimTags.Network_Event_UP:
				// process the packet from down switch or host
				processpacket_up(ev);
				break;
			case CloudSimTags.Network_Event_DOWN:
				// process the packet from uplink
				processpacket_down(ev);
				break;
			case CloudSimTags.Network_Event_send:
				processpacketforward(ev);
				break;

			case CloudSimTags.Network_Event_Host:
				processhostpacket(ev);
				break;
			// Resource characteristics answer
			case CloudSimTags.RESOURCE_Register:
				registerHost(ev);
				break;
			// other unknown tags are processed by this method
			default:
				processOtherEvent(ev);
				break;
		}
	}

        /**
         * Process a packet sent to a host.
         * @param ev The packet sent.
         */
	protected void processhostpacket(SimEvent ev) {
		// Send packet to host
		NetworkPacket hspkt = (NetworkPacket) ev.getData();
		NetworkHost hs = hostlist.get(hspkt.recieverhostid);
		hs.packetrecieved.add(hspkt);
	}

	/**
	 * Sends a packet to switches connected through a downlink port.
	 * 
	 * @param ev Event/packet to process
	 */
	protected void processpacket_down(SimEvent ev) {
		// packet coming from up level router
		// has to send downward.
		// check which switch to forward to
		// add packet in the switch list
		// add packet in the host list
		// int src=ev.getSource();
		NetworkPacket hspkt = (NetworkPacket) ev.getData();
		int recvVMid = hspkt.pkt.reciever;
		CloudSim.cancelAll(getId(), new PredicateType(CloudSimTags.Network_Event_send));
		schedule(getId(), latency, CloudSimTags.Network_Event_send);
		if (level == NetworkConstants.EDGE_LEVEL) {
			// packet is to be recieved by host
			int hostid = dc.VmtoHostlist.get(recvVMid);
			hspkt.recieverhostid = hostid;
			List<NetworkPacket> pktlist = packetTohost.get(hostid);
			if (pktlist == null) {
				pktlist = new ArrayList<NetworkPacket>();
				packetTohost.put(hostid, pktlist);
			}
			pktlist.add(hspkt);
			return;
		}
		if (level == NetworkConstants.Agg_LEVEL) {
			// packet is coming from root so need to be sent to edgelevel swich
			// find the id for edgelevel switch
			int switchid = dc.VmToSwitchid.get(recvVMid);
			List<NetworkPacket> pktlist = downlinkswitchpktlist.get(switchid);
			if (pktlist == null) {
				pktlist = new ArrayList<NetworkPacket>();
				downlinkswitchpktlist.put(switchid, pktlist);
			}
			pktlist.add(hspkt);
			return;
		}

	}

	/**
	 * Sends a packet to switches connected through a uplink port.
	 * 
	 * @param ev Event/packet to process
	 */
	protected void processpacket_up(SimEvent ev) {
		// packet coming from down level router.
		// has to be sent up.
		// check which switch to forward to
		// add packet in the switch list
		//
		// int src=ev.getSource();
		NetworkPacket hspkt = (NetworkPacket) ev.getData();
		int recvVMid = hspkt.pkt.reciever;
		CloudSim.cancelAll(getId(), new PredicateType(CloudSimTags.Network_Event_send));
		schedule(getId(), switching_delay, CloudSimTags.Network_Event_send);
		if (level == NetworkConstants.EDGE_LEVEL) {
			// packet is recieved from host
			// packet is to be sent to aggregate level or to another host in the
			// same level

			int hostid = dc.VmtoHostlist.get(recvVMid);
			NetworkHost hs = hostlist.get(hostid);
			hspkt.recieverhostid = hostid;
			if (hs != null) {
				// packet to be sent to host connected to the switch
				List<NetworkPacket> pktlist = packetTohost.get(hostid);
				if (pktlist == null) {
					pktlist = new ArrayList<NetworkPacket>();
					packetTohost.put(hostid, pktlist);
				}
				pktlist.add(hspkt);
				return;

			}
			// packet is to be sent to upper switch
			// ASSUMPTION EACH EDGE is Connected to one aggregate level switch

			Switch sw = uplinkswitches.get(0);
			List<NetworkPacket> pktlist = uplinkswitchpktlist.get(sw.getId());
			if (pktlist == null) {
				pktlist = new ArrayList<NetworkPacket>();
				uplinkswitchpktlist.put(sw.getId(), pktlist);
			}
			pktlist.add(hspkt);
			return;
		}
		if (level == NetworkConstants.Agg_LEVEL) {
			// packet is coming from edge level router so need to be sent to
			// either root or another edge level swich
			// find the id for edgelevel switch
			int switchid = dc.VmToSwitchid.get(recvVMid);
			boolean flagtoswtich = false;
			for (Switch sw : downlinkswitches) {
				if (switchid == sw.getId()) {
					flagtoswtich = true;
				}
			}
			if (flagtoswtich) {
				List<NetworkPacket> pktlist = downlinkswitchpktlist.get(switchid);
				if (pktlist == null) {
					pktlist = new ArrayList<NetworkPacket>();
					downlinkswitchpktlist.put(switchid, pktlist);
				}
				pktlist.add(hspkt);
			} else// send to up
			{
				Switch sw = uplinkswitches.get(0);
				List<NetworkPacket> pktlist = uplinkswitchpktlist.get(sw.getId());
				if (pktlist == null) {
					pktlist = new ArrayList<NetworkPacket>();
					uplinkswitchpktlist.put(sw.getId(), pktlist);
				}
				pktlist.add(hspkt);
			}
		}
		if (level == NetworkConstants.ROOT_LEVEL) {
			// get id of edge router
			int edgeswitchid = dc.VmToSwitchid.get(recvVMid);
			// search which aggregate switch has it
			int aggSwtichid = -1;
			;
			for (Switch sw : downlinkswitches) {
				for (Switch edge : sw.downlinkswitches) {
					if (edge.getId() == edgeswitchid) {
						aggSwtichid = sw.getId();
						break;
					}
				}
			}
			if (aggSwtichid < 0) {
				System.out.println(" No destination for this packet");
			} else {
				List<NetworkPacket> pktlist = downlinkswitchpktlist.get(aggSwtichid);
				if (pktlist == null) {
					pktlist = new ArrayList<NetworkPacket>();
					downlinkswitchpktlist.put(aggSwtichid, pktlist);
				}
				pktlist.add(hspkt);
			}
		}
	}
        
        /**
         * Register a host that is connected to the switch.
         * @param ev 
         */
	private void registerHost(SimEvent ev) {
		NetworkHost hs = (NetworkHost) ev.getData();
		hostlist.put(hs.getId(), hs);
	}

        /**
         * Process a received packet.
         * @param ev The packet received.
         */
	protected void processpacket(SimEvent ev) {
		// send packet to itself with switching delay (discarding other)
		CloudSim.cancelAll(getId(), new PredicateType(CloudSimTags.Network_Event_UP));
		schedule(getId(), switching_delay, CloudSimTags.Network_Event_UP);
		pktlist.add((NetworkPacket) ev.getData());

		// add the packet in the list

	}

        /**
	 * Process non-default received events that aren't processed by
         * the {@link #processEvent(org.cloudbus.cloudsim.core.SimEvent)} method.
         * This method should be overridden by subclasses in other to process
         * new defined events.
         *  
         * @todo the method should be protected to allow sub classes to override it,
         * once it does nothing here.
         */
	private void processOtherEvent(SimEvent ev) {

	}

	/**
	 * Sends a packet to hosts connected to the switch
	 * 
	 * @param ev Event/packet to process
	 */
	protected void processpacketforward(SimEvent ev) {
		// search for the host and packets..send to them
		if (downlinkswitchpktlist != null) {
			for (Entry<Integer, List<NetworkPacket>> es : downlinkswitchpktlist.entrySet()) {
				int tosend = es.getKey();
				List<NetworkPacket> hspktlist = es.getValue();
				if (!hspktlist.isEmpty()) {
					double avband = downlinkbandwidth / hspktlist.size();
					Iterator<NetworkPacket> it = hspktlist.iterator();
					while (it.hasNext()) {
						NetworkPacket hspkt = it.next();
						double delay = 1000 * hspkt.pkt.data / avband;

						this.send(tosend, delay, CloudSimTags.Network_Event_DOWN, hspkt);
					}
					hspktlist.clear();
				}
			}
		}
		if (uplinkswitchpktlist != null) {
			for (Entry<Integer, List<NetworkPacket>> es : uplinkswitchpktlist.entrySet()) {
				int tosend = es.getKey();
				List<NetworkPacket> hspktlist = es.getValue();
				if (!hspktlist.isEmpty()) {
					double avband = uplinkbandwidth / hspktlist.size();
					Iterator<NetworkPacket> it = hspktlist.iterator();
					while (it.hasNext()) {
						NetworkPacket hspkt = it.next();
						double delay = 1000 * hspkt.pkt.data / avband;

						this.send(tosend, delay, CloudSimTags.Network_Event_UP, hspkt);
					}
					hspktlist.clear();
				}
			}
		}
		if (packetTohost != null) {
			for (Entry<Integer, List<NetworkPacket>> es : packetTohost.entrySet()) {
				List<NetworkPacket> hspktlist = es.getValue();
				if (!hspktlist.isEmpty()) {
					double avband = downlinkbandwidth / hspktlist.size();
					Iterator<NetworkPacket> it = hspktlist.iterator();
					while (it.hasNext()) {
						NetworkPacket hspkt = it.next();
						// hspkt.recieverhostid=tosend;
						// hs.packetrecieved.add(hspkt);
						this.send(getId(), hspkt.pkt.data / avband, CloudSimTags.Network_Event_Host, hspkt);
					}
					hspktlist.clear();
				}
			}
		}

		// or to switch at next level.
		// clear the list

	}

        /**
         * Gets the host of a given VM.
         * @param vmid The id of the VM
         * @return the host of the VM
         */
	protected NetworkHost getHostwithVM(int vmid) {
		for (Entry<Integer, NetworkHost> es : hostlist.entrySet()) {
			Vm vm = VmList.getById(es.getValue().getVmList(), vmid);
			if (vm != null) {
				return es.getValue();
			}
		}
		return null;
	}

        /**
         * Gets a list with a given number of free VMs.
         * 
         * @param numVMReq The number of free VMs to get.
         * @return the list of free VMs.
         */
	protected List<NetworkVm> getfreeVmlist(int numVMReq) {
		List<NetworkVm> freehostls = new ArrayList<NetworkVm>();
		for (Entry<Integer, NetworkVm> et : Vmlist.entrySet()) {
			if (et.getValue().isFree()) {
				freehostls.add(et.getValue());
			}
			if (freehostls.size() == numVMReq) {
				break;
			}
		}

		return freehostls;
	}

        /**
         * Gets a list with a given number of free hosts.
         * 
         * @param numhost The number of free hosts to get.
         * @return the list of free hosts.
         */
	protected List<NetworkHost> getfreehostlist(int numhost) {
		List<NetworkHost> freehostls = new ArrayList<NetworkHost>();
		for (Entry<Integer, NetworkHost> et : hostlist.entrySet()) {
			if (et.getValue().getNumberOfFreePes() == et.getValue().getNumberOfPes()) {
				freehostls.add(et.getValue());
			}
			if (freehostls.size() == numhost) {
				break;
			}
		}

		return freehostls;
	}

	@Override
	public void shutdownEntity() {
		Log.printConcatLine(getName(), " is shutting down...");
	}

}
