//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <Placement.java Tue 2004/04/06 11:31:11 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 java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.radio.RadioNoise;
import jist.swans.misc.Location;

import org.apache.commons.math.random.RandomDataImpl;
import org.apache.log4j.Logger;

/**
 * Interface of all initial placement models.
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: Placement.java,v 1.13 2004/04/06 16:07:47 barr Exp $
 * @since SWANS1.0
 */

public interface Placement
{

  /**
   * Return location of next node.
   * 
   * opts format: [12x12:]*[12x12]
   *
   * @return location of next node
   */
  Location getNextLocation();

  public static class Manual implements Placement
  {
    /** stores the given locations as list */
    private List locations;

    /**
     * Constructs a placement from a single point.
     *
     * @param loc the location to set
     */
    public Manual(Location loc) {
      locations = new LinkedList();
      locations.add(loc);
    }

    /**
     * Constructs a placement from the given locations.
     *
     * @param locations list with locations.
     */
    public Manual(List locations) {
      this.locations = locations;
    }

    public Manual(String options) {
      this.locations = new LinkedList();

      String[] locations = options.split(":");
      for (int i = 0; i < locations.length; i++) {
        Location loc = Location.parse(locations[i].trim());
        this.locations.add(loc);
      }
    }

    /*
    * (non-Javadoc)
    * @see jist.swans.field.Placement#getNextLocation()
    */
    public Location getNextLocation() {
      if(locations.size() == 0)
        throw new IllegalStateException("points exhausted");

      Location loc = (Location) locations.get(0);
      locations.remove(0);

      return loc;
    }
  }

  //////////////////////////////////////////////////
  // random placement model
  //

  /**
   * Random (uniform) placement.
   */
  public static class Random implements Placement
  {
    /** placement boundaries. */
    private float x, y;

    public class Entry {
      protected Location.Location2D loc;
      protected RadioNoise radio;
    }

    /**
     * Initialize random placement model.
     *
     * @param x x-axis upper limit
     * @param y y-axis upper limit
     */
    public Random(float x, float y) {
      init(x, y);
    }

    /**
     * Initialize random placement.
     *
     * @param loc upper limit coordinate
     */
    public Random(Location loc) {
      init(loc.getX(), loc.getY());
    }

    /**
     * Initialize random placement.
     *
     * @param field field dimensions string
     */
    public Random(String field)
    {
      String[] data = field.split("x|,");
      if(data.length!=2) throw new IllegalArgumentException("invalid format, expected i,j");
      init(Float.parseFloat(data[0]), Float.parseFloat(data[1]));
    }

    /**
     * Initialize random placement.
     *
     * @param x field x-dimension (in meters)
     * @param y field y-dimension (in meters)
     */
    private void init(float x, float y)
    {
      this.x = x;
      this.y = y;
    }

    //////////////////////////////////////////////////
    // Placement interface
    //
    /** {@inheritDoc} */
    public Location getNextLocation()
    {
      /** we are not interested in connectivity */
      return new Location.Location2D(
        Constants.placementRandom.nextFloat()*x,
        Constants.placementRandom.nextFloat()*y);
    }

  } // class: Random


  //////////////////////////////////////////////////
  // grid placement model
  //

  /**
   * Placement along a regular grid.
   * 
   * Format:  "NoX:NoY[:StartX:StartY[:OffsetX:OffsetY]]
   */
  public static class Grid implements Placement
  {
    /** offset to start from */
    private float startx, starty;
    /** field dimensions. */
    private float offsetx, offsety;
    /** node placement array dimensions. */
    private int nodex, nodey;
    /** number of nodes already placed. */
    private long i;

    /**
     * Initialize grid placement model.
     *
     * @param loc field dimensions (in meters)
     * @param nodex number of nodes in x-dimension
     * @param nodey number of nodes in y-dimension
     */
    public Grid(Location loc, int nodex, int nodey)
    {
      init(loc.getX(), loc.getY(), nodex, nodey);
    }

    /**
     * Initialize grid placement model.
     *
     * @param loc field dimensions (in meters)
     * @param s node configuration string
     */
    public Grid(Location loc, String s)
    {
      init(loc, s);
    }

    /**
     * Initialize grid placement model.
     *
     * @param field field dimensions string
     * @param nodes node configuration string
     */
    public Grid(String field, String nodes)
    {
      init(field, nodes);
    }

