package brn.sim.builder;

import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.mac.*;
import jist.swans.net.AbstractNet;
import jist.swans.net.MessageQueue;
import jist.swans.radio.AbstractRadio;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.swans.rate.AnnoRate;
import jist.swans.rate.ConstantRate;
import jist.swans.rate.RateControlAlgorithmIF;
import brn.swans.mac.MacMcExOR;
import brn.swans.mac.MacMcExORwRCts;
import brn.swans.mac.MacTxDOR;
import brn.swans.mac.MacTxDORInterface;
import brn.swans.mac.MacTxDORMessageFactory;
import brn.swans.net.NetTxDOR;
import brn.swans.rate.OrRBAR;
import brn.swans.route.NGRouteMcExOR;

public abstract class MacBuilder extends Builder {

  public static class Params extends Builder.Params {
    private static final long serialVersionUID = 1L;

    /** @deprecated Using {@link AbstractRadio#getRadioInfo()} to get it. */
//    public short macType = Constants.MAC_802_11bg;

    /** Whether to be in promisc mode */
    public boolean macPromisc = false;

    /** message queue type */
    public int messageQueue = Constants.NET_QUEUE_DROPTAIL;
    /** message queue capacity */
    public int messageQueueCapacity = 100;
    
    /** id of the radio to use within the nodes radios */
    public int radioId = 0;

//    public short getMacType() {
//      return macType;
//    }
//
//    public void setMacType(short macType) {
//      this.macType = macType;
//    }

    public boolean isMacPromisc() {
      return macPromisc;
    }
    public void setMacPromisc(boolean macPromisc) {
      this.macPromisc = macPromisc;
    }
    public int getMessageQueue() {
      return messageQueue;
    }
    public void setMessageQueue(int messageQueue) {
      this.messageQueue = messageQueue;
    }
    public int getMessageQueueCapacity() {
      return messageQueueCapacity;
    }
    public void setMessageQueueCapacity(int messageQueueCapacity) {
      this.messageQueueCapacity = messageQueueCapacity;
    }

    /**
     * @return the radioId
     */
    public int getRadioId() {
      return radioId;
    }

    /**
     * @param radioId the radioId to set
     */
    public void setRadioId(int radioId) {
      this.radioId = radioId;
    }
  }

  public static class DumbParams extends Params {
    private static final long serialVersionUID = 1L;
  }

  public static class MDcfParams extends Params {
    private static final long serialVersionUID = 1L;

    /** Whether to use annotations for inter-layer communication */
    public boolean useAnnos = false;
    /** whether to read the bit rate from annos or use the static rate in mac */
    public boolean useBitRateAnnos = true;
    /** Retransmissions attempted for short packets (those without RTS). */
    public byte retryLimitShort = Constants.MAC_RETRY_LIMIT_SHORT;
    /** Maximum collision window (for backoff). */
    public short cwMax = Constants.CW_MAX;
    /** rate selection algorithm */
    public RateBuilder.Params rateSelection = new RateBuilder.ConstantParams();
    /** network allocation vector implementation */
    public int nav = brn.swans.Constants.MAC_NAV_SIMPLE;
    /** whether to use EIFS */
    public boolean useEifs = true; 

    public boolean isUseEifs() {
      return useEifs;
    }
    public void setUseEifs(boolean useEifs) {
      this.useEifs = useEifs;
    }
    public boolean isUseAnnos() {
      return useAnnos;
    }
    public void setUseAnnos(boolean useAnnos) {
      this.useAnnos = useAnnos;
    }
    public byte getRetryLimitShort() {
      return retryLimitShort;
    }
    public void setRetryLimitShort(byte retryLimitShort) {
      this.retryLimitShort = retryLimitShort;
    }
    public boolean isUseBitRateAnnos() {
      return useBitRateAnnos;
    }
    public void setUseBitRateAnnos(boolean useBitRateAnnos) {
      this.useBitRateAnnos = useBitRateAnnos;
    }
    public RateBuilder.Params getRateSelection() {
      return rateSelection;
    }
    public void setRateSelection(RateBuilder.Params rateSelection) {
      this.rateSelection = rateSelection;
    }
    public int getNav() {
      return nav;
    }
    public void setNav(int nav) {
      this.nav = nav;
    }
    public short getCwMax() {
      return cwMax;
    }
    public void setCwMax(short cwMax) {
      this.cwMax = cwMax;
    }
    public Object clone() throws CloneNotSupportedException {
      MDcfParams ret = (MDcfParams) super.clone();
      if (null != rateSelection) ret.rateSelection = (RateBuilder.Params) rateSelection.clone();
      return ret;
    }
  }

