package brn.sim.builder;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.field.Field;
import jist.swans.field.PathLoss;
import jist.swans.field.Placement;
import jist.swans.mac.MacInterface;
import jist.swans.misc.Location;
import jist.swans.radio.RadioFactory;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import jist.swans.radio.RadioNoise;
import jist.swans.radio.RadioNoiseAdditive;
import jist.swans.radio.RadioNoiseIndep;

import org.apache.log4j.Logger;

import brn.sim.builder.PathLossBuilder.PathLossParams;
import brn.swans.radio.Capture;
import brn.swans.radio.ChannelClearAssessment;
import brn.swans.radio.DiversityCombiner;
import brn.swans.radio.RadioDiversityReceiver;
import brn.swans.radio.RadioNoiseAdditiveBER;
import brn.swans.radio.RadioNoiseAdditiveTxDiversity;
import brn.swans.radio.TransmissionMode;
import brn.swans.radio.biterrormodels.Accumulated;
import brn.swans.radio.biterrormodels.BitErrorModel;
import brn.swans.radio.biterrormodels.MaskedPackets;
import brn.swans.radio.biterrormodels.None;
import brn.swans.radio.biterrormodels.UniformDistribution;

public abstract class RadioBuilder extends Builder {

  private static final Logger log = Logger.getLogger(RadioBuilder.class);

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

    /** Whether to use annotations for inter-layer communication */
    public boolean useAnnos                   = false;
    /** type of radio and mac to use */
    public short radioType                    = Constants.MAC_802_11bg;
    /** whether to perform carrier sensing */
    public boolean doCarrierSensing           = true;

    /** Node placement model. */
    public int placement                      = Constants.PLACEMENT_RANDOM;
    /** Node placement options. */
    public String placementOpts               = "";

    /** Field dimensions (in meters). */
    public int fieldX                         = 100;
    public int fieldY                         = 100;

    /** Random seed. 0 leads to different seed each time simulation runs. */
    public int seed                           = 1;

    /** connectivity stuff used by node placement */
    /** a node has at least this number of links */
    public int min_connectivity_betweenNodes  = 0;
    /** whether we want to use the sensing or receiving threshold */
    public boolean connectivity_use_sensing   = false;
    /** how many times we want to repeat our link estimation experiment */
    public int num_repetitions               = 100;
    /**
     * fraction of propagation realizations (num_repetitions) which have to be
     * successful in order to classify the link as existant
     */
    public double threshold                   = 0.8;
    /** bit rate to use for connectivity measurements */
    public int connectivityBitRate            = Constants.BANDWIDTH_INVALID;

    public PathLossBuilder.PathLossParams pathlossPlacement = null;

    /**
     * when to start mobility; noErrors string turns off mobility, a single value
     * starts mobility at the same point in time for all nodes; alternative:
     * [Double](:[Double])* for different start times for each node
     */
    public String startMobility = null;
    /** id of the field within node */
    public int fieldId = 0;

    /** description of the radio as properties */
    public String radioDesc = null;

    public int getFieldX() {
      return fieldX;
    }

