package jist.swans.phy;

import org.apache.log4j.Logger;

import jist.swans.Constants;
import jist.swans.misc.Util;
import jist.swans.radio.RadioInfo;

public class Phy802_11
{
  /** PHY logger */
  public static final Logger log = Logger.getLogger(Phy802_11.class);

  /** The radio information of this station. Important for the PHY layer. */
  public RadioInfo radioInfo;

  /** Constructor */
  public Phy802_11(RadioInfo radioInfo)
  {
    this.radioInfo = radioInfo;
  }

  /**
   * @param payloadRate
   *          The bit-rate at which the payload is transmitted
   * @return the PLCP header transmission bit-rate
   */
  public int getPlcpHeaderRate(int payloadRate)
  {
    return getPlcpHeaderRate(payloadRate, radioInfo.getMacType());
  }

  /**
   * @return the PLCP header time if the payload is transmitted at rate
   */
  public long headerDuration(int payloadRate)
  {
    // delegate to static method
    return headerDuration(payloadRate, radioInfo.getMacType());
  }

  /**
   * @return the preamble tx time if the payload is transmitted at rate
   */
  public long preambleDuration(int payloadRate)
  {
    return preambleDuration(payloadRate, radioInfo.getMacType());
  }

  /**
   * @return the payload tx time if the payload is transmitted at rate
   */
  public long payloadDuration(int dataLength, int dataRate)
  {
    return payloadDuration(dataLength, dataRate, false);
  }

  public long payloadDuration(int dataLength, int dataRate, boolean pbcc)
  {
    return payloadDuration(dataLength, dataRate, radioInfo.getMacType(), pbcc);
  }

  /**
   * The total duration of a message tx incl. preamble and header (in us).
   *
   * @param dataLength
   *          Number of octets of the message payload
   * @param dataRate
   *          Rate for the data transmission
   * @return The total duration of a message tx incl. preamble and header (in us).
   */
  public long transmitTime(int dataLength, int dataRate)
  {
    return preambleDuration(dataRate) + headerDuration(dataRate)
        + payloadDuration(dataLength, dataRate);
  }

  // ////////////////////////////////////////////////////////
  //
  // static methods for calculation without object reference
  //

  public static int getPlcpHeaderRate(int payloadRate, short mac)
  {
    if (Constants.MAC_802_11a == mac || Constants.MAC_802_11g_PURE == mac)
      return Constants.BANDWIDTH_6Mbps;
    if (Constants.MAC_802_11bg == mac) {
      // TODO is that correct?
      if (Util.isInArray(Constants.BITRATES_OFDM, payloadRate) >= 0)
        return Constants.BANDWIDTH_6Mbps;
    }
    if (Constants.MAC_802_11b == mac || Constants.MAC_802_11bg == mac) {
      return Constants.BANDWIDTH_1Mbps == payloadRate ? Constants.BANDWIDTH_1Mbps
          : Constants.BANDWIDTH_2Mbps;
    }
    if (Constants.MAC_802_11 == mac || Constants.MAC_802_11b_LONG == mac)
      return Constants.BANDWIDTH_1Mbps;
    throw new RuntimeException("getPlcpHeaderRate(): invalid bitrate ("
        + payloadRate + ") or mac (" + mac + ")!");
  }

  public static long headerDuration(int bitrate, short mac)
  {
    if (Util.isInArray(Constants.BITRATES_OFDM, bitrate) >= 0)
      return Constants.PLCP_HEADER_OFDM;

    if (Util.isInArray(Constants.BITRATES_DSSS, bitrate) >= 0) {
      switch (mac) {
        // Long PLCP_HEADER
        case Constants.MAC_802_11:
        case Constants.MAC_802_11b_LONG:
          return Constants.PLCP_HEADER_DSSS_LONG;

        // Short PLCP_HEADER, except for 1Mbps
        case Constants.MAC_802_11b:
        case Constants.MAC_802_11bg:
          return bitrate == Constants.BANDWIDTH_1Mbps ? Constants.PLCP_HEADER_DSSS_LONG
              : Constants.PLCP_HEADER_DSSS_SHORT;

        default: throw new RuntimeException("Phy802_11: Invalid MAC (" + mac + ")!");
      }
    }
    throw new RuntimeException("Phy802_11: Invalid bitrate (" + bitrate + ")!");

  } // headerDuration()

