package click.swans.mac;

import jist.swans.Constants;
import jist.swans.mac.Mac802_11;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDcfMessage;
import jist.swans.mac.MacInfo;
import jist.swans.mac.MacMessage;
import jist.swans.mac.MacMessageFactory;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.Util;
import jist.swans.phy.Phy802_11;
import jist.swans.phy.PhyMessage;
import jist.swans.radio.RadioInfo;
import jist.swans.rate.RateControlAlgorithmIF;

import org.apache.log4j.Logger;

/**
 * The MAC which used by Click.
 *
 * @author Zubow
 */
public class MacClick extends Mac802_11 {

  /** MAC logger. */
  public static final Logger log = Logger.getLogger(MacClick.class);

  /** whether the node ist a station */
  protected boolean isStation;

  /** bssid of the station, if in station mode */
  private MacAddress stationBSSID;

  private boolean ackCorrupt = false;

  /**
   * Instantiate new 802_11b entity.
   *
   * @param addr
   *          local mac address
   * @param radioInfo
   *          radio properties
   */
  public MacClick(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo,
      boolean isStation) {
    this(addr, radioInfo, macInfo, null, isStation);
  }

  public MacClick(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo,
      RateControlAlgorithmIF rateSelection, boolean isStation) {
    this(addr, radioInfo, macInfo, rateSelection, new Phy802_11(radioInfo),
        isStation);
  }

  public MacClick(MacAddress addr, RadioInfo radioInfo, MacInfo macInfo,
      RateControlAlgorithmIF rateSelection, Phy802_11 phy, boolean isStation) {
    super(addr, radioInfo, macInfo, rateSelection, phy);
    isStation = false;
    stationBSSID = MacAddress.NULL;
  }

  // ////////////////////////////////////////////////
  // accessors
  //

  public boolean isStation() {
    return isStation;
  }

  public void setStation(boolean isStation) {
    this.isStation = isStation;
  }

  /*
   * (non-Javadoc)
   *
   * @see jist.swans.mac.MacDcf#send(jist.swans.misc.Message,
   *      jist.swans.mac.MacAddress, jist.swans.misc.MessageAnno)
   */
  public void send(Message msg, MacAddress nextHop, MessageAnno anno) {
    Util.assertion(msg instanceof MacMessage);

    // if station, inspect packets to send for assoc-related packets
    if (isStation) {
      MacClickMessage macMsg = (MacClickMessage) msg;
      if (macMsg.get802_11Type() == MacClickMessage.WIFI_FC0_TYPE_MGT) {
        switch (macMsg.get802_11Subtype()) {
        case MacClickMessage.WIFI_FC0_SUBTYPE_ASSOC_REQ:
        case MacClickMessage.WIFI_FC0_SUBTYPE_REASSOC_REQ:
          stationBSSID = macMsg.getDst();
          break;
        case MacClickMessage.WIFI_FC0_SUBTYPE_DISASSOC:
          stationBSSID = MacAddress.NULL;
          break;
        }
      }
    }

    super.send(msg, nextHop, anno);
  }

  /*
   * (non-Javadoc)
   *
   * @see jist.swans.mac.Mac802_11#receiveData(jist.swans.mac.MacDcfMessage.Data,
   *      jist.swans.misc.MessageAnno)
   */
  protected void receiveData(MacDcfMessage.Data msg, MessageAnno anno) {
    if (checkBssid(msg))
      super.receiveData(msg, anno);
  }

  protected boolean checkBssid(MacDcfMessage.Data msg) {
    // If we are a station, check the bssid
    if (isStation) {
      // if not broadcast or from ap, discard.
      // check only data packets, check also the dir flag
      if (msg.getType() == MacDcfMessage.TYPE_DATA) {
        MacClickMessage.AbstractMessageWithBody msgData = (MacClickMessage.AbstractMessageWithBody) msg;

        if (msgData.get802_11Type() == MacClickMessage.WIFI_FC0_TYPE_DATA) {
          if (!MacAddress.ANY.equals(msg.getSrc())
              && !stationBSSID.equals(msg.getSrc())
              || msgData.getDir() != MacClickMessage.DIR_FROMDS) {
            if (log.isDebugEnabled())
              log.debug(this + " discarding packet from wrong bssid");
            return false;
          }
        }
      }
    }
    return true;
  }