  public static class M802_11Params extends MDcfParams {
    private static final long serialVersionUID = 1L;

    /** threashold for using rts/cts for channel aquisitation */
    public int thresholdRts = 2346;
    /** long retry limit, for rts/cts packets */
    public byte retryLimitLong = Constants.MAC_RETRY_LIMIT_LONG;
    /** Threshold packet size for fragmentation */
    public int thresholdFragment = Constants.MAC_THRESHOLD_RTS;

    public byte getRetryLimitLong() {
      return retryLimitLong;
    }
    public void setRetryLimitLong(byte retryLimitLong) {
      this.retryLimitLong = retryLimitLong;
    }
    public int getThresholdRts() {
      return thresholdRts;
    }
    public void setThresholdRts(int threasholdRts) {
      this.thresholdRts = threasholdRts;
    }
    public int getThresholdFragment() {
      return thresholdFragment;
    }
    public void setThresholdFragment(int threasholdFragment) {
      this.thresholdFragment = threasholdFragment;
    }

    // TODO
//    /** rate to use with data packets */
//    public int dataRate = Constants.BANDWIDTH_54Mbps;
//    /** rate to use with control packets (rts/cts/ack) */
//    public int basicRate = Constants.BANDWIDTH_12Mbps;
//    /** rate of the plcp header  */
//    public int plcpRate = Constants.BANDWIDTH_12Mbps;
//    /** Default transmission strength (units: dBm). */
//    public double transmit = Constants.TRANSMIT_DEFAULT;
//    /** Default antenna gain (units: dB). */
//    public double gain = Constants.GAIN_DEFAULT;
//    /** Default radio reception sensitivity (units: dBm) originally -91. */
//    public double sensitivity = Constants.SENSITIVITY_DEFAULT;
//    /** Default radio reception threshold (units: dBm). Originally -81 */
//    public double threshold = Constants.THRESHOLD_DEFAULT;
//    /** Default temperature (units: degrees Kelvin). */
//    public double temperature = Constants.TEMPERATURE_DEFAULT;
//    /** Default temperature noise factor. */
//    public double temperature_factor = Constants.TEMPERATURE_FACTOR_DEFAULT;
//    /** Default ambient noise (units: mW). */
//    public double ambiant_noise = Constants.AMBIENT_NOISE_DEFAULT;
  }

  public static class M802_11eParams extends M802_11Params {
    private static final long serialVersionUID = 1L;
    /** @deprecated Use {@link RateBuilder} instead.<pre>
     * In your params declaration, add:<code>
     *   RateBuilder.ConstantParams rate = new RateBuilder.ConstantParams();
     *   rate.controlBitrate = Constants.BANDWIDTH_1Mbps;
     *   rate.dataBitrate = Constants.BANDWIDTH_1Mbps;
     *   mac.rateSelection = rate;
     * </code></pre>
     *  */
    public int constBitRate = Constants.BANDWIDTH_1Mbps;
    /**
     * @deprecated
     * @return the constBitRate
     */
    public int getConstBitRate() {
      return constBitRate;
    }
    /**
     * @deprecated
     * @param constBitRate the constBitRate to set
     */
    public void setConstBitRate(int constBitRate) {
      this.constBitRate = constBitRate;
    }
  }

  public static class M802_11TxDivParams extends M802_11Params {
    private static final long serialVersionUID = 1L;

    /** whether to use txdiv for data packets */
    public boolean useDataTxDiv = true;
    /** whether to ignore nav in rts and cts packets */
    public boolean ignoreRtsCtsNav = false;
    /** whether to use the ack cancelation schema */
    public boolean useAckCancelation = true;
    /** whether to remember all promisc received packets */
    public boolean promiscStorePackets = true;

