/*
 * Mac802_11e.java
 *
 * Created on June 6, 2006, 3:30 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <Mac802_11.java Thu 2005/02/10 11:45:06 barr rimbase.rimonbarr.com>
//
// Copyright (C) 2004 by Cornell University
// All rights reserved.
// Refer to LICENSE for terms and conditions of use.
package sidnet.stack.mac;

import sidnet.models.energy.energyconsumption.EnergyModel;
import jist.runtime.JistAPI;
import jist.runtime.JistAPI.Continuation;
import jist.swans.Constants;
import jist.swans.mac.Mac802_11;
import jist.swans.mac.Mac802_11Message;
import jist.swans.mac.MacDcfMessage;
import jist.swans.mac.MacInfo;
import jist.swans.mac.MacMessage;
import jist.swans.mac.MacMessageFactory;
/**
 *
 * @modified by Oliviu Ghica, Northwestern University
 * Is absolutely the Cornell's implementation but added some functions to monitor the energy consumption 
 */
import jist.swans.mac.MacAddress;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.swans.rate.ConstantRate;
import jist.swans.rate.RateControlAlgorithmIF;
import sidnet.colorprofiles.ColorProfileGeneric;
import sidnet.core.misc.Node;

/**
 * Implementation of IEEE 802_11b. Please refer to the standards document. For
 * consistency, many of the variable names, constants and equations are taken
 * directly from the specification.
 * 
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: Mac802_11.java,v 1.62 2005/02/10 16:52:28 barr Exp $
 * @since SWANS1.0
 */

public class Mac802_11e extends Mac802_11 {
  protected class RadioEnergyDlg implements RadioInterface {

    private RadioInterface radio;

    public void endReceive(Message msg, Double power, RFChannel rfChannel,
        Object event) {
      radio.endReceive(msg, power, rfChannel, event);
    }

    public void endSetChannel(RFChannel channel) {
      radio.endSetChannel(channel);
    }

    public void endTransmit() {
      radio.endTransmit();
    }

    public RFChannel getChannel() throws Continuation {
      return radio.getChannel();
    }

    public void receive(Message msg, Double power, Long duration) {
      radio.receive(msg, power, duration);
    }

    public void setChannel(RFChannel channel, long delay) {
      radio.setChannel(channel, delay);
    }

    public void setSleepMode(boolean sleep) {
      radio.setSleepMode(sleep);
    }

    public void transmit(Message msg, long delay, long duration,
        MessageAnno anno) {
      /* Energy */
      energyModel.simulatePacketTransmit(delay + duration);
      radio.transmit(msg, delay, duration, anno);
    }

    public void transmit(Message msg, MessageAnno anno, long predelay,
        long duration, long postdelay) {
      /* Energy */
      energyModel.simulatePacketTransmit(predelay + duration + postdelay);
      radio.transmit(msg, anno, predelay, duration, postdelay);
    }

    public RadioEnergyDlg(RadioInterface radio) {
      this.radio = radio;
    }
  }

  private EnergyModel energyModel;

  /** Retransmissions attempted for short packets (those without RTS). */
  public static final byte RETRY_LIMIT_SHORT = 0; // ???? was 7
  // Timer for simulating auto-off radio
  private static final int TO_SLEEP_TIMER = 0;
  long toSleepTimerPeriod = -1;
  boolean ToSleepTimerScheduled = false;
  long ToSleepTimerSequence = 0;
  long ToSleepTimeStamp = 0;
  Node myNode = null;

  /**
   * Instantiate new 802_11b entity.
   * 
   * @param addr
   *          local mac address
   * @param radioInfo
   *          radio properties
   */
  public Mac802_11e(MacAddress addr, RadioInfo radioInfo,
      EnergyModel energyModel, Node myNode) {
    this(addr, radioInfo, null, 0, null);
  }

  /**
   * Instantiate new 802_11b entity.
   * 
   * @param addr
   *          local mac address
   * @param radioInfo
   *          radio properties
   */
  public Mac802_11e(MacAddress addr, RadioInfo radioInfo,
      EnergyModel energyModel, long toSleepTimerPeriod /* ms */, Node myNode) {
    super(addr, radioInfo, MacInfo.create(radioInfo.getMacType(),
        radioInfo.getBitratesSupported(), radioInfo.getBasicRateSet()));
    this.energyModel = energyModel;

    this.toSleepTimerPeriod = toSleepTimerPeriod;
    this.myNode = myNode;
    
    macInfo.setRetryLimitShort(RETRY_LIMIT_SHORT);
    this.useAnnotations = false;
    setMsgFactory(new MacMessageFactory.M802_11());
    setRateControlAlgorithm(new ConstantRate(macInfo.getBitrateSet()[0]));
  }

