package brn.swans.radio;

import java.util.Arrays;

import org.apache.log4j.Logger;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacInterface;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.swans.radio.RadioNoise;


/**
 * Mulitplexer for multiple radios serving one MAC.
 *
 * @author kurth
 */
public class RadioDiversityReceiver extends RadioNoise implements RadioInterface {

  /**
   * TODO embed the radio id for dempultiplexing issues.
   *
   * @author kurth
   */
  public class MacProxy implements MacInterface {

    private int radioId;

    public MacProxy(int radioId) {
      this.radioId = radioId;
    }

    public void peek(Message msg, MessageAnno anno) {
      RadioDiversityReceiver.this.peek(msg, anno, radioId);
    }

    public void receive(Message msg, MessageAnno anno) {
      RadioDiversityReceiver.this.receive(msg, anno, radioId);
    }

    public void send(Message msg, MacAddress nextHop, MessageAnno anno) {
      // pass..
    }

    public void setRadioMode(byte mode) {
      RadioDiversityReceiver.this.setRadioMode(mode, radioId);
    }
  }

  protected class State extends RadioNoise.State {}

  /** logger for mac events. */
  public static final Logger log = Logger.getLogger(RadioDiversityReceiver.class.getName());


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

  /** current received message for duplicate suppression */
  private Message[] currPeekMsg;
  private long lastPeekTime;

  private Message[] currRxMsg;
  private long lastRxTime;

  /** map of all allociated radios */
  private RadioInterface[] radios;

  /** self-referencing radio entity reference. */
  protected RadioInterface self;

  /** mac entity upcall reference. */
  protected MacInterface macEntity;

  /** the mode reported to the mac */
  private byte radioMode;
  private long radioTime;

  /** array with all radio modes */
  private byte[] mode;
  private long[] time;



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


  public RadioDiversityReceiver(RadioInfo radioInfo) {
    super(radioInfo);
    this.self = (RadioInterface)JistAPI.proxy(new RadioInterface.Dlg(this),
        RadioInterface.class);
    this.currentState = new State();
    this.radioMode = Constants.RADIO_MODE_IDLE;
  }

  //////////////////////////////////////////////////
  // entity hookups
  //

  /**
   * Return self-referencing radio entity reference.
   *
   * @return self-referencing radio entity reference
   */
  public RadioInterface getProxy()
  {
    return this.self;
  }

  /**
   * Set downcall mac entity reference.
   *
   * @param macEntity downcall mac entity reference
   */
  public void setMacEntity(MacInterface macEntity)
  {
    if(!JistAPI.isEntity(macEntity)) throw new IllegalArgumentException("entity expected");
    this.macEntity = macEntity;
  }

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

//  public String toString() {
//    return radioInfo.getIdInteger().toString();
//  }

  public void setRadios(RadioInterface[] radios) {
    for (int i = 0; i < radios.length; i++)
      if(!JistAPI.isEntity(radios[i]))
        throw new IllegalArgumentException("entity expected");
    this.radios = radios;
    this.currPeekMsg = new Message[radios.length];
    this.currRxMsg = new Message[radios.length];

    this.mode = new byte[radios.length];
    this.time = new long[radios.length];
    Arrays.fill(this.mode, Constants.RADIO_MODE_SLEEP);
  }

  public RadioInterface[] getRadios() {
    return radios;
  }

  /**
   * @return reference to the fake mac proxy
   */
  public MacInterface getInternalMacProxy(int radioId) {
    if (radioId >= this.radios.length)
      throw new RuntimeException("Unknown radio id");
    MacInterface selfMacProxy = (MacInterface) JistAPI.proxy(
        new MacInterface.Dlg(new MacProxy(radioId)), MacInterface.class);
    return selfMacProxy;
  }

  //////////////////////////////////////////////////
  // overwrites
  //

