package brn.swans.radio;

import java.util.List;

import org.apache.commons.math.complex.Complex;
import org.apache.commons.math.complex.ComplexUtils;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.phy.PhyMessage;
import brn.swans.radio.RadioNoiseAdditiveBER.RxEvent;

public abstract class DiversityCombiner {

  /**
   * Like {@link DiversityCombiner.SelectionMax} but using the worst instead.
   * @see DiversityCombiner.SelectionMax
   * @author kurth
   */
  public static class SelectionMin extends DiversityCombiner {

    /*
     * (non-Javadoc)
     * @see brn.swans.radio.DiversityCombiner#combine(brn.swans.radio.RadioNoiseAdditiveBER.RxEvent, jist.swans.phy.PhyMessage, double, long)
     */
    public void combine(RxEvent ev, PhyMessage msg, double power_mW,
        long duration) {
      // calculate new power
      ev.setRxPower_mW( Math.min(ev.getRxPower_mW(), power_mW));
    }

    /*
     * (non-Javadoc)
     * @see brn.swans.radio.DiversityCombiner#find(java.util.List, jist.swans.phy.PhyMessage)
     */
    public RxEvent find(List events, PhyMessage msg) {
      RxEvent ev = null;
      
      // TODO consider cyclic prefix event of symbol time
      long latestStart = JistAPI.getTime();
      long earliestStart = latestStart - msg.getSymbolDuration();
      ev = getMessageInInterval(events, msg, latestStart, earliestStart);

      return ev;
    }

  }

  /**
   * Always select the best out of the signals. Do not consider any delay
   * spread since this is only a fake (theoretical) combiner. It has the same
   * effect as knowing the best channel apriori and using it only.
   * 
   * @author kurth
   */
  public static class SelectionMax extends DiversityCombiner {

    /*
     * (non-Javadoc)
     * @see brn.swans.radio.DiversityCombiner#combine(brn.swans.radio.RadioNoiseAdditiveBER.RxEvent, jist.swans.phy.PhyMessage, double, long)
     */
    public void combine(RxEvent ev, PhyMessage msg, double power_mW,
        long duration) {
      // calculate new power
      ev.setRxPower_mW( Math.max(ev.getRxPower_mW(), power_mW));
    }

    /*
     * (non-Javadoc)
     * @see brn.swans.radio.DiversityCombiner#find(java.util.List, jist.swans.phy.PhyMessage)
     */
    public RxEvent find(List events, PhyMessage msg) {
      RxEvent ev = null;
      
      // TODO consider cyclic prefix event of symbol time
      long latestStart = JistAPI.getTime();
      long earliestStart = latestStart - msg.getSymbolDuration();
      ev = getMessageInInterval(events, msg, latestStart, earliestStart);

      return ev;
    }

  }

  /**
   * Nothing.
   * 
   * @author kurth
   */
  public static class None extends DiversityCombiner {

    public void combine(RxEvent ev, PhyMessage msg, double power_mW,
        long duration) {
    }

  }

  /**
   * Assumes incoming packets are orthogonal. A coherent (BF) combiner would 
   * be orthogonal, except that the transmit power does not have to be adopted.
   * 
   * @author kurth
   */
  public static class STBC extends DiversityCombiner {

    /*
     * (non-Javadoc)
     * @see brn.swans.radio.DiversityCombiner#combine(java.util.List, jist.swans.phy.PhyMessage, double, long)
     */
    public void combine(RxEvent ev, PhyMessage msg, 
        double power_mW, long duration) {
      // calculate new power
      ev.setRxPower_mW(ev.getRxPower_mW() + power_mW);
    }

  }

  /**
   * Assumes incoming packets are non-coherently combined.
   * 
   * @author kurth
   */
  public static class NonCoherent extends DiversityCombiner {

    /*
     * (non-Javadoc)
     * @see brn.swans.radio.DiversityCombiner#combine(java.util.List, jist.swans.phy.PhyMessage, double, long)
     */
    public void combine(RxEvent ev, PhyMessage msg, 
        double power_mW, long duration) {
      // addition of two CN distributed variables
      double phaseOffset = 2 * Math.PI * Constants.random.nextDouble();
      Complex s1 = new Complex(Math.sqrt(ev.getRxPower_mW()), 0);
      Complex s2 = ComplexUtils.polar2Complex(Math.sqrt(power_mW), phaseOffset);
      s1 = s1.add(s2);
      // Note: s * conj(s) = |s|^2
      double powerCombined_mW = s1.multiply(s1.conjugate()).getReal();
      ev.setRxPower_mW(powerCombined_mW);
    }

  }

  /**
   * Tries to combine the given message with messages from the list.
   * 
   * @param event the event to combine
   * @param msg the message to be matched.
   * @param power_mW the messages power
   * @param duration the messages duration
   */
  public abstract void combine(RxEvent event, PhyMessage msg, 
      double power_mW, long duration);

//  public abstract RxEvent find(List events, PhyMessage msg);

  /**
   * Searches a message to combine
   * 
   * @param events list of {@link RadioNoiseAdditiveBER.RxEvent}s
   * @param msg the message to be matched.
   * @return the event to be combined, null otherwise
   */
  public RxEvent find(List events, PhyMessage msg) {
    RxEvent ev = null;
    
    // TODO consider cyclic prefix event of symbol time
    long latestStart = JistAPI.getTime();
    long earliestStart = latestStart - msg.getSymbolDuration();
    ev = getMessageInInterval(events, msg, latestStart, earliestStart);

    if (null == ev)
      return null;

    // Delay spread larger than cyclic prefix
    // TODO is this right?? getCyclicPrefix or getSymbolDuration
    if (latestStart - ev.getStartTime() > msg.getSymbolDuration()) {
      // TODO log this event
//      if (radioDropEvent.isActive())
//        radioDropEvent.handle("multipath", radioInfo, msg, anno, power_mW); 
      return null;
    }

    return ev;
  }

  
  /**
   * Find an equal message in the given interval.
   * 
   * @param events list of {@link RadioNoiseAdditiveBER.RxEvent}s
   * @param msg the message to be matched.
   * @param intvStart interval start
   * @param intvEnd interval end
   * @return the RxEvent belonging to the message, or null
   */
  protected RxEvent getMessageInInterval(List events, PhyMessage msg,
      long intvStart, long intvEnd) {
    RxEvent ev = null;
    
    // list is ordered in ascending start order
    for (int idx = events.size()-1; idx >= 0; idx--) {
      ev = (RxEvent)events.get(idx);
      long start = ev.getStartTime();
      
      // this and all elements with smaller index are too early, break...
      if (start < intvEnd)
        break;
      if (Main.ASSERT) Util.assertion (start <= intvStart);
        
      // if true the incoming message is a delayed copy of the message locked on
      if (msg.equals(ev.getMsg()))
        return ev;
    }
    
    return null;
  }


}
