//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <MacInfo.java Tue 2004/04/06 11:32:13 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.mac;

import java.io.Serializable;

import jist.swans.Constants;
import jist.swans.misc.Util;
import jist.swans.phy.Phy802_11;

/**
 * Mac properties.
 * 
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: MacInfo.java,v 1.4 2004/04/06 16:07:48 barr Exp $
 * @since SWANS1.0
 * 
 * TODO: Make MacInfo object immutable/singletons for smaller memory footprint
 */

public abstract class MacInfo implements Serializable {
  private static final long serialVersionUID = -7421890966738323204L;

  /** bit-rates this MAC supports */
  protected int[] bitrates;

  /**
   * basic rate set (bit-rates other MACs have to support to comm. with this
   * one)
   */
  protected int[] basicRates;

  /** Retransmissions attempted for short packets (those without RTS). */
  protected byte retryLimitShort = Constants.MAC_RETRY_LIMIT_SHORT;

  /** Retransmissions attempted for long packets (those with RTS). */
  protected byte retryLimitLong = Constants.MAC_RETRY_LIMIT_LONG;

  /**
   * Threshold packet size to activate RTS. Default=3000. Broadcast packets
   * never use RTS. Set to zero to always use RTS.
   */
  protected int thresholdRts = Constants.MAC_THRESHOLD_RTS;

  /** Maximum collision window (for backoff). */
  protected short cwMax = Constants.CW_MAX;

  // //////////////////////////////////////////////////
  // Constructor
  //

  protected MacInfo(int[] bitrates, int[] basicRates) {
    this.bitrates = bitrates;
    this.basicRates = basicRates;
  }

  // //////////////////////////////////////////////////
  // Accessors
  //

  /**
   * @return the retryLimitLong
   */
  public byte getRetryLimitLong() {
    return retryLimitLong;
  }

  /**
   * @param retryLimitLong
   *          the retryLimitLong to set
   */
  public void setRetryLimitLong(byte retryLimitLong) {
    this.retryLimitLong = retryLimitLong;
  }

  /**
   * @return the retryLimitShort
   */
  public byte getRetryLimitShort() {
    return retryLimitShort;
  }

  /**
   * @param retryLimitShort
   *          the retryLimitShort to set
   */
  public void setRetryLimitShort(byte retryLimitShort) {
    this.retryLimitShort = retryLimitShort;
  }

  /**
   * @return the thresholdRts
   */
  public int getThresholdRts() {
    return thresholdRts;
  }

  /**
   * @param thresholdRts
   *          the thresholdRts to set
   */
  public void setThresholdRts(int thresholdRts) {
    this.thresholdRts = thresholdRts;
  }

  /** @return The MAC type constant of the currently handled MacInfo object. */
  public abstract short getMacType();

  /** @return A string representation of the MAC type constant of this object. */
  public String getMacTypeString() {
    return getMacTypeString(getMacType());
  }

  // //////////////////////////////////////////////////
  // short 802.11 lexicon:
  // slot - minimum time to sense medium
  // nav - network allocation vector (virtual carrier sense)
  // sifs - short inter frame space
  // pifs - point coordination inter frame space
  // difs - distributed inter frame space
  // eifs - extended inter frame space
  // cw - collision (avoidance) window
  // DSSS - Direct Sequence Spread spectrum
  // FHSS - Frequency Hopping Spread Spectrum
  //

  /** @return Slot time for the current MAC (unit: us). */
  public abstract long getSlotTime();

  /**
   * @return SIFS time for the current MAC (unit: us). Includes Rx-Tx-Turnaround
   *         time.
   */
  public abstract long getSifs();

  /**
   * @return The SIFS minus the Rx/Tx turnaround time. During this time the
   *         radio is not deaf.
   */
  public long getTxSifs() {
    return getSifs() - getRxTxTurnaround();
  }

  /** @return PIFS time for the current MAC (unit: us). */
  public long getPifs() {
    return getSlotTime() + getSifs();
  }

