//////////////////////////////////////////////////
//JIST (Java In Simulation Time) Project
//Timestamp: <RadioInfo.java Tue 2004/04/06 11:31:32 barr pompom.cs.cornell.edu>
//

//Copyright (C) 2004 by Cornell University
//All rights reserved.
//Refer to LICENSE for terms and conditions of use.

package jist.swans.radio;

import jist.swans.Constants;
import jist.swans.misc.Message;
import jist.swans.misc.Util;
import jist.swans.phy.PhyMessage;
import jist.swans.radio.RadioInterface.RFChannel;

import jist.runtime.JistAPI;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Radio properties. Not Timeless anymore because parts can change.
 * 
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: RadioInfo.java,v 1.18 2004/04/06 16:07:50 barr Exp $
 * @since SWANS1.0
 */
public class RadioInfo {

  private static int idCounter = 1;

  public static int getNextId() {
    return idCounter++;
  }

  // private static Logger log = Logger.getLogger(RadioInfo.class);

  /**
   * Information specific to a particular WiFi device. Since the current setting
   * can change according to an on-going transmission, this class is not marked
   * with the JistAPI.Timeless interface.
   */
  private RadioInfoStatic timeless;

  /**
   * Timeless information possibly shared among numerous Radio instances (only
   * to save simulation memory).
   */
  private RadioInfoDynamic dynamic;

  /**
   * The static radio description.
   */
  private static class RadioInfoStatic implements JistAPI.Timeless {
    // /////////////////////////////////////////
    // WiFi card information.
    //

    /** Unique radio identifier. */
    protected Integer id;

    /** Name string for this radio device. */
    protected String name = "";

    /** A hashtable of supported frequency bands (key = macType). */
    protected Hashtable supportedFrequencies = null;

    /** Transmission power values for bitrates (key = bitrate (Integer)) */
    protected Hashtable bitrateTxPower = null;

    /**
     * Each {@link #supportedFrequencies} is divided into many channels. This
     * variable indicates the channel spacing between them, e.g.: - 802.11b/g
     * 5MHz, 802.11a 20MHz
     */
    protected Hashtable supportedChannelSpacings = null;

    /**
     * Each supported channel in the {@link #supportedFrequencies} band has a
     * channel width, e.g. - 802.11b 22MHz, 802.11g 16.25 MHz, 802.11a ??? MHz
     */
    protected Hashtable supportedChannelWidth = null;

    /** number of supported RF Channels (key = macType). */
    protected Hashtable supportedNumberOfChannels = null;

    /** A mapping of bitrates to receiver SNR threshold values for this card. */
    protected Hashtable bitrateSnrThresholds = null;

    /**
     * The list of bitrates, that any station trying to connect to this one, has
     * to support (for transmission and reception). This set is treated as an
     * equivalent to the BSS basic rate set, that is used in infrastructure
     * networks.
     */
    protected int[] basicRateSet;

    /**
     * Reception sensitivity (units: mW). AZu: Defines the receiver sensitivity
     * value of the radio receiver for arriving packets. Packets with a power
     * less than the threshold are not sensed by the receiver. Such packets
     * don't change the receiver's radio status to busy and they are not
     * detected by the MAC through its physical sensing mechanism. Such packets
     * cause interference.
     */
    protected double sensitivity_mW;

    /**
     * Reception threshold (units: mW).
     */
    // protected double threshold_mW;
    // //////////////////////////////////////////////////
    //
    // Constructors
    //
    /**
     * Default constructor; uses default values.
     */
    public RadioInfoStatic() {
      this.id = new Integer(getNextId());
      this.name = "Default WiFi Card " + id;
    }

    // //////////////////////////////////////////////////
    //
    // Getters
    //

    /** {@inheritDoc} */
    public String toString() {
      SortedSet bitratesSorted = new TreeSet(bitrateSnrThresholds.keySet());
      Iterator it = bitratesSorted.iterator();
      StringBuffer thr = new StringBuffer();
      Integer r = null;
      while (it.hasNext())
        thr.append((r = (Integer) it.next()).intValue()
            / Constants.BANDWIDTH_1Mbps + "/"
            + Math.round(((Double) bitrateSnrThresholds.get(r)).doubleValue())
            + " ");
      StringBuffer brs = new StringBuffer();
      for (int i = 0; i < basicRateSet.length; ++i)
        brs.append(Math.round(basicRateSet[i] / Constants.BANDWIDTH_1Mbps)
            + " ");

      return "id=" + id + " thr:" + thr + " bas:" + brs;
    }
  } // class: RadioInfoStatic

  /**
   * Dynamic information possibly shared among numerous Radio instances (only to
   * save simulation memory.
   */
  private static class RadioInfoDynamic {

    /** Current MAC type of this station. */
    protected short macType;

