/*
 * ** Network and Service Differentiation Extensions to CloudSim 3.0 **
 *
 * Gokul Poduval & Chen-Khong Tham
 * Computer Communication Networks (CCN) Lab
 * Dept of Electrical & Computer Engineering
 * National University of Singapore
 * August 2004
 *
 * Licence: GPL - http://www.gnu.org/copyleft/gpl.html
 * Copyright (c) 2004, The University of Melbourne, Australia and National
 * University of Singapore
 * InfoPacket.java - Implementation of a Information Packet.
 *
 */

package org.cloudbus.cloudsim;

import java.text.DecimalFormat;
import java.util.Vector;

import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.core.CloudSimTags;

/**
 * InfoPacket class can be used to gather information from the network layer. An InfoPacket
 * traverses the network topology similar to a {@link gridsim.net.NetPacket}, but it collects
 * information like bandwidths, and Round Trip Time etc. It is the equivalent of ICMP in physical
 * networks.
 * <p>
 * You can set all the parameters to an InfoPacket that can be applied to a NetPacket. So if you
 * want to find out the kind of information that a particular type of NetPacket is experiencing, set
 * the size and network class of an InfoPacket to the same as the NetPacket, and send it to the same
 * destination from the same source.
 * 
 * @author Gokul Poduval
 * @author Chen-Khong Tham, National University of Singapore
 * @since CloudSim Toolkit 1.0
 */
public class InfoPacket implements Packet {

	/** The packet name. */
	private final String name;

	/** The size of this packet. */
	private long size;

	/** The id of this packet. */
	private final int packetId;

	/** The original sender id. */
	private final int srcId;

	/** The destination id. */
	private int destId;

	/** The last hop. */
	private int last;

	/** Whether going or returning. */
	private int tag;

	/** The number of hops. */
	private int hopsNumber;

	/** The original ping size. */
	private long pingSize;

	/** The level of service type. */
	private int netServiceType;

	/** The bandwidth bottleneck. */
	private double bandwidth;

	/** The list of entity IDs. */
	private Vector<Integer> entities;

	/** The list of entry times. */
	private Vector<Double> entryTimes;

	/** The list of exit times. */
	private Vector<Double> exitTimes;

	/** The list of entity's baud rate. */
	private Vector<Double> baudRates;

	/** The formatting the decimal points. */
	private DecimalFormat num;

	/**
	 * Constructs a new Information packet.
	 * 
	 * @param name Name of this packet
	 * @param packetID The ID of this packet
	 * @param size size of the packet
	 * @param srcID The ID of the entity that sends out this packet
	 * @param destID The ID of the entity to which this packet is destined
	 * @param netServiceType the class of traffic this packet belongs to
	 * @pre name != null
	 * @post $none
	 */
	public InfoPacket(String name, int packetID, long size, int srcID, int destID, int netServiceType) {
		this.name = name;
		packetId = packetID;
		srcId = srcID;
		destId = destID;
		this.size = size;
		this.netServiceType = netServiceType;

		init();
	}

	/**
	 * Initialises common attributes.
	 * 
	 * @pre $none
	 * @post $none
	 */
	private void init() {
		last = srcId;
		tag = CloudSimTags.INFOPKT_SUBMIT;
		bandwidth = -1;
		hopsNumber = 0;
		pingSize = size;

		if (name != null) {
			entities = new Vector<Integer>();
			entryTimes = new Vector<Double>();
			exitTimes = new Vector<Double>();
			baudRates = new Vector<Double>();
			num = new DecimalFormat("#0.000#"); // 4 decimal spaces
		}
	}

	/**
	 * Returns the ID of this packet.
	 * 
	 * @return packet ID
	 * @pre $none
	 * @post $none
	 */
	@Override
	public int getId() {
		return packetId;
	}

	/**
	 * Sets original size of ping request.
	 * 
	 * @param size ping data size (in bytes)
	 * @pre size >= 0
	 * @post $none
	 */
	public void setOriginalPingSize(long size) {
		pingSize = size;
	}

	/**
	 * Gets original size of ping request.
	 * 
	 * @return original size
	 * @pre $none
	 * @post $none
	 */
	public long getOriginalPingSize() {
		return pingSize;
	}

