//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <RadioNoiseIndep.java Tue 2004/04/20 09:00:20 barr pompom.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 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;
import org.apache.log4j.Logger;

/**
 * <code>RadioNoiseIndep</code> implements a radio with an independent 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 StateIndep} from {@link #states}.
 * - the successive receive() and endReceive() methods are operating exclusively on this state.
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&rt;
 * @author Zubow
 * @version $Id: RadioNoiseIndep.java,v 1.25 2004/11/05 03:38:34 barr Exp $
 * @since SWANS1.0
 */

public final class RadioNoiseIndep extends RadioNoise {

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

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

    /** current signal power. */
    public double currPower_mW;

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

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

  /**
   * Create new radio with independent noise model.
   *
   * @param radioInfo radio properties
   */
  public RadioNoiseIndep(RadioInfo radioInfo) {
    super(radioInfo);
    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 */
      StateIndep tState = new StateIndep(rfChannel);
      // unlock signal */
      unlockSignal(tState);
      // set the background noise as initial value for the total power */
      tState.currPower_mW = radioInfo.getBackground_mW();
      tState.signals = 0;

      if (tState.currPower_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
  //

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

  /** {@inheritDoc} */
  public void receive(Message msg, Double powerObj_mW, Long durationObj) {
    if (msg instanceof PhyMessage) {
      // retrieve the state which belongs to the RF channel of the receiving message.
      StateIndep state = (StateIndep)states.get(((PhyMessage)msg).getRfChannel());
      receive(state, msg, powerObj_mW, durationObj);
    } else {
      // legacy support
      log.warn("received non-phy message");
      receive( (StateIndep)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)
   */
  private void receive(StateIndep state, Message msg, Double powerObj_mW, Long durationObj) {
    final double power_mW = powerObj_mW.doubleValue();
    final long duration   = durationObj.longValue();

    // ignore if below sensitivity 
    if(power_mW < radioInfo.getSensitivity_mW())
      return;

    // SNR between incoming signal and background noise (here: thermal + ambient) 
    double packetSnr      = Util.toDB(power_mW / radioInfo.getBackground_mW());
    // SNR between incoming signal and previous signal
    double snrToPrevPack  = Util.toDB(power_mW / state.signalPower_mW);

    // discard message if below threshold
    if (packetSnr < radioInfo.getThresholdSnr(msg))
      msg = null;

    switch(state.mode) {
      case Constants.RADIO_MODE_IDLE:
        setMode(state, msg != null ? 
            Constants.RADIO_MODE_RECEIVING : Constants.RADIO_MODE_SENSING);
        // lock radio on this signal 
        lockSignal(state, msg, power_mW, duration);
        if (!isRFChannelDeaf(state) && radioReceiveEvent.isActive())
          radioReceiveEvent.handle(radioInfo, msg, state.staticAnnos, power_mW, duration);
        break;
      // handle state receiving and sensing in the same way 
      case Constants.RADIO_MODE_RECEIVING:
      case Constants.RADIO_MODE_SENSING:
        if(Main.ASSERT)
          Util.assertion(state.signals > 0);
        if (snrToPrevPack >= radioInfo.getThresholdSnr(msg)) {
          // if we are already in the receiving mode --> capture effect
          lockSignal(state, msg, power_mW, duration);
        } else if (state.mode == Constants.RADIO_MODE_RECEIVING
            && -snrToPrevPack < radioInfo.getThresholdSnr(state.signalBuffer)) {
          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("invalid radio mode: " + state.mode);
    }

    // increment number of incoming signals
    state.signals++;
    // schedule end of reception - airtime */
    JistAPI.sleep(duration);
    // schedule the related endXXX function.
    self.endReceive(msg, powerObj_mW, state.rfChannel, null);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioInterface#endReceive(java.lang.Double, jist.swans.radio.RadioInterface.RFChannel)
   */
  public void endReceive(Message msg, final Double powerObj_mW, RFChannel rfChannel, Object event) {
    if (rfChannel != null) {
      StateIndep state = (StateIndep)states.get(rfChannel);
      endReceive(state, powerObj_mW);
    } else {
      // legacy support
      endReceive((StateIndep)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(Message, Double, jist.swans.radio.RadioInterface.RFChannel, Object)
   */
  private void endReceive(StateIndep state, final Double powerObj_mW) {
    if(state.mode==Constants.RADIO_MODE_SLEEP)
      return;

    if(Main.ASSERT)
      Util.assertion(state.signals > 0);
    // decrement number of incoming signals
    state.signals--;

    if(state.mode == Constants.RADIO_MODE_RECEIVING) {
      if(state.signalBuffer != null && JistAPI.getTime() == state.signalFinish) {
        // 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);
        // signalAnno is created during lockSignal
        unlockSignal(state);
      }
    } else if (state.mode == Constants.RADIO_MODE_TRANSMITTING) {
      return;
    }
    if(state.signals == 0)
      setMode(state, Constants.RADIO_MODE_IDLE);
  }

  public void dropPacket(State state, String reason) {
    if (null != currentState.signalBuffer) {

      super.dropPacket(state, reason);
      // set mode */
      setMode(currentState, currentState.signals > 0 ? Constants.RADIO_MODE_RECEIVING
          : Constants.RADIO_MODE_IDLE);
    }
  }

  /**
   * @inheritDoc
   */
  public void endSetChannel(RFChannel channel) {

    super.endSetChannel(channel);

    if (((StateIndep)currentState).currPower_mW > radioInfo.getSensitivity_mW()) {
      // current power is greater than sensitivity */
      setMode(currentState, Constants.RADIO_MODE_SENSING);
    } else {
      setMode(currentState, Constants.RADIO_MODE_IDLE);
    }
  }

//  public double getNoise_mW() {
//    double currSignalPwr = ((StateIndep)currentState).signalPower_mW;
//
//    return currSignalPwr > radioInfo.getSensitivity_mW() ? currSignalPwr : 0;
//  }
} // class: RadioNoiseIndep