    public boolean isUseDataTxDiv() {
      return useDataTxDiv;
    }
    public void setUseDataTxDiv(boolean useDataTxDiv) {
      this.useDataTxDiv = useDataTxDiv;
    }
    public boolean isIgnoreRtsCtsNav() {
      return ignoreRtsCtsNav;
    }
    public void setIgnoreRtsCtsNav(boolean ignoreRtsCtsNav) {
      this.ignoreRtsCtsNav = ignoreRtsCtsNav;
    }
    public boolean isUseAckCancelation() {
      return useAckCancelation;
    }
    public void setUseAckCancelation(boolean useAckCancelation) {
      this.useAckCancelation = useAckCancelation;
    }
    public boolean isPromiscStorePackets() {
      return promiscStorePackets;
    }
    public void setPromiscStorePackets(boolean promiscStorePackets) {
      this.promiscStorePackets = promiscStorePackets;
    }
  }

  public static class MCExORParams extends M802_11Params {
    private static final long serialVersionUID = 1L;

    public boolean useCompression = false;
    public boolean usePreferencedAck = false;
    public boolean useAppSeqNumbers = false;
    public long delayChannelSwitch  = brn.swans.Constants.DEFAULT_DELAY_CHANNEL_SWITCH;
    public double refNoiseFactor =  brn.swans.Constants.DEFAULT_REF_NOISE_FACTOR;
    public boolean updatePrioOnNoiseDetection = true;
    public boolean useVariableAckSize         = true;
    public boolean useNAVinAcks               = true;
    public boolean useNAVinData               = true;
    public boolean useRtsCts                  = false;
    public boolean useNAVinRtsCts             = true;
    public boolean useVariableCtsSize         = false;
    public boolean useCtsCompression          = false;

    /** Number of different RF channels. */
    public int channelNumber = Constants.CHANNEL_NUMBER_DEFAULT;
    public int candidateSetSize;
    public boolean useRtsCtsFeedback          = false;

    public int constantDataBitRate      = -1;
    public int constantCtrlBitRate      = -1;

    public int rca                      = Constants.RCA_CONSTANT_RATE;

    public boolean testRun              = false;

    public boolean isUseAppSeqNumbers() {
      return useAppSeqNumbers;
    }
    public void setUseAppSeqNumbers(boolean useAppSeqNumbers) {
      this.useAppSeqNumbers = useAppSeqNumbers;
    }
    public boolean isUseCompression() {
      return useCompression;
    }
    public void setUseCompression(boolean useCompression) {
      this.useCompression = useCompression;
    }
    public boolean isUsePreferencedAck() {
      return usePreferencedAck;
    }
    public void setUsePreferencedAck(boolean usePreferencedAck) {
      this.usePreferencedAck = usePreferencedAck;
    }
    public int getChannelNumber() {
      return channelNumber;
    }
    public void setChannelNumber(int channelNumber) {
      this.channelNumber = channelNumber;
    }
    public long getDelayChannelSwitch() {
      return delayChannelSwitch;
    }
    public void setDelayChannelSwitch(long delayChannelSwitch) {
      this.delayChannelSwitch = delayChannelSwitch;
    }
    public int getConstantDataBitRate() {
      return constantDataBitRate;
    }
    public void setConstantDataBitRate(int constantDataBitRate) {
      this.constantDataBitRate = constantDataBitRate;
    }
    public int getConstantCtrlBitRate() {
      return constantCtrlBitRate;
    }
    public void setConstantCtrlBitRate(int constantCtrlBitRate) {
      this.constantCtrlBitRate = constantCtrlBitRate;
    }
    public double getRefNoiseFactor() {
      return refNoiseFactor;
    }
    public void setRefNoiseFactor(double refNoiseFactor) {
      this.refNoiseFactor = refNoiseFactor;
    }
    public boolean isUpdatePrioOnNoiseDetection() {
      return updatePrioOnNoiseDetection;
    }
    public void setUpdatePrioOnNoiseDetection(boolean updatePrioOnNoiseDetection) {
      this.updatePrioOnNoiseDetection = updatePrioOnNoiseDetection;
    }
    public boolean isUseVariableAckSize() {
      return useVariableAckSize;
    }
    public void setUseVariableAckSize(boolean useVariableAckSize) {
      this.useVariableAckSize = useVariableAckSize;
    }
    public boolean isUseNAVinAcks() {
      return useNAVinAcks;
    }
    public void setUseNAVinAcks(boolean useNAVinAcks) {
      this.useNAVinAcks = useNAVinAcks;
    }
    public boolean isUseNAVinData() {
      return useNAVinData;
    }
    public void setUseNAVinData(boolean useNAVinData) {
      this.useNAVinData = useNAVinData;
    }
    public boolean isUseRtsCts() {
      return useRtsCts;
    }
    public void setUseRtsCts(boolean useRtsCts) {
      this.useRtsCts = useRtsCts;
    }
    public boolean isUseNAVinRtsCts() {
      return useNAVinRtsCts;
    }
    public void setUseNAVinRtsCts(boolean useNAVinRtsCts) {
      this.useNAVinRtsCts = useNAVinRtsCts;
    }
    public boolean isUseVariableCtsSize() {
      return useVariableCtsSize;
    }
    public void setUseVariableCtsSize(boolean useVariableCtsSize) {
      this.useVariableCtsSize = useVariableCtsSize;
    }
    public boolean isUseCtsCompression() {
      return useCtsCompression;
    }
    public void setUseCtsCompression(boolean useCtsCompression) {
      this.useCtsCompression = useCtsCompression;
    }
    public int getCandidateSetSize() {
      return candidateSetSize;
    }
    public void setCandidateSetSize(int candidateSetSize) {
      this.candidateSetSize = candidateSetSize;
    }
    public boolean isUseRtsCtsFeedback() {
      return useRtsCtsFeedback;
    }
    public void setUseRtsCtsFeedback(boolean useRtsCtsFeedback) {
      this.useRtsCtsFeedback = useRtsCtsFeedback;
    }
    public int getRca() {
      return rca;
    }
    public void setRca(int rca) {
      this.rca = rca;
    }
    public boolean isTestRun() {
      return testRun;
    }
    public void setTestRun(boolean testRun) {
      this.testRun = testRun;
    }
  }

