//////////////////////////////////////////////////
// JIST (Java In Simulation Time) Project
// Timestamp: <Mobility.java Sun 2005/03/13 11:02:59 barr rimbase.rimonbarr.com>
//

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

package jist.swans.field;

import jist.swans.misc.Location;
import jist.swans.misc.Util;
import jist.swans.misc.Location.Location2D;
import jist.swans.Constants;

import jist.runtime.JistAPI;
import jist.runtime.Main;

/**
 * Interface of all mobility models.
 *
 * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
 * @version $Id: Mobility.java,v 1.22 2005/03/13 16:11:54 barr Exp $
 * @since SWANS1.0
 */
public interface Mobility
{

  /**
   * Initiate mobility; initialize mobility data structures.
   *
   * @param f field entity
   * @param id node identifier
   * @param loc node location
   * @return mobility information object
   */
  MobilityInfo init(FieldInterface f, Integer id, Location loc);

  /**
   * Schedule next movement. This method will again be called after every
   * movement on the field.
   *
   * @param f field entity
   * @param id radio identifier
   * @param loc destination of move
   * @param info mobility information object
   */
  void next(FieldInterface f, Integer id, Location loc, MobilityInfo info);


  //////////////////////////////////////////////////
  // mobility information
  //

  /**
   * Interface of algorithm-specific mobility information objects.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */

  public static interface MobilityInfo
  {
    /** The null MobilityInfo object. */
    MobilityInfo NULL = new MobilityInfo()
    {
    };
  }


  //////////////////////////////////////////////////
  // static mobility model
  //


  /**
   * Static (noop) mobility model.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */

  public static class Static implements Mobility
  {
    //////////////////////////////////////////////////
    // Mobility interface
    //

    /** {@inheritDoc} */
    public MobilityInfo init(FieldInterface f, Integer id, Location loc)
    {
      return null;
    }

    /** {@inheritDoc} */
    public void next(FieldInterface f, Integer id, Location loc, MobilityInfo info)
    {
    }

  } // class Static


  //////////////////////////////////////////////////
  // random waypoint mobility model
  //

  /**
   * Random waypoint state object.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */
  public static class RandomWaypointInfo implements MobilityInfo
  {
    /** number of steps remaining to waypoint. */
    public int steps;

    /** duration of each step. */
    public long stepTime;

    /** waypoint. */
    public Location waypoint;
  }

  /**
   * Random waypoint mobility model.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */
  public static class RandomWaypoint implements Mobility
  {
    /** thickness of border (for float calculations). */
    public static final float BORDER = (float)0.0005;

    /** Movement boundaries. */
    private Location.Location2D bounds;

    /** Waypoint pause time. */
    private long pauseTime;

    /** Step granularity. */
    private float precision;

    /** Minimum movement speed. */
    private float minspeed;

    /** Maximum movement speed. */
    private float maxspeed;

    /**
     * Initialize random waypoint mobility model.
     *
     * @param bounds boundaries of movement
     * @param pauseTime waypoint pause time
     * @param precision step granularity
     * @param minspeed minimum speed
     * @param maxspeed maximum speed
     */
    public RandomWaypoint(Location.Location2D bounds, long pauseTime,
        float precision, float minspeed, float maxspeed)
    {
      init(bounds, pauseTime, precision, minspeed, maxspeed);
    }