  /**
   * @return DIFS time for the current MAC (unit: us). Contains SIFS, thus
   *         includes Rx-Tx-Turnaround time.
   */
  public long getDifs() {
    return 2 * getSlotTime() + getSifs();
  }

  /**
   * @return The DIFS time minus the Rx/Tx turnaround time. During this time the
   *         radio is not deaf.
   */
  public long getTxDifs() {
    return 2 * getSlotTime() + getTxSifs();
  }

  /**
   * Extended inter frame space. Wait used by stations to gain access to the
   * medium after an error.
   * 
   * @return EIFS time for the current MAC (unit: us).
   */
  public abstract long getEifs(int ackSize);

  private long getEifs(int ackSize, int dataRate, short macType) {
    // See 802.11-1999.pdf, p.85
    return getSifs() // for ACK tx by other station
        + Phy802_11.transmitTime(ackSize, dataRate, macType)
        + Phy802_11.preambleDuration(dataRate, macType)
        + Phy802_11.headerDuration(dataRate, macType) + getDifs();
    // for next tx of this station
  }

  /**
   * @return The EIFS time minus the Rx/Tx turnaround time. During this time the
   *         radio is not deaf.
   */
  public long getTxEifs(int ackSize) {
    return getEifs(ackSize) - getRxTxTurnaround();
  }

  /**
   * Get RX-TX-turnaround time depending on MAC.
   * 
   * A wlan client is not able to send and receive (snoop on the medium) at the
   * same point in time. When the radio switches from receiving to sending mode
   * or vice versa, there is a small time interval where both sending and
   * receiving are not possible (RX/TX turnaround time).
   */
  public abstract long getRxTxTurnaround();

  /** Minimum collision window (for backoff) for the current MAC. */
  public abstract short cwMin();

  /** Maximum collision window (for backoff) for the current MAC. */
  public short cwMax() {
    return this.cwMax;
  }
  
  public void setCwMax(short cwMax) {
    this.cwMax = cwMax;
  }

  public int getHighestBasicRate(int dataRate) {
    if (Util.isInArray(bitrates, dataRate) == -1)
      throw new RuntimeException("Bit-rate " + dataRate
          / Constants.BANDWIDTH_1Mbps + " not supported by "
          + getMacTypeString());
    for (int idx = basicRates.length - 1; idx >= 0; idx--) {
      if (basicRates[idx] <= dataRate)
        return basicRates[idx];
    }
    throw new RuntimeException("No basic rate found < " + dataRate
        / Constants.BANDWIDTH_1Mbps);
  }

  /**
   * @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;
    }
  }

  public static class MacInfo80211a extends MacInfo {
    /**
     * XXX: update this field when class changes
     */
    private static final long serialVersionUID = 1L;

    public MacInfo80211a(int[] bitrates, int[] basicRates) {
      super(bitrates, basicRates);
    }

    public MacInfo80211a() {
      super(Constants.BITRATES_OFDM, Constants.BASIC_RATES_80211AG);
    }

    public final short getMacType() {
      return Constants.MAC_802_11a;
    }

    public final short cwMin() {
      return Constants.CW_MIN_OFDM;
    }

    public final long getEifs(int ackSize) {
      return super.getEifs(ackSize, Constants.BANDWIDTH_6Mbps, getMacType());
    }

    public final long getRxTxTurnaround() {
      return Constants.RX_TX_TURNAROUND__802_11a;
    }

    public final long getSifs() {
      return Constants.SIFS_OFDM_5GHZ;
    }

