//////////////////////////////////////////////////
// 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 brn.swans.radio;

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.PhyMessage;
import jist.swans.radio.BERTable;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioNoiseAdditive;

import org.apache.log4j.Logger;

/**
 *
 * @author kurth
 *
 * TODO how to measure the benefit of txdiv (higher signal strength) in the
 * case of prevented collisions (i.e. txdiv increased ss in a way that a former
 * collision could be tolerated now).
 */
public class RadioNoiseAdditiveTxDiversity extends RadioNoiseAdditive
{
  /**
   * IP logger.
   */
  public static final Logger log = Logger.getLogger(RadioNoiseAdditiveTxDiversity.class.getName());

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

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

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

  /**
   * Create new radio with additive noise model.
   *
   * @param radioInfo radioInfo radio properties
   */
  public RadioNoiseAdditiveTxDiversity(RadioInfo radioInfo)
  {
    super(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 RadioNoiseAdditiveTxDiversity(int id, RadioInfo radioInfo, BERTable ber)
  {
    super(id, radioInfo, ber);
  }

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

  /**
   * @param state
   * @param power_mW
   * @return whether the message could be sensed at the current point of time
   * in the given state.
   */
  private boolean canSense(StateAdditive state, double power_mW, boolean txDiversityRecv) {
    // for txdiv recv, the stored signal power does not matter since we receive
    // the same signal
    return (txDiversityRecv ? true : power_mW >= state.signalPower_mW)
      && state.totalPower_mW+power_mW > radioInfo.getSensitivity_mW();
  }

  /**
   * @param state
   * @param msg
   * @param power_mW
   * @return whether the message could be received at the current point of time
   * in the given state.
   */
  private boolean canReceive(StateAdditive state, final Message msg, double power_mW, boolean txDiversityRecv) {
    // for txdiv recv, the stored signal power does not matter since we receive
    // the same signal
    return (txDiversityRecv ? true : power_mW >= state.signalPower_mW)
        && (Util.toDB(power_mW / state.totalPower_mW) >= radioInfo.getThresholdSnr(msg));
  }

  /**
   * @param state
   * @param msg
   * @param power_mW
   * @param duration
   */
  private void setSensing(StateAdditive state, Message msg, double power_mW,
      long duration, boolean txDiversityRecv, boolean lockSignal) {
    if (state.mode == Constants.RADIO_MODE_RECEIVING) {
      if (txDiversityRecv) {
        // for tx diversity, a large delay spread may lower the snr
        if (radioDropEvent.isActive())
          radioDropEvent.handle("multipath", radioInfo, state.signalBuffer, state.staticAnnos,
              state.signalPower_mW, duration);
        if (radioDropEvent.isActive())
          radioDropEvent.handle("multipath", radioInfo, msg, state.staticAnnos,
              power_mW, duration);
      } else {
        if (radioDropEvent.isActive())
          radioDropEvent.handle("collision", radioInfo, state.signalBuffer, state.staticAnnos,
              state.signalPower_mW, duration);
        if (radioDropEvent.isActive())
          radioDropEvent.handle("collision", radioInfo, msg, null, power_mW, duration);
      }
    }

    setMode(state, Constants.RADIO_MODE_SENSING);
    if (lockSignal)
      lockSignal(state, msg, power_mW, duration);
    else
      unlockSignal(state);
  }

  /**
   * Switch state to receiving
   *
   * @param state
   * @param msg
   * @param power_mW signal power in mW (combined in the case of txdiv)
   * @param duration
   * @param txDiversityRecv whether we are using txdiv
   */
  private void setReceiving(StateAdditive state, Message msg, double power_mW,
      long duration, boolean txDiversityRecv) {
    // in the case of tx diversity, lock on the stronger signal
    if (state.mode == Constants.RADIO_MODE_RECEIVING
        && !txDiversityRecv && 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 (radioReceiveEvent.isActive())
      radioReceiveEvent.handle(radioInfo, msg, state.staticAnnos, power_mW, duration);
  }

  /**
   * Calculate the resulting signal power for two overlapping copies of the
   * same packet.
   *
   * @param msg
   * @param delaySpread
   * @param power_mW
   * @param signalPower_mW
   * @return tx power
   */
  private double txDiversitySignalPower(PhyMessage msg,
      long delaySpread, double power_mW, double signalPower_mW) {

    // if one signal is much stronger, ignore the other
    double P_a = Math.max(power_mW, signalPower_mW);
    double P_b = Math.min(power_mW, signalPower_mW);

    /*
    // TODO: think about this !!!!!!
    double powerRatio = Util.toDB(P_a / P_b);
    if (powerRatio >= radioInfo.getThresholdSnr(msg))
      return P_a;
    */
    // TODO calculate lookup-tables x: Pw^2 x rate x delay x symbol time -> Pw in matlab

    long T = msg.getSymbolDuration();

    /*
     * Heuristics:
     * for delays between 0 and symbol time T, use a linear function with points
     * (0,P_a+P_b) and (T,abs(P_a-P_b)), for delays greater T use value of T.
     *
     *  P_ab = - 2 * P_b / T * t + P_a + P_b
     */
    double t = Math.abs(delaySpread);
    if (t > T)
      t = T;

    return ( P_a + P_b - 2 * P_b * t / T);
  }

  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioNoiseAdditive#receive(jist.swans.radio.RadioNoiseAdditive.StateAdditive, jist.swans.misc.Message, java.lang.Double, java.lang.Long)
   */
  protected void receive(StateAdditive state, final Message msg, final Double powerObj_mW,
      final Long durationObj)
  {
    double power_mW = powerObj_mW.doubleValue();
    double powerDelta_mW = power_mW;
    final long duration = durationObj.longValue();

    // TODO use length-dependent calculation of receive probabilities (aka BER)

    // if true the incoming message is a delayed copy of the message locked on
    boolean txDiversityRecv = msg.equals(state.signalBuffer);

    // if so, calculate the delay spread and signal power with diversity gain
    if (txDiversityRecv) {
      // TODO how would the equation look like for more than 2 signals?
      long delaySpread = JistAPI.getTime() + duration - state.signalFinish;

      // assume 10 symbol times is the upper limit for txdiv
//      if (delaySpread < 10 * ((PhyMessage)msg).getSymbolDuration()) {
        power_mW = txDiversitySignalPower((PhyMessage)msg, delaySpread, power_mW, state.signalPower_mW);
        // do not account for the signal power of the copy which is currently received
        state.totalPower_mW -= state.signalPower_mW;
        powerDelta_mW = power_mW - state.signalPower_mW;

        if (radioRxDiversityEvent.isActive()) {
          boolean causedRecv = (state.mode != Constants.RADIO_MODE_RECEIVING)
            && !canReceive(state, msg, powerObj_mW.doubleValue(), true)
            &&  canReceive(state, msg,    power_mW, true);
          boolean causedDrop = (state.mode == Constants.RADIO_MODE_RECEIVING)
            && !canReceive(state, msg,    power_mW, txDiversityRecv);

          radioRxDiversityEvent.handle(msg, delaySpread, state.signalPower_mW,
              powerObj_mW.doubleValue(), power_mW, causedRecv, causedDrop);
        }
//      } else {
//        txDiversityRecv = false;
//      }
    }

    switch(state.mode)
    {
      case Constants.RADIO_MODE_IDLE:
        if(canReceive(state, msg, power_mW, txDiversityRecv)) {
          setReceiving(state, msg, power_mW, duration, txDiversityRecv);
        }
        else if(canSense(state, power_mW, txDiversityRecv)) {
          // lock on signal because there could be anohter version of it with a delay
          setSensing(state, msg, power_mW, duration, txDiversityRecv, true);
        }
        break;

      case Constants.RADIO_MODE_SENSING:
        if(canReceive(state, msg, power_mW, txDiversityRecv)) {
          setReceiving(state, msg, power_mW, duration, txDiversityRecv);
        }
        else if(canSense(state, power_mW, txDiversityRecv)) {
          // lock on signal because there could be anohter version of it with a delay
          setSensing(state, msg, power_mW, duration, txDiversityRecv, true);
        }
        break;

      case Constants.RADIO_MODE_RECEIVING:
        if(canReceive(state, msg, power_mW, txDiversityRecv)) {
          setReceiving(state, msg, power_mW, duration, txDiversityRecv);
        }
        else if (txDiversityRecv) {
          // for tx diversity, a large delay spread may lower the snr
          // TODO lock on stronger signal ??
          setSensing(state, msg, power_mW, duration, txDiversityRecv, false);
        }
        // check if we could still receive the previous signal...
        else if (Util.toDB(state.signalPower_mW / (state.totalPower_mW - state.signalPower_mW + power_mW))
            < radioInfo.getThresholdSnr(state.signalBuffer)) {
          // do not lock on signal, this is a collision
          // TODO lock on stronger signal ??
          setSensing(state, msg, power_mW, duration, txDiversityRecv, false);
        }
        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, new Double(powerDelta_mW), state.rfChannel, null);
  } // function: receive

  /*
   * (non-Javadoc)
   * @see jist.swans.radio.RadioNoiseAdditive#endReceive(jist.swans.radio.RadioNoiseAdditive.StateAdditive, java.lang.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:
        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)
          {
            MessageAnno signalAnno = null;
            if (this.useAnnotations) {
              signalAnno = (MessageAnno) state.staticAnnos.clone();
            }

            // check if the receiving radio is working on the right RF 
            // channel before handing the packet to the MAC layer
            sendToMac(state, powerObj_mW, signalAnno);
          }
          else if (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);
        unlockSignal(state);
        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

}

