package jist.swans.phy;

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

/**
 * Physical layer wrapping around the MAC message; encapsulates all PHY specific
 * parameters.
 *
 * @author Oliver Friedrich
 *
 */
public class PhyMessage implements Message
{

  /** The PLCP header of this message. */
  private PlcpHeader plcpHeader;

  /** The encapsulated MAC message as payload. */
  private Message    payload;


  //
  // Packet annotations
  //
  
  /** The frequency + channel at which this message is being transmitted. */
  private RadioInterface.RFChannel        rfChannel;
  
  private double power;

  /** Unique id per packet, for viz purposes  */
  protected int messageId;

  /** used to derive message ids */
  protected static int messageIdBase = 0;

  
  // Constructors

  /**
   * Constructs a new message on the PHY layer with the appropriate
   * synchronization and PLCP header fields.
   *
   * @param payload
   *          The PSDU of this PHY message (payload).
   * @param phy
   *          Representation of PHY logic, contains radio information about
   *          hardware radio properties etc.
   */
  public PhyMessage(Message payload, int payloadRate, Phy802_11 phy)
  {
    /** no explicit RF channel was assigned; use the current channel */
    this(payload, payloadRate, phy, phy.radioInfo.getRFChannel());
  }

  /**
   * Constructs a new message on the PHY layer with the appropriate
   * synchronization and PLCP header fields.
   *
   * @param payload
   *          The PSDU of this PHY message (payload).
   * @param phy
   *          Representation of PHY logic, contains radio information about
   *          hardware radio properties etc.
   */
  public PhyMessage(Message payload, int payloadRate, Phy802_11 phy, RadioInterface.RFChannel channel)
  {
    messageId = messageIdBase++;

    this.payload = payload;
    RadioInfo radioInfo = phy.radioInfo;

    Util.assertion(radioInfo.getRFChannel().getFrequency() == channel.getFrequency());
    this.rfChannel = channel;

    this.power = radioInfo.getPower(Integer.valueOf(payloadRate)); 

    // 802.11a
    if (radioInfo.getRFChannel().getFrequency() == Constants.FREQUENCY_5GHZ) {
      plcpHeader = new PlcpHeader.OFDM(phy, payloadRate, payload.getSize());
    }
    // 802.11, b, g
    else if (radioInfo.getRFChannel().getFrequency() == Constants.FREQUENCY_2_4GHZ) {
      if (Util.isInArray(Constants.BITRATES_DSSS, payloadRate) >= 0)
        plcpHeader = new PlcpHeader.DSSS(phy, payloadRate, payload.getSize());
      else if (Util.isInArray(Constants.BITRATES_OFDM, payloadRate) >= 0)
        plcpHeader = new PlcpHeader.OFDM(phy, payloadRate, payload.getSize());
      else throw new RuntimeException("bitrate (" + payloadRate
          + ") not supported in 802.11a/b/g!");
    } 
//    else if (radioInfo.getRFChannel().getFrequency() == Constants.FREQUENCY_2_4GHZ_DSSS) {
//        plcpHeader = new PlcpHeader.DSSS(phy, payloadRate, payload.getSize());
//    } 
    else throw new RuntimeException("unsupported frequency: "
        + radioInfo.getRFChannel().getFrequency() + "Hz");
  }

  // duration calculators

  /** The total duration of this message incl. preamble and header (in us). */
  public long txDuration()
  {
    return plcpHeader.getPreambleDuration() + plcpHeader.getHeaderDuration()
        + plcpHeader.getPayloadDuration();
  }

  /** @return The PLCP header of this message. */
  public PlcpHeader getPlcpHeader()
  {
    return plcpHeader;
  }

  /**
   * @return The bit-rate at which the payload of this PHY message is/was
   *         transmitted.
   */
  public int getPlcpHeaderRate()
  {
    return plcpHeader.plcpRate;
  }

  /** @return The MPDU ( == payload == MAC message) of this message */
  public Message getPayload()
  {
    return payload;
  }

  /**
   * @return The bit-rate at which the payload of this PHY message is/was
   *         transmitted.
   */
  public int getPayloadRate()
  {
    return plcpHeader.payloadRate;
  }

  /**
   * @return The bit-rate at which the payload of this PHY message is/was
   *         transmitted.
   */
  public Integer getPayloadRateObj()
  {
    return plcpHeader.payloadRateObj;
  }

  public double getPower() {
    return power;
  }

  public RadioInterface.RFChannel getRfChannel() {
    return rfChannel;
  }

  /**
   * @return The symbol transmission time for this message.
   */
  public long getSymbolDuration()
  {
    return plcpHeader.getSymbolDuration();
  }

  public long getCyclicPrefix() 
  {
    return plcpHeader.getCyclicPrefix();
  }

  public int getId() {
    return messageId;
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.misc.Message#getBytes(byte[], int)
   */
  public void getBytes(byte[] msg, int offset)
  {
    payload.getBytes(msg, offset);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.misc.Message#getSize()
   */
  public int getSize()
  {
    return payload.getSize();
  }

  /*
   * (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public String toString() {
    PlcpHeader plcpHd = getPlcpHeader();
    String ret = getId() + " PHY pre:" + plcpHd.getPreambleDuration()
        / Constants.MICRO_SECOND + " hdr:" + plcpHd.getHeaderDuration()
        / Constants.MICRO_SECOND + " rat:" + plcpHd.getDataRate()
        / Constants.BANDWIDTH_1Mbps + "M len:" + plcpHd.getDataLength()
        + " dur:" + plcpHd.getPayloadDuration() / Constants.MICRO_SECOND;

    ret += getPayload().toString();
    return ret;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + ((payload == null) ? 0 : payload.hashCode());
    result = PRIME * result + ((plcpHeader == null) ? 0 : plcpHeader.hashCode());
    result = PRIME * result + ((rfChannel == null) ? 0 : rfChannel.hashCode());
    return result;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final PhyMessage other = (PhyMessage) obj;
    if (payload == null) {
      if (other.payload != null)
        return false;
    } else if (!payload.equals(other.payload))
      return false;
    if (plcpHeader == null) {
      if (other.plcpHeader != null)
        return false;
    } else if (!plcpHeader.equals(other.plcpHeader))
      return false;
    if (rfChannel == null) {
      if (other.rfChannel != null)
        return false;
    } else if (!rfChannel.equals(other.rfChannel))
      return false;
    return true;
  }

}