    /**
     * Initialize random waypoint mobility model.
     *
     * @param bounds boundaries of movement
     * @param config configuration string
     */
    public RandomWaypoint(Location.Location2D bounds, String config)
    {
      //END -- Added by Emre Atsan
	// @author Elmar Schoch >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
	// Implemented functionality
	// throw new RuntimeException("not implemented");
        // parse config string of the form
        // <pause-time>,<precicion>,<minspeed>,<maxspeed>
    	String[] data = config.split(",");
    	init(bounds,Integer.parseInt(data[0]),Float.parseFloat(data[1]),
                   Float.parseFloat(data[2]), Float.parseFloat(data[3]));
	// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    /**
     * Initialize random waypoint mobility model.
     *
     * @param bounds boundaries of movement
     * @param pauseTime waypoint pause time (in ticks)
     * @param precision step granularity
     * @param minspeed minimum speed
     * @param maxspeed maximum speed
     */
    private void init(Location.Location2D bounds, long pauseTime,
        float precision, float minspeed, float maxspeed)
    {
      this.bounds = bounds;
      this.pauseTime = pauseTime;
      this.precision = precision;
      this.minspeed = minspeed;
      this.maxspeed = maxspeed;
    }

    //////////////////////////////////////////////////
    // Mobility interface
    //

    /** {@inheritDoc} */
    public MobilityInfo init(FieldInterface f, Integer id, Location loc)
    {
      return new RandomWaypointInfo();
    }

    /** {@inheritDoc} */
    public void next(FieldInterface f, Integer id, Location loc, MobilityInfo info)
    {
      if(Main.ASSERT) Util.assertion(loc.inside(bounds));
      try
      {
        RandomWaypointInfo rwi = (RandomWaypointInfo)info;
        if(rwi.steps==0)
        {
          // reached waypoint
          JistAPI.sleep(pauseTime);
          rwi.waypoint = new Location.Location2D(
              (float)(BORDER + (bounds.getX()-2*BORDER)*Constants.random.nextFloat()),
              (float)(BORDER + (bounds.getY()-2*BORDER)*Constants.random.nextFloat()));
          if(Main.ASSERT) Util.assertion(rwi.waypoint.inside(bounds));
          float speed = minspeed + (maxspeed-minspeed) * Constants.random.nextFloat();
          float dist = loc.distance(rwi.waypoint);
          rwi.steps = (int)Math.max(Math.floor(dist / precision),1);
          if(Main.ASSERT) Util.assertion(rwi.steps>0);
          float time = dist / speed;
          rwi.stepTime = (long)(time*Constants.SECOND/rwi.steps);
        }
        // take step
        JistAPI.sleep(rwi.stepTime);
        Location step = loc.step(rwi.waypoint, rwi.steps--);
        f.moveRadioOff(id, step);
      }
      catch(ClassCastException e)
      {
        // different mobility model installed
      }
    }

  } // class RandomWaypoint


  //////////////////////////////////////////////////
  // Teleport mobility model
  //

  /**
   * Teleport mobility model: pick a random location and teleport to it,
   * then pause for some time and repeat.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */
  public static class Teleport implements Mobility
  {
    /** Movement boundaries. */
    private Location.Location2D bounds;

    /** Waypoint pause time. */
    private long pauseTime;

    /**
     * Initialize teleport mobility model.
     *
     * @param bounds boundaries of movement
     * @param pauseTime waypoint pause time (in ticks)
     */
    public Teleport(Location.Location2D bounds, long pauseTime)
    {
      this.bounds = bounds;
      this.pauseTime = pauseTime;
    }

    /** {@inheritDoc} */
    public MobilityInfo init(FieldInterface f, Integer id, Location loc)
    {
      if(pauseTime==0) return null;
      return MobilityInfo.NULL;
    }