    public void setFieldX(int fieldX) {
      this.fieldX = fieldX;
    }
    public int getFieldY() {
      return fieldY;
    }
    public void setFieldY(int fieldY) {
      this.fieldY = fieldY;
    }
    public int getPlacement() {
      return placement;
    }
    public void setPlacement(int placement) {
      this.placement = placement;
    }
    public String getPlacementOpts() {
      return placementOpts;
    }
    public void setPlacementOpts(String placementOpts) {
      this.placementOpts = placementOpts;
    }
    public int getSeed() {
      return seed;
    }
    public void setSeed(int seed) {
      this.seed = seed;
    }
    public boolean isUseAnnos() {
      return useAnnos;
    }
    public void setUseAnnos(boolean useAnnos) {
      this.useAnnos = useAnnos;
    }
    public short getRadioType() {
      return radioType;
    }
    public void setRadioType(short radioType) {
      this.radioType = radioType;
    }
    public int getMin_connectivity_betweenNodes() {
      return min_connectivity_betweenNodes;
    }
    public void setMin_connectivity_betweenNodes(int min_connectivity_betweenNodes) {
      this.min_connectivity_betweenNodes = min_connectivity_betweenNodes;
    }
    public boolean isConnectivity_use_sensing() {
      return connectivity_use_sensing;
    }
    public void setConnectivity_use_sensing(boolean connectivity_use_sensing) {
      this.connectivity_use_sensing = connectivity_use_sensing;
    }
    public double getThreshold() {
      return threshold;
    }
    public void setThreshold(double threshold) {
      this.threshold = threshold;
    }
    public int getNum_repetitions() {
      return num_repetitions;
    }
    public void setNum_repetitions(int num_repetitions) {
      this.num_repetitions = num_repetitions;
    }
    public int getConnectivityBitRate() {
      return connectivityBitRate;
    }
    public void setConnectivityBitRate(int connectivityBitRate) {
      this.connectivityBitRate = connectivityBitRate;
    }
    public PathLossBuilder.PathLossParams getPathlossPlacement() {
      return pathlossPlacement;
    }
    public void setPathlossPlacement(
        PathLossBuilder.PathLossParams pathlossPlacement) {
      this.pathlossPlacement = pathlossPlacement;
    }

    public String getStartMobility() {
      return startMobility;
    }

    public void setStartMobility(String startMobility) {
      this.startMobility = startMobility;
    }

    /* (non-Javadoc)
     * @see brn.sim.builder.Builder.Params#clone()
     */
    public Object clone() throws CloneNotSupportedException {
      NoiseParams ret = (NoiseParams) super.clone();
      if (null != pathlossPlacement)
        ret.pathlossPlacement = (PathLossParams) pathlossPlacement.clone();
      return ret;
    }

    /**
     * @return the fieldId
     */
    public int getFieldId() {
      return fieldId;
    }

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

    public boolean isDoCarrierSensing() {
      return doCarrierSensing;
    }

    public void setDoCarrierSensing(boolean doCarrierSensing) {
      this.doCarrierSensing = doCarrierSensing;
    }

    public String getRadioDesc() {
      return radioDesc;
    }