  public void setRadioEntity(RadioInterface radio) {
    super.setRadioEntity(radio);
    this.radioEntity = new RadioEnergyDlg(radio);
  }

  protected void sendPacket() {
    if (myNode.getEnergyManagement().getBattery().getPercentageEnergyLevel() <= 0) {
      myNode.getNodeGUI().colorCode.mark(ColorProfileGeneric.DEAD,
          ColorProfileGeneric.FOREVER);
      myNode.getNodeGUI().repaint();
      return;
    }
    simulateRadioWakes();
    super.sendPacket();
    simulateRadioGoesToSleep();
  }

  public void peek(Message msg) {
    if (true) // ????
      return;
  }

  protected void receivePacket(MacMessage _msg) {
    MacDcfMessage msg = (MacDcfMessage) _msg;
    if (myNode.getEnergyManagement().getBattery().getPercentageEnergyLevel() <= 0) {
      myNode.getNodeGUI().colorCode.mark(ColorProfileGeneric.DEAD,
          ColorProfileGeneric.FOREVER);
      myNode.getNodeGUI().repaint();
      return;
    }

    long delay = 0;
    long duration = (Long) anno.get(MessageAnno.ANNO_MAC_DURATION);

    /* Energy */
    energyModel.simulatePacketReceive(JistAPI.getTime() - (delay + duration),
        delay + duration);

    needEifs = false;
    MacAddress dst = msg.getDst();
    if (localAddr.equals(dst)) {
      simulateRadioWakes();
      switch (msg.getType()) {
      case Mac802_11Message.TYPE_RTS:
        delay = getRxTxTurnaround();
        receiveRts((Mac802_11Message.Rts) msg, null);
        break;
      case Mac802_11Message.TYPE_CTS:
        delay = getSifs();
        receiveCts((Mac802_11Message.Cts) msg, null);
        break;
      case MacDcfMessage.TYPE_ACK:
        delay = getSifs();
        receiveAck((MacDcfMessage.Ack) msg, null);
        break;
      case MacDcfMessage.TYPE_DATA:
        delay = getRxTxTurnaround();
        receiveData((MacDcfMessage.Data) msg, null);
        break;
      default:
        throw new RuntimeException("illegal frame type");
      }
      simulateRadioGoesToSleep();
    } else if (MacAddress.ANY.equals(dst)) {
      simulateRadioWakes();
      switch (msg.getType()) {
      case MacDcfMessage.TYPE_DATA:
        delay = getRxTxTurnaround();
        receiveData((MacDcfMessage.Data) msg, null);
        break;
      default:
        throw new RuntimeException("illegal frame type");
      }
      simulateRadioGoesToSleep();
    } else {
      // does not belong to node
      receiveForeign(msg, null);
    }
  }

  private void simulateRadioWakes() {
    energyModel.simulateRadioWakes();
    myNode.getNodeGUI().colorCode.mark(ColorProfileGeneric.RADIO_SLEEP,
        ColorProfileGeneric.CLEAR);
    myNode.getNodeGUI().repaint();

  }

  private void simulateRadioGoesToSleep() {
    if (toSleepTimerPeriod > 0)
      scheduleTimer(TO_SLEEP_TIMER, toSleepTimerPeriod);
  }

  private void scheduleTimer(int timer, long period) // Period must be expressed
                                                     // in milliseconds
  {
    if (timer == TO_SLEEP_TIMER) {
      /*
       * If a new timer is set later, we must inform the previoius one to cancel
       * its effect
       */
      long oldSequence;
      ToSleepTimerSequence++;
      oldSequence = ToSleepTimerSequence;
      ToSleepTimeStamp = JistAPI.getTime() / Constants.MILLI_SECOND;
      JistAPI.sleepBlock(period * Constants.MILLI_SECOND);
      if (oldSequence == ToSleepTimerSequence) {
        energyModel.simulateRadioGoesToSleep();
        myNode.getNodeGUI().colorCode.mark(ColorProfileGeneric.RADIO_SLEEP,
            ColorProfileGeneric.FOREVER);
        myNode.getNodeGUI().repaint();
      } else
        return;
    }
  }

  protected void cancelTimer(int timer) {
    switch (timer) {
    case TO_SLEEP_TIMER:
      ToSleepTimerSequence++;
      ToSleepTimerScheduled = false;
      break;
    }
  }

  protected boolean timerExpired(int timer) {
    if (timer == TO_SLEEP_TIMER
        && JistAPI.getTime() / Constants.MILLI_SECOND - ToSleepTimeStamp >= toSleepTimerPeriod)
      return true;
    else
      return false;
  }
}
