package brn.swans.radio;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.swans.Constants;
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.phy.PlcpHeader;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioNoise;

import org.apache.log4j.Logger;

import brn.swans.radio.biterrormodels.BitErrorMask;
import brn.swans.radio.biterrormodels.BitErrorModel;
import brn.util.ObjectPool;
import brn.util.PoolableObject;


/**
 * <code>RadioNoiseAdditive</code> implements a radio with an additive noise
 * model and AWGN channel.
 *
 * <h2>Signal Strength and Bit Error Performance</h2>
 *
 * A SNR to BER mapping based on theoretical curves is used. The formulas are
 * taken from ns-3/yans. However, the derivation is very coarse, so when working
 * with these values it is better to adjust (shift) them to the theoretical values
 * reported in http://nova.stanford.edu/~bbaas/ps/isscc2002_fig5.pdf.
 * Since the receiver is not perfect, there is an implementation loss (which
 * includes non-ideal receiver effects such as channel estimation errors, tracking
 * errors, quantization errors, and phase noise). In order to include realistic
 * implementation losses, we shift the SNR curves to match the transmission
 * performance of 1000 Byte at 10% PER as reported by Atheros
 * http://nova.stanford.edu/~bbaas/ps/isscc2002_fig5.pdf.
 * TODO do we have to scale the no. bits according to the FEC portion in the
 * bit error model? In ns-3, this is not done.
 *
 * Required SNR for 10% Packet Error Rate for Atheros designs
 * MCS | Data Rate (Mb/s) | SNR 802.11a (dB) | SNR Atheros (dB) | Theoretical SNR (dB) | Implementation Loss (dB)
 * BPSK 1/2 6 9.7 5.4 1.0 4.4
 * BPSK 3/4 9 10.7 5.8 3.5 2.3
 * QPSK 1/2 12 12.7 7.0 3.8 3.2
 * QPSK 3/4 18 14.7 9.5 6.5 3.0
 * 16-QAM 1/2 24 17.7 11.3 8.8 2.5
 * 16-QAM 3/4 36 21.7 14.9 12.3 2.6
 * 64-QAM 2/3 48 25.7 18.6 16.8 1.8
 * 64-QAM 3/4 54 26.7 20.6 19.0 1.6
 * (http://nova.stanford.edu/~bbaas/ps/isscc2002_fig5.pdf):
 *
 * Details about Atheros calculates RSSI and SNR values can be found in the
 * patent "Method and system for noise floor calibration and receive signal
 * strength detection" (http://www.freepatentsonline.com/7245893.html).
 * It is important to note that the analogous portion of the receiver introduces
 * addtional noise, called noise factor (in linear scale) or noise figure (in
 * logarithmic scale). This implies that SNR gets worse as we process the signal.
 * Praktically speaking, the noise figure shifts the SNR-BER curve by a fixed
 * amount.
 * This noise figure is typically in the range of 5 to 10 dB. The 802.11g standard
 * assumes a noise figure (NF) of 10 dB and an implementation loss of 5 dB.
 * The ns-3 simulator uses a noise figure of 7 dB.
 * However, depending on the number from the Atheros report
 * http://users.ece.utexas.edu/~jandrews/ee381v/supp_reading/Atheros_OFDM_implementation.pdf
 * we use 8 dB. Note further, that the noise figure only affects signals near
 * the noise floor. For much stronger signals, which are interfered by another
 * signal, the noise figure does not apply!
 * TODO it seems both noise figure and implementation loss was improved with
 * the last years. But what are reasonable values?
 *
 * There is no dedicated energy detection threshold. NOTE: Optimizations within
 * the Field deliver only signals with SNR >= 0 dB ({@code SpatialTransmitVisitor}).
 * All signals below 0dB are clipped. Be aware that this can lead to errors if
 * reasonable strong signals are clipped! You have to make sure that the
 * probability of preamble reception is reasonable small for all clipped signals!
 *
 * <h3>Performance Considerations</h3>
 * Caching of PER values speeds up the simulation a lot. E.g. the simulation
 * with 50 nodes and 500 sim-sec (only link probing) took
 *   with snr           1m25s
 *   with ber           2m34s
 *   with ber+cache     1m29s (!)
 *
 * TODO the hard decision viterbi decoder calculation could be optimized according
 * to http://se.aminet.net/pub/conferencing/ivs/papers/MSc-Masood.pdf
 * "Study and Implementation of IEEE 802.11 Physical Layer Model
 * in YANS (Future NS-3) Network Simulator"
 *
 *
 * <h2>Additive Interference</h2>
 * All incoming signals are stored. This way, it is possible to reconstruct
 * all signal and interfering powers during packet reception. It is assumed that
 * interference adds up in power, which is a worst case assumption.
 * TODO move RxEvent to {@link RadioNoise} (RadioMessage)
 *
 *
 * <h2>Receiver and Capture Model</h2>
 * The reception of the preamble is modeled explicitly, because the outcome is
 * important for the further capture and CCA modeling.
 *
 * <h3>Message-In-Message (Receiver Restart) Mode</h3>
 * mim
 * ns-3 and prism: w/o mim
 * atheros: w/ mim
 *
 * http://csdl.computer.org/dl/proceedings/lcn/2006/0418/00/04116670.pdf
 * "To eliminate HN in this scenario, the second requirement is to enable the
 * so-called Receiver Restart (RS) Mode, which is incorporated
 * into some commercial 802.11 chip (e.g., Atheros Chip) but is
 * usually not enabled by default.
 * With RS, the receiver switches to receive a new signal in
 * the midst of receiving an old signal if the power of the new
 * signal is sufficiently larger (say, 10dB stronger) than that of
 * the old one.
 *
 * MIM Patent from Atheros
 *
 * Prism and ns-3 do not implement the MIM mode
 *
 * <h2>Channel Clear Assessment</h2>
 * Tbd.
 *
 * currently it is assumed that the medium is detected to be busy as long
 * as the SNR is high enough. The preamble detection should be modeled as
 * stochastic process, too.
 *
 */