    public void setRadioDesc(String radioDesc) {
      this.radioDesc = radioDesc;
    }
  }

  public static class NoiseIndepParams extends NoiseParams {
    private static final long serialVersionUID = 1L;
  }

  public static class NoiseAdditiveParams extends NoiseParams {
    private static final long serialVersionUID = 1L;
  }

  public static class NoiseAdditiveTxDivParams extends NoiseAdditiveParams {
    private static final long serialVersionUID = 1L;
  }

  public static class BitErrorParams {}
  public static class MaskedPacketsParams extends BitErrorParams {
    public double backgroundNoise_mW;
    public String[] dumpFiles;
    public String referenceDumpFile;

    public void setBackgroundNoise_mW(double backgroundNoise_mW) {
      this.backgroundNoise_mW = backgroundNoise_mW;
    }
    public double getBackgroundNoise_mW() {
      return backgroundNoise_mW;
    }
    public void setDumpFiles(String[] dumpFiles) {
      this.dumpFiles = dumpFiles;
    }
    public String[] getDumpFiles() {
      return dumpFiles;
    }
    public String getReferenceDumpFile() {
      return referenceDumpFile;
    }
    public void setReferenceDumpFile(String file) {
      referenceDumpFile = file;
    }
  }

  public static class NoiseAdditiveBerParams extends NoiseAdditiveParams {
    private static final long serialVersionUID = 1L;

    /** whether to receive CRC and PHY corrupted packets */
    public boolean recvCorruptPackets = false;

    public int diversityCombiner = brn.swans.Constants.RADIO_DIVERSITY_NONE;

    /** whether to activate the rx chain for reception */
    public boolean doReceive = true;

    /** the bit error model to be used */
    public int bitErrorModel = brn.swans.Constants.BITERRORS_NONE;

    /** parameters for the bitErrorModel */
    public BitErrorParams bitErrorParams = null;

    /** Noise figure: ratio of energy lost by receiver (not dB) */
    public double rxNoiseFactor = jist.swans.misc.Util.fromDB(8);

    /** default capture model: message-in-message */
    public int captureModel = brn.swans.Constants.RADIO_CAPTURE_MIM;

    /** default cca: preamble + energy */
    public int ccaModel = brn.swans.Constants.RADIO_CCA_MODE3;
    /** -77dBm is mentioned in several experimental papers, only in mode 1&3 */
    public double ccaEnergyLevel_dBm = -77;
    /** 10 dB capture threshold */
    public double captureEnergyLevel_dB = jist.swans.misc.Util.fromDB(10);


    public double getCaptureEnergyLevel_dB() {
      return captureEnergyLevel_dB;
    }
    public void setCaptureEnergyLevel_dB(double captureEnergyLevel_dB) {
      this.captureEnergyLevel_dB = captureEnergyLevel_dB;
    }
    public double getCcaEnergyLevel_dBm() {
      return ccaEnergyLevel_dBm;
    }
    public void setCcaEnergyLevel_dBm(double ccaEnergyLevel_dBm) {
      this.ccaEnergyLevel_dBm = ccaEnergyLevel_dBm;
    }
    public boolean isRecvCorruptPackets() {
      return recvCorruptPackets;
    }
    public void setRecvCorruptPackets(boolean recvCorruptPackets) {
      this.recvCorruptPackets = recvCorruptPackets;
    }
    public int getDiversityCombiner() {
      return diversityCombiner;
    }
    public void setDiversityCombiner(int diversityCombiner) {
      this.diversityCombiner = diversityCombiner;
    }
    public boolean isDoReceive() {
      return doReceive;
    }
    public void setDoReceive(boolean doReceive) {
      this.doReceive = doReceive;
    }
    public void setBitErrorModel(int bitErrorModel) {
      this.bitErrorModel = bitErrorModel;
    }
    public int getBitErrorModel() {
      return bitErrorModel;
    }
    public void setBitErrorParams(BitErrorParams bitErrorParams) {
      this.bitErrorParams = bitErrorParams;
    }
    public BitErrorParams getBitErrorParams() {
      return bitErrorParams;
    }
    public int getCaptureModel() {
      return captureModel;
    }
    public void setCaptureModel(int captureModel) {
      this.captureModel = captureModel;
    }
    public int getCcaModel() {
      return ccaModel;
    }
    public void setCcaModel(int ccaModel) {
      this.ccaModel = ccaModel;
    }
    public double getRxNoiseFactor() {
      return rxNoiseFactor;
    }
    public void setRxNoiseFactor(double rxNoiseFactor) {
      this.rxNoiseFactor = rxNoiseFactor;
    }
  }

