//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <Field.java Tue 2004/04/06 11:30:54 barr pompom.cs.cornell.edu>
//

// Copyright (C) 2004 by Cornell University
// All rights reserved.
// Refer to LICENSE for terms and conditions of use.

package jist.swans.field;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.field.PathLoss.Shadowing;
import jist.swans.misc.Location;
import jist.swans.misc.Message;
import jist.swans.misc.Util;
import jist.swans.phy.Phy802_11;
import jist.swans.phy.PhyMessage;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;

import org.apache.log4j.Logger;

/**
 * An abstract parent of Field implementations, which contains
 * the common code.
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: Field.java,v 1.57 2004/04/06 16:07:47 barr Exp $
 * @since SWANS1.0
 */

public class Field extends AbstractField implements FieldInterface
{

  /** logger for field events. */
  public static final Logger logField = Logger.getLogger(Field.class.getName());

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

  /**
   * Propagation limit (in dBm). This is the signal strength threshold.
   */
  protected double limit;

  /**
   * Pathloss model.
   */
  protected PathLoss pathloss;

  /**
   * Fading model.
   */
  protected Fading fading;

  /**
   * Propagation delay model.
   */
  protected PropagationDelay propagationDelay;

  /**
   * Mobility model.
   */
  protected Mobility mobility;

  /**
   * Spatial data structure.
   */
  protected Spatial spatial;

  /**
   * Self-referencing field entity.
   */
  protected FieldInterface self;

  /**
   * Array of radios on this field.
   */
  protected RadioData[] radios;


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

  /**
   * Initialize new, empty field with default fading (FadingNone) and pathloss
   * (PathLossFreeSpace) models, and default propagation limits.
   *
   * @param bounds spatial limits of field
   */
  public Field(Location bounds)
  {
    this(bounds, false);
  }

  /**
   * Initialize new, empty field with default fading (FadingNone) and pathloss
   * (PathLossFreeSpace) models, and default propagation limits, possibly
   * with wrapped edges.
   *
   * @param bounds spatial limits of field
   * @param wrap whether to wrap field edges
   */
  public Field(Location bounds, boolean wrap)
  {
    this(wrap
          ? (Spatial)new Spatial.TiledWraparound(new Spatial.HierGrid(bounds, 5))
          : (Spatial)new Spatial.HierGrid(bounds, 5),
        new Fading.None(),
        new PathLoss.FreeSpace(),
        new PropagationDelay.None(),
        new Mobility.Static(),
        Constants.PROPAGATION_LIMIT_DEFAULT);
  }

  /**
   * Initialize new, empty field with given fading and pathloss models, using default
   * propagation limits.
   *
   * @param spatial binning model
   * @param fading fading model
   * @param pathloss pathloss model
   * @param mobility mobility model
   * @param propagationLimit minimum signal threshold (in dBm)
   */
  public Field(Spatial spatial, Fading fading, PathLoss pathloss,
      Mobility mobility, double propagationLimit)
  {
    this(spatial, fading, pathloss, new PropagationDelay.None(), mobility,
        propagationLimit);
  }

  /**
   * Initialize new, empty field with given fading and pathloss models, using default
   * propagation limits.
   *
   * @param spatial binning model
   * @param fading fading model
   * @param pathloss pathloss model
   * @param propagationDelay propagation delay model
   * @param mobility mobility model
   * @param propagationLimit minimum signal threshold (in dBm)
   */
  public Field(Spatial spatial, Fading fading, PathLoss pathloss,
      PropagationDelay propagationDelay, Mobility mobility, double propagationLimit)
  {
    radios = new RadioData[10];
    this.spatial = spatial;
    setFading(fading);
    setPathloss(pathloss);
    setMobility(mobility);
    setPropagationDelay(propagationDelay);
    setPropagationLimit(propagationLimit);
    this.self = (FieldInterface)JistAPI.proxy(this, FieldInterface.class);
  }


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

  /**
   * Set field fading model.
   *
   * @param fading fading model
   */
  public void setFading(Fading fading)
  {
    this.fading = fading;
  }

  /**
   * Set mobility model.
   *
   * @param mobility mobility model
   */
  public void setMobility(Mobility mobility)
  {
    this.mobility = mobility;
  }

  /**
   * Set signal propagation threshold.
   *
   * @param limit minimum signal threshold (in dBm)
   */
  public void setPropagationLimit(double limit)
  {
    this.limit = limit;
  }

  /**
   * @param propagationDelay the propagationDelay to set
   */
  public void setPropagationDelay(PropagationDelay propagationDelay) {
    this.propagationDelay = propagationDelay;
  }

  /**
   * @return the pathloss
   */
  public PathLoss getPathloss() {
    return pathloss;
  }