    /** {@inheritDoc} */
    public void next(FieldInterface f, Integer id, Location loc, MobilityInfo info)
    {
      if(pauseTime>0)
      {
        JistAPI.sleep(pauseTime);
        loc = new Location.Location2D(
            (float)bounds.getX()*Constants.random.nextFloat(),
            (float)bounds.getY()*Constants.random.nextFloat());
        f.moveRadio(id, loc);
      }
    }

  } // class: Teleport

//  public static class ManualInfo implements MobilityInfo
//  {
//    /** number of steps remaining to waypoint. */
//    public int steps;
//
//    /** duration of each step. */
//    public long stepTime;
//
//    /** waypoint. */
//    public Location waypoint;
//
//    /** time to wait after reaching the waypoint */
//    public long pauseTime;
//
//    /** the speed to use */
//    public float speed;
//
//    /**
//     * Assigns the given ManualInfo.
//     *
//     * @param next
//     */
//    public void assign(ManualInfo next) {
//      this.steps = next.steps;
//      this.stepTime = next.stepTime;
//      this.waypoint = next.waypoint;
//      this.pauseTime = next.pauseTime;
//      this.speed = next.speed;
//    }
//  }
//
//  public static class Manual implements Mobility
//  {
//    /** Movement boundaries. */
//    private Location.Location2D bounds;
//
//    /** Step granularity. */
//    private float precision;
//
//    /** list of {@link ManualInfo} */
//    private List infos;
//
//    /**
//     * Initialize random waypoint mobility model.
//     *
//     * @param bounds boundaries of movement
//     * @param pauseTime waypoint pause time
//     * @param precision step granularity
//     * @param minspeed minimum speed
//     * @param maxspeed maximum speed
//     */
//    public Manual(Location.Location2D bounds, Location next, float speed,
//        float precision, long pauseTime)
//    {
//      this.bounds = bounds;
//      this.precision = precision;
//      this.infos = new LinkedList();
//
//      if(Main.ASSERT) Util.assertion(next.inside(bounds));
//
//      ManualInfo info = new ManualInfo();
//      info.waypoint = next;
//      info.pauseTime = pauseTime;
//      info.speed = speed;
//
//      this.infos.add(info);
//    }
//
//    public void addWaypoint(Location next, float speed, long pauseTime) {
//      ManualInfo info = new ManualInfo();
//      info.waypoint = next;
//      info.pauseTime = pauseTime;
//      info.speed = speed;
//
//      this.infos.add(info);
//    }
//
//    //////////////////////////////////////////////////
//    // Mobility interface
//    //
//
//    /** {@inheritDoc} */
//    public MobilityInfo init(FieldInterface f, Integer id, Location loc)
//    {
//      ManualInfo info = (ManualInfo) infos.get(0);
//      infos.remove(0);
//
//      float dist = loc.distance(info.waypoint);
//      info.steps = (int)Math.max(Math.floor(dist / precision),1);
//      if(Main.ASSERT) Util.assertion(info.steps>0);
//
//      float time = dist / info.speed;
//      info.stepTime = (long)(time*Constants.SECOND/info.steps);
//
//      return info;
//    }
//
//    /** {@inheritDoc} */
//    public void next(FieldInterface f, Integer id, Location loc, MobilityInfo info)
//    {
//      if(Main.ASSERT) Util.assertion(loc.inside(bounds));
//      try
//      {
//        ManualInfo rwi = (ManualInfo)info;
//        if(rwi.steps==0)
//        {
//          // reached waypoint
//          JistAPI.sleep(rwi.pauseTime);
//
//          // if no more points, then do not move anymore
//          if (0 == infos.size()) {
//            rwi.waypoint = null;
//            return;
//          }
//
//          ManualInfo next = (ManualInfo) infos.get(0);
//          infos.remove(0);
//
//          rwi.assign(next);
//
//          float dist = loc.distance(rwi.waypoint);
//          rwi.steps = (int)Math.max(Math.floor(dist / precision),1);
//          if(Main.ASSERT) Util.assertion(rwi.steps>0);
//
//          float time = dist / rwi.speed;
//          rwi.stepTime = (long)(time*Constants.SECOND/rwi.steps);
//        }
//        // take step
//        JistAPI.sleep(rwi.stepTime);
//        Location step = loc.step(rwi.waypoint, rwi.steps--);
//        f.moveRadioOff(id, step);
//      }
//      catch(ClassCastException e)
//      {
//        // different mobility model installed
//      }
//    }
//
//  }

  /**
   * Random Walk mobility model: pick a direction, walk a certain distance in
   * that direction, with some fixed and random component, reflecting off walls
   * as necessary, then pause for some time and repeat.
   *
   * @author Rimon Barr &lt;barr+jist@cs.cornell.edu&gt;
   * @since SWANS1.0
   */
  public static class RandomWalk implements Mobility
  {
    /** fixed component of step size. */
    private double fixedRadius;
    /** random component of step size. */
    private double randomRadius;
    /** time wait between steps. */
    private long pauseTime;
    /** field boundaries. */
    private Location.Location2D bounds;