//  public static class MultipleEqualParams extends Builder.Params {
//    private static final long serialVersionUID = 1L;
//
//    /** number of radios to build */
//    public int no = 0;
//
//    /** type of radios to build */
//    public Builder.Params radio = null;
//
//    public int getNo() {
//      return no;
//    }
//    public void setNo(int no) {
//      this.no = no;
//    }
//    public Builder.Params getRadio() {
//      return radio;
//    }
//    public void setRadio(Builder.Params radio) {
//      this.radio = radio;
//    }
//    public Object clone() throws CloneNotSupportedException {
//      MultipleEqualParams params = (MultipleEqualParams) super.clone();
//      if (null != params.radio)
//        params.radio = (Params) params.radio.clone();
//      return params;
//    }
//  }

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

    /** the radios to combine */
    public Builder.Params[] radios = null;

    /** type of radio and mac to use */
    public short radioType                    = Constants.MAC_802_11bg;

    /** description of the radio as properties */
    public String radioDesc = null;

    public Builder.Params[] getRadios() {
      return radios;
    }
    public void setRadios(Builder.Params[] radios) {
      this.radios = radios;
    }
    public Object clone() throws CloneNotSupportedException {
      DiversityReceiverParams params = (DiversityReceiverParams) super.clone();
      if (null != params.radios)
        params.radios = (Builder.Params[]) params.radios.clone();
      return params;
    }
    public short getRadioType() {
      return radioType;
    }
    public void setRadioType(short radioType) {
      this.radioType = radioType;
    }
    public String getRadioDesc() {
      return radioDesc;
    }
    public void setRadioDesc(String radioDesc) {
      this.radioDesc = radioDesc;
    }
  }


  public static abstract class Noise extends RadioBuilder {

    private Map placementMap = new HashMap();
    private Map /* List<Double> */ mobilityStartsMap = new HashMap();

    public Placement getPlacement(NoiseParams opts, RadioNoise radio) throws BuilderException {
      Placement place = (Placement) placementMap.get(opts);
      if (null == place) {
        Location.Location2D fieldLoc = new Location.Location2D(opts.fieldX,
            opts.fieldY);

        // initialize node placement model
        switch (opts.placement) {
        case Constants.PLACEMENT_RANDOM:
          place = new Placement.Random(fieldLoc);
          break;
        case Constants.PLACEMENT_GRID:
          // opts.placementOpts = ("");
          place = new Placement.Grid(fieldLoc, opts.placementOpts);
          break;
        // case Constants.PLACEMENT_STREET_RANDOM:
        // StreetMobility smr = (StreetMobility) mobility;
        // Location.Location2D bounds[] = (Location.Location2D[]) smr.getBounds();
        // place = new StreetPlacementRandom(bounds[0], bounds[3], smr,
        // opts.driverStdDev);
        // break;
        case Constants.PLACEMENT_MANUAL:
          place = new Placement.Manual(opts.placementOpts);
          break;
        case Constants.PLACEMENT_POISSON:
          place = new Placement.Poisson(fieldLoc, opts.seed, opts.placementOpts);
          break;
        case Constants.PLACEMENT_PYRAMID:
          place = new Placement.Pyramid(fieldLoc, opts.placementOpts);
          break;
        default:
          throw new BuilderException("unknown node placement model");
        }
        placementMap.put(opts, place);
      }

      return place;
    }

    protected RadioInfo getRadioInfo(short radioType, String radioDesc) {
      /* the logic is handled in the RadioFactory */
      if (null != radioDesc) {
        if (log.isDebugEnabled())
          log.debug("radioDesc: '" + radioDesc + "'");
        return RadioFactory.createRadioInfo(radioType,
            new ByteArrayInputStream(radioDesc.getBytes()));
      }
      return RadioFactory.createRadioInfoDefault(radioType);
    }

    public void hookUp(Params params, Node node, Object entity) throws BuilderException {
      RadioNoise radio = (RadioNoise) entity;
      NoiseParams opts = (NoiseParams) params;
      Field field = node.getField(opts.fieldId);

      // placement
      Location location = getPlacement(opts, radio).getNextLocation();
      RadioInfo radioInfo = radio.getRadioInfo();
      field.addRadio(node.getNodeId(), radioInfo, radio.getProxy(), location);

      /** make sure that the placement meets our connectivity constraints */
      if (opts.min_connectivity_betweenNodes > 0) {
        PathLoss plOld = null;
        if (null != opts.pathlossPlacement) {
          Builder pathlossBuilder = getProvider().getBuilder(opts.pathlossPlacement);
          PathLoss pl = (PathLoss) pathlossBuilder.build(opts.pathlossPlacement, node);
          getProvider().addHookUp(pathlossBuilder, opts.pathlossPlacement, node, pl);
          plOld = field.getPathloss();
          field.setPathloss(pl);
        }

        while(true) {
          long minConnectivity = field.checkMinConnectivity(radioInfo.getId(),
              opts.connectivity_use_sensing, opts.num_repetitions, opts.threshold,
              opts.connectivityBitRate);

          if (minConnectivity >= Math.min(field.computeNumberOfPlacedNodes()-1,
              opts.min_connectivity_betweenNodes))
            break;

          /** this placement does not meets our connectivity requirement; search for another...  */
          if (opts.placement == Constants.PLACEMENT_GRID
              || opts.placement == Constants.PLACEMENT_MANUAL)
              throw new BuilderException("Placement does not meet our connectivity requirements.");

          location = getPlacement(opts, radio).getNextLocation();
          field.moveRadio(node.getNodeId(), new Integer(radioInfo.getId()), location);
        }

        if (null != plOld)
          field.setPathloss(plOld);
      }

      radio.setFieldEntity(field.getProxy());
      radio.setUseAnnotations(opts.useAnnos);
      radio.setDoCarrierSensing(opts.doCarrierSensing);

      // start mobility
      if (null != opts.startMobility && opts.startMobility.length() >= 0) {
        List mobilityStarts =  (List) mobilityStartsMap.get(opts);
        // Create the start time queue in time
        if (null == mobilityStarts) {
          String[] starts = opts.startMobility.split(":");
          mobilityStarts = new ArrayList(starts.length);
          for (int i = 0; i < starts.length; i++)
            mobilityStarts.add(Double.valueOf(Double.parseDouble(starts[i])));
          mobilityStartsMap.put(opts, mobilityStarts);
        }
        // Pop the next start time
        Double start = (Double) mobilityStarts.get(0);
        if (mobilityStarts.size() > 1)
          mobilityStarts.remove(0);

        if (start.doubleValue() >= 0) {
          JistAPI.sleep((long)(start.doubleValue()*Constants.SECOND));
          field.startMobility(new Integer(radioInfo.getId()));
          // TODO remove this hack
          JistAPI.sleep(- (long)(start.doubleValue()*Constants.SECOND));
        }
      }

      if (Main.ASSERT) {
        Util.assertion(radioInfo.getSensitivity_mW() > radioInfo.getBackground_mW());
        /*
        for (int i = 0; i < radioInfo.getSupportedBitrates().length; i++) {
          Util.assertion(radioInfo.getThreshold_mW(radioInfo.getSupportedBitrates()[i])
              > radioInfo.getSensitivity_mW());
        }
        */
        // Check if the temperature is ok
        Util.assertion(273 < radioInfo.getTemperature()
            && radioInfo.getTemperature() < 308);
      }
    }
  }

  public static class NoiseIndep extends Noise {
    public Class getParamClass() {
      return NoiseIndepParams.class;
    }

    public Object build(Params params, Node node) throws BuilderException {
      NoiseParams opts = (NoiseParams) params;
      RadioInfo radioInfo = getRadioInfo(opts.radioType, opts.radioDesc);
      // TODO inspect nodeId
      return new RadioNoiseIndep(radioInfo);
    }
  }

  public static class NoiseAdditive extends Noise {
    public Class getParamClass() {
      return NoiseAdditiveParams.class;
    }

    public Object build(Params params, Node node) throws BuilderException {
      NoiseParams opts = (NoiseParams) params;
      RadioInfo radioInfo = getRadioInfo(opts.radioType, opts.radioDesc);
      // TODO inspect nodeId
      return new RadioNoiseAdditive(radioInfo);
    }
  }

  public static class NoiseAdditiveTxDiv extends Noise {
    public Class getParamClass() {
      return NoiseAdditiveTxDivParams.class;
    }

    public Object build(Params params, Node node) throws BuilderException {
      NoiseParams opts = (NoiseParams) params;
      RadioInfo radioInfo = getRadioInfo(opts.radioType, opts.radioDesc);
      // TODO inspect nodeId
      return new RadioNoiseAdditiveTxDiversity(radioInfo);
    }
  }

  public static class NoiseAdditiveBer extends Noise {
    public Class getParamClass() {
      return NoiseAdditiveBerParams.class;
    }

    public Object build(Params opts, Node node) throws BuilderException {
      NoiseAdditiveBerParams params = (NoiseAdditiveBerParams) opts;
      String radioDesc = "sensitivity=-99.5 \n";
      if (null != params.radioDesc)
        radioDesc += params.radioDesc;
      else
        radioDesc += "mac=802.11g \n"
          + "bitrates=6,9,12,18,24,36,48,54 \n"
          + "txpowers=19,19,19,19,19,18,16,15 \n"
          // from http://nova.stanford.edu/~bbaas/ps/isscc2002_fig5.pdf
          + "thresholds=5.4,5.8,7.0,9.5,11.3,14.9,18.6,20.6 \n"
          // + "thresholds=1.,3.5,3.8,6.5,8.8,12.3,16.8,19.0 \n" // theoretical
          + "basicRates=6,12,24 \n"
          + "frequencies=2400 \n"
          + "frequency_spacings=5 \n"
          + "frequency_widths=20 \n"
          + "number_of_channels=1 \n";
      RadioInfo radioInfo = getRadioInfo(params.radioType, radioDesc);

      // Adjust curves to theoretical limit (http://nova.stanford.edu/~bbaas/ps/isscc2002_fig5.pdf)
      TransmissionMode.setReceiverLoss(1000, .1, radioInfo.getBitrateSnrThresholds());

      DiversityCombiner combiner = null;
      switch (params.diversityCombiner) {
      case brn.swans.Constants.RADIO_DIVERSITY_STBC:
        combiner = new DiversityCombiner.STBC();
        break;
      case brn.swans.Constants.RADIO_DIVERSITY_NC:
        combiner = new DiversityCombiner.NonCoherent();
        break;
      case brn.swans.Constants.RADIO_DIVERSITY_SEL_MAX:
        combiner = new DiversityCombiner.SelectionMax();
        break;
      case brn.swans.Constants.RADIO_DIVERSITY_SEL_MIN:
        combiner = new DiversityCombiner.SelectionMin();
        break;
      case brn.swans.Constants.RADIO_DIVERSITY_NONE:
      default:
        combiner = new DiversityCombiner.None();
        break;
      }

      BitErrorModel bitErrors = null;
      switch(params.bitErrorModel) {
      case brn.swans.Constants.BITERRORS_NONE:
        bitErrors = new None();
        break;
      case brn.swans.Constants.BITERRORS_UNIFORM:
        bitErrors = new UniformDistribution();
        break;
      case brn.swans.Constants.BITERRORS_ACCUMULATED:
        bitErrors = new Accumulated();
        break;
      case brn.swans.Constants.BITERRORS_MASKED:
        MaskedPacketsParams errParams = (MaskedPacketsParams)params.getBitErrorParams();
        bitErrors = new MaskedPackets(errParams.getBackgroundNoise_mW(), errParams.getReferenceDumpFile(), errParams.getDumpFiles());
        break;
      }

      Capture capture = null;
      switch (params.captureModel) {
      case brn.swans.Constants.RADIO_CAPTURE_NONE:
        capture = new Capture.None();
        break;
      case brn.swans.Constants.RADIO_CAPTURE_MIM:
        capture = new Capture.Mim(params.captureEnergyLevel_dB);
        break;
      }

      ChannelClearAssessment cca = null;
      switch (params.ccaModel) {
      case brn.swans.Constants.RADIO_CCA_MODE1:
        cca = new ChannelClearAssessment.Mode1(
            jist.swans.misc.Util.fromDB(params.ccaEnergyLevel_dBm));
        break;
      case brn.swans.Constants.RADIO_CCA_MODE2:
        cca = new ChannelClearAssessment.Mode2();
        break;
      case brn.swans.Constants.RADIO_CCA_MODE3:
        cca = new ChannelClearAssessment.Mode3(
            jist.swans.misc.Util.fromDB(params.ccaEnergyLevel_dBm));
        break;
      }

      // TODO inspect nodeId
      RadioNoiseAdditiveBER radio = new RadioNoiseAdditiveBER(radioInfo,
          params.rxNoiseFactor, params.recvCorruptPackets,
          combiner, bitErrors, capture, cca);
      radio.setDoReceive(params.doReceive);

      return radio;
    }
  }

