/**
 *
 */
package brn.sim.builder;

import java.util.List;

import brn.sim.builder.PathLossBuilder.PathLossParams;
import brn.swans.field.ChannelPattern;
import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.field.Fading;
import jist.swans.field.Field;
import jist.swans.field.Mobility;
import jist.swans.field.PathLoss;
import jist.swans.field.PropagationDelay;
import jist.swans.field.Spatial;
import jist.swans.misc.Location;
import jist.swans.misc.Util;
import org.apache.log4j.Logger;

/**
 * @author kurth
 *
 */
public class FieldBuilder extends Builder {

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

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

    // ////////////////////////////////////////////////
    // field settings
    //

    /** Field wrap-around. */
    public boolean wrapField = false;
    /** Field dimensions (in meters). */
    public int fieldX =100;
    public int fieldY =100;

    // ////////////////////////////////////////////////
    // placement settings
    //

    /** binning mode. */
    public int spatial_mode = Constants.SPATIAL_HIER;
    /** binning degree. */
    public int spatial_div = 5;

    // ////////////////////////////////////////////////
    // mobility settings
    //

    /** Node mobility model. */
    public int mobility = Constants.MOBILITY_STATIC;
    /** Node mobility options. */
    public String mobilityOpts = "";

    // RWP mobility settings
    /** random waypoint pause time. */
    public int pause_time = 1;
    /** random waypoint granularity. */
    public int granularity = 2;
    /** random waypoint minimum speed. */
    public int min_speed = 1;
    /** random waypoint maximum speed. */
    public int max_speed = 3;

    // ////////////////////////////////////////////////
    // pathloss settings
    //

    /** the pathloss model to use */
    public PathLossBuilder.PathLossParams pathloss = new PathLossBuilder.FreeSpaceParams();

    /** the fading model */
    public FadingBuilder.FadingParams fading = new FadingBuilder.NoneParams();

    /** default propagation delay model */
    public int propDelay = Constants.PROPAGATION_DELAY_NONE;
    /** propagation delay for type fixed */
    public long propDelayFixed = Constants.PROPAGATION;
    /** std dev for dist-normal propagation delay in ns,
     * two standard deviations from the mean account for 95.45%
     */
    public double propDelayStdDev = 100.;

    // ////////////////////////////////////////////////
    // RF channel settings
    //

    /** Number of different RF channels. */
    public int channelNumber = Constants.CHANNEL_NUMBER_DEFAULT;

    /** Node RF channel pattern. */
    public int channelPattern = Constants.CHANNEL_PATTERN_HOMOGENEOUS;


