package brn.sim.builder;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Node;
import jist.swans.app.AbstractApplication;
import jist.swans.field.AbstractField;
import jist.swans.field.Field;
import jist.swans.misc.Location;
import jist.swans.misc.Location.Location2D;
import jist.swans.net.NetAddress;
import jist.swans.radio.AbstractRadio;
import brn.sim.builder.FlowBuilder.FlowParams;

/**
 * Builds different types and patterns of traffic flows. Input is the type
 * and number of application flow and it creates all necessary stuff.
 *
 * @author kurth
 */
public abstract class TrafficBuilder extends Builder {

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

    /** type of flows to set up */
    public FlowBuilder.FlowParams flowParams = new FlowBuilder.CaUdpParams();

    /** number of flows to set up */
    public int flows = 1;
    /** spacing between two flows in ms */
    public int startOffset = 0;

    /** whether to increment the port number by one for each flow */
    public boolean incPortNumbers = true;

    /** whether to increment the flow IDs by one for each flow */
    public boolean incFlowIds = true;

    public FlowBuilder.FlowParams getFlowParams() {
      return flowParams;
    }

    public void setFlowParams(FlowBuilder.FlowParams flowParams) {
      this.flowParams = flowParams;
    }

    public int getFlows() {
      return flows;
    }

    public void setFlows(int flows) {
      this.flows = flows;
    }

    public boolean isIncPortNumbers() {
      return incPortNumbers;
    }

    public void setIncPortNumbers(boolean incPortNumbers) {
      this.incPortNumbers = incPortNumbers;
    }

    public boolean isIncFlowIds() {
      return incFlowIds;
    }

    public void setIncFlowIds(boolean incFlowIds) {
      this.incFlowIds = incFlowIds;
    }

    public int getStartOffset() {
      return startOffset;
  }

    public void setStartOffset(int startOffset) {
      this.startOffset = startOffset;
    }