    /**
     * Create and initialize new random walk object.
     *
     * @param bounds field boundaries
     * @param fixedRadius fixed component of step size
     * @param randomRadius random component of step size
     * @param pauseTime time wait between steps
     */
    public RandomWalk(Location.Location2D bounds, double fixedRadius, double randomRadius, long pauseTime)
    {
      init(bounds, fixedRadius, randomRadius, pauseTime);
    }

    /**
     * Create an initialize a new random walk object.
     *
     * @param bounds field boundaries
     * @param config configuration string: "fixed,random,time(in seconds)"
     */
    public RandomWalk(Location.Location2D bounds, String config)
    {
      String[] data = config.split(",");
      if(data.length!=3)
      {
        throw new RuntimeException("expected format: fixedradius,randomradius,pausetime(in seconds)");
      }
      double fixedRadius = Double.parseDouble(data[0]);
      double randomRadius = Double.parseDouble(data[1]);
      long pauseTime = Long.parseLong(data[2])*Constants.SECOND;
      init(bounds, fixedRadius, randomRadius, pauseTime);
    }

    /**
     * Initialize random walk object.
     *
     * @param bounds field boundaries
     * @param fixedRadius fixed component of step size
     * @param randomRadius random component of step size
     * @param pauseTime time wait between steps
     */
    private void init(Location.Location2D bounds, double fixedRadius, double randomRadius, long pauseTime)
    {
      if(fixedRadius+randomRadius>bounds.getX() || fixedRadius+randomRadius>bounds.getY())
      {
        throw new RuntimeException("maximum step size can not be larger than field dimensions");
      }
      this.bounds = bounds;
      this.fixedRadius = fixedRadius;
      this.randomRadius = randomRadius;
      this.pauseTime = pauseTime;
    }

    //////////////////////////////////////////////////
    // mobility interface
    //

    /** {@inheritDoc} */
    public MobilityInfo init(FieldInterface f, Integer id, Location loc)
    {
      if(pauseTime==0) return null;
      return MobilityInfo.NULL;
    }

    /** {@inheritDoc} */
    public void next(FieldInterface f, Integer id, Location loc, MobilityInfo info)
    {
      // compute new random position with fixedRadius+randomRadius() distance
      double randomAngle = 2*Math.PI*Constants.random.nextDouble();
      double r = fixedRadius + Constants.random.nextDouble()*randomRadius;
      double x = r * Math.cos(randomAngle), y = r * Math.sin(randomAngle);
      double lx = loc.getX()+x, ly = loc.getY()+y;
      // bounds check and reflect
      if(lx<0) lx=-lx;
      if(ly<0) ly=-ly;

      //START -- Added By Emre Atsan
      if(lx>bounds.getX()) lx = bounds.getX()-(lx-bounds.getX());
      if(ly>bounds.getY()) ly = bounds.getY()-(ly-bounds.getY());
      //END -- Added by Emre Atsan

//      if(lx>bounds.getX()) lx = bounds.getX()-lx;
//      if(ly>bounds.getY()) ly = bounds.getY()-ly;
      // move
      if(pauseTime>0)
      {
        JistAPI.sleep(pauseTime);
        Location l = new Location.Location2D((float)lx, (float)ly);
        //System.out.println("move at t="+JistAPI.getTime()+" to="+l);
        f.moveRadio(id, l);
      }
    }

    /** {@inheritDoc} */
    public String toString()
    {
      return "RandomWalk(r="+fixedRadius+"+"+randomRadius+",p="+pauseTime+")";
    }

  } // class: RandomWalk


  public static class RandomDirectionInfo implements MobilityInfo
  {
    //random direction selected between [0,2pi] uniformly.
    public double direction = 2*Math.PI*Constants.random.nextDouble();


  } // class: RandomDirectionInfo

  public static class RandomDirection implements Mobility
  {
    private long pauseTime;

    private float constantVelocity;

    private Location.Location2D bounds;


    public RandomDirection(Location.Location2D bounds, String config)
      {
        //START --- Added by Emre Atsan

        String directionConfigOptions [];
        directionConfigOptions= config.split(":");

        init(bounds,Float.parseFloat(directionConfigOptions[0]),Long.parseLong(directionConfigOptions[1]));

        // throw new RuntimeException("not implemented");

        //END -- Added by Emre Atsan
      }