  /**
   * @param pathloss the pathloss to set
   */
  public void setPathloss(PathLoss pathloss) {
    this.pathloss = pathloss;
  }

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

  /**
   * Return the proxy entity of this field.
   *
   * @return proxy entity of this field
   */
  public FieldInterface getProxy()
  {
    return this.self;
  }


  //////////////////////////////////////////////////
  // radio management
  //

  /**
   * Radio information stored by the Field entity. Includes
   * identifier, location, and a radio entity reference for
   * upcalls in doubly linked list.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */
  public static class RadioData
  {
    /**
     * radio entity reference for upcalls.
     */
    protected RadioInterface entity;

    /**
     * timeless radio properties.
     */
    protected RadioInfo info;

    /**
     * radio location.
     */
    protected Location loc;

    /**
     * mobility information.
     */
    protected Mobility.MobilityInfo mobilityInfo;

    /**
     * linked list pointers.
     */
    protected RadioData prev, next;

    public Location getLoc() {
      return loc;
    }

  } // class: RadioData


  /**
   * Add a radio onto the field
   * derive the node ID from the radio, which is wrong as a node might have multiple
   * radios with different IDs.
   * 
   * @deprecated use addRadio(int, RadioInfo, RadioInterface, Location) instead
   * @param radioInfo
   * @param proxy
   * @param location
   */
  public void addRadio(RadioInfo radioInfo, RadioInterface proxy, Location location) {
    addRadio(radioInfo.getId(), radioInfo, proxy, location);
  }
  
  
  /**
   * Add a radio onto the field.
   *
   * @param nodeId ID of the node the radio belongs to
   * @param info radio properties (includes radio identifier)
   * @param entity radio upcall entity reference
   * @param loc radio location
   */
  public void addRadio(int nodeId, RadioInfo info, RadioInterface entity, Location loc)
  {
    if(!JistAPI.isEntity(entity)) throw new IllegalArgumentException("entity expected");
    if(logField.isInfoEnabled())
    {
      logField.info("add radio: info=["+info+"] loc="+loc);
    }
    if (nodeMoveEvent.isActive())
      nodeMoveEvent.handle(nodeId, info, loc);
    RadioData data = new RadioData();
    data.entity = entity;
    data.info = info;
    data.loc = loc;
    // insert into array
    int idi = info.getId();
    while(radios.length<=idi)
    {
      RadioData[] radios2 = new RadioData[radios.length*2];
      System.arraycopy(radios, 0, radios2, 0, radios.length);
      radios = radios2;
    }
    radios[idi] = data;
    // add into spatial data structure
    spatial.add(data);
    if (this.limit > Util.toDB(info.getSensitivity_mW()))
      setPropagationLimit(Util.toDB(info.getSensitivity_mW()));
  }

  /**
   * Remove a radio from the field.
   *
   * @param id radio identifier
   */
  public void delRadio(Integer id)
  {
    if(logField.isInfoEnabled())
    {
      logField.info("delete radio: id="+id);
    }
    // remove from array
    RadioData data = getRadioData(id);
    radios[id.intValue()] = null;
    // remove from spatial data structure
    spatial.del(data);
  }

  /**
   * Return radio properties.
   *
   * @param id radio identifier
   * @return radio properties
   */
  public RadioData getRadioData(Integer id)
  {
    return radios[id.intValue()];
  }

  //////////////////////////////////////////////////
  // FieldInterface implementation
  //

  
  /** {@inheritDoc} 
   *  use the radio ID as node id, which is wrong.
   *  @deprecated 
   */
  public void moveRadio(Integer radioId, Location loc) {
    moveRadio(radioId.intValue(), radioId, loc);
  }
  
  
  public void moveRadio(int nodeId, Integer id, Location loc)
  {
    if(logField.isInfoEnabled())
    {
      logField.info("move radio id="+id+" to="+loc);
    }
    // update spatial data structure
    RadioData rd = getRadioData(id);
    spatial.moveInside(rd, loc);
    if (nodeMoveEvent.isActive())
      nodeMoveEvent.handle(nodeId, rd.info, loc);
    // schedule next step
    if(rd.mobilityInfo!=null)
    {
      mobility.next(self, id, loc, rd.mobilityInfo);
    }
  }

  /** {@inheritDoc} */
  public void moveRadioOff(Integer id, Location delta)
  {
    Location newLoc = getRadioData(id).loc.getClone();
    newLoc.add(delta);
    moveRadio(id, newLoc);
  }