  public static long preambleDuration(int bitrate, short mac)
  {
    if (Util.isInArray(Constants.BITRATES_OFDM, bitrate) >= 0) {
      return Constants.PREAMBLE_OFDM;
    }

    if (Util.isInArray(Constants.BITRATES_DSSS, bitrate) >= 0) {
      switch (mac) {
        // Long preamble
        case Constants.MAC_802_11:
        case Constants.MAC_802_11b_LONG:
          return Constants.PREAMBLE_DSSS_LONG;

          // Short preamble, except for 1Mbps
        case Constants.MAC_802_11b:
        case Constants.MAC_802_11bg:
          return bitrate == Constants.BANDWIDTH_1Mbps ? Constants.PREAMBLE_DSSS_LONG
              : Constants.PREAMBLE_DSSS_SHORT;

        default: throw new RuntimeException("Phy802_11: Invalid MAC (" + mac +
            ") for bit rate " + bitrate + "!");
      }
    }
    throw new RuntimeException("Phy802_11: Invalid bitrate (" + bitrate + ")!");
  } // preambleDuration()

  public static long payloadDuration(int dataLength, int dataRate, short mac,
                                     boolean pbcc)
  {
    if (Util.isInArray(Constants.BITRATES_OFDM, dataRate) >= 0)
      return payloadDurationOfdm(dataLength, dataRate, mac);

    if (Util.isInArray(Constants.BITRATES_DSSS, dataRate) >= 0) {
      // ////////////////////////////////////////////////////////
      // CCK : duration = length * 8 / rate (Mbps)
      // PBCC: duration = (length + 1) * 8 / rate (Mbps)
      float rateInMbps = (float) dataRate / (float) Constants.BANDWIDTH_1Mbps;
      int dataLenInBits = (dataLength + (pbcc ? 1 : 0)) * 8;
      return ((long) Math.ceil( ((float) dataLenInBits) / rateInMbps) )
          * Constants.MICRO_SECOND; // to "us"
      // ////////////////////////////////////////////////////////
    } // if

    throw new RuntimeException("Phy802_11: Invalid bitrate (" + dataRate + ")!");
  }

  /**
   * Calculate the tx time for OFDM modulation. Small difference between 802.11a
   * and g (signal extension time).
   */
  private static long payloadDurationOfdm(int dataLength, int dataRate,
                                          short mac)
  {
    int dataBitsPerSymbol = (dataRate / Constants.BANDWIDTH_1Mbps) * 4;

    /*
     * Calculate the number of OFDM symbols that will have to be transmitted if
     * all data from the MAC + the PLCP service field (16) + the 6 bit tail + a
     * variable sized pad are modulated with the appropriate method.
     */
    long numberOfOFDMSymbolsToTransmit = (long) Math
        .ceil(((double) (16 + 8 * dataLength + 6)) / ((double) dataBitsPerSymbol));

    long tt = numberOfOFDMSymbolsToTransmit * Constants.SYMBOL_TX_TIME_OFDM;
    // Add 6us signal extension time if 802.11g.
    // See 802.11g, p.21, 19.3.2.3 for details.
    if (Constants.MAC_802_11g_PURE == mac || Constants.MAC_802_11bg == mac)
      tt += 6 * Constants.MICRO_SECOND;

    if (log.isDebugEnabled())
      log.debug("len:" + dataLength + " rate:" + dataRate/Constants.BANDWIDTH_1Mbps
        + " time:" + tt/Constants.MICRO_SECOND + "us");

    return tt;
  }

  /**
   * The total duration of tranmitting a message of length dataLength incl.
   * preamble and header (in us) and signal extension (802.11g only),
   * but excluding any IFS.
   *
   * @param dataLength
   *          Number of octets of the message payload
   * @param dataRate
   *          Rate for the data transmission
   * @return The total duration of tranmitting a message of length dataLength incl.
   * preamble and header (in us) and signal extension (802.11g only),
   * but excluding any IFS.
   */
  public static long transmitTime(int dataLength, int dataRate, short mac)
  {
    long pre = preambleDuration(dataRate, mac);
    long hea = headerDuration(dataRate, mac);
    long pay = payloadDuration(dataLength, dataRate, mac, false);
    long tt = pre + hea + pay;
    // if (log.isDebugEnabled())
    // log.debug("TT: len:" + dataLength + " rat:" +
    // dataRate/Constants.MILLI_SECOND
    // + " pre:" + pre + " hea:" + hea + " pay:" + pay);
    return tt;
  }
}