    public FadingBuilder.FadingParams getFading() {
      return fading;
    }
    public void setFading(FadingBuilder.FadingParams fading) {
      this.fading = fading;
    }
    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 getGranularity() {
      return granularity;
    }
    public void setGranularity(int granularity) {
      this.granularity = granularity;
    }
    public int getMax_speed() {
      return max_speed;
    }
    public void setMax_speed(int max_speed) {
      this.max_speed = max_speed;
    }
    public int getMin_speed() {
      return min_speed;
    }
    public void setMin_speed(int min_speed) {
      this.min_speed = min_speed;
    }
    public int getMobility() {
      return mobility;
    }
    public void setMobility(int mobility) {
      this.mobility = mobility;
    }
    public String getMobilityOpts() {
      return mobilityOpts;
    }
    public void setMobilityOpts(String mobilityOpts) {
      this.mobilityOpts = mobilityOpts;
    }
    public int getPause_time() {
      return pause_time;
    }
    public void setPause_time(int pause_time) {
      this.pause_time = pause_time;
    }
    public int getSpatial_div() {
      return spatial_div;
    }
    public void setSpatial_div(int spatial_div) {
      this.spatial_div = spatial_div;
    }
    public int getSpatial_mode() {
      return spatial_mode;
    }
    public void setSpatial_mode(int spatial_mode) {
      this.spatial_mode = spatial_mode;
    }
    public boolean isWrapField() {
      return wrapField;
    }
    public void setWrapField(boolean wrapField) {
      this.wrapField = wrapField;
    }
    public int getChannelNumber() {
      return channelNumber;
    }
    public void setChannelNumber(int channelNumber) {
      this.channelNumber = channelNumber;
    }
    public int getChannelPattern() {
      return channelPattern;
    }
    public void setChannelPattern(int channelPattern) {
      this.channelPattern = channelPattern;
    }
    public int getPropDelay() {
      return propDelay;
    }
    public void setPropDelay(int propagationDelay) {
      this.propDelay = propagationDelay;
    }
    public long getPropDelayFixed() {
      return propDelayFixed;
    }
    public void setPropDelayFixed(long propDelayFixed) {
      this.propDelayFixed = propDelayFixed;
    }
    public double getPropDelayStdDev() {
      return propDelayStdDev;
    }
    public void setPropDelayStdDev(double propDelayStdDev) {
      this.propDelayStdDev = propDelayStdDev;
    }
    public PathLossBuilder.PathLossParams getPathloss() {
      return pathloss;
    }
    public void setPathloss(PathLossBuilder.PathLossParams pathloss) {
      this.pathloss = pathloss;
    }
    /* (non-Javadoc)
     * @see brn.sim.builder.Builder.Params#clone()
     */
    public Object clone() throws CloneNotSupportedException {
      FieldParams ret = (FieldParams) super.clone();
      if (null != pathloss) ret.pathloss = (PathLossParams) pathloss.clone();
      return ret;
    }
  }

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

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

    // initialize node mobility model
    Mobility mobility = null;
    Location.Location2D fieldLoc = new Location.Location2D(opts.fieldX, opts.fieldY);
    switch (opts.mobility) {
    case Constants.MOBILITY_STATIC:
      mobility = new Mobility.Static();
      break;
    case Constants.MOBILITY_WAYPOINT:
      mobility = new Mobility.RandomWaypoint(fieldLoc, opts.pause_time,
          opts.granularity, opts.min_speed, opts.max_speed);
      // mobility = new Mobility.RandomWaypoint(opts.field, opts.mobilityOpts);
      break;
    case Constants.MOBILITY_TELEPORT:
      mobility = new Mobility.Teleport(fieldLoc, Long.parseLong(opts.mobilityOpts));
      break;
    case Constants.MOBILITY_RANDOMWALK:
      mobility = new Mobility.RandomWalk(fieldLoc, opts.mobilityOpts);
      break;
    case Constants.MOBILITY_BOUNDLESS_SIM_AREA:
      mobility = new Mobility.BoundlessSimulationArea(fieldLoc, opts.mobilityOpts);
      break;
    // case Constants.MOBILITY_STRAW_SIMPLE:
    // tr = new Location.Location2D(opts.maxLong, opts.maxLat);
    // bl = new Location.Location2D(opts.minLong, opts.minLat);
    // mobility = new StreetMobilityRandom(opts.segmentFile, opts.streetFile,
    // opts.shapeFile, opts.degree, opts.probability, opts.granularity, bl, tr,
    // r);
    // break;
    // case Constants.MOBILITY_STRAW_OD:
    // tr = new Location.Location2D(opts.maxLong, opts.maxLat);
    // bl = new Location.Location2D(opts.minLong, opts.minLat);
    // mobility = new StreetMobilityOD(opts.segmentFile, opts.streetFile,
    // opts.shapeFile, opts.degree, bl, tr, r);
    // break;
    default:
      throw new BuilderException("unknown node mobility model " + opts.mobility);
    }

    // make all four points
    Location.Location2D corners[] = new Location.Location2D[4];
    // if (opts.mobility != Constants.MOBILITY_STRAW_SIMPLE
    // && opts.mobility != Constants.MOBILITY_STRAW_OD) {
    corners[0] = new Location.Location2D(0, 0);
    corners[1] = new Location.Location2D(opts.fieldX, 0);
    corners[2] = new Location.Location2D(0, opts.fieldY);
    corners[3] = new Location.Location2D(opts.fieldX, opts.fieldY);

    // } else {
    // opts.sm = (StreetMobility) mobility;
    // StreetMobility smr = (StreetMobility) mobility;
    // Location.Location2D cornersTemp[] = new Location.Location2D[4];
    // cornersTemp = (Location.Location2D[]) smr.getBounds();
    // corners[0] = cornersTemp[2];
    // corners[1] = cornersTemp[3];
    // corners[2] = cornersTemp[0];
    // corners[3] = cornersTemp[1];
    // }

    // initialize spatial binning
    Spatial spatial = null;
    switch (opts.spatial_mode) {
    case Constants.SPATIAL_LINEAR:
      spatial = new Spatial.LinearList(corners[0], corners[1], corners[2],
          corners[3]);
      break;
    case Constants.SPATIAL_GRID:
      spatial = new Spatial.Grid(corners[0], corners[1], corners[2],
          corners[3], opts.spatial_div);
      break;
    case Constants.SPATIAL_HIER:
      spatial = new Spatial.HierGrid(corners[0], corners[1], corners[2],
          corners[3], opts.spatial_div);
      break;
    default:
      throw new BuilderException("unknown spatial binning model");
    }
    if (opts.wrapField)
      spatial = new Spatial.TiledWraparound(spatial);

    // pathloss model
    Builder pathlossBuilder = getProvider().getBuilder(opts.pathloss);
    PathLoss pl = (PathLoss) pathlossBuilder.build(opts.pathloss, node);
    getProvider().addHookUp(pathlossBuilder, opts.pathloss, node, pl);

    Builder fadingBuilder = getProvider().getBuilder(opts.fading);
    Fading fading = (Fading) fadingBuilder.build(opts.fading, node);
    getProvider().addHookUp(fadingBuilder, opts.fading, node, fading);

    PropagationDelay propagationDelay = null;
    switch (opts.propDelay) {
    case Constants.PROPAGATION_DELAY_NONE:
      propagationDelay = new PropagationDelay.None();
      break;
    case Constants.PROPAGATION_DELAY_FIXED:
      propagationDelay = new PropagationDelay.Fixed(opts.propDelayFixed);
      break;
    case Constants.PROPAGATION_DELAY_DIST:
      propagationDelay = new PropagationDelay.Dist();
      break;
    case Constants.PROPAGATION_DELAY_DIST_NORMAL:
      propagationDelay = new PropagationDelay.DistNormal(opts.propDelayStdDev);
      break;
    default:
      throw new BuilderException("Unsupported propagation delay model!");
    }

    /* initialize field
     * NOTE: PROPAGATION_LIMIT_DEFAULT is the sensing threashold and we compute 
     * a the 95th quantile from our propagation distribution!
     */
    Field field = new Field(spatial, fading, pl, propagationDelay, mobility,
        Constants.PROPAGATION_LIMIT_DEFAULT);

    return field;
  }

  /* (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 {
  }

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

    Field field = (Field) entity;

    // initialize the initial RF channel
    assignRFChannel((FieldParams)params, field.getNodes());

    // print out some nice field statistics

    if (log.isInfoEnabled()) {
      log.info("Average field density  = " + field.computeDensity()*1000*1000+"/km^2");
      log.info("Average field sensing  = " + field.computeAvgConnectivity(true));
      log.info("Average field receive  = " + field.computeAvgConnectivity(false));
    }
  }

  protected void assignRFChannel(FieldParams opts, List nodes) {
    if (Main.ASSERT)
      Util.assertion(JistAPI.getTime() <= 100);

    switch (opts.channelPattern) {
      case Constants.CHANNEL_PATTERN_ROUNDROBIN:
        ChannelPattern.RoundRobin.assign(nodes, opts.channelNumber);
        break;
//      case Constants.CHANNEL_PATTERN_P2:
//        throw new IllegalArgumentException("Channel pattern CHANNEL_PATTERN_P2 Not yet implemented.");
        //ChannelPattern.Diag.assign(nodes, opts.channelNumber, opts.field, options.density);
        //break;
      case Constants.CHANNEL_PATTERN_HOMOGENEOUS:
        ChannelPattern.Single.assign(nodes, Constants.CHANNEL_DEFAULT);
        break;
      case Constants.CHANNEL_PATTERN_RANDOM:
        ChannelPattern.Random.assign(nodes, opts.channelNumber);
        break;
//      case Constants.CHANNEL_PATTERN_STRING:
//        throw new IllegalArgumentException("Channel pattern CHANNEL_PATTERN_STRING Not yet implemented.");
        /*
        if (LOCATION_STRING != options.location)
          options.bobbles = 1;
        ChannelPattern.String.assign(listNodes, options.channels,
                Szenario.getXNodes(options.field, options.density),
                Szenario.getYNodes(options.field, options.density),
                options.bobbles);
        */
        //break;
//      case Constants.CHANNEL_PATTERN_OFFSET:
//        throw new IllegalArgumentException("Channel pattern CHANNEL_PATTERN_OFFSET Not yet implemented.");
        //ChannelPattern.Offset.assign(listNodes, options.channels, options.field, options.density);
        //break;
//      case Constants.CHANNEL_PATTERN_VERTICAL:
//        throw new IllegalArgumentException("Channel pattern CHANNEL_PATTERN_VERTICAL Not yet implemented.");
        /*
        if (LOCATION_STRING != options.location)
          options.bobbles = 1;
        ChannelPattern.Vertical.assign(listNodes, options.channels,
                Szenario.getXNodes(options.field, options.density),
                Szenario.getYNodes(options.field, options.density),
                options.bobbles);
        */
        //break;
//      case Constants.CHANNEL_PATTERN_POISSON:
//        throw new IllegalArgumentException("Channel pattern CHANNEL_PATTERN_POISSON Not yet implemented.");
        //break;

//      default:
//        throw new RuntimeException("unknown channel assignment pattern");
    }
  }

}