  /**
   * Start mobility; schedule first mobility event.
   * 
   * @param id radio identifier
   * 
   * TODO the mobility object defines the mobility model, but it does not hold
   * any radio-specific state. Replace the single mobility instance with one
   * instance per radio (or one instance per radio group with common mobiliy
   * model) in order to allow different mobility models concurrently. 
   */
  public void startMobility(Integer id)
  {
    RadioData rd = getRadioData(id);
    rd.mobilityInfo = mobility.init(self, id, rd.loc);
    self.moveRadio(id, rd.loc);
  }


  //////////////////////////////////////////////////
  // communication
  //

  /**
   * Transmission visitor object.
   */
  private Spatial.SpatialTransmitVisitor transmitVisitor = new Spatial.SpatialTransmitVisitor()
  {
    public double computeSignalLimit(RadioInfo srcInfo, Location srcLoc, 
        Location dstLoc, Message msg) {
      double loss = pathloss.computeLimit(srcInfo, srcLoc, srcInfo, dstLoc);
      // TODO Fading??
      double fade = fading.computeLimit(srcInfo, srcLoc, srcInfo, dstLoc);
      // TODO: pass the currently handled message down here?
      return srcInfo.getPower(msg) - loss + fade;
    }
    public void visitTransmit(RadioInfo srcInfo, Location srcLoc, RadioInfo dstInfo,
        RadioInterface dstEntity, Location dstLoc, Message msg, Long durationObj)
    {
      if(srcInfo.getId() == dstInfo.getId()) return;
      // compute signal strength
      double loss = pathloss.compute(srcInfo, srcLoc, dstInfo, dstLoc);
      double fade = fading.compute(srcInfo, srcLoc, dstInfo, dstLoc);
      double dstPower = srcInfo.getPower(msg) - loss + fade;
      // additional cuttoffs
      double dstPower_mW = Util.fromDB(dstPower);
      
      /*
       * use background noise instead of sensitivity because we meight have
       * an additive radio model (every dBm counts!)
       */
      if(dstPower_mW < dstInfo.getBackground_mW()) 
        return;
      
      // propagation delay
      long propagation = propagationDelay.compute(srcInfo, srcLoc, dstInfo, dstLoc);
      JistAPI.sleep(propagation);
      dstEntity.receive(msg, Double.valueOf(dstPower_mW), durationObj);
      // TODO HACK clean propagation delay implementation
      JistAPI.sleep(-propagation);
    }
  };

  // FieldInterface interface
  /** {@inheritDoc} */
  public void transmit(RadioInfo srcInfo, Message msg, long duration)
  {
    RadioData srcData = getRadioData(srcInfo.getIdInteger());
    spatial.visitTransmit(transmitVisitor, srcData.info, srcData.loc, msg, 
        new Long(duration), limit);
  }


  //////////////////////////////////////////////////
  // compute density metrics
  //

  /**
   * Returns the number of placed nodes.
   *
   * @return number of placed nodes
   */
  public int computeNumberOfPlacedNodes()
  {
    return spatial.size;
  }


  /**
   * Compute field density.
   *
   * @return field node density
   */
  public double computeDensity()
  {
    return (double)spatial.size / spatial.area();
  }

  /**
   * Connectivity visitor interface.
   */
  public static interface ConnectivityVisitor
      extends Spatial.SpatialTransmitVisitor, Spatial.SpatialVisitor
  {
    /**
     * Return average number of links (connectivity).
     *
     * @return average number of links
     */
    double getAvgLinks();
  }

  /**
   * Connectivity visitor interface.
   */
  public static interface MinConnectivityVisitor
      extends Spatial.SpatialTransmitVisitor, Spatial.SpatialVisitor
  {
    /**
     * Return the number of links of the node with the minimum number of neighboring links.
     *
     * @return minimum number of links
     */
    long getMinLinks();
  }

  /**
   * Compute field connectivity.
   *
   * @param sense whether to use radio sensing or reception signal strength
   * @return field connectivity
   */
  public double computeAvgConnectivity(final boolean sense)
  {
    // create new connectivity visitor
    ConnectivityVisitor connectivityVisitor = new ConnectivityVisitor()
    {
      private long links = 0, nodes = 0;
      public double getAvgLinks()
      {
        return (double)links/(double)nodes;
      }
      public double computeSignalLimit(RadioInfo srcInfo, Location srcLoc, 
          Location dstLoc, Message msg)
      {
        return transmitVisitor.computeSignalLimit(srcInfo, srcLoc, dstLoc, msg);
      }
      public void visitTransmit(RadioInfo srcInfo, Location srcLoc,
                                RadioInfo dstInfo, RadioInterface dstEntity, Location dstLoc,
                                Message msg, Long durationObj)
      {
        if(srcInfo.getId() == dstInfo.getId()) return;
        // compute signal strength
        double loss = pathloss.compute(srcInfo, srcLoc, dstInfo, dstLoc);
        double fade = fading.compute(srcInfo, srcLoc, dstInfo, dstLoc);
        double dstPower = srcInfo.getPower(msg) - loss + fade;
        // additional cuttoffs
        double dstPower_mW = Util.fromDB(dstPower);
        // if(dstPower_mW < shared.getBackground_mW()) return;

        if (sense && dstPower_mW < dstInfo.getSensitivity_mW())
          return;

        if (!sense &&
            !dstInfo.isAboveThresholdSnr(msg, dstPower_mW, dstInfo.getBackground_mW()))
          return;

        links++;
      }
      public void visit(Field.RadioData dst)
      {
        spatial.visitTransmit(this, dst.info, dst.loc, null, null, limit);
        nodes++;
      }
    };
    spatial.visit(connectivityVisitor);
    return connectivityVisitor.getAvgLinks();
  }