public class RadioNoiseAdditiveBER extends RadioNoise implements RadioExInterface {

  /** logger. */
  public static final Logger log = Logger.getLogger(RadioNoiseAdditiveBER.class.getName());

  private static final int POOL_SIZE = 250;

  /** Derived from {@link jist.swans.radio.RadioNoise.State} to support multiple RF channels. */
  public class StateAdditive extends State {

    /** total signal power (-> additive radio model). */
    public double   totalPower_mW;

    /** list of RxEvents which may influence the current reception */
    private List      events;

    /** signal on which the receiver is currently locked on. basically the
     * same as the signal* fields in the base class.
     */
    private RxEvent  signal;

    protected StateAdditive(RFChannel channel) {
      super(channel);
      this.events = new ArrayList();
    }

    public void appendEvent(RxEvent event, Message msg) {
      /* attempt to remove the events which are
       * not useful anymore.
       * i.e.: all events which end _before_
       *       now - m_maxPacketDuration
       */
      Util.assertion(msg instanceof PhyMessage);

      long time = JistAPI.getTime();
      if (time > maxPacketDuration) {
        long end_us = time - maxPacketDuration;

        int i;
        for (i = 0; i < this.events.size(); i++) {
          RxEvent ev = (RxEvent)this.events.get(i);
          if (ev.getEndTime() > end_us)
            break;
        }

        for (int j = 0; j < i; j++) {
          poolRxEvent.returnObject(this.events.get(0));
          this.events.remove(0);
        }
      }
      this.events.add(event);
    }

    public List getEvents() {
      return this.events;
    }
  }

  private class PhyMsgProperties {
    public double initInterference_mW;

    public double initSinr;

    public long preamble_start;

    public long plcp_header_start;

    public long plcp_payload_start;

    public long payload_front_dead;

    public long payload_back_dead;

    public long plcp_payload_end;

    public long payload_data_start;

    public long payload_data_end;

    List calculateProperties(StateAdditive state, RxEvent event, PhyMessage msg) {
      List /* NiChange */ni = tmpList;

      // interference_mW includes interference from ongoing other tramsmissions
      initInterference_mW = calculateInterference_mW(state, event, ni);
      initSinr = calculateSnr(event.rxPower_mW, initInterference_mW);

      NiChange first = (NiChange) ni.get(0);

      PlcpHeader plcp = msg.getPlcpHeader();
      preamble_start = first.getTime();
      plcp_header_start = preamble_start + plcp.getPreambleDuration();
      plcp_payload_start = plcp_header_start + plcp.getHeaderDuration();

      payload_front_dead = plcp.getServiceBitsDuration();
      payload_back_dead = plcp.getPadDuration() + plcp.getTailDuration();
      plcp_payload_end = plcp_payload_start + plcp.getPayloadDuration();

      payload_data_start = plcp_payload_start + payload_front_dead;
      payload_data_end = plcp_payload_end - payload_back_dead;

      return ni;
    }

    void cutTimes(long endTime) {
      preamble_start = Math.min(preamble_start, endTime);
      plcp_header_start = Math.min(plcp_header_start, endTime);
      plcp_payload_start = Math.min(plcp_payload_start, endTime);
      plcp_payload_end = Math.min(plcp_payload_end, endTime);
      payload_data_start = Math.min(payload_data_start, endTime);
      payload_data_end = Math.min(payload_data_end, endTime);
      payload_front_dead = payload_data_start - plcp_payload_start;
      payload_back_dead = plcp_payload_end - payload_data_end;
    }

  }

  public static class RxEvent implements PoolableObject {

    private long startTime;
    private long endTime;
    private double rxPower_mW;
    private PhyMessage msg;

    public RxEvent() {
    }

    public final void init(PhyMessage msg, long duration_us, double rx_power) {
      this.msg = msg;
        startTime = JistAPI.getTime();
        endTime = startTime + duration_us;
        rxPower_mW = rx_power;
    }

    public void init() {
      this.init(null, 0, 0);
    }

    public void returned() {
    }

    public long getStartTime() {
      return startTime;
    }
    public long getEndTime() {
      return endTime;
    }
    public boolean overlaps(long time) {
      if (startTime <= time &&
          endTime >= time) {
        return true;
      } else {
        return false;
      }
    }
    public Integer getPayloadRate () {
      return msg.getPayloadRateObj();
    }
    public Integer getHeaderRate() {
      return Integer.valueOf(msg.getPlcpHeaderRate());
    }

    public double getRxPower_mW() {
      return rxPower_mW;
    }

    public void setRxPower_mW(double rxPower_mW) {
      this.rxPower_mW = rxPower_mW;
    }

    public PhyMessage getMsg() {
      return msg;
    }
  }

  /**
   *  Class which records SNIR change events for a  short period of time.
   */
  public static class NiChange implements Comparable, PoolableObject {
    public long time;
    public double delta;

    protected NiChange() {
    }

    public final void init(long time, double delta) {
      this.time  = time;
      this.delta = delta;
    }

    public void init() {
      init(0,0);
    }