	/**
	 * Returns a human-readable information of this packet.
	 * 
	 * @return description of this packet
	 * @pre $none
	 * @post $none
	 */
	@Override
	public String toString() {
		if (name == null) {
			return "Empty InfoPacket that contains no ping information.";
		}

		int SIZE = 1000;   // number of chars
		StringBuffer sb = new StringBuffer(SIZE);
		sb.append("Ping information for " + name + "\n");
		sb.append("Entity Name\tEntry Time\tExit Time\t Bandwidth\n");
		sb.append("----------------------------------------------------------\n");

		String tab = "    ";  // 4 spaces
		for (int i = 0; i < entities.size(); i++) {
			int resID = entities.get(i).intValue();
			sb.append(CloudSim.getEntityName(resID) + "\t\t");

			String entry = getData(entryTimes, i);
			String exit = getData(exitTimes, i);
			String bw = getData(baudRates, i);

			sb.append(entry + tab + tab + exit + tab + tab + bw + "\n");
		}

		sb.append("\nRound Trip Time : " + num.format(getTotalResponseTime()));
		sb.append(" seconds");
		sb.append("\nNumber of Hops  : " + getNumHop());
		sb.append("\nBottleneck Bandwidth : " + bandwidth + " bits/s");
		return sb.toString();
	}

	/**
	 * Gets relevant data from a list.
	 * 
	 * @param v a list
	 * @param index the location in a list
	 * @return the data
	 * @pre v != null
	 * @post index > 0
	 */
	private String getData(Vector<Double> v, int index) {
		String result;
		try {
			Double obj = v.get(index);
			double id = obj.doubleValue();
			result = num.format(id);
		} catch (Exception e) {
			result = "    N/A";
		}

		return result;
	}

	/**
	 * Gets the size of this packet.
	 * 
	 * @return size of the packet.
	 * @pre $none
	 * @post $none
	 */
	@Override
	public long getSize() {
		return size;
	}

	/**
	 * Sets the size of this packet.
	 * 
	 * @param size size of the packet
	 * @return <tt>true</tt> if it is successful, <tt>false</tt> otherwise
	 * @pre size >= 0
	 * @post $none
	 */
	@Override
	public boolean setSize(long size) {
		if (size < 0) {
			return false;
		}

		this.size = size;
		return true;
	}

	/**
	 * Gets the id of the entity to which the packet is destined.
	 * 
	 * @return the desination ID
	 * @pre $none
	 * @post $none
	 */
	@Override
	public int getDestId() {
		return destId;
	}

	/**
	 * Gets the id of the entity that sent out this packet.
	 * 
	 * @return the source ID
	 * @pre $none
	 * @post $none
	 */
	@Override
	public int getSrcId() {
		return srcId;
	}

	/**
	 * Returns the number of hops that this packet has traversed. Since the packet takes a round
	 * trip, the same router may have been traversed twice.
	 * 
	 * @return the number of hops this packet has traversed
	 * @pre $none
	 * @post $none
	 */
	public int getNumHop() {
		int PAIR = 2;
		return ((hopsNumber - PAIR) + 1) / PAIR;
	}

	/**
	 * Gets the total time that this packet has spent in the network. This is basically the RTT.
	 * Dividing this by half should be the approximate latency.
	 * <p>
	 * RTT is taken as the final entry time - first exit time.
	 * 
	 * @return total round time
	 * @pre $none
	 * @post $none
	 */
	public double getTotalResponseTime() {
		if (exitTimes == null || entryTimes == null) {
			return 0;
		}

		double time = 0;
		try {
			double startTime = exitTimes.firstElement().doubleValue();
			double receiveTime = entryTimes.lastElement().doubleValue();
			time = receiveTime - startTime;
		} catch (Exception e) {
			time = 0;
		}

		return time;
	}

	/**
	 * Returns the bottleneck bandwidth between the source and the destination.
	 * 
	 * @return the bottleneck bandwidth
	 * @pre $none
	 * @post $none
	 */
	public double getBaudRate() {
		return bandwidth;
	}

	/**
	 * This method should be called by network entities that count as hops, for e.g. Routers or
	 * CloudResources. It should not be called by links etc.
	 * 
	 * @param id the id of the hop that this InfoPacket is traversing
	 * @pre id > 0
	 * @post $none
	 */
	public void addHop(int id) {
		if (entities == null) {
			return;
		}

		hopsNumber++;
		entities.add(Integer.valueOf(id));
	}