  /**
   * Checks whether the field is sufficient connected.
   *
   * @param sense whether to use radio sensing or reception signal strength
   * @return field connectivity
   */
  public long checkMinConnectivity(final int ctxNodeId, final boolean sense,
      final int num_repititions, final double threshold, final int bitrate)
  {
    // create new connectivity visitor
    MinConnectivityVisitor connectivityVisitor = new MinConnectivityVisitor()
    {
      private long links                            = 0;
      private long currMinLinks                     = Long.MAX_VALUE;

      public long getMinLinks()
      {
        return currMinLinks;
      }
      public double computeSignalLimit(RadioInfo srcInfo, Location srcLoc, 
          Location dstLoc, Message msg)
      {
        return transmitVisitor.computeSignalLimit(srcInfo, srcLoc, dstLoc, msg);
      }
      public void visitTransmit(RadioInfo srcInfo, Location srcLoc,
          RadioInfo dstInfo, RadioInterface dstEntity, Location dstLoc,
          Message msg, Long durationObj)
      {
        if(srcInfo.getId() == dstInfo.getId()) return;

        long links_t = 0;
        /** repeat the experiment NUMBER_OF_REPITITIONS times */
        for (int i = 0; i < num_repititions; i++) {
          // compute signal strength
          double loss = pathloss.compute(srcInfo, srcLoc, dstInfo, dstLoc);
          double fade = fading.compute(srcInfo, srcLoc, dstInfo, dstLoc);
          double dstPower = srcInfo.getPower(msg) - loss + fade;
          // additional cuttoffs
          double dstPower_mW = Util.fromDB(dstPower);
          // if(dstPower_mW < shared.getBackground_mW()) return;

          if (sense && dstPower_mW < dstInfo.getSensitivity_mW())
            continue;

          if (!sense &&
              !dstInfo.isAboveThresholdSnr(msg, dstPower_mW, dstInfo.getBackground_mW()))
            continue;

          links_t++;
        }

        if (links_t > threshold * num_repititions) {
          /** the link was links_t times good */
          if (logField.isDebugEnabled())
            logField.debug(srcInfo.getId() + " with " + dstInfo.getId() + " = nice ("
              + (double)links_t / (double)num_repititions + ")");
          links++;
        } else {
          if (logField.isDebugEnabled())
            logField.debug(srcInfo.getId() + " with " + dstInfo.getId() + " = bad ("
              + (double)links_t / (double)num_repititions + ")");
        }
      }
      public void visit(Field.RadioData dst)
      {
        if (ctxNodeId == dst.info.getId()) {
          /** reset the number of links */
          links = 0;
          Message msg = Constants.BANDWIDTH_INVALID == bitrate ? null :
            new PhyMessage(Message.NULL, bitrate, new Phy802_11(dst.info));
          spatial.visitTransmit(this, dst.info, dst.loc, msg, null, limit);
          currMinLinks = Math.min(currMinLinks, links);
        }
      }
    };

    spatial.visit(connectivityVisitor);
    return connectivityVisitor.getMinLinks();
  }

  /**
   * Computes the average path loss between the given source and destination.
   *
   * @param dist
   * @param src
   * @param dst
   * @param quantile
   * @return an array of double
   */
  public double[] computeAvgPathLoss(double[] dist, int src, int dst, double quantile) {
    double[] loss = new double[dist.length];
    
    PathLoss.Shadowing shadow = (Shadowing) pathloss;

    RadioInfo srcInfo = radios[src].info;
    RadioInfo dstInfo = radios[dst].info;
    Location srcLoc = new Location.Location2D((float).0, (float).0);

    for (int i = 0; i < dist.length; i++) {
      Location dstLoc = new Location.Location2D((float).0, (float)dist[i]);
      loss[i] = shadow.computeLimit(srcInfo, srcLoc, dstInfo, dstLoc, quantile);
    }

    return loss;
  }

  

} // class: Field