    public final long getSlotTime() {
      return Constants.SLOT_TIME_OFDM;
    }
  }

  public static class MacInfo80211b extends MacInfo {
    /**
     * XXX: update this field when class changes
     */
    private static final long serialVersionUID = 1L;

    public MacInfo80211b(int[] bitrates, int[] basicRates) {
      super(bitrates, basicRates);
    }

    public MacInfo80211b() {
      super(Constants.BITRATES_DSSS, Constants.BASIC_RATES_80211B);
    }

    public short getMacType() {
      return Constants.MAC_802_11b;
    }

    public final short cwMin() {
      return Constants.CW_MIN_DSSS;
    }

    public final long getEifs(int ackSize) {
      return super.getEifs(ackSize, Constants.BANDWIDTH_1Mbps, getMacType());
    }

    public final long getRxTxTurnaround() {
      return Constants.RX_TX_TURNAROUND__802_11bg;
    }

    public final long getSifs() {
      return Constants.SIFS_DSSS;
    }

    public final long getSlotTime() {
      return Constants.SLOT_TIME_DSSS;
    }
  }

  public static final class MacInfo80211bLong extends MacInfo80211b {
    /**
     * XXX: update this field when class changes
     */
    private static final long serialVersionUID = 1L;

    public MacInfo80211bLong() {
      super();
    }

    public MacInfo80211bLong(int[] bitrates, int[] basicrates) {
      super(bitrates, basicrates);
    }

    public final short getMacType() {
      return Constants.MAC_802_11b_LONG;
    }
  }

  public static final class MacInfo80211bg extends MacInfo80211b {
    /**
     * XXX: update this field when class changes
     */
    private static final long serialVersionUID = 1L;

    public MacInfo80211bg() {
      this(Constants.BITRATES_ALL, Constants.BASIC_RATES_80211BG);
    }

    public MacInfo80211bg(int[] bitrates, int[] basicrates) {
      super(bitrates, basicrates);
    }

    public final short getMacType() {
      return Constants.MAC_802_11bg;
    }
  }

  public static class MacInfo80211gPure extends MacInfo {
    /**
     * XXX: update this field when class changes
     */
    private static final long serialVersionUID = 1L;

    public MacInfo80211gPure(int[] bitrates, int[] basicRates) {
      super(bitrates, basicRates);
    }

    public MacInfo80211gPure() {
      super(Constants.BITRATES_OFDM, Constants.BASIC_RATES_80211AG);
    }

    public final short getMacType() {
      return Constants.MAC_802_11g_PURE;
    }

    public final short cwMin() {
      return Constants.CW_MIN_OFDM;
    }

    public final long getEifs(int ackSize) {
      return super.getEifs(ackSize, Constants.BANDWIDTH_6Mbps, getMacType());
    }

    public final long getRxTxTurnaround() {
      return Constants.RX_TX_TURNAROUND__802_11bg;
    }

    public final long getSifs() {
      return Constants.SIFS_OFDM__2_4_GHZ;
    }

    public final long getSlotTime() {
      return Constants.SLOT_TIME_OFDM;
    }
  }

  public static MacInfo create(short macType) {
    switch (macType) {
    case Constants.MAC_802_11a:
      return new MacInfo80211a();

    case Constants.MAC_802_11b:
      return new MacInfo80211b();

    case Constants.MAC_802_11b_LONG:
      return new MacInfo80211bLong();

    case Constants.MAC_802_11bg:
      return new MacInfo80211bg();

    case Constants.MAC_802_11g_PURE:
      return new MacInfo80211gPure();

    default:
      throw new RuntimeException("Invalid MAC type constant: " + macType);
    }
  }

  public static MacInfo create(short macType, int[] bitrates, int[] basicrates) {
    switch (macType) {
    case Constants.MAC_802_11a:
      return new MacInfo80211a(bitrates, basicrates);

    case Constants.MAC_802_11b:
      return new MacInfo80211b(bitrates, basicrates);

    case Constants.MAC_802_11b_LONG:
      return new MacInfo80211bLong(bitrates, basicrates);

    case Constants.MAC_802_11bg:
      return new MacInfo80211bg(bitrates, basicrates);

    case Constants.MAC_802_11g_PURE:
      return new MacInfo80211gPure(bitrates, basicrates);

    default:
      throw new RuntimeException("Invalid MAC type constant: " + macType);
    }
  }

  public int[] getBasicRateSet() {
    return basicRates;
  }

  public int[] getBitrateSet() {
    return bitrates;
  }

  // public void setBitrateSet(int[] bitrates) {
  // this.bitrates = bitrates;
  // }

} // class: MacInfo