  public RandomDirection(Location.Location2D bounds, float constantVelocity, long pauseTime )
  {
    init(bounds,constantVelocity,pauseTime);
  }


  private void init(Location.Location2D bounds, float constantVelocity, long pauseTime) {

    if(constantVelocity > bounds.getX() || constantVelocity > bounds.getY())
    {
      throw new RuntimeException("Speed (m/sec) cannot be larger than simulation area size!");
    }
    else
    {
      this.bounds = bounds;
      this.constantVelocity = constantVelocity;
      this.pauseTime = pauseTime*Constants.SECOND;

    }
  }


  public MobilityInfo init(FieldInterface f, Integer id, Location loc) {
        if(pauseTime==0) return null;
        return new RandomDirectionInfo();
  }

  public void next(FieldInterface f, Integer id, Location loc, MobilityInfo info) {

    if(Main.ASSERT) Util.assertion(loc.inside(bounds));
        try
        {
          RandomDirectionInfo rdi = (RandomDirectionInfo)info;
          double nodeAngle = rdi.direction;

          double x = constantVelocity * Math.cos(nodeAngle), y = constantVelocity * Math.sin(nodeAngle);
          double lx = loc.getX()+x, ly = loc.getY()+y;
          boolean sleptBefore =false;
          // bounds check and reflect
          if(lx<0)
          {
            lx=-lx;
            //Update Node Movement Angle after reflection from the bound.
            rdi.direction = Math.PI-nodeAngle;
            JistAPI.sleep(pauseTime);
            sleptBefore=true;
          }
          else if(lx>bounds.getX())
          {
            lx = bounds.getX()-(lx-bounds.getX());
            rdi.direction = Math.PI-nodeAngle;
            JistAPI.sleep(pauseTime);
            sleptBefore=true;
          }

          if(ly<0)
          {
            ly=-ly;
            rdi.direction = -nodeAngle;
            if(!sleptBefore)
            {
              JistAPI.sleep(pauseTime);
            }
          }
          else if(ly>bounds.getY())
          {
            ly = bounds.getY()-(ly-bounds.getY());
            rdi.direction = -nodeAngle;

            if(!sleptBefore)
            {
              JistAPI.sleep(pauseTime);
            }
          }

          //Sleep for one second in every step of the movement.
          JistAPI.sleep(1*Constants.SECOND);

          Location l = new Location.Location2D((float)lx, (float)ly);

         if(Main.ASSERT) Util.assertion(l.inside(bounds));


         if(id.intValue() ==1)
         {
           System.out.println(id+"\t"+l.getX()+"\t"+l.getY());
         }
         f.moveRadio(id, l);

        }
        catch(ClassCastException e)
        {
          // different mobility model installed
        }

  }

    public String toString()
    {
      return "RandomDirection(speed="+constantVelocity+" ,p="+pauseTime+")";
    }

  } // class: RandomDirection

  public static class BoundlessSimulationAreaInfo implements MobilityInfo
  {
    //velocity of the mobile node
    public double velocity;
    public double direction;

    public BoundlessSimulationAreaInfo(float velocity, double direction)
    {
      this.velocity = velocity;
      this.direction= direction;
    }

  } // class: BoundlessSimulationAreaInfo

  /**
   * change in velocity is uniformly distributed between [aMin*deltaT,aMax*deltaT]
   * change in direction is uniformly distributed between [-MaxAngularChange*deltaT,maxAngularChange*deltaT]
   * velocity is constraint in [vMin,vMax]
   *
   * mobilityOpts format: vMin:vMax:aMin:aMax:deltaT:maxAngularChange[:vStart:angStart]
   *
   * @author kurth
   */
  public static class BoundlessSimulationArea implements Mobility {
    private Location.Location2D bounds;

    private double deltaT;

    private double vMin;
    private double vMax;

    private double aMin;
    private double aMax;

    private double maxAngularChange;

    private float vStart;
    private double angularStart;

