package jist.swans.mac;

import jist.runtime.JistAPI;
import jist.swans.Node;
import jist.swans.misc.Event;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.misc.MessageEvent;
import jist.swans.net.MessageQueue;
import jist.swans.phy.Phy802_11;

public abstract class AbstractMac {

  // ////////////////////////////////////////////////
  // Mode and Internal State Events
  //

  public static class MacModeChanged extends Event {
    public int oldMode;
    public long oldModeChangeTime;
    public int newMode;
    public String detail = "";
    public int internalNode;

    public MacModeChanged() { super(false); }

    public MacModeChanged(int nodeId) {
      this.nodeId = nodeId;
      this.oldModeChangeTime = 0L;
    }
    
    public void handle(int oldMode, int newMode) {
      handle(oldMode, newMode, -1);    
    }

    public void handle(int oldMode, int newMode, String detail) {
      handle(oldMode, newMode, detail, -1);
    }
    
    public void handle(int oldMode, int newMode, int internalNode) {
      this.oldMode = oldMode;
      this.newMode = newMode;
      this.internalNode = internalNode;
      super.handle(nodeId);
      this.oldModeChangeTime  = JistAPI.getTime();
    }

    public void handle(int oldMode, int newMode, String detail, int internalNode) {
      this.internalNode = internalNode;
      this.detail = detail;
      handle(oldMode, newMode);
    }
  }
  
  /**
   * reports time spent waiting in backoff,
   * Note: each paused backoff fraction is reported individually
   */
  public static class TXOPEvent extends Event {
    public boolean hasTXOP;
    public long remainingTXOP;
    public int internalNode;

    public TXOPEvent() { super(false); }
    public TXOPEvent(int nodeId) {
      this.nodeId = nodeId;
    }

    public void handle(boolean hasTXOP, long remainingTXOP, int internalNode) {
      this.hasTXOP = hasTXOP;
      this.remainingTXOP = remainingTXOP;
      this.internalNode = internalNode;
      super.handle(nodeId);
    }
    
    public void handle(boolean hasTXOP, int internalNode) {
      handle(hasTXOP, -1, internalNode);
    }
  }
  
  /**
   * reports time spent waiting in backoff,
   * Note: each paused backoff fraction is reported individually
   */
  public static class TXOPTimeEvent extends Event {
    public long duration;
    public int internalNode;

    public TXOPTimeEvent() { super(false); }
    public TXOPTimeEvent(int nodeId) {
      this.nodeId = nodeId;
    }

    public void handle(long duration) {
      this.duration = duration;
      handle(duration, -1);
    }
    
    public void handle(long duration, int internalNode) {
      this.internalNode = internalNode;
      this.duration = duration;
      super.handle(nodeId);
    }
  }
  
  //enqueueTraceEvent, dequeueTraceEvent: Queue events
  public static class EnqueueEvent extends MessageEvent {
    public MacAddress nextHop;
    public MessageQueue queue;
    public int internalNode;

    public EnqueueEvent() { super(false); }
    public EnqueueEvent(int nodeId) {
      super(nodeId);
    }

    public void handle(Message msg, MessageAnno anno,
        MacAddress nextHop,
        MessageQueue queue) {
      handle(msg, anno, nextHop, queue, -1);
    }
    
    public void handle(Message msg, MessageAnno anno,
        MacAddress nextHop,
        MessageQueue queue, int internalNode) {
      this.internalNode = internalNode;
      this.nextHop = nextHop;
      this.queue = queue;
      super.handle(msg, anno);
    }
  }

  public static class DequeueEvent extends MessageEvent {
    public MacAddress nextHop;
    public MessageQueue queue;
    public int internalNode;

    public DequeueEvent() { super(false); }
    public DequeueEvent(int nodeId) {
      super(nodeId);
    }
    
    public void handle(Message msg, MessageAnno anno,
        MacAddress nextHop,
        MessageQueue queue) {
      handle(msg, anno, nextHop, queue, -1);
    }

    public void handle(Message msg, MessageAnno anno,
        MacAddress nextHop,
        MessageQueue queue, int internalNode) {
      this.internalNode = internalNode;
      this.nextHop = nextHop;
      this.queue = queue;
      super.handle(msg, anno);
    }
  }

  public static class RadioModeChanged extends Event {
    public int oldMode;
    public int newMode;
    public int internalNode;

    public RadioModeChanged() { super(false); }
    public RadioModeChanged(int nodeId) {
      this.nodeId = nodeId;
    }

    public void handle(int oldMode, int newMode) {
      handle(oldMode, newMode, -1);
    }
    
    public void handle(int oldMode, int newMode, int internalNode) {
      this.oldMode = oldMode;
      this.newMode = newMode;
      this.internalNode = internalNode;
      super.handle(nodeId);
    }
  }

  /** reports change in contention window size */
  public static class CwChangedEvent extends Event {
    public short cw;

    public CwChangedEvent() { super(false); }
    public CwChangedEvent(int nodeId) {
      this.nodeId = nodeId;
    }