//  /**
//   * Creates multiple, equal radios.
//   *
//   * @author kurth
//   */
//  public static class MultipleEqual extends RadioBuilder {
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#getParamClass()
//     */
//    public Class getParamClass() {
//      return MultipleEqualParams.class;
//    }
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#build(brn.sim.builder.Builder.Params, jist.swans.Node)
//     */
//    public Object build(Params p, Node node) throws BuilderException {
//      MultipleEqualParams params = (MultipleEqualParams) p;
//
//      Builder builder = getProvider().getBuilder(params.radio);
//      for (int i = 0; i < params.no-1; i++) {
//        AbstractRadio radio = (AbstractRadio) builder.build(params.radio, node);
//        getProvider().addHookUp(builder, params.radio, node, radio);
//        node.addRadio(radio);
//      }
//
//      AbstractRadio radio = (AbstractRadio) builder.build(params.radio, node);
//      getProvider().addHookUp(builder, params.radio, node, radio);
//      return radio;
//    }
//
//    /*
//     * (non-Javadoc)
//     * @see brn.sim.builder.Builder#hookUp(brn.sim.builder.Builder.Params, jist.swans.Node, java.lang.Object)
//     */
//    public void hookUp(Params params, Node node, Object entity)
//        throws BuilderException {
//      // pass...
//    }
//  }

  public static class DiversityReceiver extends RadioBuilder {

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

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

    protected RadioInfo getRadioInfo(short radioType, String radioDesc) {
      /* the logic is handled in the RadioFactory */
      if (null != radioDesc) {
        return RadioFactory.createRadioInfo(
            new ByteArrayInputStream(radioDesc.getBytes()));
      }
      return RadioFactory.createRadioInfoDefault(radioType);
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#build(brn.sim.builder.Builder.Params, jist.swans.Node)
     */
    public Object build(Params p, Node node) throws BuilderException {
      DiversityReceiverParams params = (DiversityReceiverParams) p;

      RadioInfo radioInfo = getRadioInfo(params.radioType, params.radioDesc);
      RadioDiversityReceiver radio = new RadioDiversityReceiver(radioInfo);

      return radio;
    }

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

      //radio.setDoCarrierSensing(params.doCarrierSensing);

      RadioNoise[] radios = new RadioNoise[params.radios.length];
//      RadioInterface[] radioEntities = new RadioInterface[params.radios.length];
      List l = new ArrayList();
      for (int i = 0; i < params.radios.length; i++) {
        if (params.radios[i] == null)
          continue;

        NoiseParams np = (NoiseParams)params.radios[i];
        if (np.radioType != params.radioType) {
          np.radioType = params.radioType;
          log.warn("overwriting radioType");
        }
        if (np.radioDesc != params.radioDesc
            && np.radioDesc != null && !np.radioDesc.equals(params.radioDesc)) {
          np.radioDesc = params.radioDesc;
          log.warn("overwriting radioDesc");
        }

        Builder builder = getProvider().getBuilder(params.radios[i]);
        radios[i] = (RadioNoise) builder.build(params.radios[i], node);
        getProvider().addHookUp(builder, params.radios[i], node, radios[i]);

        l.add(radios[i].getProxy());
        node.addRadio(radios[i]);
      }

      radio.setRadios((RadioInterface[]) l.toArray(new RadioInterface[0]));
      for (int i = 0; i < radios.length; i++) {
        MacInterface macProxy = radio.getInternalMacProxy(i);
        radios[i].setMacEntity(macProxy);
      }
    }
  }

}