    /**
     * Initialize grid placement model.
     *
     * @param field field dimensions string
     * @param nodes node configuration string
     */
    private void init(String field, String nodes)
    {
      String[] data = field.split("x|,");
      if(data.length!=2) throw new IllegalArgumentException("invalid format, expected i,j");
      init(new Location.Location2D(Float.parseFloat(data[0]), Float.parseFloat(data[1])), nodes);
    }

    /**
     * Initialize grid placement model.
     *
     * @param loc field dimensions (in meters)
     * @param s node configuration string
     */
    private void init(Location loc, String s)
    {
      String[] data = s.split("x|,");
      if(data.length==2) {
        init(loc.getX(), loc.getY(), Integer.parseInt(data[0]), Integer.parseInt(data[1]));
      } else if (data.length==4) {
        init(Float.parseFloat(data[2]), Float.parseFloat(data[3]),
            loc.getX()-Float.parseFloat(data[2]), loc.getY()-Float.parseFloat(data[3]), 
            Integer.parseInt(data[0]), Integer.parseInt(data[1]));
      } else if (data.length==6) {
        init(Float.parseFloat(data[2]), Float.parseFloat(data[3]),
            Float.parseFloat(data[4]), 
              Float.parseFloat(data[5]), 
            Integer.parseInt(data[0]), Integer.parseInt(data[1]));
      } else
        throw new IllegalArgumentException("invalid format, expected i,j");
    }

    /**
     * Initialize grid placement model.
     *
     * @param fieldx field x-dimension (in meters)
     * @param fieldy field y-dimension (in meters)
     * @param nodex number of nodes in x-dimension
     * @param nodey number of nodes in y-dimension
     */
    private void init(float fieldx, float fieldy, int nodex, int nodey)
    {
      this.startx = 0;
      this.starty = 0;
      this.offsetx = fieldx/nodex;
      this.offsety = fieldy/nodey;
      this.nodex = nodex;
      this.nodey = nodey;
      i = 0;
    }

    private void init(float startx, float starty, float offsetx, float offsety, int nodex, int nodey)
    {
      this.startx = startx;
      this.starty = starty;
      this.offsetx = offsetx;
      this.offsety = offsety;
      this.nodex = nodex;
      this.nodey = nodey;
      i = 0;
    }

    //////////////////////////////////////////////////
    // Placement interface
    //

    /** {@inheritDoc} */
    public Location getNextLocation()
    {
      if(i/nodex==nodey)
        throw new IllegalStateException("grid points exhausted");
      Location l = new Location.Location2D(
          (i%nodex)*offsetx + startx, (i/nodex)*offsety + starty);
      i++;
      return l;
    }
  } // class: Grid


  //////////////////////////////////////////////////
  // poisson placement model
  //

  /**
   * Poisson placement.
   */
  public static class Poisson implements Placement
  {

    /** logger. */
    public static final Logger log = Logger
                                       .getLogger(Poisson.class);

    /** placement boundaries. */
    private float x, y;
    private float[] mx, my, lambda, scaling;
    /** The poisson distribution */
    private RandomDataImpl rd;
    /** pointer to the next group */
    private int next_group = 0;

    /**
     * Initialize poisson placement model.
     *
     * @param x x-axis upper limit
     * @param y y-axis upper limit
     * @param seed the seed for the PRNG generator
     * @param mx the geographical position of the cluster
     * @param my the geographical position of the cluster
     * @param lambda the mean of the poisson distribution
     */
    public Poisson(float x, float y, int seed, float mx, float my, float lambda, float scaling)
    {
      init(x, y, seed, mx, my, lambda, scaling);
    }

    /**
     * Initialize poisson placement.
     *
     * @param loc upper limit coordinate
     * @param seed the seed for the PRNG generator
     * @param mx the geographical position of the cluster
     * @param my the geographical position of the cluster
     * @param lambda the mean of the poisson distribution
     */
    public Poisson(Location loc, int seed, float mx, float my, float lambda, float scaling)
    {
      init(loc.getX(), loc.getY(), seed, mx, my, lambda, scaling);
    }