    public void handle(short cw) {
      this.cw = cw;
      super.handle(nodeId);
    }
  }

  /**
   * reports time spent waiting in backoff,
   * Note: each paused backoff fraction is reported individually
   */
  public static class BackoffEvent extends Event {
    public long waitedBo;
    public long remainingBo;

    public BackoffEvent() { super(false); }
    public BackoffEvent(int nodeId) {
      this.nodeId = nodeId;
    }

    public void handle(long waitedBo) {
      handle(waitedBo, -1);
    }

    public void handle(long waitedBo, long remainingBo) {
      this.waitedBo = waitedBo;
      this.remainingBo = remainingBo;
      super.handle(nodeId);
    }
  }

  /**
   * reports when the NAV is updated
   */
  public static class NavChangedEvent extends Event {
    public long navValue;
    /** the node id from which I overheard the NAV or -1 when resetted by myself */
    public MacAddress initiator;

    public NavChangedEvent() { super(false); }
    public NavChangedEvent(int nodeId) {
      this.nodeId = nodeId;
    }

    public void handle(long navValue, MacAddress initiator) {
      this.navValue = navValue;
      this.initiator = initiator;
      super.handle(nodeId);
    }
  }

  // ////////////////////////////////////////////////
  // Send Events
  //

  public static class SendEvent extends MessageEvent {
    public long duration;
    public boolean retry;
    public int phyBitRate;

    public SendEvent() { super(false); }
    public SendEvent(int nodeId) { super(nodeId); }

    public void handle(Message data, MessageAnno anno, long duration, int phyBitRate, boolean retry) {
      this.duration   = duration;
      this.retry      = retry;
      this.phyBitRate = phyBitRate;
      super.handle(data, anno);
    }
  }

  public static class SendImmediateEvent extends MessageEvent {
    public long delay;

    public SendImmediateEvent() { super(false); }
    public SendImmediateEvent(int nodeId) {
      super(nodeId);
    }

    public void handle(Message packet, MessageAnno anno, long delay) {
      this.delay = delay;
      super.handle(packet, anno);
    }
  }

  public static class PromiscReceiveEvent extends MessageEvent {
    public PromiscReceiveEvent() { super(false); }
    public PromiscReceiveEvent(int nodeId) { super(nodeId); }
  }

  public static class SendAckCancelationEvent extends MessageEvent {
    public SendAckCancelationEvent() { super(false); }
    public SendAckCancelationEvent(int nodeId) { super(nodeId); }
  }

  /** was MAC_PACKETDONE */
  public static class MacTxFinished extends MessageEvent {
    public boolean success;
    public byte longRetry;

    public MacTxFinished() { super(false); }
    public MacTxFinished(int nodeId) {
      super(nodeId);
    }

    public void handle(Message data, MessageAnno anno, boolean success, byte longRetry) {
      this.success = success;
      this.longRetry = longRetry;
      super.handle(data, anno);
    }
  }

  // ////////////////////////////////////////////////
  // Recv Events
  //

  public static class ReceiveStartEvent extends MessageEvent {
    public ReceiveStartEvent() { super(false); }
    public ReceiveStartEvent(int nodeId) { super(nodeId); }
  }

  public static class ReceiveEvent extends MessageEvent {
    public boolean forwarder;
    public long duration;
    public long realStart;

    public ReceiveEvent() { super(false); }
    public ReceiveEvent(int nodeId) { super(nodeId); }

    public void handle(Message data, MessageAnno anno, long duration, long realStart, boolean forwarder) {
      this.forwarder  = forwarder;
      this.realStart  = realStart;
      this.duration   = duration;
      super.handle(data, anno);
    }

    public void handle(Message data, MessageAnno anno, long duration) {
      this.forwarder  = false;
      this.realStart  = -1;
      this.handle(data, anno, duration, realStart, false);
    }
  }

  public static class ReceiveForeignEvent extends ReceiveEvent {
    public ReceiveForeignEvent() { super(); }
    public ReceiveForeignEvent(int nodeId) { super(nodeId); }
  }

  /** an ack for this node was received, but we ignored it */
  public static class MacIgnoredAcks extends MessageEvent {
    public MacIgnoredAcks() { super(false); }
    public MacIgnoredAcks(int nodeId) { super(nodeId); }
  }

  // ////////////////////////////////////////////////
  // Misc Events
  //

  /** Retry attempt */
  public static class RetryEvent extends MessageEvent {
    public byte retryCount;
    public boolean shortRetry;

    public RetryEvent() { super(false); }
    public RetryEvent(int nodeId) { super(nodeId); }

    public void handle(Message data, MessageAnno anno, byte retryCount, boolean shortRetry) {
      this.retryCount = retryCount;
      this.shortRetry = shortRetry;
      super.handle(data, anno);
    }
  }

  /** Packet is discarded du to max. retransmissions */
  public static class DiscardEvent extends MessageEvent {
    public DiscardEvent() { super(false); }
    public DiscardEvent(int nodeId) { super(nodeId); }
  }

