//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <RadioNoiseAdditive.java Tue 2004/04/13 18:16:53 barr glenlivet.cs.cornell.edu>
//

// Copyright (C) 2004 by Cornell University
// All rights reserved.
// Refer to LICENSE for terms and conditions of use.

package jist.swans.radio;

import org.apache.log4j.Logger;

import jist.swans.misc.Message;
import jist.swans.misc.Util;

import jist.swans.Constants;
import jist.swans.phy.PhyMessage;

import jist.runtime.JistAPI;
import jist.runtime.Main;

/**
 * <code>RadioNoiseAdditive</code> implements a radio with an additive noise
 * model.
 *
 * The modifications to support multiple RF channels changed the bahavior of the radio in the following way:
 * - each (from field) received physical message is annotated with the RF channel on which the message was sent.
 * - the information about the RF channel is used to choose the appropriate {@link StateAdditive} from {@link #states}.
 * - the successive receive() and endReceive() methods are operating exclusively on this state.
 *
 * TODO ANNO_RADIO_NOISE_LEVEL
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&rt;
 * @author Zubow
 * @version $Id: RadioNoiseAdditive.java,v 1.26 2004/11/19 15:55:34 barr Exp $
 * @since SWANS1.0
 */
public class RadioNoiseAdditive extends RadioNoise {
  /** logger. */
  public static final Logger log = Logger.getLogger(RadioNoiseAdditive.class.getName());

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

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