    /**
     * Initialize poisson placement.
     *
     * @param field field dimensions string
     * @param seed the seed for the PRNG generator
     * @param mx the geographical position of the cluster
     * @param my the geographical position of the cluster
     * @param lambda the mean of the poisson distribution
     */
    public Poisson(String field, int seed, float mx, float my, float lambda, float scaling)
    {
      String[] data = field.split("x|,");
      if(data.length!=2) throw new IllegalArgumentException("invalid format, expected i,j");
      init(Float.parseFloat(data[0]), Float.parseFloat(data[1]), seed, mx, my, lambda, scaling);
    }

    /**
     * Initialize poisson placement model.
     *
     * @param x x-axis upper limit
     * @param y y-axis upper limit
     * @param seed the seed for the PRNG generator
     * @param mx the geographical position of the cluster
     * @param my the geographical position of the cluster
     * @param lambda the mean of the poisson distribution
     */
    public Poisson(float x, float y, int seed, float[] mx, float[] my, float[] lambda, float[] scaling)
    {
      init(x, y, seed, mx, my, lambda, scaling);
    }

    /**
     * Initialize poisson placement.
     *
     * @param loc upper limit coordinate
     * @param seed the seed for the PRNG generator
     * @param mx the geographical position of the cluster
     * @param my the geographical position of the cluster
     * @param lambda the mean of the poisson distribution
     */
    public Poisson(Location loc, int seed, float[] mx, float[] my, float[] lambda, float[] scaling)
    {
      init(loc.getX(), loc.getY(), seed, mx, my, lambda, scaling);
    }

    /**
     * Initialize poisson placement.
     *
     * @param loc upper limit coordinate
     * @param seed the seed for the PRNG generator
     * @param group_location the geographical position of the poisson groups
     */
    public Poisson(Location loc, int seed, String group_location)
    {

      String[] pairs = group_location.split(":|,");

      float[] mx = new float[pairs.length];
      float[] my = new float[pairs.length];
      float[] lambda = new float[pairs.length];
      float[] scaling = new float[pairs.length];

      for (int i = 0; i < pairs.length; i++) {
        String[] values = pairs[i].split("x|,");
        mx[i] = Float.parseFloat(values[0]);
        my[i] = Float.parseFloat(values[1]);
        lambda[i] = Float.parseFloat(values[2]);
        scaling[i] = Float.parseFloat(values[3]);
      }

      init(loc.getX(), loc.getY(), seed, mx, my, lambda, scaling);
    }
    /**
     * Initialize poisson placement.
     *
     * @param field field dimensions string
     * @param seed the seed for the PRNG generator
     * @param mx the geographical position of the cluster
     * @param my the geographical position of the cluster
     * @param lambda the mean of the poisson distribution
     */
    public Poisson(String field, int seed, float[] mx, float[] my, float[] lambda, float[] scaling)
    {
      String[] data = field.split("x|,");
      if(data.length!=2) throw new IllegalArgumentException("invalid format, expected i,j");
      init(Float.parseFloat(data[0]), Float.parseFloat(data[1]), seed, mx, my, lambda, scaling);
    }


    /**
     * Initialize poisson placement.
     *
     * @param x field x-dimension (in meters)
     * @param y field y-dimension (in meters)
     * @param seed the seed for the PRNG generator
     * @param mx the mean of the poisson distribution for the x-axis
     * @param my the mean of the poisson distribution for the y-axis
     */
    private void init(float x, float y, int seed, float mx, float my, float lambda, float scaling)
    {
      float[] mx_arr = {mx};
      float[] my_arr = {my};
      float[] lambda_arr = {lambda};
      float[] scaling_arr = {scaling};
      init(x, y, seed, mx_arr, my_arr, lambda_arr, scaling_arr);
    }

    /**
     * Initialize poisson placement.
     *
     * @param x field x-dimension (in meters)
     * @param y field y-dimension (in meters)
     * @param seed the seed for the PRNG generator
     * @param mx the geographical position of the cluster
     * @param my the geographical position of the cluster
     * @param lambda the mean of the poisson distribution
     */
    private void init(float x, float y, int seed, float[] mx, float[] my, float[] lambda, float[] scaling)
    {
      Util.assertion(mx.length == my.length);
      this.x = x;
      this.y = y;
      this.mx = mx;
      this.my = my;
      this.lambda = lambda;
      this.scaling = scaling;
      this.rd = new RandomDataImpl();
      this.rd.reSeedSecure(seed);
      this.rd.reSeed(seed);
    }