  public static class MultipleParams extends Builder.Params {
    private static final long serialVersionUID = 1L;

    /** type of radios to build */
    public Builder.Params[] macs = null;

    public Builder.Params[] getMacs() {
      return macs;
    }
    public void setMacs(Builder.Params[] macs) {
      this.macs = macs;
    }
    public Object clone() throws CloneNotSupportedException {
      MultipleParams params = (MultipleParams) super.clone();
      if (null != params.macs) 
        params.macs = (Builder.Params[]) params.macs.clone();
      return params;
    }
  }

  protected MessageQueue createMessageQueue(Params opts) {
    MessageQueue messageQueue;
    switch (opts.messageQueue) {
    case Constants.NET_QUEUE_NODROP:
      messageQueue = new MessageQueue.NoDropMessageQueue(
          Constants.NET_PRIORITY_NUM, (byte) opts.messageQueueCapacity);
      break;
    case Constants.NET_QUEUE_DROPTAIL:
      messageQueue = new MessageQueue.DropTailMessageQueue(
          Constants.NET_PRIORITY_NUM, opts.messageQueueCapacity);
      break;
    case brn.swans.Constants.NET_QUEUE_ITERABLE:
      messageQueue = new NetTxDOR.IterableMessageQueue(
          Constants.NET_PRIORITY_NUM, opts.messageQueueCapacity);
      break;
    default:
      throw new RuntimeException("Invalid message queue!");
    }
    return messageQueue;
  }

  public static class Dumb extends MacBuilder {
    public Class getParamClass() {
      return DumbParams.class;
    }

    public Object build(Builder.Params params, Node node) throws BuilderException {
      DumbParams opts = (DumbParams) params;
      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);

      MacDumb mac = new MacDumb(new MacAddress(node.getNodeId()), radio.getRadioInfo());
      mac.setMsgFactory(new MacDumbMessageFactory());
      mac.setPromiscuous(opts.macPromisc);
      return mac;
    }