    protected StateAdditive(RFChannel channel) {
      super(channel);
    }

//    /**
//     * Lock onto current packet signal.
//     *
//     * Overwritten to use the right noise value (it is more than background !!!).
//     */
//    protected void lockSignal(Message msg, double power_mW, long duration) {
//      super.lockSignal(msg, power_mW, duration);
//
//      /** fill-up the annotations... */
//      if (useAnnotations) {
//        /** the noise is the sum of all current active signal powers */        
//        signalAnno.put(MessageAnno.ANNO_RADIO_NOISE_LEVEL,
//            new Double(totalPower_mW));
//      }
//    }
  }

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

  /** signal-to-noise error model constant. */
  public static final byte   SNR = 0;

  /** bit-error-rate error model constant. */
  public static final byte   BER = 1;

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

  //
  // properties
  //

  /** radio type: SNR or BER. */
  protected byte             type;

  /** bit-error-rate table. */
  protected BERTable         ber;


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

  /**
   * Create a new radio with additive noise model.
   *
   * @param radioInfo
   *          radioInfo radio properties
   */
  public RadioNoiseAdditive(RadioInfo radioInfo) {
    super(radioInfo);
    this.type = SNR;
    init(radioInfo);
  }

  /**
   * Create a new radio with additive noise model.
   *
   * @param id
   *          radio identifier
   * @param radioInfo
   *          radioInfo radio properties
   * @param ber
   *          bit-error-rate table
   */
  public RadioNoiseAdditive(int id, RadioInfo radioInfo, BERTable ber) {
    super(radioInfo);
    this.type = BER;
    this.ber = ber;
    init(radioInfo);
  }

  /**
   * 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 the radioInfo obj
   */
  private void init(RadioInfo radioInfo) {
    // 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;

      if (tState.totalPower_mW > radioInfo.getSensitivity_mW())
        tState.mode = Constants.RADIO_MODE_SENSING;

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

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

  /**
   * Register a bit-error-rate table.
   *
   * @param ber
   *          bit-error-rate table
   */
  public void setBERTable(BERTable ber) {
    this.ber = ber;
  }

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

  // RadioInterface interface
  /** {@inheritDoc} */
  public void receive(final Message msg, final Double powerObj_mW,
                      final Long durationObj) {
    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 {
      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 msg, final Double powerObj_mW,
                      final Long durationObj) {
    final double power_mW = powerObj_mW.doubleValue();
    final long duration   = durationObj.longValue();

    /** SNR between incoming signal and background noise (here: thermal + ambient + other tx signals) */
    double packetSnr      = Util.toDB(power_mW / state.totalPower_mW);
    /** SNR of previous signal when this signal is counted as noise */
    double prevPacketSnr  = Util.toDB(state.signalPower_mW / (state.totalPower_mW - state.signalPower_mW + power_mW) );

    switch (state.mode) {
      case Constants.RADIO_MODE_IDLE:
        //
        // TODO: Carrier sensing according to one of 5 modes:
        // Fledermaus Ch. 12, Clear Channel Assessment
        // hier nicht beruecksichtigt: Mode 2/4 - signal detection below
        // threshold
        //
        if (packetSnr >= radioInfo.getThresholdSnr(msg)) {
          setMode(state, Constants.RADIO_MODE_RECEIVING);
          lockSignal(state, msg, power_mW, duration);

          if (!isRFChannelDeaf(state) && radioReceiveEvent.isActive())
              radioReceiveEvent.handle(radioInfo, msg, state.staticAnnos, power_mW, duration);
        } else if (state.totalPower_mW + power_mW > radioInfo.getSensitivity_mW()) {
          setMode(state, Constants.RADIO_MODE_SENSING);
        }
        break;
      case Constants.RADIO_MODE_SENSING:
        if (packetSnr >= radioInfo.getThresholdSnr(msg)) {
          setMode(state, Constants.RADIO_MODE_RECEIVING);
          lockSignal(state, msg, power_mW, duration);
          if (!isRFChannelDeaf(state) && radioReceiveEvent.isActive())
            radioReceiveEvent.handle(radioInfo, msg, state.staticAnnos, power_mW, duration);
        }
        break;
      case Constants.RADIO_MODE_RECEIVING:
        if (power_mW > state.signalPower_mW  /* do we really need the first expression ???? */
            && packetSnr >= radioInfo.getThresholdSnr(msg)) {

          if (!isRFChannelDeaf(state) && radioDropEvent.isActive())
            radioDropEvent.handle("stronger_signal", radioInfo, state.signalBuffer,
                state.staticAnnos, state.signalPower_mW, duration);
          setMode(state, Constants.RADIO_MODE_RECEIVING);
          lockSignal(state, msg, power_mW, duration);
          if (!isRFChannelDeaf(state) && radioReceiveEvent.isActive())
            radioReceiveEvent.handle(radioInfo, msg, state.staticAnnos, power_mW, duration);
        } else if (type == SNR
            && prevPacketSnr < radioInfo.getThresholdSnr(state.signalBuffer)) {

          /** both packets collide with each other and lost. */
          if (!isRFChannelDeaf(state) && radioDropEvent.isActive()) {
            radioDropEvent.handle("collision", radioInfo, state.signalBuffer,
                state.staticAnnos, state.signalPower_mW, duration);
            // TODO set anno
            radioDropEvent.handle("collision", radioInfo, msg, null, power_mW, duration);
          }
          unlockSignal(state);
          setMode(state, Constants.RADIO_MODE_SENSING);
        }
        break;
      case Constants.RADIO_MODE_TRANSMITTING:
        break;
      case Constants.RADIO_MODE_SLEEP:
        break;
      default:
        throw new RuntimeException("unknown radio mode");
    }

    // cumulative signal
    state.signals++;
    state.totalPower_mW += power_mW;
    // schedule an endReceive
    JistAPI.sleep(duration);
    self.endReceive(msg, powerObj_mW, state.rfChannel, null);
  } // function: receive

  // RadioInterface interface
  /** {@inheritDoc} */
  public void endReceive(Message msg, Double powerObj_mW, RFChannel rfChannel, Object event) {
    if (rfChannel != null) {
      StateAdditive state = (StateAdditive)states.get(rfChannel);
      endReceive(state, powerObj_mW);
    } else {
      // legacy support; ignore the existence of RF channels
      endReceive((StateAdditive)currentState, powerObj_mW);
    }
  }

  /**
   * Helper method that executes the endReceive() on the given state.
   * @param state use this state for the reception of the given frame.
   * @see #endReceive(jist.swans.radio.RadioNoiseAdditive.StateAdditive, Double)
   */
  protected void endReceive(StateAdditive state, Double powerObj_mW) {
    final double power_mW = powerObj_mW.doubleValue();
    // cumulative signal
    state.signals--;

    if (Main.ASSERT)
      Util.assertion(state.signals >= 0);

    state.totalPower_mW =
        state.signals == 0 ? radioInfo.getBackground_mW() : state.totalPower_mW - power_mW;

    switch (state.mode) {
      case Constants.RADIO_MODE_RECEIVING:
        // this condition is required to handle capture effects */
        if (JistAPI.getTime() == state.signalFinish) {
          boolean dropped = false;
          dropped |= type == BER
              && state.totalPower_mW > 0
              && ber.shouldDrop(state.signalPower_mW / state.totalPower_mW,
                  8 * state.signalBuffer.getSize());
          if (!dropped) {
            // check if the receiving radio is working on the right RF channel before handing the packet to the MAC layer */
            sendToMac(state, powerObj_mW, null);
          } else if (!isRFChannelDeaf(state) && radioDropEvent.isActive()) {
            radioDropEvent.handle("BER drop", radioInfo, state.signalBuffer,
                state.staticAnnos, state.signalPower_mW);
          }
          unlockSignal(state);
          setMode(state, state.totalPower_mW >= radioInfo.getSensitivity_mW() ?
              Constants.RADIO_MODE_SENSING
              : Constants.RADIO_MODE_IDLE);
        }
        break;
      case Constants.RADIO_MODE_SENSING:
        if (state.totalPower_mW < radioInfo.getSensitivity_mW())
          setMode(state, Constants.RADIO_MODE_IDLE);
        break;
      case Constants.RADIO_MODE_TRANSMITTING:
        break;
      case Constants.RADIO_MODE_IDLE:
        break;
      case Constants.RADIO_MODE_SLEEP:
        break;
      default:
        throw new RuntimeException("unknown radio mode");
    }
  } // function: endReceive

  public void dropPacket(State state, String reason) {
    if (null != currentState.signalBuffer) {
      super.dropPacket(state, reason);
      if (currentState.signals > 0
          &&((StateAdditive)currentState).totalPower_mW >= radioInfo.getSensitivity_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.getSensitivity_mW()) {
      setMode(currentState, Constants.RADIO_MODE_SENSING);
    } else {
      setMode(currentState, Constants.RADIO_MODE_IDLE);
    }
  }

//  public double getNoise_mW() {
//    double currTotalPwr = ((StateAdditive)currentState).totalPower_mW;
//    return currTotalPwr > radioInfo.getSensitivity_mW() ? currTotalPwr : 0;
//  }
} // class: RadioNoiseAdditive