  protected void transmit(RadioNoise.State state, Message msg, long delay, long duration, MessageAnno anno) {
    for (int i = 0; i < radios.length; i++)
      radios[i].transmit(msg, delay, duration, anno);
  }

  /**
   * Demultiplex messags. Only forward to mac if it is a new message;
   * @param msg the message from radio
   * @param anno the anno from radio
   */
  public void peek(Message msg, MessageAnno anno, int radioId) {
    Util.assertion(0 == radioId); // XXX remove hack

    long now = JistAPI.getTime();
    if (now - lastPeekTime > Constants.SYMBOL_TX_TIME_OFDM)
      Arrays.fill(this.currPeekMsg, null);
    lastPeekTime = now;

    this.currPeekMsg[radioId] = msg;
    for (int i = 0; i < this.currPeekMsg.length; i++) {
      if (i == radioId || null == this.currPeekMsg[i])
        continue;
      if (msg == this.currPeekMsg[i] || msg.equals(this.currPeekMsg[i]))
        return;
    }
    macEntity.peek(msg, anno);
  }

  /**
   * Demultiplex messags. Only forward to mac if it is a new message;
   * @param msg the message from radio
   * @param anno the anno from radio
   */
  public void receive(Message msg, MessageAnno anno, int radioId) {
    Util.assertion(0 == radioId); // XXX remove hack

    long now = JistAPI.getTime();
    if (now - lastRxTime > Constants.SYMBOL_TX_TIME_OFDM)
      Arrays.fill(this.currRxMsg, null);
    lastRxTime = now;

    this.currRxMsg[radioId] = msg;
    for (int i = 0; i < this.currRxMsg.length; i++) {
      if (i == radioId || null == this.currRxMsg[i])
        continue;
      if (msg == this.currRxMsg[i] || msg.equals(this.currRxMsg[i]))
        return;
    }
    if (Main.ASSERT)
      Util.assertion(this.radioMode != Constants.RADIO_MODE_IDLE);
    macEntity.receive(msg, anno);
  }

  public void setRadioMode(byte mode, int radioId) {

    this.mode[radioId] = mode;
    long now = JistAPI.getTime();
    if (now <= this.time[radioId]) {
      // sometimes, we get multiple changes at the same point in time
      // ignore the lower state seems the better choice...
      if (this.mode[radioId] > mode) {
        this.time[radioId] = now;
        return;
      }
      JistAPI.sleep(this.time[radioId] - now + Constants.EPSILON_DELAY);
      now = JistAPI.getTime();
    }
    this.time[radioId] = now;

    // always report the greatest mode to the mac
    byte max = mode;
    for (int i = 0; i < this.mode.length; i++)
      max = (max > this.mode[i] ? max : this.mode[i]);

    if (this.radioMode != max) {
      this.radioMode = max;
      if (now < this.radioTime) {
        // Make the events arrive in right ordering
        JistAPI.sleep(this.radioTime - now);
        now = JistAPI.getTime();
      }
      this.radioTime = now;

      if (log.isDebugEnabled())
        log.debug(this + "(" + JistAPI.getTime() + "): setting radio mode " + this.radioMode);

      if (doCarrierSensing)
        macEntity.setRadioMode(this.radioMode);
    }
  }

  protected void endTransmit(RadioNoise.State state) {
    // pass..
  }

  public void receive(Message msg, Double power, Long duration) {
    // pass..
  }

  public void endReceive(Message msg, Double power, RFChannel rfChannel,
      Object event) {
    // pass..
  }


//  public RFChannel getChannel() throws Continuation {
//    // TODO Auto-generated method stub
//    return null;
//  }
//  public void setChannel(RFChannel channel, long delay) {
//    // TODO Auto-generated method stub
//  }
//  public void setSleepMode(boolean sleep) {
//    // TODO Auto-generated method stub
//  }
//  public void endSetChannel(RFChannel channel) {
//    // TODO Auto-generated method stub
//  }

}