    public void hookUp(Builder.Params params, Node node, Object entity) throws BuilderException {
      MacDumb mac = (MacDumb) entity;
      DumbParams opts = (DumbParams) params;

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      radio.setMacEntity(mac.getProxy());
      mac.setRadioEntity(radio.getProxy());

      AbstractNet net = node.getNet();
      byte netid = net.addInterface(mac.getProxy(), createMessageQueue(opts));
      mac.setNetEntity(net.getProxy(), netid);
    }
  }

  public static class MDcf extends MacBuilder {

    public Class getParamClass() {
      return MDcfParams.class;
    }

    public Object build(Builder.Params params, Node node) throws BuilderException {
      MDcfParams opts = (MDcfParams) params;

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      RadioInfo ri = radio.getRadioInfo();

//      Builder builder = getProvider().getBuilder(opts.rateSelection);
//      Object rateSelection = builder.build(opts.rateSelection, node);
//      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);
      
      // validate combination of macType and radioType
//      if (Main.ASSERT)
//        Util.assertion(ri.getMacType() == opts.macType);

      MacInfo macInfo = MacInfo.create(ri.getMacType(), ri
          .getBitratesSupported(), ri.getBasicRateSet());
      macInfo.setRetryLimitShort(opts.retryLimitShort);
      macInfo.setCwMax(opts.cwMax);

      MacDcf mac = new MacDcf(new MacAddress(node.getNodeId()), ri,
          macInfo);
      mac.setUseAnnotations(opts.useAnnos);
      mac.setPromiscuous(opts.macPromisc);
      mac.setUseEifs(opts.useEifs);
//      if (opts.useBitRateAnnos)
//        mac.setRateControlAlgorithm(new AnnoRate(mac.getRateControlAlgorithm()));
      return mac;
    }

    public void hookUp(Builder.Params params, Node node, Object entity) throws BuilderException {
      MDcfParams opts = (MDcfParams) params;
      MacDcf mac = (MacDcf) entity;
      mac.setMsgFactory(new MacMessageFactory.Dcf());

      if (opts.nav == brn.swans.Constants.MAC_NAV_SIMPLE)
        mac.setNav(mac.new SimpleNav());
      else if (opts.nav == brn.swans.Constants.MAC_NAV_SHORTENING)
        mac.setNav(mac.new ShorteningNav());
      else
        throw new BuilderException("unknown nav found");

      // Create RCA only here
      Builder builder = getProvider().getBuilder(opts.rateSelection);
      RateControlAlgorithmIF rateSelection = (RateControlAlgorithmIF) builder
          .build(opts.rateSelection, node);
      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);
      if (opts.useBitRateAnnos)
        mac.setRateControlAlgorithm(new AnnoRate(rateSelection));
      else
        mac.setRateControlAlgorithm(rateSelection);

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      AbstractNet net = node.getNet();

      byte intId = net.addInterface(mac.getProxy(), createMessageQueue(opts));
      mac.setNetEntity(net.getProxy(), intId);

      mac.setRadioEntity(radio.getProxy());
      radio.setMacEntity(mac.getProxy());
    }
  }

  public static class M802_11 extends MacBuilder {

    public Class getParamClass() {
      return M802_11Params.class;
    }

    public Object build(Builder.Params params, Node node) throws BuilderException {
      M802_11Params opts = (M802_11Params) params;

//      Builder builder = getProvider().getBuilder(opts.rateSelection);
//      Object rateSelection = builder.build(opts.rateSelection, node);
//      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      RadioInfo ri = radio.getRadioInfo();
      
      // validate combination of macType and radioType
//      if (Main.ASSERT)
//        Util.assertion(ri.getMacType() == opts.macType);

      MacInfo macInfo = MacInfo.create(ri.getMacType(), ri
          .getBitratesSupported(), ri.getBasicRateSet());
      macInfo.setRetryLimitShort(opts.retryLimitShort);
      macInfo.setRetryLimitLong(opts.retryLimitLong);
      macInfo.setThresholdRts(opts.thresholdRts);
      macInfo.setCwMax(opts.cwMax);

      Mac802_11 mac = new Mac802_11(new MacAddress(node.getNodeId()), ri,
          macInfo);
      mac.setUseAnnotations(opts.useAnnos);
      mac.setPromiscuous(opts.macPromisc);
//      if (opts.useBitRateAnnos)
//        mac.setRateControlAlgorithm(new AnnoRate(mac.getRateControlAlgorithm()));
      return mac;
    }

    public void hookUp(Builder.Params params, Node node, Object entity) throws BuilderException {
      M802_11Params opts = (M802_11Params) params;
      Mac802_11 mac = (Mac802_11) entity;
      mac.setMsgFactory(new MacMessageFactory.M802_11());

      mac.THRESHOLD_FRAGMENT = opts.thresholdFragment;

      if (opts.nav == brn.swans.Constants.MAC_NAV_SIMPLE)
        mac.setNav(mac.new SimpleNav());
      else if (opts.nav == brn.swans.Constants.MAC_NAV_SHORTENING)
        mac.setNav(mac.new ShorteningNav());
      else
        throw new BuilderException("unknown nav found");

      // Create RCA only here
      Builder builder = getProvider().getBuilder(opts.rateSelection);
      RateControlAlgorithmIF rateSelection = (RateControlAlgorithmIF) builder
          .build(opts.rateSelection, node);
      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);
      if (opts.useBitRateAnnos)
        mac.setRateControlAlgorithm(new AnnoRate(rateSelection));
      else
        mac.setRateControlAlgorithm(rateSelection);

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      AbstractNet net = node.getNet();

      if (net != null) {
        byte intId = net.addInterface(mac.getProxy(), createMessageQueue(opts));
        mac.setNetEntity(net.getProxy(), intId);
      }

      mac.setRadioEntity(radio.getProxy());
      radio.setMacEntity(mac.getProxy());
    }
  }

  public static class M802_11e extends MacBuilder {

    public Class getParamClass() {
      return M802_11eParams.class;
    }

    public Object build(Builder.Params params, Node node) throws BuilderException {
      M802_11eParams opts = (M802_11eParams) params;

//      Builder builder = getProvider().getBuilder(opts.rateSelection);
//      Object rateSelection = builder.build(opts.rateSelection, node);
//      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      RadioInfo ri = radio.getRadioInfo();
      
      // validate combination of macType and radioType
//      if (Main.ASSERT)
//        Util.assertion(ri.getMacType() == opts.macType);

      MacInfo macInfo = MacInfo.create(ri.getMacType(), ri
          .getBitratesSupported(), ri.getBasicRateSet());
      
      return new Mac802_11e(new MacAddress(node.getNodeId()), ri,
          macInfo);
    }

    public void hookUp(Builder.Params params, Node node, Object entity) throws BuilderException {
      M802_11eParams opts = (M802_11eParams) params;
      Mac802_11e mac = (Mac802_11e) entity;
      mac.setMsgFactory(new MacMessageFactory.M802_11e());

      mac.THRESHOLD_FRAGMENT = opts.thresholdFragment;
      mac.RETRY_LIMIT_SHORT = opts.retryLimitShort;
      mac.RETRY_LIMIT_LONG = opts.retryLimitLong;
      mac.THRESHOLD_RTS = opts.thresholdRts;

      if (opts.nav == brn.swans.Constants.MAC_NAV_SIMPLE)
        mac.setNav(mac.getMac().new SimpleNav());
      else if (opts.nav == brn.swans.Constants.MAC_NAV_SHORTENING)
        mac.setNav(mac.getMac().new ShorteningNav());
      else
        throw new BuilderException("unknown nav found");

      // Create RCA only here
      // TODO: Use this code instead of the one below for arbitrary RCA support
//      Builder builder = getProvider().getBuilder(opts.rateSelection);
//      RateControlAlgorithmIF rateSelection = (RateControlAlgorithmIF) builder
//          .build(opts.rateSelection, node);
//      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);
//      mac.setRateControlAlgorithm(rateSelection);
      mac.setRateControlAlgorithm(new ConstantRate(opts.constBitRate));
      
      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      AbstractNet net = node.getNet();

      byte intId = net.addInterface(mac.getProxy(), createMessageQueue(opts));
      mac.setNetEntity(net.getProxy(), intId);

      mac.setRadioEntity(radio.getProxy());
      mac.setUseAnnotations(opts.useAnnos);
      mac.setPromiscuous(opts.macPromisc);
      radio.setMacEntity(mac.getProxy());
    }
  }

  public static class M802_11TxDiv extends MacBuilder {

    public Class getParamClass() {
      return M802_11TxDivParams.class;
    }

    public Object build(Builder.Params params, Node node) throws BuilderException {
      M802_11TxDivParams opts = (M802_11TxDivParams) params;

//      Builder builder = getProvider().getBuilder(opts.rateSelection);
//      Object rateSelection = builder.build(opts.rateSelection, node);
//      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);
      RadioInfo ri = radio.getRadioInfo();
      
      // validate combination of macType and radioType
//      if (Main.ASSERT)
//        Util.assertion(ri.getMacType() == opts.macType);

      MacInfo macInfo = MacInfo.create(ri.getMacType(), ri
          .getBitratesSupported(), ri.getBasicRateSet());
      macInfo.setRetryLimitShort(opts.retryLimitShort);
      macInfo.setRetryLimitLong(opts.retryLimitLong);
      macInfo.setThresholdRts(opts.thresholdRts);
      macInfo.setCwMax(opts.cwMax);

      return new MacTxDOR(new MacAddress(node.getNodeId()), ri,
          macInfo, null);
    }

    public void hookUp(Builder.Params params, Node node, Object entity) throws BuilderException {
      M802_11TxDivParams opts = (M802_11TxDivParams) params;
      MacTxDOR mac = (MacTxDOR) entity;
      mac.setMsgFactory(new MacTxDORMessageFactory());
      mac.setUseDataTxDiv(opts.useDataTxDiv);
      mac.setUseAckCancelation(opts.useAckCancelation);
      mac.setIgnoreRtsCtsNav(opts.ignoreRtsCtsNav);
      mac.setPromiscStorePackets(opts.promiscStorePackets);
      if (opts.thresholdRts > 2400 && opts.ignoreRtsCtsNav)
        throw new BuilderException("ignoreRtsCtsNav is useless");

      if (opts.nav == brn.swans.Constants.MAC_NAV_SIMPLE)
        mac.setNav(mac.new SimpleNav());
      else if (opts.nav == brn.swans.Constants.MAC_NAV_SHORTENING)
        mac.setNav(mac.new ShorteningNav());
      else
        throw new BuilderException("unknown nav found");

      mac.THRESHOLD_FRAGMENT = opts.thresholdFragment;

      // TODO get radio id
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);

      AbstractNet net = node.getNet();
      byte intId = net.addInterface(mac.getProxy(), createMessageQueue(opts));
      mac.setNetEntity(net.getProxy(), intId);
      
      ((MacTxDORInterface)mac.getProxy()).cleanUpFwdBuffer();

      mac.setRadioEntity(radio.getProxy());
      mac.setUseAnnotations(opts.useAnnos);
      mac.setPromiscuous(opts.macPromisc);

      // Create RCA only here
      Builder builder = getProvider().getBuilder(opts.rateSelection);
      RateControlAlgorithmIF rateSelection = (RateControlAlgorithmIF) builder
          .build(opts.rateSelection, node);
      getProvider().addHookUp(builder, opts.rateSelection, node, rateSelection);
      if (opts.useBitRateAnnos)
        mac.setRateControlAlgorithm(new AnnoRate(rateSelection));
      else
        mac.setRateControlAlgorithm(rateSelection);
      
      radio.setMacEntity(mac.getProxy());
    }
  }

  public static class MCExOR extends MacBuilder {

    public Class getParamClass() {
      return MCExORParams.class;
    }

    public Object build(Builder.Params params, Node node) throws BuilderException {
      MCExORParams opts = (MCExORParams) params;

      // currently we are supporting only this radio model
      // TODO: make it indep compliant
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);

      MacMcExOR mac = null;

      if (opts.useRtsCts) {
        mac = new MacMcExORwRCts(new MacAddress(node.getNodeId()),
          radio.getRadioInfo(), RadioInterface.RFChannel.DEFAULT_RF_CHANNEL, opts.channelNumber,
          opts.useCompression, opts.usePreferencedAck, opts.useAppSeqNumbers, opts.delayChannelSwitch,
            opts.refNoiseFactor, opts.updatePrioOnNoiseDetection, opts.useVariableAckSize,
            opts.useNAVinAcks, opts.useNAVinData, opts.candidateSetSize, opts.useNAVinRtsCts, opts.useVariableCtsSize,
            opts.useCtsCompression, opts.useRtsCtsFeedback, opts.testRun);
      } else {
        mac = new MacMcExOR(new MacAddress(node.getNodeId()),
            radio.getRadioInfo(), RadioInterface.RFChannel.DEFAULT_RF_CHANNEL, opts.channelNumber,
            opts.useCompression, opts.usePreferencedAck, opts.useAppSeqNumbers, opts.delayChannelSwitch,
            opts.refNoiseFactor, opts.updatePrioOnNoiseDetection, opts.useVariableAckSize,
            opts.useNAVinAcks, opts.useNAVinData, opts.candidateSetSize, opts.testRun);
      }
      return mac;
    }


    public void hookUp(Builder.Params params, Node node, Object entity) throws BuilderException {
      MCExORParams opts = (MCExORParams) params;
      MacMcExOR mac = (MacMcExOR) entity;
      AbstractRadio radio = (AbstractRadio) node.getRadio(opts.radioId);

//      mac.THRESHOLD_FRAGMENT = opts.thresholdFragment;
//      mac.RETRY_LIMIT_SHORT = opts.retryLimitShort;
//      mac.RETRY_LIMIT_LONG = opts.retryLimitLong;
//      mac.THRESHOLD_RTS = opts.thresholdRts;

      AbstractNet net = node.getNet();
      byte intId = net.addInterface(mac.getProxy(), createMessageQueue(opts));
      mac.setNetEntity(net.getProxy(), intId);

      radio.setMacEntity(mac.getProxy());
      mac.setRadioEntity(radio.getProxy());

      RateControlAlgorithmIF dataRateControl, ctrlRateControl = null;
      switch (opts.rca) {
        case Constants.RCA_OR_RBAR_MIN_RATE:
          // use fixed control rate ...
          ctrlRateControl = new ConstantRate(opts.constantCtrlBitRate);
          mac.setCtrlRateControlAlgorithm(ctrlRateControl);
          // and dynamic data rate ...
          dataRateControl = new OrRBAR.MinRate(radio.getRadioInfo(),
              new MacAddress(node.getNodeId()), opts.constantDataBitRate);
          mac.setDataRateControlAlgorithm(dataRateControl);
          break;
        case Constants.RCA_OR_RBAR_MIN_SAFE_NB:
          // use fixed control rate ...
          ctrlRateControl = new ConstantRate(opts.constantCtrlBitRate);
          mac.setCtrlRateControlAlgorithm(ctrlRateControl);
          // and dynamic data rate ...
          dataRateControl = new OrRBAR.MinSafeNb(radio.getRadioInfo(),
              new MacAddress(node.getNodeId()), opts.constantDataBitRate);
          mac.setDataRateControlAlgorithm(dataRateControl);
          break;
        case Constants.RCA_CONSTANT_RATE:
        default:
          // fixed rate for data and control messages
          dataRateControl = new ConstantRate(opts.constantDataBitRate);
          ctrlRateControl = new ConstantRate(opts.constantCtrlBitRate);
          mac.setDataRateControlAlgorithm(dataRateControl);
          mac.setCtrlRateControlAlgorithm(ctrlRateControl);
          break;
      }

      //mac.setUseAnnotations(opts.useAnnos);
      //mac.setUseBitRateAnnotations(opts.useBitRateAnnos);
      //mac.setPromiscuous(opts.macPromisc);
    }

    public void postHookUp(Builder.Params params, Node node, Object entity)
        throws BuilderException {

      MacMcExOR mcexormac = (MacMcExOR)node.getMac(0);
      NGRouteMcExOR routing = (NGRouteMcExOR)node.getRoute();
      // inform me about rx/tx feedbacks
      mcexormac.setRXTXFeedback(routing.getFeedbackProxy());
  }
  }

  /**
   * Creates multiple macs.
   * 
   * @author kurth
   */
  public static class Multiple extends MacBuilder {

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#getParamClass()
     */
    public Class getParamClass() {
      return MultipleParams.class;
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#build(brn.sim.builder.Builder.Params, jist.swans.Node)
     */
    public Object build(Builder.Params p, Node node) throws BuilderException {
      MultipleParams params = (MultipleParams) p;
      
      for (int i = 0; i < params.macs.length-1; i++) {
        Builder builder = getProvider().getBuilder(params.macs[i]);
        AbstractMac mac = (AbstractMac) builder.build(params.macs[i], node);
        getProvider().addHookUp(builder, params.macs[i], node, mac);
        node.addMac(mac); 
      }

      int i = params.macs.length-1;
      Builder builder = getProvider().getBuilder(params.macs[i]);
      AbstractMac mac = (AbstractMac) builder.build(params.macs[i], node);
      getProvider().addHookUp(builder, params.macs[i], node, mac);
      return mac; 
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#hookUp(brn.sim.builder.Builder.Params, jist.swans.Node, java.lang.Object)
     */
    public void hookUp(Builder.Params params, Node node, Object entity)
        throws BuilderException {
      // pass...
    }
  }

}
