/*
 * 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.Entry;

import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.core.CloudSimTags;
import org.cloudbus.cloudsim.core.SimEvent;
import org.cloudbus.cloudsim.core.predicates.PredicateType;

/**
 * This class allows to simulate Edge switch for Datacenter network. It interacts with other
 * switches in order to exchange packets.
 * 
 * Please refer to following publication for more details:
 * 
 * Saurabh Kumar Garg and Rajkumar Buyya, NetworkCloudSim: Modelling Parallel Applications in Cloud
 * Simulations, Proceedings of the 4th IEEE/ACM International Conference on Utility and Cloud
 * Computing (UCC 2011, IEEE CS Press, USA), Melbourne, Australia, December 5-7, 2011.
 * 
 * @author Saurabh Kumar Garg
 * @since CloudSim Toolkit 3.0
 */
public class EdgeSwitch extends Switch {

	/**
	 * Constructor for Edge Switch We have to specify switches that are connected to its downlink
	 * and uplink ports, and corresponding bandwidths. In this switch downlink ports are connected
	 * to hosts not to a switch.
	 * 
	 * @param name Name of the switch
	 * @param level At which level switch is with respect to hosts.
	 * @param dc Pointer to Datacenter
	 */
	public EdgeSwitch(String name, int level, NetworkDatacenter dc) {
		super(name, level, dc);
		hostlist = new HashMap<Integer, NetworkHost>();
		uplinkswitchpktlist = new HashMap<Integer, List<NetworkPacket>>();
		packetTohost = new HashMap<Integer, List<NetworkPacket>>();
		uplinkbandwidth = NetworkConstants.BandWidthEdgeAgg;
		downlinkbandwidth = NetworkConstants.BandWidthEdgeHost;
		switching_delay = NetworkConstants.SwitchingDelayEdge;
		numport = NetworkConstants.EdgeSwitchPort;
		uplinkswitches = new ArrayList<Switch>();
	}

	/**
	 * Send Packet to switch connected through a uplink port
	 * 
	 * @param ev Event/packet to process
	 */
	@Override
	protected void processpacket_up(SimEvent ev) {
		// packet coming from down level router/host.
		// has to send 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);

		// 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;

		// packet needs to go to a host which is connected directly to switch
		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;

		}
		// otherwise
		// packet is to be sent to upper switch
		// ASSUMPTION EACH EDGE is Connected to one aggregate level switch
		// if there are more than one Aggregate level switch one need to modify following code

		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;

	}

	/**
	 * Send Packet to hosts connected to the switch
	 * 
	 * @param ev Event/packet to process
	 */
	@Override
	protected void processpacketforward(SimEvent ev) {
		// search for the host and packets..send to them

		if (uplinkswitchpktlist != null) {
			for (Entry<Integer, List<NetworkPacket>> es : uplinkswitchpktlist.entrySet()) {
				int tosend = es.getKey();
				List<NetworkPacket> hspktlist = es.getValue();
				if (!hspktlist.isEmpty()) {
					// sharing bandwidth between packets
					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

	}

}