    //////////////////////////////////////////////////
    // Placement interface
    //

    /** {@inheritDoc} */
    public Location getNextLocation()
    {
      float mean = lambda[next_group];
      float fac = scaling[next_group];

      float _x_v = x * mx[next_group] + fac * (rd.nextPoisson(mean) - mean);
      float _y_v = y * my[next_group] + fac * (rd.nextPoisson(mean) - mean);

      float x_v = Math.min(Math.max(0, _x_v), x);
      float y_v = Math.min(Math.max(0, _y_v), y);

      Location.Location2D loc
              = new Location.Location2D(x_v, y_v);
      next_group = (next_group + 1) % mx.length;
      if (log.isDebugEnabled())
        log.debug("placing node at: " + loc);
      return loc;
    }
  } // class: Poisson


  //////////////////////////////////////////////////
  // pyramid placement model
  //

  /**
   * Rectangular Placement.
   * todo: under development.
   */
  public static class Pyramid implements Placement
  {
    /** field dimensions. */
    private float fieldx, fieldy;
    /** node placement array dimensions. */
    private int nodex, nodey;
    /** number of nodes already placed. */
    private long i;
    private List locs;

    /**
     * Initialize grid placement model.
     *
     * @param loc field dimensions (in meters)
     * @param nodex number of nodes in x-dimension
     * @param nodey number of nodes in y-dimension
     */
    public Pyramid(Location loc, int nodex, int nodey)
    {
      init(loc.getX(), loc.getY(), nodex, nodey);
    }

    /**
     * Initialize grid placement model.
     *
     * @param loc field dimensions (in meters)
     * @param s node configuration string
     */
    public Pyramid(Location loc, String s)
    {
      init(loc, s);
    }

    /**
     * Initialize grid placement model.
     *
     * @param field field dimensions string
     * @param nodes node configuration string
     */
    public Pyramid(String field, String nodes)
    {
      init(field, nodes);
    }

    /**
     * Initialize grid placement model.
     *
     * @param field field dimensions string
     * @param nodes node configuration string
     */
    private void init(String field, String nodes)
    {
      String[] data = field.split("x|,");
      if(data.length!=2) throw new IllegalArgumentException("invalid format, expected i,j");
      init(new Location.Location2D(Float.parseFloat(data[0]), Float.parseFloat(data[1])), nodes);
    }

    /**
     * Initialize grid placement model.
     *
     * @param loc field dimensions (in meters)
     * @param s node configuration string
     */
    private void init(Location loc, String s)
    {
      String[] data = s.split("x|,");
      if(data.length!=2) throw new IllegalArgumentException("invalid format, expected i,j");
      init(loc.getX(), loc.getY(), Integer.parseInt(data[0]), Integer.parseInt(data[1]));
    }

    /**
     * Initialize grid placement model.
     *
     * @param fieldx field x-dimension (in meters)
     * @param fieldy field y-dimension (in meters)
     * @param nodex number of nodes in x-dimension
     * @param nodey number of nodes in y-dimension
     */
    private void init(float fieldx, float fieldy, int nodex, int nodey)
    {
      this.fieldx = fieldx;
      this.fieldy = fieldy;
      this.nodex = nodex;
      this.nodey = nodey;

      locs = new ArrayList();
      // construct grid of nodes
      do {
        long row = i/nodex;
        long col = i%nodex;

        Location nLoc = getNext();

        long nRow = (row <= nodey/2) ? row : nodey - row;
        if (i > 1 && col <= nRow) {
          locs.add(nLoc);
        }
      } while(i/nodex != nodey);
      i = 0;
    }

    private Location getNext()
    {
      Location l = new Location.Location2D(
          (i%nodex)*fieldx/nodex, (i/nodex)*fieldy/nodey);
      i++;
      return l;
    }

    //////////////////////////////////////////////////
    // Placement interface
    //

    /** {@inheritDoc} */
    public Location getNextLocation()
    {
      if (i >= locs.size())
        throw new IllegalStateException("points exhausted");
      Location loc = (Location)locs.get((int)i);
      i++;
      return loc;
    }
  } // class: Pyramid

} // interface: Placement