    /* (non-Javadoc)
     * @see brn.sim.builder.Builder.Params#clone()
     */
    public Object clone() throws CloneNotSupportedException {
      TrafficParams params = (TrafficParams) super.clone();
      if (null != flowParams) params.flowParams = (FlowParams) flowParams.clone();
      return params;
    }
  }

  public static class RandomParams extends TrafficParams {
    private static final long serialVersionUID = 1L;

    public boolean twoWay = false;

    public long seed = 1;

    public boolean isTwoWay() {
      return twoWay;
    }
    public void setTwoWay(boolean twoWay) {
      this.twoWay = twoWay;
    }
    public long getSeed() {
      return seed;
    }
    public void setSeed(long seed) {
      this.seed = seed;
    }
  }

  public static class RandomSingleHopParams extends RandomParams {
    private static final long serialVersionUID = 1L;

    /** maximal radius around a transmitter to look for a receiver */
    public double maxHopDist = 100;
    /** whether multiple flows per node are allowed */
    public boolean distinctNodes = false;

    public boolean isDistinctNodes() {
      return distinctNodes;
    }
    public void setDistinctNodes(boolean distinctNodes) {
      this.distinctNodes = distinctNodes;
    }
    public double getMaxHopDist() {
      return maxHopDist;
    }
    public void setMaxHopDist(double maxHopDist) {
      this.maxHopDist = maxHopDist;
    }
  }

  public static class RandomGatewayParams extends RandomParams {
    private static final long serialVersionUID = 1L;
  }

  public static class HorizontalParams extends TrafficParams {
    private static final long serialVersionUID = 1L;

    public boolean twoWay = false;

    public boolean isTwoWay() {
      return twoWay;
    }

    public void setTwoWay(boolean twoWay) {
      this.twoWay = twoWay;
    }
  }

  public static class RadialParams extends TrafficParams {
    private static final long serialVersionUID = 1L;

    public boolean twoWay = false;
    /** whether to use only a half instead of a full circle for flow allocation */
    public boolean useHalfCircle = true;
    /** angle to start with in radians */
    public double startAngle = 0;

    public boolean isTwoWay() {
      return twoWay;
    }
    public void setTwoWay(boolean twoWay) {
      this.twoWay = twoWay;
    }
    public boolean isUseHalfCircle() {
      return useHalfCircle;
    }
    public void setUseHalfCircle(boolean useHalfCircle) {
      this.useHalfCircle = useHalfCircle;
    }
    public double getStartAngle() {
      return startAngle;
    }
    public void setStartAngle(double startAngle) {
      this.startAngle = startAngle;
    }
  }

  public static class RadialGatewayParams extends RadialParams {
    private static final long serialVersionUID = 1L;
  }

  /**
   * Traffic flows with random start/end (optional twoway)
   *
   * @author kurth
   */
  public static class RandomBuilder extends TrafficBuilder {

    protected Random random;
    protected Map mapClients  = new HashMap();
    protected Map mapServers  = new HashMap();

    // TODO store per param
    private int clientPort;
    private int serverPort;
    private int flowId;

    /**
     * Sets up the client and server lists. Overwrite to implement your own!
     *
     * @param opts
     * @param field
     * @throws BuilderException
     */
    protected void init(RandomParams opts, Field field) throws BuilderException {
      int nodes = field.getNodes().size();
      clientPort = opts.flowParams.clientPort;
      serverPort = opts.flowParams.serverPort;
      flowId = opts.flowParams.flowId;
      try {
        random = SecureRandom.getInstance("SHA1PRNG");
      } catch (NoSuchAlgorithmException e) {
        throw new BuilderException("Could not create PRNG", e);
      }
      random.setSeed(opts.seed);
      List clients = new ArrayList();
      List servers = new ArrayList();
      for (int i = 0; i < opts.flows; i++) {
        int client = random.nextInt(nodes);
        int server = random.nextInt(nodes-1);
        if (server == client) server++;
        if (0 == client) client = nodes;
        if (0 == server) server = nodes;
        clients.add(field.getNode(client).getNet().getAddress());
        servers.add(field.getNode(server).getNet().getAddress());
      }
      mapClients.put(opts, clients);
      mapServers.put(opts, servers);
    }

    private void createApp(Node node, FlowParams flowParams) throws BuilderException {
      // set up flow
      Builder appBuilder = getProvider().getBuilder(flowParams);
      AbstractApplication app = (AbstractApplication) appBuilder.build(flowParams, node);
      if (null != app) {
        getProvider().addHookUp(appBuilder, flowParams, node, app);
        node.addApplication(app);
      }
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#getParamClass()
     */
    public Class getParamClass() {
      return RandomParams.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 {
      // all stuff is done in third stage (postHookUp) because we need to
      // calc the bounding box

      return null;
    }

    /*
     * (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 {
      // all stuff is done in third stage (postHookUp) because we need to
      // calc the bounding box
    }

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

      RandomParams opts = (RandomParams) params;

      List clients = (List) mapClients.get(opts);
      List servers = (List) mapServers.get(opts);

      if (null == clients || null == servers) {
        init(opts, node.getField(0));
        clients = (List) mapClients.get(opts);
        servers = (List) mapServers.get(opts);
      }


      try {
        for (int i = 0; i < opts.flows; i++) {
          NetAddress client = (NetAddress)clients.get(i);
          NetAddress server = (NetAddress)servers.get(i);

          FlowParams flowParams = (FlowParams) opts.flowParams.clone();
          flowParams.clientAddr = client;
          flowParams.serverAddr = server;
          flowParams.clientPort = clientPort + (opts.incPortNumbers? i : 0);
          flowParams.serverPort = serverPort + (opts.incPortNumbers? i : 0);
          flowParams.flowId = flowId + i;
          flowParams.deferFlow(opts.startOffset * i);
          createApp(node, flowParams);

          if (opts.twoWay) {
            flowParams = (FlowParams) opts.flowParams.clone();
            flowParams.clientAddr = server;
            flowParams.serverAddr = client;
            flowParams.clientPort = serverPort + (opts.incPortNumbers? i : 0);
            flowParams.serverPort = clientPort + (opts.incPortNumbers? i : 0);
            flowParams.flowId = flowId + i + opts.flows;
            flowParams.deferFlow(opts.startOffset * i);
            createApp(node, flowParams);
          }
        }
      } catch (CloneNotSupportedException e) {
        throw new BuilderException(e);
      }
    }
  }

  /**
   * Sets up random single hop flows.
   *
   * @author Kurth
   */
  public static class RandomSingleHop extends RandomBuilder {

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

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.TrafficBuilder.RandomBuilder#init(brn.sim.builder.TrafficBuilder.RandomParams, jist.swans.field.Field)
     */
    protected void init(RandomParams params, Field field) throws BuilderException {
      RandomSingleHopParams opts = (RandomSingleHopParams) params;
      super.init(opts, field);

      List newClients = new ArrayList<Integer>();
      List newServers = new ArrayList<Integer>();
      List blackList  = new ArrayList<Integer>();
      List clients = (List) mapClients.get(opts);

      int nodes = field.getNodes().size();
      while (clients.size() > 0) {
        int client = getNodeId(field, (NetAddress) clients.remove(0));
        if (opts.distinctNodes) {
          if (blackList.size() >= nodes)
            break;
          while (blackList.contains(client))
            client = random.nextInt(nodes)+1;
          blackList.add(client);
        }
        Location locClient = field.getRadioData(client).getLoc();

        // find possible servers
        List possibleServers = new ArrayList<Integer>();
        for (int j = 1; j <= nodes; j++) {
          if (client == j || opts.distinctNodes && blackList.contains(j))
            continue;
          Location locServer = field.getRadioData(j).getLoc();
          if (locServer.distance(locClient) <= opts.maxHopDist)
            possibleServers.add(j);
        }

        // choose server
        int size = possibleServers.size();
        if (size > 0) {
          int server = (Integer) possibleServers.get(random.nextInt(size));
          newClients.add(field.getNode(client).getNet().getAddress());
          newServers.add(field.getNode(server).getNet().getAddress());
          if (opts.distinctNodes)
            blackList.add(server);
        } else { // no neighbors, move to blacklist
          blackList.add(client);
          // not enough nodes to realize all flows
          if (blackList.size() >= nodes)
            break;
          // find a new client
          while (blackList.contains(client))
            client = random.nextInt(nodes)+1;
          clients.add(field.getNode(client).getNet().getAddress());
        }
      }

      if (clients.size() > 0)
        opts.flows -= clients.size() + 1;

      mapClients.put(opts, newClients);
      mapServers.put(opts, newServers);
    }

    private int getNodeId(AbstractField field, NetAddress addr){
      List nodes = field.getNodes();
      for (int i = 0; i < nodes.size(); i++) {
        Node node = (Node) nodes.get(i);
        if (node.getNet().getAddress().equals(addr))
          return node.getNodeId();
      }
      return -1;
    }
  }

  /**
   * Server is always a centered gateway. Clients see {@link TrafficBuilder.RandomBuilder}.
   *
   * @author kurth
   */
  public static class RandomGatewayBuilder extends RandomBuilder {

    private int server = -1;

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

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.TrafficBuilder.RandomBuilder#init(brn.sim.builder.TrafficBuilder.RandomParams, jist.swans.field.Field)
     */
    protected void init(RandomParams opts, Field field) throws BuilderException {
      super.init(opts, field);

      if (-1 == server) {
        BoundingBox boundingBox = calcBoundingBox(field);
        Location2D middle = boundingBox.getMiddle();
        server = findNearestNode(field, middle);
      }

      List servers = new ArrayList();
      for (int i = 0; i < opts.flows; i++) {
        servers.add(new Integer(server));
      }
      mapServers.put(opts, servers);

      int nodes = field.getNodes().size();
      List clients = (List) mapClients.get(opts);
      for (int i = 0; i < clients.size(); i++) {
        int client = ((Integer)clients.get(i)).intValue();
        if (server == client) {
          client = ((client + 1) >= nodes ? client -1 : client + 1);
          clients.set(i, new Integer(client));
        }
      }
    }
  }

  /**
   * Horizontal traffic flows left to right (optional twoway)
   *
   * @author kurth
   */
  public static class HorizontalBuilder extends TrafficBuilder {

    private BoundingBox boundingBox;
    private float yOffset;
    private float yPos;

    private int clientPort;
    private int serverPort;
    private int flowId;

    private void init(HorizontalParams opts, Field field) {
      boundingBox = calcBoundingBox(field);

      float deltaY = boundingBox.max.getY() - boundingBox.min.getY();
      yOffset = deltaY / (opts.flows + 1);
      yPos = boundingBox.min.getY() + yOffset;

      clientPort = opts.flowParams.clientPort;
      serverPort = opts.flowParams.serverPort;
      flowId = opts.flowParams.flowId;
    }

    private void createApp(Node node, FlowParams flowParams) throws BuilderException {
      // set up flow
      Builder appBuilder = getProvider().getBuilder(flowParams);
      AbstractApplication app = (AbstractApplication) appBuilder.build(flowParams, node);
      if (null != app) {
        getProvider().addHookUp(appBuilder, flowParams, node, app);
        node.addApplication(app);
      }
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#getParamClass()
     */
    public Class getParamClass() {
      return HorizontalParams.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 {
//      HorizontalParams opts = (HorizontalParams) params;

      // all stuff is done in third stage (postHookUp) because we need to
      // calc the bounding box

      return null;
    }

    /*
     * (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 {
      // all stuff is done in third stage (postHookUp) because we need to
      // calc the bounding box
    }

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

      HorizontalParams opts = (HorizontalParams) params;
      Field field = node.getField(0);

      if (null == boundingBox)
        init(opts, field);

      try {
        for (int i = 0; i < opts.flows; i++) {
          Location locClient = new Location.Location2D(boundingBox.min.getX(),
              yPos + i * yOffset);
          Location locServer = new Location.Location2D(boundingBox.max.getX(),
              yPos + i * yOffset);

          int client = findNearestNode(field, locClient);
          int server = findNearestNode(field, locServer);

          if (Main.ASSERT)
            Util.assertion(client != server);

          FlowParams flowParams = (FlowParams) opts.flowParams.clone();
          flowParams.clientAddr = field.getNode(client).getNet().getAddress();
          flowParams.serverAddr = field.getNode(server).getNet().getAddress();
          flowParams.clientPort = clientPort + (opts.incPortNumbers? i : 0);
          flowParams.serverPort = serverPort + (opts.incPortNumbers? i : 0);
          flowParams.flowId = flowId + i;
          flowParams.deferFlow(opts.startOffset * i);
          createApp(node, flowParams);

          if (opts.twoWay) {
            flowParams = (FlowParams) opts.flowParams.clone();
            flowParams.clientAddr = field.getNode(server).getNet().getAddress();
            flowParams.serverAddr = field.getNode(client).getNet().getAddress();
            flowParams.clientPort = serverPort + (opts.incPortNumbers? i : 0);
            flowParams.serverPort = clientPort + (opts.incPortNumbers? i : 0);
            flowParams.flowId = flowId + i + opts.flows;
            flowParams.deferFlow(opts.startOffset * i);
            createApp(node, flowParams);
          }
        }
      } catch (CloneNotSupportedException e) {
        throw new BuilderException(e);
      }
    }
  }

  /**
   * allocates flows start and ends on opposite sites of a circle. the space
   * between neighboring flows is maximized (optional twoway).
   *
   * @author kurth
   */
  public static class RadialBuilder extends TrafficBuilder {

    /** middle of the underlaying circle */
    protected Location.Location2D middle;
    /** radius of the circle */
    private double radius;
    /** angle between flows in rad */
    private double radFlowOffset;

    private int clientPort;
    private int serverPort;
    private int flowId;

    protected void init(RadialParams opts, Field field) {
      BoundingBox boundingBox = calcBoundingBox(field);

      middle = boundingBox.getMiddle();
      radFlowOffset = 2 * Math.PI / opts.flows;
      if (opts.useHalfCircle)
        radFlowOffset = radFlowOffset / 2.;
      this.radius = boundingBox.max.distance(boundingBox.min) / 2.;

      clientPort = opts.flowParams.clientPort;
      serverPort = opts.flowParams.serverPort;
      flowId = opts.flowParams.flowId;
    }

    protected void createApp(Node node, FlowParams flowParams, boolean fistFlow) throws BuilderException {
      // set up flow
      Builder appBuilder = getProvider().getBuilder(flowParams);
      AbstractApplication app = (AbstractApplication) appBuilder.build(flowParams, node);
      if (null != app) {
        getProvider().addHookUp(appBuilder, flowParams, node, app);
        node.addApplication(app);
      }
    }

    /*
     * (non-Javadoc)
     * @see brn.sim.builder.Builder#getParamClass()
     */
    public Class getParamClass() {
      return RadialParams.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 {
//      RadialParams opts = (RadialParams) params;

      // all stuff is done in third stage (postHookUp) because we need to
      // calc the bounding box

      return null;
    }

    /*
     * (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 {
      // all stuff is done in third stage (postHookUp) because we need to
      // calc the bounding box
    }

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

      RadialParams opts = (RadialParams) params;
      Field field = node.getField(0);

      if (null == middle)
        init(opts, field);

      try {
        for (int i = 0; i < opts.flows; i++) {
          Location locClient = new Location.Location2D(
              (int)(middle.getX() + radius * Math.cos(i*radFlowOffset + opts.startAngle)),
              (int)(middle.getY() + radius * Math.sin(i*radFlowOffset + opts.startAngle)));
          Location locServer = new Location.Location2D(
              (int)(middle.getX() - radius * Math.cos(i*radFlowOffset + opts.startAngle)),
              (int)(middle.getY() - radius * Math.sin(i*radFlowOffset + opts.startAngle)));

          int client = findNearestNode(field, locClient);
          int server = findNearestNode(field, locServer);

          if (Main.ASSERT)
            Util.assertion(client != server);

          FlowParams flowParams = (FlowParams) opts.flowParams.clone();
          flowParams.clientAddr = field.getNode(client).getNet().getAddress();
          flowParams.serverAddr = field.getNode(server).getNet().getAddress();
          flowParams.clientPort = clientPort + (opts.incPortNumbers? i : 0);
          flowParams.serverPort = serverPort + (opts.incPortNumbers? i : 0);
          flowParams.flowId = flowId + i;
          flowParams.deferFlow(opts.startOffset * i);
          createApp(node, flowParams, true);

          if (opts.twoWay) {
            flowParams = (FlowParams) opts.flowParams.clone();
            flowParams.clientAddr = field.getNode(server).getNet().getAddress();
            flowParams.serverAddr = field.getNode(client).getNet().getAddress();
            flowParams.clientPort = serverPort + (opts.incPortNumbers? i : 0);
            flowParams.serverPort = clientPort + (opts.incPortNumbers? i : 0);
            flowParams.flowId = flowId + i + opts.flows;
            flowParams.deferFlow(opts.startOffset * i);
            createApp(node, flowParams, false);
          }
        }
      } catch (CloneNotSupportedException e) {
        throw new BuilderException(e);
      }
    }
  }
  public static class RadialGatewayBuilder extends RadialBuilder {

    private NetAddress serverAddr = null;

    /* (non-Javadoc)
     * @see brn.sim.builder.TrafficBuilder.RadialBuilder#createApp(jist.swans.Node, brn.sim.builder.AppBuilder.FlowParams)
     */
    protected void createApp(Node node, FlowParams flowParams, boolean fistFlow)
        throws BuilderException {
      if (fistFlow)
        flowParams.serverAddr = serverAddr;
      else
        flowParams.clientAddr = serverAddr;

      if (Main.ASSERT)
        Util.assertion(flowParams.serverAddr != flowParams.clientAddr);
      super.createApp(node, flowParams, fistFlow);
    }

    /* (non-Javadoc)
     * @see brn.sim.builder.TrafficBuilder.RadialBuilder#init(brn.sim.builder.TrafficBuilder.RadialParams, jist.swans.field.Field)
     */
    protected void init(RadialParams opts, Field field) {
      super.init(opts, field);
      int server = findNearestNode(field, this.middle);
      serverAddr = field.getNode(server).getNet().getAddress();
    }

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

  protected static class BoundingBox {
    Location min;
    Location max;

    public BoundingBox(Location2D min, Location2D max) {
      this.min = min;
      this.max = max;
    }

    public Location2D getMiddle() {
      Location step = min.step(max, 2);
      step.add(min);
      return (Location2D) step;
    }
  }

  protected static BoundingBox calcBoundingBox(Field field) {
    Iterator iter = field.getNodes().iterator();
    assert (null != iter && iter.hasNext());

    Node nodeStart = (Node) iter.next();
    Integer idStart = ((AbstractRadio) nodeStart.getRadio(0)).getRadioInfo()
        .getIdInteger();
    Location locStart = field.getRadioData(idStart).getLoc();

    float xMin = locStart.getX();
    float xMax = locStart.getX();
    float yMin = locStart.getY();
    float yMax = locStart.getY();

    while (null != iter && iter.hasNext()) {
      Node node = (Node) iter.next();
      Integer id = ((AbstractRadio) node.getRadio(0)).getRadioInfo()
          .getIdInteger();
      Location loc = field.getRadioData(id).getLoc();

      xMin = Math.min(xMin, loc.getX());
      xMax = Math.max(xMax, loc.getX());
      yMin = Math.min(yMin, loc.getY());
      yMax = Math.max(yMax, loc.getY());
    }

    return new BoundingBox(new Location.Location2D(xMin, yMin),
            new Location.Location2D(xMax, yMax));
  }

  /**
   * Find the node closest to the given point.
   *
   * @param field field to consider.
   * @param locPref   The location to be nearest to.
   * @return The index of the node nearest to the given location.
   */
  protected static int findNearestNode(Field field, Location locPref) {
    Iterator iter = field.getNodes().iterator();
    assert (iter != null && iter.hasNext());

    Node nodeNearest = (Node) iter.next();
    int idNearest = nodeNearest.getNodeId();
    Integer radioIdNearest = ((AbstractRadio) nodeNearest.getRadio(0))
        .getRadioInfo().getIdInteger();
    Location locNearest = field.getRadioData(radioIdNearest).getLoc();
    float distNearest = locNearest.distanceSqr(locPref);

    while (iter != null && iter.hasNext()) {
      Node node = (Node) iter.next();
      int id = node.getNodeId();
      Integer radioId = ((AbstractRadio) node.getRadio(0)).getRadioInfo()
          .getIdInteger();
      Location loc = field.getRadioData(radioId).getLoc();
      float dist = loc.distanceSqr(locPref);

      if (dist < distNearest) {
        nodeNearest = node;
        idNearest = id;
        locNearest = loc;
        distNearest = dist;
      }
    }
    return idNearest;
  }

}