  public static class ForwarderEvent extends MessageEvent {
    public MacAddress localAddr;

    public ForwarderEvent() { super(false); }
    public ForwarderEvent(int nodeId) { super(nodeId); }

    public void handle(Message msg, MessageAnno anno, MacAddress localAddr) {
      this.localAddr = localAddr;
      super.handle(msg, anno);
    }
  }

  /** duplicate message received */
  public static class DuplicateEvent extends ForwarderEvent {
    public DuplicateEvent() { super(); }
    public DuplicateEvent(int nodeId) { super(nodeId); }
  }


  // ////////////////////////////////////////////////
  // constants
  //

  // ////////////////////////////////////////////////
  // locals
  //

  protected Node node;

  protected MacModeChanged macModeChanged;

  protected MacTxFinished macTxFinished;

  protected RadioModeChanged radioModeChanged;

  protected ReceiveStartEvent receiveStartEvent;

  protected ReceiveEvent receiveEvent;

  protected ReceiveForeignEvent receiveForeignEvent;

  protected SendEvent sendEvent;

  protected RetryEvent retryEvent;

  protected DiscardEvent discardEvent;

  protected ForwarderEvent forwarderEvent;

  protected DuplicateEvent duplicateEvent;

  protected CwChangedEvent cwChangedEvent;

  protected BackoffEvent backoffEvent;

  protected NavChangedEvent navChangedEvent;

  protected MacIgnoredAcks macIgnoredAcks;

  protected SendImmediateEvent sendImmediateEvent;

  protected SendAckCancelationEvent sendAckCancelationEvent;
  
  protected PromiscReceiveEvent promiscReceiveEvent;
  
  protected TXOPEvent txopEvent;
  
  protected TXOPTimeEvent txopTimeEvent;
  
  protected EnqueueEvent enqueueEvent;

  protected DequeueEvent dequeueEvent;


  // ////////////////////////////////////////////////
  // initialization
  //

  public AbstractMac() {
    macModeChanged = new MacModeChanged();
    macTxFinished = new MacTxFinished();
    radioModeChanged = new RadioModeChanged();
    receiveEvent = new ReceiveEvent();
    receiveForeignEvent = new ReceiveForeignEvent();
    receiveStartEvent = new ReceiveStartEvent();
    sendEvent = new SendEvent();
    retryEvent = new RetryEvent();
    discardEvent = new DiscardEvent();
    forwarderEvent = new ForwarderEvent();
    duplicateEvent = new DuplicateEvent();
    cwChangedEvent = new CwChangedEvent();
    backoffEvent = new BackoffEvent();
    navChangedEvent = new NavChangedEvent();
    macIgnoredAcks = new MacIgnoredAcks();
    sendImmediateEvent = new SendImmediateEvent();
    sendAckCancelationEvent = new SendAckCancelationEvent();
    promiscReceiveEvent = new PromiscReceiveEvent();
    txopEvent = new TXOPEvent();
    txopTimeEvent = new TXOPTimeEvent();
    enqueueEvent = new EnqueueEvent();
    dequeueEvent = new DequeueEvent();
  }

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

  public Node getNode() {
    return node;
  }

  public void setNode(Node node) {
    this.node = node;

    macModeChanged = new MacModeChanged(node.getNodeId());
    macTxFinished = new MacTxFinished(node.getNodeId());
    radioModeChanged = new RadioModeChanged(node.getNodeId());
    receiveEvent = new ReceiveEvent(node.getNodeId());
    receiveForeignEvent = new ReceiveForeignEvent(node.getNodeId());
    receiveStartEvent = new ReceiveStartEvent(node.getNodeId());
    sendEvent = new SendEvent(node.getNodeId());
    retryEvent = new RetryEvent(node.getNodeId());
    discardEvent = new DiscardEvent(node.getNodeId());
    forwarderEvent = new ForwarderEvent(node.getNodeId());
    duplicateEvent = new DuplicateEvent(node.getNodeId());
    cwChangedEvent = new CwChangedEvent(node.getNodeId());
    backoffEvent = new BackoffEvent(node.getNodeId());
    navChangedEvent = new NavChangedEvent(node.getNodeId());
    macIgnoredAcks = new MacIgnoredAcks(node.getNodeId());
    sendImmediateEvent = new SendImmediateEvent(node.getNodeId());
    sendAckCancelationEvent = new SendAckCancelationEvent(node.getNodeId());
    promiscReceiveEvent = new PromiscReceiveEvent(node.getNodeId());
    txopEvent = new TXOPEvent(node.getNodeId());
    txopTimeEvent = new TXOPTimeEvent(node.getNodeId());
    enqueueEvent = new EnqueueEvent(node.getNodeId());
    dequeueEvent = new DequeueEvent(node.getNodeId());
  }

  // ////////////////////////////////////////////////
  // overwrites
  //

  public abstract MacInterface getProxy();

  public abstract Phy802_11 getPhy();

  public abstract MacInfo getMacInfo();

  public abstract MacAddress getAddress();

  // ////////////////////////////////////////////////
  // operations
  //

}