    /** Antenna gain (units: dBm). */
    protected double gain;

    /** The outside temperature; used for the calculation of thermal noise. */
    protected double temperature;

    /** Ambient noise. */
    protected double ambientNoise_mW;

    // //////////////////////////////////////////////////////////////////////////
    // derived values (calculated from the above ones, only cached here)

    protected int numberOfChannels;

    protected double bandwidth;

    /** Background noise, including bandwidth factor (units: mW * bits/second). */
    protected double background_mW;

    protected RFChannel rfChannel;

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    public String toString() {
      return "";
      // return "pow:" + Math.round(transmit) + " gai:" + Math.round(gain);
    }
  } // class: RadioInfoDynamic

  /**
   * Create radio information object with dynamic and timeless properties.
   * 
   * @param timeless
   *          timeless radio properties
   * @param dynamic
   *          dynamic radio properties (dynamic only to save some memory)
   */
  public RadioInfo(RadioInfoStatic timeless, RadioInfoDynamic dynamic) {
    this.timeless = timeless;
    this.dynamic = dynamic;
  }

  /**
   * @deprecated
   */
  public RadioInfo(String name, Hashtable supportedFrequencies,
      Hashtable supportedChannelSpacings, Hashtable supportedChannelWidths,
      Hashtable numberOfChannels, Hashtable bitrateTxPower,
      Hashtable bitrateSnrThresholds, int[] basicRateSet, double transmit_dB,
      double gain_dB, double sensitivity_dB, double temperature,
      double ambientNoise_mW, short macType) {

    timeless = new RadioInfoStatic();
    dynamic = new RadioInfoDynamic();

    /** static configuration - does not change over time. */
    timeless.name = name;
    timeless.supportedFrequencies = supportedFrequencies;
    timeless.supportedChannelSpacings = supportedChannelSpacings;
    timeless.supportedChannelWidth = supportedChannelWidths;
    timeless.supportedNumberOfChannels = numberOfChannels;
    timeless.bitrateTxPower = bitrateTxPower;
    timeless.bitrateSnrThresholds = bitrateSnrThresholds;
    timeless.basicRateSet = basicRateSet;

    /** dynamic configuration - may change over time. */
    dynamic.temperature = temperature;
    dynamic.ambientNoise_mW = ambientNoise_mW;
    // dynamic.transmit = transmit_dB;
    dynamic.gain = gain_dB;
    // receive
    timeless.sensitivity_mW = Util.fromDB(sensitivity_dB);
    // TODO make it dynamic!!!
    // timeless.threshold_mW = Util.fromDB(Constants.THRESHOLD_DEFAULT);
    // mac
    setMacType(macType);
  }

  public RadioInfo(RadioInfoStatic timeless, RadioInfoDynamic dynamic,
      String name, Hashtable supportedFrequencies,
      Hashtable supportedChannelSpacings, Hashtable supportedChannelWidths,
      Hashtable numberOfChannels, Hashtable bitrateTxPower,
      Hashtable bitrateSnrThresholds, int[] basicRateSet,
      double sensitivity_dB, double temperature, double ambientNoise_mW,
      short macType) {

    this.timeless = timeless;
    this.dynamic = dynamic;

    /** static configuration - does not change over time. */
    timeless.name = name;
    timeless.supportedFrequencies = supportedFrequencies;
    timeless.supportedChannelSpacings = supportedChannelSpacings;
    timeless.supportedChannelWidth = supportedChannelWidths;
    timeless.supportedNumberOfChannels = numberOfChannels;
    timeless.bitrateTxPower = bitrateTxPower;
    timeless.bitrateSnrThresholds = bitrateSnrThresholds;
    timeless.basicRateSet = basicRateSet;

    /** dynamic configuration - may change over time. */
    dynamic.temperature = temperature;
    dynamic.ambientNoise_mW = ambientNoise_mW;
    // dynamic.transmit = transmit_dB;
    dynamic.gain = 0; // Gain values are calculated into tx power values!
    // receive
    timeless.sensitivity_mW = Util.fromDB(sensitivity_dB);
    // TODO make it dynamic!!!
    // timeless.threshold_mW = Util.fromDB(Constants.THRESHOLD_DEFAULT);
    // mac
    setMacType(macType);
  }
  