    public void returned() {
    }

   public long getTime() {
     return time;
   }

   public double getDelta() {
     return delta;
   }

   public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;

     final NiChange niChange = (NiChange) o;

     if (Double.compare(niChange.delta, delta) != 0) return false;
     if (time != niChange.time) return false;

     return true;
   }

   public int hashCode() {
     int result;
     long temp;
     result = (int) (time ^ (time >>> 32));
     temp = delta != +0.0d ? Double.doubleToLongBits(delta) : 0L;
     result = 29 * result + (int) (temp ^ (temp >>> 32));
     return result;
   }

   public int compareTo(Object o) {
     long thisVal = this.time;
     long anotherVal = ((NiChange)o).time;
     return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
     }
  }

  public static class ChunkErrorRates {
    private TransmissionMode mode;
    private SortedMap bitErrors;
    private int truncated;
    private double psr;
    private boolean errorsPresent;
    private double sinr;
    private double intf_mW;

    public void setTruncated(int truncated) {
      this.truncated = truncated;
      errorsPresent = true;
    }

    public int getTruncated() {
      return truncated;
    }

    public ChunkErrorRates(TransmissionMode mode, double sinr, double intf_mW,
        boolean generateErrorMask) {
      this.mode = mode;
      this.sinr = sinr;
      this.intf_mW = intf_mW;
      this.psr = 1;
      this.errorsPresent = false;

      if (generateErrorMask)
        this.bitErrors = new TreeMap();
    }

    public TransmissionMode getMode() {
      return mode;
    }

    public double getInterference_mW() {
      return intf_mW;
    }

    public double getSnr() {
      return sinr;
    }

    public SortedMap getBitErrors() {
      return bitErrors;
    }

    public double getPer() {
      return 1. - psr;
    }

    public boolean errorsPresent() {
      return errorsPresent;
    }

    public void updateChunk(int bitPosition, double csr, BitErrorMask chunkErrorMask) {
      psr *= csr;
      if (chunkErrorMask != BitErrorMask.noErrors)
        errorsPresent = true;
      if (null != bitErrors)
        bitErrors.put(bitPosition, chunkErrorMask);
    }
  }


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

  // ////////////////////////////////////////////////
  // properties
  //

  protected Map /* TransmissionMode */ modes;

  /** whether to receive CRC and PHY corrupted packets */
  protected boolean recvCorruptPackets = false;

  /** whether to activate the rx chain for reception */
  protected boolean doReceive = true;

  /** e.g. 4095 bytes at a 6Mb/s rate with a 1/2 coding rate.
   * we do not deal with phy layer changes!
   */
  protected final long maxPacketDuration;

  protected final Phy802_11 phy;

  protected DiversityCombiner diversityCombiner;

  protected BitErrorModel bitErrorModel;

  protected Capture capture;

  protected ChannelClearAssessment cca;

  /** Noise figure: ratio of energy lost by receiver (not dB) */
  protected double rxNoiseFactor;

  protected ObjectPool poolNiChange = new ObjectPool.FixedSize(POOL_SIZE) {
    protected PoolableObject newObject() {
      return new NiChange();
    }
  };

  protected ObjectPool poolRxEvent = new ObjectPool.FixedSize(POOL_SIZE) {
    protected PoolableObject newObject() {
      return new RxEvent();
    }
  };

  /** only a temp list, prevents object creation all the time */
  private List tmpList = new ArrayList();


  // ////////////////////////////////////////////////
  // initialize
  //

//  /**
//   * Create a new radio with additive noise model.
//   *
//   * @param radioInfo  radioInfo radio properties
//   * @param recvCorruptPackets whether to recv corrupted packets
//   */
//  public RadioNoiseAdditiveBER(RadioInfo radioInfo, boolean recvCorruptPackets) {
//    this (radioInfo, recvCorruptPackets, new DiversityCombiner.None());
//  }