  public void receive(Message in_msg, MessageAnno anno) {
    if (null != anno && anno.get(MessageAnno.ANNO_RADIO_RECV_STATUS) != Constants.RADIO_RECV_STATUS_OK) {
      PhyMessage phyMsg = (PhyMessage) in_msg;
      MacDcfMessage macMsg = (MacDcfMessage) phyMsg.getPayload();

      if (macMsg.getType() != MacDcfMessage.TYPE_DATA) {
        return; // discard corrupt ACK, RTS, CTS
      } else if (ackCorrupt) {
        // packet will be ACKed even though it's corrupt
        super.receive(in_msg, anno);
      } else {
        // receive the packet, call all handlers, but don't set any state
        // packet should pass right through and behave as if it was suppressed
        // by radio
        // don't report to RCA, as this packet isn't OK
        anno.put(MessageAnno.ANNO_MAC_BITRATE, phyMsg.getPayloadRateObj());
        anno.put(MessageAnno.ANNO_MAC_DURATION, Long.valueOf(phyMsg
            .txDuration()));
        anno.put(MessageAnno.ANNO_MSG_ID, phyMsg.getId());

        MacDcfMessage.Data msg = (MacDcfMessage.Data) macMsg;
        MacAddress dst = msg.getDst();

        if (localAddr.equals(dst) || MacAddress.ANY.equals(dst)) {
          receiveNonAcked(msg, anno);
        } else {
          receiveCorruptForeign(msg, anno);
        }
      }
    } else {
      // not corrupt, use normal procedure
      super.receive(in_msg, anno);
    }

  }

  protected void receiveNonAcked(MacDcfMessage.Data msg, MessageAnno anno) {
    // for some reason this class doesn't implement a regular receiveForeign
    // so the BSSID check isn't done for promiscous packets; I replicate that
    // for corrupt packets
    if (!checkBssid(msg))
      return;

    if (isAwaitingResponse())
      return;

    if (!MacAddress.ANY.equals(msg.getDst())) {
      if (msg.getRetry() && getSeqEntry(msg.getSrc()) == msg.getSeq()) {
        if (duplicateEvent.isActive())
          duplicateEvent.handle(msg, anno, localAddr);
        // suppress dupes, but only if one of them was correctly received
        // otherwise the sequence number isn't updated
        return;
      } else {
        // why do we do this here? The logic is from MacDcf.receiveData(...)
        // it wasn't my idea
        if (forwarderEvent.isActive())
          forwarderEvent.handle(msg, anno, localAddr);
      }
    }

    if (receiveEvent.isActive())
      receiveEvent.handle(msg, anno, null == anno ? -1 : ((Long) anno
          .get(MessageAnno.ANNO_MAC_DURATION)).longValue()
          + getRxTxTurnaround());

    if (netEntity != null) {
      netEntity.receive(((MacMessageFactory.M802_11)getMsgFactory()).getPayload(msg),
          msg.getSrc(), netId, false, anno);
    }
    // don't call cfDone, endReceive, cancelTimer; all they do is modify local
    // state
  }

  protected void receiveCorruptForeign(MacDcfMessage.Data msg, MessageAnno anno) {

    if (receiveForeignEvent.isActive())
      receiveForeignEvent.handle(msg, anno, null == anno ? -1 : ((Long) anno
          .get(MessageAnno.ANNO_MAC_DURATION)).longValue()
          + getRxTxTurnaround());

    if (promisc) {
      if (receiveEvent.isActive())
        receiveEvent.handle(msg, anno, null == anno ? -1 : ((Long) anno
            .get(MessageAnno.ANNO_MAC_DURATION)).longValue()
            + getRxTxTurnaround());

      netEntity.receive(((MacMessageFactory.M802_11)getMsgFactory()).getPayload(msg),
          msg.getSrc(), netId, true, anno);
    }

  }
}