  public RadioInfo(String name, Hashtable supportedFrequencies,
      Hashtable supportedChannelSpacings, Hashtable supportedChannelWidths,
      Hashtable numberOfChannels, Hashtable bitrateTxPower,
      Hashtable bitrateSnrThresholds, int[] basicRateSet,
      double sensitivity_dB, double temperature, double ambientNoise_mW,
      short macType) {

    timeless = new RadioInfoStatic();
    dynamic = new RadioInfoDynamic();

    /** static configuration - does not change over time. */
    timeless.name = name;
    timeless.supportedFrequencies = supportedFrequencies;
    timeless.supportedChannelSpacings = supportedChannelSpacings;
    timeless.supportedChannelWidth = supportedChannelWidths;
    timeless.supportedNumberOfChannels = numberOfChannels;
    timeless.bitrateTxPower = bitrateTxPower;
    timeless.bitrateSnrThresholds = bitrateSnrThresholds;
    timeless.basicRateSet = basicRateSet;

    /** dynamic configuration - may change over time. */
    dynamic.temperature = temperature;
    dynamic.ambientNoise_mW = ambientNoise_mW;
    // dynamic.transmit = transmit_dB;
    dynamic.gain = 0; // Gain values are calculated into tx power values!
    // receive
    timeless.sensitivity_mW = Util.fromDB(sensitivity_dB);
    // TODO make it dynamic!!!
    // timeless.threshold_mW = Util.fromDB(Constants.THRESHOLD_DEFAULT);
    // mac
    setMacType(macType);
  }

  /**
   * Return static radio properties.
   * 
   * @return Static radio properties
   */
  public RadioInfoStatic getStatic() {
    return timeless;
  }

  /**
   * @return The basic rate set.
   * @see RadioInfoStatic#basicRateSet
   */
  public int[] getBasicRateSet() {
    return timeless.basicRateSet;
  }

  /**
   * @return The supported bit-rates of this station in ascending order.
   */
  public int[] getBitratesSupported() {
    SortedSet bitratesSorted = new TreeSet(timeless.bitrateSnrThresholds
        .keySet());
    int ct = bitratesSorted.size();
    Iterator it = bitratesSorted.iterator();
    int bitrates[] = new int[ct];
    for (int i = 0; i < ct; i++)
      if (it.hasNext())
        bitrates[i] = ((Integer) it.next()).intValue();
    return bitrates;
  }

  /**
   * @return Returns the bitrateThresholds.
   */
  public Hashtable getBitrateSnrThresholds() {
    return timeless.bitrateSnrThresholds;
  }

  /**
   * @return Returns the supported frequencies (--> 802.11a, b, g).
   */
  public Hashtable getSupportedFrequencies() {
    return timeless.supportedFrequencies;
  }

  /**
   * @return Returns the supported channel spacings.
   */
  public Hashtable getSupportedChannelSpacings() {
    return timeless.supportedChannelSpacings;
  }

  /**
   * @return Returns the supported channel widths.
   */
  public Hashtable getSupportedChannelWidths() {
    return timeless.supportedChannelWidth;
  }

  /**
   * @return Returns the supported channel widths.
   */
  public Hashtable getSupportedNumberOfChannels() {
    return timeless.supportedNumberOfChannels;
  }

  /**
   * @return Returns the bitrateTxPower.
   */
  public Hashtable getBitrateTxPower() {
    return timeless.bitrateTxPower;
  }

  /**
   * Return dynamic radio properties.
   * 
   * @return dynamic radio properties
   */
  public RadioInfoDynamic getDynamic() {
    return dynamic;
  }

  // //////////////////////////////////////////////////
  //
  // Getters
  //

  /**
   * Convenience function
   * 
   * @return The currently active MAC type (802.11, 802.11a, 802.11b, 802.11g).
   */
  public short getMacType() {
    return dynamic.macType;
  }

  /**
   * @return The string representation for the current MAC.
   */
  public String getMacTypeString() {
    return getMacTypeString(dynamic.macType);
  }

  /**
   * @return The string representation for the given MAC.
   */
  public static String getMacTypeString(short macType) {
    switch (macType) {
    case Constants.MAC_802_11:
      return "802.11";
    case Constants.MAC_802_11a:
      return "802.11a";
    case Constants.MAC_802_11b:
      return "802.11b";
    case Constants.MAC_802_11g_PURE:
      return "802.11g";
    case Constants.MAC_802_11bg:
      return "802.11bg";
    case Constants.MAC_802_11b_LONG:
      return "802.11b[long]";
    default:
      return "Unknown:" + macType;
    }
  }

  /**
   * @return radio identifier as int
   */
  public final int getId() {
    return timeless.id.intValue();
  }

  /**
   * @return radio identifier as Integer
   */
  public final Integer getIdInteger() {
    return timeless.id;
  }

  /**
   * @return the name of this WiFi card
   */
  public String getName() {
    return timeless.name;
  }

  /**
   * @return the channel bandwidth in 802.11
   */
  public final double getBandwidth() {
    return dynamic.bandwidth;
  }

  // /**
  // * @return the frequency used
  // */
  // public final long getFrequency()
  // {
  // return dynamic.frequency;
  // }
  //
  // /**
  // * @return the rf channel used
  // */
  // public final int getChannel()
  // {
  // return dynamic.channel;
  // }