//  /**
//   * Create a new radio with additive noise model.
//   *
//   * @param radioInfo  radioInfo radio properties
//   * @param recvCorruptPackets whether to recv corrupted packets
//   */
//  public RadioNoiseAdditiveBER(RadioInfo radioInfo, boolean recvCorruptPackets, DiversityCombiner combiner) {
//    this (radioInfo, recvCorruptPackets, combiner, new None());
//  }

  /**
   * Create a new radio with additive noise model.
   *
   * @param radioInfo  radioInfo radio properties
   * @param d
   * @param recvCorruptPackets whether to recv corrupted packets
   */
  public RadioNoiseAdditiveBER(RadioInfo radioInfo, double rxNoiseFactor,
      boolean recvCorruptPackets, DiversityCombiner combiner,
      BitErrorModel errorModel, Capture capture, ChannelClearAssessment cca) {
    super(radioInfo);
    this.self = (RadioExInterface)JistAPI.proxy(new RadioExInterface.Dlg(this),
        RadioExInterface.class);

    this.rxNoiseFactor = rxNoiseFactor;
    this.phy = new Phy802_11(radioInfo);
    this.maxPacketDuration = phy.transmitTime(4095, phy.radioInfo.getBitratesSupported()[0]);
    init(radioInfo, recvCorruptPackets, combiner, errorModel, capture, cca);
  }

  /**
   * This method creates @param radioInfo.getNumberOfChannels() number of states. Each state represents one available
   * RF channel. Finally, the current state of the radio is set.
   *
   * @param radioInfo  radioInfo radio properties
   * @param recvCorruptPackets whether to recv corrupted packets
   */
  private void init(RadioInfo radioInfo, boolean recvCorruptPackets,
      DiversityCombiner combiner, BitErrorModel errorModel,
      Capture capture, ChannelClearAssessment cca) {
    this.diversityCombiner = combiner;
    this.bitErrorModel = errorModel;
    this.capture = capture;
    this.cca = cca;

    // we want to play around with .11a stations
    configure80211a();

    // initialize all radio states
    for (int i = 0; i < radioInfo.getNumberOfChannels(); i++) {
      RFChannel rfChannel = new RFChannel(radioInfo.getRFChannel().getFrequency(), i + 1);
      // create new state
      StateAdditive tState = new StateAdditive(rfChannel);
      // unlock signal
      unlockSignal(tState);
      // set the background noise as initial value for the total power
      tState.totalPower_mW = radioInfo.getBackground_mW();
      tState.signals = 0;

      states.put(rfChannel, tState);
    }

    this.recvCorruptPackets = recvCorruptPackets;

    // set radio on the inital RF channel
    currentState = (State)states.get(new RFChannel(radioInfo.getRFChannel()));
  }

  private void configure80211a() {
    //m_plcp_header_length = 4 + 1 + 12 + 1 + 6 + 16 + 6;
    //m_plcp_preamble_delay_us = 20;
    /* 4095 bytes at a 6Mb/s rate with a 1/2 coding rate. */
    //m_max_packet_duration_us = (uint64_t)(1000000 * 4095.0*8.0/6000000.0*(1.0/2.0));

    int bandwidth = (int)radioInfo.getBandwidth();
    Util.assertion(bandwidth == 20e6);

    modes = new HashMap();
    modes.put(Integer.valueOf(Constants.BANDWIDTH_6Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_6Mbps));
    modes.put(Integer.valueOf(Constants.BANDWIDTH_9Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_9Mbps));
    modes.put(Integer.valueOf(Constants.BANDWIDTH_12Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_12Mbps));
    modes.put(Integer.valueOf(Constants.BANDWIDTH_18Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_18Mbps));
    modes.put(Integer.valueOf(Constants.BANDWIDTH_24Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_24Mbps));
    modes.put(Integer.valueOf(Constants.BANDWIDTH_36Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_36Mbps));
    modes.put(Integer.valueOf(Constants.BANDWIDTH_48Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_48Mbps));
    modes.put(Integer.valueOf(Constants.BANDWIDTH_54Mbps),
        TransmissionMode.get80211gMode(bandwidth, Constants.BANDWIDTH_54Mbps));

    // Begin Finger Weg!
    // Sanity check: PSR at 0 dB must be sufficient small, see class comment
    if (Main.ASSERT) {
      // Important, because the field optimizes according to this value
      double snir = calculateSnr(radioInfo.getBackground_mW(), 0);
      TransmissionMode mode = (TransmissionMode) modes.get(Integer.valueOf(Constants.BANDWIDTH_6Mbps));
      int numBits = mode.getNumDataBits(Constants.SYNCHRONIZATION_OFDM);
      double psr = mode.getChunkSuccessRate(snir, numBits);
      Util.assertion(psr < 0.001);

      // Assure we have the correct sensitivity value
      // Important, because the field optimizes according to this value
      snir = calculateSnr(radioInfo.getSensitivity_mW(), 0);
      psr = mode.getChunkSuccessRate(snir, numBits);
      Util.assertion(psr < 0.01);
    }
    // End Finger Weg!

    if (log.isDebugEnabled()) {
      StringBuffer str = new StringBuffer();
      for (double db = 0; db < 30; db+= 0.5) {
        str.append(db).append(" ");
        int[] bitrates = Constants.BITRATES_OFDM;
        for (int i = 0; i < bitrates.length; i++) {
          TransmissionMode mode = (TransmissionMode) modes.get(Integer.valueOf(bitrates[i]));
          double ber = 1-mode.getChunkSuccessRate(Util.fromDB(db), 1);
          str.append(ber).append(" ");
        }
        log.debug(" " + str.toString());
      }
    }
  }

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

  /**
   * @return the doReceive
   */
  public boolean isDoReceive() {
    return doReceive;
  }

  /**
   * @param doReceive the doReceive to set
   */
  public void setDoReceive(boolean doReceive) {
    this.doReceive = doReceive;
  }

  // ////////////////////////////////////////////////
  // transmission
  //

  /**
   * Internal helper method.
   * @param state use this state.
   * @see #endTransmit()
   */
  protected void endTransmit(State state)
  {
    if (log.isDebugEnabled())
      log.debug(radioInfo.getId() + "(" + JistAPI.getTime() + "): endTransmit ... ");

    // radio in sleep mode
    if(state.mode == Constants.RADIO_MODE_SLEEP)
      return;
    // check that we are currently transmitting
    if(state.mode != Constants.RADIO_MODE_TRANSMITTING)
      throw new RuntimeException("radio is not transmitting");

    // set the mode to sensing if there are ongoing message receptions, otherwise to idle.
    setMode(state, cca.doesSense((StateAdditive) state) ?
        Constants.RADIO_MODE_SENSING : Constants.RADIO_MODE_IDLE);
  }

  // ////////////////////////////////////////////////
  // reception
  //

  /** {@inheritDoc} */
  public void receive(final Message msg, final Double powerObj_mW,
      final Long durationObj) {
    if (!this.doReceive)
      return;
    if (msg instanceof PhyMessage) {
      // retrieve the state information to the RF channel used by this packet
      StateAdditive state = (StateAdditive)states.get(((PhyMessage)msg).getRfChannel());
      receive(state, msg, powerObj_mW, durationObj);
    } else {
      RadioNoiseAdditiveBER.log.warn("received non-phy message");
      // legacy support; ignore the used RF channel
      receive((StateAdditive)currentState, msg, powerObj_mW, durationObj);
    }
  }

  /**
   * Receive the incoming message on the RF channel represented by @param state.
   * @param state use this state for the reception of the given frame.
   * @see #receive(jist.swans.misc.Message, Double, Long)
   */
  protected void receive(StateAdditive state, final Message rxMsg,
      final Double powerObj_mW, final Long durationObj) {
    final long duration   = durationObj.longValue();
//    final byte oldMode = state.mode;
    double power_mW = powerObj_mW.doubleValue();
    boolean diversityRecv = false;
    double oldRxEventPower_mW = 0;
    PhyMessage msg = (PhyMessage) rxMsg;

    // handle STC and NonCoherent Transmit Diversity
    RxEvent event = diversityCombiner.find(state.getEvents(), msg);
    if (null != event) {
      oldRxEventPower_mW = event.rxPower_mW;
      state.totalPower_mW -= oldRxEventPower_mW;
      diversityCombiner.combine(event, msg, power_mW, duration);

      // TODO if the new power is less, we may loose this packet...
      if (Main.ASSERT)
        Util.assertion(event.rxPower_mW >= oldRxEventPower_mW);

      // if we already locked on this signal, all is done (no need for equals...)
      if (event == state.signal) {
        if (radioRxDiversityEvent.isActive())
          radioRxDiversityEvent.handle(msg, JistAPI.getTime() - event.startTime,
              state.signalPower_mW, power_mW, event.rxPower_mW, false, false);

        state.signalPower_mW = event.rxPower_mW;
        state.totalPower_mW += event.rxPower_mW;

        return;
      }

      // else imagine we only receive the combined packet and try to lock on
      power_mW = event.rxPower_mW;

      // process endReceive only for the first copy...
      diversityRecv = true;
    } else {
      state.signals++;

      // store receive event here...
      event = (RxEvent) poolRxEvent.getObject();
      event.init(msg, duration, power_mW);
      state.appendEvent(event, msg);
    }

    // cumulative signal
    state.totalPower_mW += power_mW;

    // this is a combining, so we have already an event stored
    if (diversityRecv) {
      if (radioRxDiversityEvent.isActive()) {
        // note that we did not receive THIS packet, because otherwise
        // we had used the return above!

        // TODO moved state machine, make it work again!
//        boolean causedRecv = (state.mode == Constants.RADIO_MODE_RECEIVING
//          && state.signal == event);
//        boolean causedDrop = (oldMode == Constants.RADIO_MODE_RECEIVING
//            && state.mode != Constants.RADIO_MODE_RECEIVING);
//        long delaySpread = JistAPI.getTime() - event.startTime;
//        radioRxDiversityEvent.handle(msg, delaySpread , oldRxEventPower_mW,
//            powerObj_mW.doubleValue(), power_mW, causedRecv, causedDrop);
      }

      // do not go to endReceive for combined messages...
      return;
    }

    // TODO what about CCA during preamble??
    if (state.mode == Constants.RADIO_MODE_IDLE && cca.doesSense(state))
      setMode(state, Constants.RADIO_MODE_SENSING);
    // XXX

    // schedule an preambleReceived
    PlcpHeader plcp = msg.getPlcpHeader();
    // TODO really include header??
    long sync = plcp.getPreambleDuration() + plcp.getHeaderDuration();
    JistAPI.sleep(sync);
    ((RadioExInterface)self).preambleReceived(msg, duration-sync, state.rfChannel, event);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioInterface#preambleReceived(jist.swans.misc.Message, jist.swans.radio.RadioInterface.RFChannel, java.lang.Object)
   */
  public void preambleReceived(Message msg, Long duration, RFChannel rfChannel, Object event) {
    if (rfChannel != null) {
      StateAdditive state = (StateAdditive)states.get(rfChannel);
      preambleReceived(state, msg, duration, (RxEvent)event);
    } else {
      // legacy support; ignore the existence of RF channels
      preambleReceived((StateAdditive)currentState, msg, duration, (RxEvent)event);
    }
  }

  /**
   * Receive the incoming message on the RF channel represented by @param state.
   * @param state use this state for the reception of the given frame.
   * @see #receive(jist.swans.misc.Message, Double, Long)
   */
  protected void preambleReceived(StateAdditive state,
      final Message msg, final Long duration, final RxEvent event) {

    // Process state machine
    switch (state.mode) {
      case Constants.RADIO_MODE_RECEIVING:
        // if not able to capture, forget it...
        if (!capture.doesCapture(state, event))
          break;

        // Discard the old signal, if any
        if (this.recvCorruptPackets) {
          ChunkErrorRates rates = calculateDataErrorRates(state, state.signal,
              (PhyMessage) state.signalBuffer, JistAPI.getTime());
          MessageAnno signalAnno = setRecvAnnos(state, state.signalBuffer,
              state.signal, rates);
          // test shows: if we don't draw additional random numbers in
          // calculateErrorRates, but instead set truncated manually here, a run with
          // recvCorruptPackets is exactly the same as without (as long as packets
          // flagged as corrupt are discarded by the MAC or Net layer).
          // So, this assertion actually makes sure that there are no more bugs related to
          // recvCorruptPackets!
          if (Main.ASSERT)
            Util.assertion(rates.getTruncated() >= 0);
          Integer  truncated = new Integer(rates.getTruncated());
          signalAnno.put(MessageAnno.ANNO_RADIO_PACKET_TRUNCATED, truncated);
          if (!isRFChannelDeaf(state) && radioReceiveEvent.isActive()) {
            radioReceiveEvent.handle(radioInfo, state.signalBuffer, signalAnno,
                state.signalPower_mW, state.signal.startTime, JistAPI
                    .getTime());
          }
          sendToMac(state, state.signalPower_mW, signalAnno);
        } else if (!isRFChannelDeaf(state) && radioDropEvent.isActive()) {
          radioDropEvent.handle("stronger_signal", radioInfo, state.signalBuffer,
              state.staticAnnos, state.signalPower_mW, duration);
        }
        unlockSignal(state);

        // HACK: unset receiving, in the case we do not really capture the incoming frame
        // Real state change is done in the following case statement!
        state.mode = Constants.RADIO_MODE_SENSING;

        if (Main.ASSERT)
          Util.assertion(!(state.signal == null && state.mode == Constants.RADIO_MODE_RECEIVING));

        // fall through ...
      case Constants.RADIO_MODE_IDLE:
      case Constants.RADIO_MODE_SENSING:
        // assume we are not able to receive signals with snr < 0dB
        // TODO is this right? what happens if the total power is not constant over time?
        if (event.rxPower_mW >= (state.totalPower_mW-event.rxPower_mW)) {

          // Detect preamble, if false detect, forget about it...
          ChunkErrorRates rates  = calculatePlcpHeaderErrorRates(state, event, (PhyMessage)msg);
          //ChunkErrorRates rates  = calculateErrorRates(state, event, (PhyMessage)msg, false);
          if (!rates.errorsPresent()) {
            // inform cca about the new duration field
            cca.updateDuration(duration);

            // Preamble successfully received, go to state receiving
            setMode(state, Constants.RADIO_MODE_RECEIVING);
            lockSignal(state, event, duration);
          }
        }

        if (Main.ASSERT)
          Util.assertion(!(state.signal == null && state.mode == Constants.RADIO_MODE_RECEIVING));

        // in the case of preamble detection error, choose new state...
        if (state.mode != Constants.RADIO_MODE_RECEIVING) {
          // set new state only if it differs (setMode calls into the MAC in any case)
          byte newMode = cca.doesSense(state) ?
              Constants.RADIO_MODE_SENSING : Constants.RADIO_MODE_IDLE;
          if (newMode != state.mode)
            setMode(state, newMode);
        }
        break;
      case Constants.RADIO_MODE_TRANSMITTING:
        break;
      case Constants.RADIO_MODE_SLEEP:
        break;
      default:
        throw new RuntimeException("unknown radio mode");
    }

    if (Main.ASSERT) {
      Util.assertion(!(state.signal == null && state.mode == Constants.RADIO_MODE_RECEIVING));
    }

    // schedule an endReceive
    JistAPI.sleep(duration);
    // do not use power here, get it from RxEvent
    self.endReceive(msg, null, state.rfChannel, event);
  }


  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioInterface#endReceive(jist.swans.misc.Message, java.lang.Double, jist.swans.radio.RadioInterface.RFChannel, java.lang.Object)
   */
  public void endReceive(Message msg, Double powerObj_mW, RFChannel rfChannel, Object event) {
    if (rfChannel != null) {
      StateAdditive state = (StateAdditive)states.get(rfChannel);
      endReceive(state, msg, powerObj_mW, (RxEvent)event);
    } else {
      // legacy support; ignore the existence of RF channels
      endReceive((StateAdditive)currentState, msg, powerObj_mW, (RxEvent)event);
    }
  }

  /**
   * Helper method that executes the endReceive() on the given state.
   *
   * @param state use this state for the reception of the given frame.
   * @see #endReceive(Message, Double, jist.swans.radio.RadioInterface.RFChannel, Object)
   */
  protected void endReceive(StateAdditive state,
      Message msg, Double __power_mW, RxEvent event) {
    if (Main.ASSERT)
      Util.assertion(state.signals >= 1);
    if (Main.ASSERT)
      Util.assertion(!(state.signal == null && state.mode == Constants.RADIO_MODE_RECEIVING));

    // cumulative signal
    state.signals--;
    state.totalPower_mW = (state.signals == 0 ?
        radioInfo.getBackground_mW() : state.totalPower_mW - event.rxPower_mW);

    switch (state.mode) {
      case Constants.RADIO_MODE_RECEIVING:
        // this condition is required to handle capture effect.
        // But it doesn't do what we want. Two different signals can end at the same time!
        //if (JistAPI.getTime() != state.signalFinish)
        // Instead, check if the event is actually the same
        if(state.signal != event)
          break;

        // Note: Preamble+PLCP not considered here, this is done in #preambleReceived
        ChunkErrorRates rates  = calculateDataErrorRates(state, event, (PhyMessage)msg, -1);
        //ChunkErrorRates rates  = calculateErrorRates(state, event, (PhyMessage)msg, true);
        boolean packetError  = rates.errorsPresent();
        //double per = rates.getPer();

        // generation of annos for corrupted frames is expensive!
        MessageAnno signalAnno = setRecvAnnos(state, msg, event, rates);

        if (!isRFChannelDeaf(state) && radioReceiveEvent.isActive())
          radioReceiveEvent.handle(radioInfo, msg, signalAnno,
              event.rxPower_mW, event.startTime, event.endTime);

        if (!packetError || this.recvCorruptPackets) {
          sendToMac(state, Double.valueOf(event.rxPower_mW), signalAnno);
        } else {
          if (!isRFChannelDeaf(state) && radioDropEvent.isActive())
            radioDropEvent.handle("BER drop", radioInfo, state.signalBuffer,
                signalAnno, state.signalPower_mW);
        }

        unlockSignal(state);
        // fall through...

      case Constants.RADIO_MODE_SENSING:
      case Constants.RADIO_MODE_IDLE:
        // set new state only if it differs (setMode calls into the MAC in any case)
        byte newMode = cca.doesSense(state) ?
            Constants.RADIO_MODE_SENSING : Constants.RADIO_MODE_IDLE;
        if (newMode != state.mode)
          setMode(state, newMode);

        // prevent stall due to not resetted carrier sensing
        if (Main.ASSERT)
          Util.assertion(!(state.signals == 0 && state.mode != Constants.RADIO_MODE_IDLE));
        break;

      case Constants.RADIO_MODE_SLEEP:
      case Constants.RADIO_MODE_TRANSMITTING:
        // do not update mode!
        break;
      default:
        throw new RuntimeException("unknown radio mode");
    }
  }

  protected MessageAnno setRecvAnnos(StateAdditive state, Message msg,
      RxEvent event, ChunkErrorRates rates) {
    // generation of annos for corrupted frames is expensive!
    MessageAnno signalAnno = null;
    boolean packetError = rates.errorsPresent();
    double per = rates.getPer();
    if (this.useAnnotations) {
      signalAnno = (MessageAnno) state.staticAnnos.clone();
      signalAnno.put(MessageAnno.ANNO_RADIO_SIGNAL_POWER,
          Double.valueOf(event.rxPower_mW));
      if (rates.getInterference_mW() > .0)
        signalAnno.put(MessageAnno.ANNO_RADIO_INTF_POWER,
            Double.valueOf(rates.getInterference_mW()));
      signalAnno.put(MessageAnno.ANNO_RADIO_PACKET_SNR,
          Double.valueOf(rates.getSnr()));
      signalAnno.put(MessageAnno.ANNO_RADIO_PACKET_PER, Double.valueOf(per));
      signalAnno.put(MessageAnno.ANNO_RADIO_PACKET_BER, rates.getBitErrors());
      // Constants.RADIO_RECV_STATUS_OK is already in static annos!
      if (packetError)
        signalAnno.put(MessageAnno.ANNO_RADIO_RECV_STATUS,
            Constants.RADIO_RECV_STATUS_CRC);
    }
    return signalAnno;
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioNoise#dropPacket(jist.swans.radio.RadioNoise.State, java.lang.String)
   */
  public void dropPacket(State state, String reason) {
    if (null != currentState.signalBuffer) {
      super.dropPacket(state, reason);
      if (currentState.signals > 0
          &&((StateAdditive)currentState).
          totalPower_mW > radioInfo.getBackground_mW())
        setMode(currentState, Constants.RADIO_MODE_SENSING);
      else
        setMode(currentState, Constants.RADIO_MODE_IDLE);
    }
  }


  /**
   * @inheritDoc
   */
  public void endSetChannel(RFChannel channel) {
    super.endSetChannel(channel);
    // set the mode accordingly
    if (((StateAdditive)currentState).totalPower_mW > radioInfo.getBackground_mW()) {
      setMode(currentState, Constants.RADIO_MODE_SENSING);
    } else {
      setMode(currentState, Constants.RADIO_MODE_IDLE);
    }
  }

  private double calculateInterference_mW(StateAdditive state, RxEvent event,
      List /*NiChange*/ ni) {
    double interference_mW = 0.0;

    List events = state.getEvents();
    for (int i = 0; i < events.size(); i++) {
      RxEvent rxEv = (RxEvent)events.get(i);
      if (event.equals(rxEv)) {
        continue;
      }
      if (event.overlaps(rxEv.getStartTime())) {
        NiChange niChange = (NiChange) poolNiChange.getObject();
        niChange.init(rxEv.getStartTime(), rxEv.getRxPower_mW());
        ni.add(niChange);
      }
      if (event.overlaps(rxEv.getEndTime())) {
        NiChange niChange = (NiChange) poolNiChange.getObject();
        niChange.init(rxEv.getEndTime(), -rxEv.getRxPower_mW());
        ni.add(niChange);
      }
      if (rxEv.overlaps(event.getStartTime())) {
        interference_mW += rxEv.getRxPower_mW();
      }
    }
    NiChange niChange = (NiChange) poolNiChange.getObject();
    niChange.init(event.getStartTime(), interference_mW);
    ni.add(niChange);

    niChange = (NiChange) poolNiChange.getObject();
    niChange.init(event.getEndTime(), 0);
    ni.add(niChange);

    // quicksort vector of NI changes by time.
    // TODO sort constructs a lot of Object[], is there a better solution??
    Collections.sort(ni);

    return interference_mW;
  }

  /**
   * Calculates SNIR from signal and interference powers. Note: Noise power is
   * determined internally and must not be included in <code>interference</code>.
   *
   * @param signal_mW signal power in mW
   * @param interference_mW interference power in mW.
   * @return the SNIR.
   */
  private double calculateSnr(double signal_mW, double interference_mW) {
    double noise_floor_mW = rxNoiseFactor * radioInfo.getBackground_mW();
    double noise_mW = noise_floor_mW + interference_mW;
    double snr = signal_mW / noise_mW;
    return snr;
  }

  private ChunkErrorRates calculatePlcpHeaderErrorRates(StateAdditive state,
      RxEvent event, PhyMessage msg) {
    PhyMsgProperties props = new PhyMsgProperties();
    List /* NiChange */ni = props.calculateProperties(state, event, msg);

    // calculate the SNIR at the start of the packet and accumulate
    // all SNIR changes in the snir vector.

    NiChange first = (NiChange) ni.get(0);
    long previous = first.getTime();

    double interference_mW = first.getDelta();
    double power_mW = event.getRxPower_mW();

    TransmissionMode header_mode = (TransmissionMode) modes.get(event
        .getHeaderRate());
    ChunkErrorRates cer = new ChunkErrorRates(header_mode, props.initSinr,
        props.initInterference_mW, recvCorruptPackets);

    int bitPosition = 0;

    for (int i = 1; i < ni.size(); i++) {
      NiChange curr = (NiChange) ni.get(i);
      long current = curr.getTime();

      // changes in the PSDU part not interesting
      if (previous >= props.plcp_payload_start)
        break;

      // only until start of payload
      if (current > props.plcp_payload_start)
        current = props.plcp_payload_start;
      Util.assertion(current >= previous);

      double snr = calculateSnr(power_mW, interference_mW);

      if (previous >= props.preamble_start) {
        bitPosition += bitErrorModel.treatChunk(cer, bitPosition, current,
            previous, snr);
      } else if (current >= props.preamble_start) {
        bitPosition += bitErrorModel.treatChunk(cer, bitPosition, current,
            props.preamble_start, snr);
      }

      interference_mW += curr.getDelta();
      previous = curr.getTime();
    }
    finishErrorCalculation(ni, cer, event, state, props);

    return cer;
  }

  /**
   * Calculates the error rates for the given packet, considering its physical
   * parameters and all other interfering signals.
   *
   * @param state
   *          current receiver state (channel).
   * @param event
   *          the signal properties of the frame to receive
   * @param msg
   *          the frame to receive
   * @param endTime
   *          if > 0, don't treat the whole msg, but only up to endTime
   * @return an {@link ChunkErrorRates} object containing the error rates.
   */
  private ChunkErrorRates calculateDataErrorRates(StateAdditive state,
      RxEvent event, PhyMessage msg, long endTime) {
    PhyMsgProperties props = new PhyMsgProperties();
    List /* NiChange */ni = props.calculateProperties(state, event, msg);
    if (endTime >= 0) {
      props.cutTimes(endTime);
    }

    // calculate the SNIR at the start of the packet and accumulate
    // all SNIR changes in the snir vector.

    NiChange first = (NiChange) ni.get(0);
    long previous = first.getTime();

    double interference_mW = first.getDelta();
    double power_mW = event.getRxPower_mW();

    TransmissionMode payload_mode =
      (TransmissionMode) modes.get(event.getPayloadRate());
    ChunkErrorRates cer = new ChunkErrorRates(payload_mode,
        props.initSinr, props.initInterference_mW, recvCorruptPackets);
    int bitPosition = 0;

    for (int i = 1; i < ni.size(); i++) {
      NiChange curr = (NiChange) ni.get(i);
      long current = curr.getTime();

      // changes during the trailing dead time aren't interesting
      if (previous >= props.payload_data_end)
        break;

      // skip trailing dead time
      if (current > props.payload_data_end)
        current = props.payload_data_end;
      Util.assertion(current >= previous);

      double snr = calculateSnr(power_mW, interference_mW);

      // TODO what about signal field??
      if (previous >= props.payload_data_start) {
        bitPosition += bitErrorModel.treatChunk(cer, bitPosition, current,
            previous, snr);
      } else if (current >= props.payload_data_start) {
        bitPosition += bitErrorModel.treatChunk(cer, bitPosition, current,
            props.payload_data_start, snr);
      }

      interference_mW += curr.getDelta();
      previous = curr.getTime();
    }

    if (endTime >= 0) {
      cer.setTruncated(bitPosition / 8 + (bitPosition % 8 > 0 ? 1 : 0));
    }
    finishErrorCalculation(ni, cer, event, state, props);

    return cer;
  }

  private void finishErrorCalculation(List ni, ChunkErrorRates cer,
      RxEvent event, StateAdditive state, PhyMsgProperties props) {
    for (int i = 0; i < ni.size(); i++)
      poolNiChange.returnObject(ni.get(i));
    tmpList.clear();

    bitErrorModel.postProcess(cer);

    if (log.isInfoEnabled()) {
      TransmissionMode mode = (TransmissionMode) modes.get(event
          .getPayloadRate());
      log.info("mode=" + event.getPayloadRate() + ", ber="
          + (1 - mode.getChunkSuccessRate(props.initSinr, 1)) + ", snr="
          + props.initSinr + ", per=" + cer.getPer() + ", size="
          + state.signalBuffer.getSize());
    }
  }


  protected void lockSignal(StateAdditive state, RxEvent event, long duration) {
    state.signal = event;
    super.lockSignal(state, event.msg, event.rxPower_mW, duration);
    if (Main.ASSERT)
      Util.assertion(state.signal != null);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioNoise#lockSignal(jist.swans.radio.RadioNoise.State, jist.swans.misc.Message, double, long)
   */
  protected void lockSignal(State state, Message msg, double power_mW,
      long duration) {
    // use the other locksignal
    Util.assertion(false);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioNoise#unlockSignal(jist.swans.radio.RadioNoise.State)
   */
  protected void unlockSignal(State s) {
    StateAdditive state = (StateAdditive) s;
    super.unlockSignal(state);
    state.signal = null;
  }

} // class: RadioNoiseAdditive
