package brn.sim.handler;

import jist.swans.misc.Event;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetMessage;
import jist.swans.mac.AbstractMac;
import jist.swans.mac.MacMessage;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacMessage.HasBody;
import jist.swans.phy.PhyMessage;
import jist.swans.radio.RadioInterface;
import jist.runtime.Util;
import brn.sim.data.javis.*;

import java.util.List;

/**
 * Captures node positions as well as all MAC events.
 *
 * @author kurth
 */
public class JavisMacEventHandler extends JavisFieldEventHandler {

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

  public final static byte PROTOCOL_TYPE = (byte)88;

  protected HopTraceEvent hopTraceEvent           = new HopTraceEvent();
  protected ReceiveTraceEvent receiveTraceEvent   = new ReceiveTraceEvent();
  protected LinkDropTraceEvent linkDropTraceEvent = new LinkDropTraceEvent();
  protected MarkTraceEvent channelTraceEvent      = new MarkTraceEvent();

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

  public JavisMacEventHandler(Trace trace) {
    super(trace);
  }

  public void registerHandlers() {
    super.registerHandlers();

    Event.addHandler(AbstractMac.SendEvent.class, new JavisMacEventHandler.SendToPhyHandler());
    Event.addHandler(AbstractMac.MacTxFinished.class, new JavisMacEventHandler.SendToPhyFinishHandler());

    Event.addHandler(AbstractMac.ReceiveStartEvent.class, new JavisMacEventHandler.RecvFromPhyStartHandler());
    Event.addHandler(AbstractMac.ReceiveEvent.class, new JavisMacEventHandler.RecvFromPhyFinishHandler());
    Event.addHandler(AbstractMac.ReceiveForeignEvent.class, new JavisMacEventHandler.RecvFromPhyFinishHandler());

    Event.addHandler(AbstractMac.DiscardEvent.class, new JavisMacEventHandler.DiscardHandler());
  }

  // ////////////////////////////////////////////////
  // event handlers
  //

  protected class SendToPhyHandler implements Event.Handler {
    public void handle(Event event) {
      AbstractMac.SendEvent ev = (AbstractMac.SendEvent) event;

      int msgId = -1;
      int msgSize = ev.data.getSize();
      MacMessage macMessage = (MacMessage)ev.data;
      msgId = macMessage.getId();

      if (ev.anno != null) {
        RadioInterface.RFChannel rfChannel
                = (RadioInterface.RFChannel) ev.anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);

        if (null != rfChannel) {
          channelTraceEvent.set(ev.time, TRACE_CHANNEL_EVENT_NAME,
                ev.nodeId, rfChannel.getChannel(), MarkTraceEvent.SHAPE_CIRCLE, false);
          handleTrace(channelTraceEvent);
        }
      }

      MacMessage.HasDst dstMsg = (MacMessage.HasDst)
        macMessage.getAdapter(MacMessage.HasDst.class);
      if (dstMsg != null) {
        long dstId = dstMsg.getDst().getId();
        hopTraceEvent.set(ev.time, ev.nodeId, dstId,
            PROTOCOL_TYPE, msgSize, msgId);
        handleTrace(hopTraceEvent);
      }

      MacMessage.HasCandidates dstCand = (MacMessage.HasCandidates)
        macMessage.getAdapter(MacMessage.HasCandidates.class);
      if (dstCand != null) {
        // TODO
        List dsts = dstCand.getCandidates();
        for (int i = 0; dsts != null && i < dsts.size(); i++) {
          long dstId = ((MacAddress)dsts.get(i)).getId();
          hopTraceEvent.set(ev.time, ev.nodeId, dstId,
              PROTOCOL_TYPE, msgSize, msgId);
          handleTrace(hopTraceEvent);
        }
      }

    }
  }

  protected class SendToPhyFinishHandler implements Event.Handler {
    public void handle(Event event) {
      AbstractMac.MacTxFinished ev = (AbstractMac.MacTxFinished) event;

      if (ev.anno != null) {
        RadioInterface.RFChannel rfChannel
                = (RadioInterface.RFChannel) ev.anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);

        if (ev.success && null != rfChannel) {
          channelTraceEvent.set(ev.time, TRACE_CHANNEL_EVENT_NAME,
                  ev.nodeId, rfChannel.getChannel(), MarkTraceEvent.SHAPE_CIRCLE, true);
          handleTrace(channelTraceEvent);
        }
      }
    }
  }

  protected class RecvFromPhyStartHandler implements Event.Handler {
    public void handle(Event event) {
      AbstractMac.ReceiveStartEvent ev = (AbstractMac.ReceiveStartEvent) event;
      PhyMessage msg = (PhyMessage) ev.data;

      if (ev.anno != null) {
        RadioInterface.RFChannel rfChannel
                = (RadioInterface.RFChannel) ev.anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);

        Util.assertion(rfChannel != null);
        channelTraceEvent.set(ev.time, TRACE_CHANNEL_EVENT_NAME,
                ev.nodeId, rfChannel.getChannel(), MarkTraceEvent.SHAPE_SQUARE, false);
        handleTrace(channelTraceEvent);
      }

        // TODO use getAdapter
      receiveTraceEvent.set(ev.time, ((MacMessage.HasSrc)msg.getPayload())
          .getSrc().getId(), ev.nodeId, PROTOCOL_TYPE, msg.getSize(), msg.getId());
      handleTrace(receiveTraceEvent);
    }
  }

  protected class RecvFromPhyFinishHandler implements Event.Handler {
    public void handle(Event event) {
      AbstractMac.ReceiveEvent ev = (AbstractMac.ReceiveEvent) event;

      if (ev.anno != null) {
        RadioInterface.RFChannel rfChannel
                = (RadioInterface.RFChannel) ev.anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);

        Util.assertion(rfChannel != null);

        channelTraceEvent.set(ev.time, TRACE_CHANNEL_EVENT_NAME,
                ev.nodeId, rfChannel.getChannel(), MarkTraceEvent.SHAPE_SQUARE, true);
        handleTrace(channelTraceEvent);
      }
    }
  }

  protected class DiscardHandler implements Event.Handler {
    public void handle(Event event) {
      AbstractMac.DiscardEvent ev = (AbstractMac.DiscardEvent) event;
      
      // Payload is not necessarily an IP message!
      Message msg2 = ev.data;
      if (ev.data instanceof MacMessage) {
        HasBody body = (HasBody) ((MacMessage)ev.data).getAdapter(HasBody.class);
        msg2 = body.getBody();
      }
      try {
        NetMessage.Ip msg = (NetMessage.Ip) msg2;

        long src = ev.nodeId;
        long dst = -1;
        // TODO dst
        linkDropTraceEvent.set(ev.time, src, dst, PROTOCOL_TYPE, msg.getSize(),
            msg.getId());
        handleTrace(linkDropTraceEvent);
      } catch (Exception e) {
      }
    }
  }
}