  public final RFChannel getRFChannel() {
    return dynamic.rfChannel;
  }

  /**
   * @return the number of rf channels
   */
  public final int getNumberOfChannels() {
    return dynamic.numberOfChannels;
  }

  /**
   * @return the wavelength used
   */
  public final double getWaveLength() {
    return Constants.SPEED_OF_LIGHT / dynamic.rfChannel.frequency;
  }

  /**
   * @return antenna gain (units: dBm)
   */
  public final double getGain() {
    return dynamic.gain;
  }

  /**
   * @return transmission power (units: dBm)
   */
  public final double getPower(Integer bitrate) {
    return ((Double) timeless.bitrateTxPower.get(bitrate)).doubleValue();
  }

  /**
   * @return transmission power (units: dBm)
   */
  public final double getPower(Message msg) {
    if (msg instanceof PhyMessage) {
      return ((PhyMessage) msg).getPower();
    }
    return getPower(Integer.valueOf(getBasicRateSet()[0]));
  }

  /**
   * @return background noise (units: mW)
   */
  public final double getBackground_mW() {
    return dynamic.background_mW;
  }

  /**
   * @return temperature in Kelvin
   */
  public final double getTemperature() {
    return dynamic.temperature;
  }

  /**
   * sets the temperature in Kelvin TODO background noise has to be ajusted!!
   */
  protected final void setTemperature(double temperature) {
    dynamic.temperature = temperature;
    throw new RuntimeException("FixMe");
  }

  /**
   * @return reception sensitivity (units: mW)
   */
  public final double getSensitivity_mW() {
    return timeless.sensitivity_mW;
  }

  /**
   * Returns the SNR threshold for the given bit-rate at which the packet can
   * successfully be received.
   * 
   * @param bitrate
   *          the bitrate
   * @return Demodulation threshold (units: mW)
   */
  private final double getThresholdSnr(Integer bitrate) {
    return ((Double) timeless.bitrateSnrThresholds.get(bitrate)).doubleValue();
  }

  /**
   * Returns the SNR threshold for the given bit-rate at which the packet can
   * successfully be received.
   * 
   * @param msg
   *          message with annotated bitrate
   * @return Demodulation threshold (units: dB)
   */
  public final double getThresholdSnr(Message msg) {
    if (msg instanceof PhyMessage)
      return getThresholdSnr(((PhyMessage) msg).getPayloadRateObj());
    return getThresholdSnr(new Integer(getBasicRateSet()[0]));
  }

  /**
   * Checks whether the SNR value of the received message is higher than the
   * required threshold. Note: the threshold depends on the used modulation.
   * 
   * @param msg
   *          incoming message
   * @param power_mW
   *          message was received with this power
   * @param noise_mW
   *          the current noise level
   * @return true or false
   */
  public final boolean isAboveThresholdSnr(Message msg, double power_mW,
      double noise_mW) {
    return Util.toDB(power_mW / noise_mW) >= getThresholdSnr(msg);
  }

  // ////////////////////////////////////////////
  //
  // Setters
  //

  /**
   * Set current MAC type for this station.
   */
  public final void setMacType(short macType) {
    // Do not check for bit-rates, so that definition of custom RadioInfo
    // objects is possible.

    dynamic.macType = macType;

    dynamic.numberOfChannels = ((Integer) timeless.supportedNumberOfChannels
        .get(new Short(getMacType()))).intValue();
    long frequency = ((Long) timeless.supportedFrequencies.get(new Short(
        getMacType()))).longValue();
    dynamic.bandwidth = ((Double) timeless.supportedChannelWidth.get(new Short(
        getMacType()))).doubleValue();
    int channel = (null == dynamic.rfChannel ? 0 : dynamic.rfChannel
        .getChannel());
    dynamic.rfChannel = new RadioInterface.RFChannel(frequency, channel);

    // noise
    /*
     * kurth, 07.11.06. thermal noise does not depend on data rate, but on
     * bandwidth of the used carrier. removed thermal noise factor. thermal
     * noise floor for 802.11a/b/g is ~ -100dBm
     */
    double thermalNoise_mW = Constants.BOLTZMANN * dynamic.temperature * 1000.0
        * dynamic.bandwidth;
    dynamic.background_mW = dynamic.ambientNoise_mW + thermalNoise_mW;
  }

  /**
   * @param channel
   *          The RF channel to set.
   */
  public final void setChannel(int channel) {
    // dynamic.channel = channel;
    dynamic.rfChannel = new RadioInterface.RFChannel(dynamic.rfChannel
        .getFrequency(), channel);
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Object#toString()
   */
  public String toString() {
    return getMacTypeString() + "  sen:"
        + Math.round(Util.toDB(getStatic().sensitivity_mW)) + "  " + timeless
        + " " + dynamic;
  }

} // class: RadioInfo