	/**
	 * This method should be called by routers and other entities when this InfoPacket reaches them
	 * along with the current simulation time.
	 * 
	 * @param time current simulation time, use {@link gridsim.CloudSim#clock()} to obtain this
	 * @pre time >= 0
	 * @post $none
	 */
	public void addEntryTime(double time) {
		if (entryTimes == null) {
			return;
		}

		if (time < 0) {
			time = 0.0;
		}

		entryTimes.add(Double.valueOf(time));
	}

	/**
	 * This method should be called by routers and other entities when this InfoPacket is leaving
	 * them. It should also supply the current simulation time.
	 * 
	 * @param time current simulation time, use {@link gridsim.CloudSim#clock()} to obtain this
	 * @pre time >= 0
	 * @post $none
	 */
	public void addExitTime(double time) {
		if (exitTimes == null) {
			return;
		}

		if (time < 0) {
			time = 0.0;
		}

		exitTimes.add(Double.valueOf(time));
	}

	/**
	 * Every entity that the InfoPacket traverses should add the baud rate of the link on which this
	 * packet will be sent out next.
	 * 
	 * @param baudRate the entity's baud rate in bits/s
	 * @pre baudRate > 0
	 * @post $none
	 */
	public void addBaudRate(double baudRate) {
		if (baudRates == null) {
			return;
		}

		baudRates.add(new Double(baudRate));
		if (bandwidth < 0 || baudRate < bandwidth) {
			bandwidth = baudRate;
		}
	}

	/**
	 * Returns the list of all the bandwidths that this packet has traversed.
	 * 
	 * @return a Double Array of links bandwidths
	 * @pre $none
	 * @post $none
	 */
	public Object[] getDetailBaudRate() {
		if (baudRates == null) {
			return null;
		}

		return baudRates.toArray();
	}

	/**
	 * Returns the list of all the hops that this packet has traversed.
	 * 
	 * @return an Integer Array of hop ids
	 * @pre $none
	 * @post $none
	 */
	public Object[] getDetailHops() {
		if (entities == null) {
			return null;
		}

		return entities.toArray();
	}

	/**
	 * Returns the list of all entry time that this packet has traversed.
	 * 
	 * @return an Integer Array of entry time
	 * @pre $none
	 * @post $none
	 */
	public Object[] getDetailEntryTimes() {
		if (entryTimes == null) {
			return null;
		}

		return entryTimes.toArray();
	}

	/**
	 * Returns the list of all exit time that this packet has traversed.
	 * 
	 * @return an Integer Array of exit time
	 * @pre $none
	 * @post $none
	 */
	public Object[] getDetailExitTimes() {
		if (exitTimes == null) {
			return null;
		}

		return exitTimes.toArray();
	}

	/**
	 * Gets an entity ID from the last hop that this packet has traversed.
	 * 
	 * @return an entity ID
	 * @pre $none
	 * @post $none
	 */
	@Override
	public int getLast() {
		return last;
	}

	/**
	 * Sets an entity ID from the last hop that this packet has traversed.
	 * 
	 * @param last an entity ID from the last hop
	 * @pre last > 0
	 * @post $none
	 */
	@Override
	public void setLast(int last) {
		this.last = last;
	}

	/**
	 * Gets the network service type of this packet.
	 * 
	 * @return the network service type
	 * @pre $none
	 * @post $none
	 */
	@Override
	public int getNetServiceType() {
		return netServiceType;
	}

	/**
	 * Sets the network service type of this packet.
	 * 
	 * @param netServiceType the packet's network service type
	 * @pre netServiceType >= 0
	 * @post $none
	 */
	@Override
	public void setNetServiceType(int netServiceType) {
		this.netServiceType = netServiceType;
	}

	/**
	 * Gets this packet tag.
	 * 
	 * @return this packet tag
	 * @pre $none
	 * @post $none
	 */
	@Override
	public int getTag() {
		return tag;
	}

	/**
	 * Sets the tag of this packet.
	 * 
	 * @param tag the packet's tag
	 * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
	 * @pre tag > 0
	 * @post $none
	 */
	public boolean setTag(int tag) {
		boolean flag = false;
		switch (tag) {
			case CloudSimTags.INFOPKT_SUBMIT:
			case CloudSimTags.INFOPKT_RETURN:
				this.tag = tag;
				flag = true;
				break;

			default:
				flag = false;
				break;
		}

		return flag;
	}

	/**
	 * Sets the destination ID for this packet.
	 * 
	 * @param id this packet's destination ID
	 * @pre id > 0
	 * @post $none
	 */
	public void setDestId(int id) {
		destId = id;
	}

}