    public BoundlessSimulationArea(Location.Location2D bounds, double vMin, double vMax,
        double aMin, double aMax, double deltaT, double maxAngularChange) {
      init(bounds, vMin, vMax, aMin, aMax, deltaT, maxAngularChange, 0, 0);

    }

    public BoundlessSimulationArea(Location2D bounds, String config) {
      String directionConfigOptions[];
      directionConfigOptions = config.split(":");
      float vStart = 0;
      double angularStart = 0;
      if (directionConfigOptions.length > 6)
        vStart = Float.parseFloat(directionConfigOptions[6]);
      if (directionConfigOptions.length > 7)
        angularStart = Double.parseDouble(directionConfigOptions[7]);
      init(bounds, Double.parseDouble(directionConfigOptions[0]),
          Double.parseDouble(directionConfigOptions[1]),
          Double.parseDouble(directionConfigOptions[2]),
          Double.parseDouble(directionConfigOptions[3]),
          Double.parseDouble(directionConfigOptions[4]),
          Double.parseDouble(directionConfigOptions[5]),
          vStart,
          angularStart);
    }

    private void init(Location.Location2D bounds, double vMin, double vMax,
        double aMin, double aMax, double deltaT, double maxAngularChange,
        float vStart, double angularStart) {
      this.vMin = vMin;
      this.vMax = vMax;
      this.aMin = aMin;
      this.aMax = aMax;
      this.deltaT = deltaT;
      this.maxAngularChange = maxAngularChange * Math.PI;
      this.vStart = vStart;
      this.angularStart = angularStart;
      this.bounds = bounds;
    }

    public MobilityInfo init(FieldInterface f, Integer id, Location loc) {
      return new BoundlessSimulationAreaInfo(this.vStart, this.angularStart);
    }

    public void next(FieldInterface f, Integer id, Location loc,
        MobilityInfo info) {

      if (Main.ASSERT)
        Util.assertion(loc.insidePartition(bounds));

      try {
        BoundlessSimulationAreaInfo bsai = (BoundlessSimulationAreaInfo) info;

        double currVelocity = bsai.velocity;
        double currDirection = bsai.direction;

        //change in the velocity which is uniformly distributed between [aMin*deltaT,aMax*deltaT]
        double deltaV = (Constants.random.nextDouble() * (aMax-aMin) + aMin) * deltaT;

        //change in the direction which is uniformly distributed between [-MaxAngularChange*deltaT,maxAngularChange*deltaT]
        double changeInDirection = ((Constants.random.nextDouble() * 2.0 * maxAngularChange) - maxAngularChange)
            * deltaT;

        double nextVelocity = Math.min(Math.max(currVelocity + deltaV, vMin), vMax);
        double nextDirection = currDirection + changeInDirection;

        //coordinates of calculated next location.
        double lx = (loc.getX() + currVelocity * Math.cos(currDirection) * deltaT);
        double ly = (loc.getY() + currVelocity * Math.sin(currDirection) * deltaT);

        //update the MobilityInfo data for the next step calculations.
        bsai.velocity = nextVelocity;
        bsai.direction = nextDirection;

        // bounds check and wrap-around
        if (lx < 0) {
          lx = bounds.getX() + lx;
        } else if (lx >= bounds.getX()) {
          lx = lx - bounds.getX();
        }

        if (ly < 0) {
          ly = bounds.getY() + ly;
        } else if (ly >= bounds.getY()) {
          ly = ly - bounds.getY();
        }

        //Sleep for one second in every step of the movement.
        JistAPI.sleep((long) (deltaT * Constants.SECOND));

        Location l = new Location.Location2D((float) lx, (float) ly);

        if (Main.ASSERT)
          Util.assertion(l.insidePartition(bounds));

        f.moveRadio(id, l);

      } catch (ClassCastException e) {
        // different mobility model installed
      }

    }

    public String toString() {
      return "BoundlessSimulationArea(Min. Velocity=" + vMin
          + " ,Max. Velocity=" + vMax
          + " ,Max.Accelaration=" + aMax + " ,deltaT=" + deltaT
          + " ,Max. Angular Change in direction (per sec.)" + maxAngularChange
          + ")";
    }

  }//class: BoundlessSimulationArea

} // interface Mobility

