package alive;

/**
 * <ALIVE Project>
 * Agent base class
 * By Rogerio Neves
 * LSI - POLI - USP
 */


import java.util.*;
import javax.vecmath.*;

public abstract class Agent extends Thread {
  Vector agents = null;
  Environment env = null;
  Actor actor = null;
  public int id = 0;
  public Vector3d position = new Vector3d();     // Current object's position
  public Vector3d direction = null;    // Vector pointng to object's view direction
  public Vector3d speed = new Vector3d();        // Position increased every interaction
  public Vector3d acceleration = new Vector3d(); // Speed increased every interaction
  public double size = 1;                        // Default object's visual size
  public boolean alive = false;                   // Run while() condition
  public double[] param =  null;
  public String[] paramName = null;
  public Color3f color = new Color3f();
  public String path = null;
  public int highlightTime = 0;

  // ------------- methods that "Must be" overriden by child ---------------
  public abstract void behavior();
  public abstract Actor createActor();
  public abstract void updateActor();

  // ------------- this methods "Can be" overriden by child ----------------
  public void startUp() { } // Method executes before agent starts to run
  public void cleanUp() { } // Method executes after agent dies
  public synchronized void die() {
    /**
     * Method can be overriden
     * class must remove Actor from the Environment
     * and finalize all created objects to release memory.
     */
    alive = false;
    if (env.config.RENDER) env.removeActor(actor);
    // actor = null;
    env.removeAgent(this);
  }
  public String getInfo() { // Method returns information on agent
    String s = new String();
    s = "\n" + id + " Agent parameters\n";
    if (param!=null) for (int i = 0; i < param.length; i++)
      s = s + paramName[i] + " = " + round(param[i],5) + "\n";
    s = s + id + " Agent tuples"
        + "\npos = " + position.toString()
        + "\ndir = " + direction.toString()
        + "\nspd = " + speed.toString()
        + "\nacl = " + acceleration.toString()
        + "\ncolor = " + color.toString();
    return s;
  }
  public double get(String s) {
    for (int i = 0; i < param.length; i++) if (paramName[i].equals(s)) return param[i];
    return 0;
  }
  public synchronized boolean set(int i, double value) { // parameter adjust
    boolean done = false;
    try {
      param[i] = value;
      done = true;
    } catch (Exception e) { done = false;}
    return done;
  }
  public synchronized boolean set(String s, double value) { // parameter adjust
    boolean done = false;
    for (int i = 0; i < param.length; i++)
      if (s.equals(paramName[i])) done = set(i,value);
    return done;
  }
  public synchronized boolean set(int n, String s, double value) { // parameter adjust
    boolean done = false;
    try {
      param[n] = value;
      paramName[n] = s;
      done = true;
    } catch (Exception e) { done = false;}
    return done;
  }
  public synchronized boolean adjust(String s, double value) { // parameter adjust by extern
    return set(s,get(s)+value);
  }

  // ----------------------- Engine methods -----------------------------
  public final void configureToEnvironment(Environment env) {
    this.env = env;
    position.set(rnd(env.config.xMin,env.config.xMax),
                 rnd(env.config.yMin,env.config.yMax),
                 rnd(env.config.zMin,env.config.zMax));
    direction = generateRandomVector();
    path = env.config.codeBase;
  }
  public final void makeAlive() {
    if (paramName==null) {
      param = new double[1];
      paramName = new String[1];
      set(0,"No Parameters",0);
    }
    if (env.config.RENDER)  {
      actor = createActor();
      if (env.renderEnabled) { actor.setColor(color); updateActor();}
      env.addActor(actor);
    }
    alive = true;
    startUp();
  }
  public final void run() {
    /**
     * Custom procedures and agent dinamycs
     * must be implemented in behaviors() method.
     */
    while (alive&&env.alive) {
      // default rest, gives time for other processes to run
      do rest(env.config.SLEEP); while (env.PAUSED);
      execute();
    }
    cleanUp();
  }
  public final void execute() {
    move();
    behavior();
    if (env.config.RENDER&&env.renderEnabled&&actor!=null) {
      actor.setColor(color);
      if (highlightTime>0) {
        this.actor.setColor(1f,1f,1f);
        highlightTime--;
      }
      updateActor();
    }
  }
  public final void move() {
    speed.add(acceleration);
    if (speed.length()>env.MAX_SPEED) {
      speed.normalize();
      speed.scale(env.MAX_SPEED);
    }
    position.add(speed);
    collideBounds();
    clampBounds();
  }
  public final void highLight() { highlightTime = 1000/(env.config.SLEEP+1); }

  // --------------------------- Bounding handling ---------------------------
  private final void clampBounds() {
    if (position.x<env.config.xMin) position.x = (env.config.wrapx?env.config.xMax:env.config.xMin);
    if (position.y<env.config.yMin) position.y = (env.config.wrapy?env.config.yMax:env.config.yMin);
    if (position.z<env.config.zMin) position.z = (env.config.wrapz?env.config.zMax:env.config.zMin);
    if (position.x>env.config.xMax) position.x = (env.config.wrapx?env.config.xMin:env.config.xMax);
    if (position.y>env.config.yMax) position.y = (env.config.wrapy?env.config.yMin:env.config.yMax);
    if (position.z>env.config.zMax) position.z = (env.config.wrapz?env.config.zMin:env.config.zMax);
  }
  private final void collideBounds() {
    if (!env.config.wrapx) {
      if (position.x < env.config.xMin) speed.set( -env.config.k * speed.x, speed.y, speed.z);
      if (position.x > env.config.xMax) speed.set( -env.config.k * speed.x, speed.y, speed.z);
    }
    if (!env.config.wrapy) {
      if (position.y < env.config.yMin) speed.set(speed.x, -env.config.k * speed.y, speed.z);
      if (position.y > env.config.yMax) speed.set(speed.x, -env.config.k * speed.y, speed.z);
    }
    if (!env.config.wrapz) {
      if (position.z < env.config.zMin) speed.set(speed.x, speed.y, -env.config.k * speed.z);
      if (position.z > env.config.zMax) speed.set(speed.x, speed.y, -env.config.k * speed.z);
    }
  }
  // --------------------------- Access methods ---------------------------
  public Vector3d getPosition() { return position; }
  public Vector3d getDirection() { return direction; }
  public Vector3d getSpeed() { return speed; }
  public Vector3d getAcceleration() { return acceleration; }
  public synchronized void setDirection(Vector3d v) { direction = v; }
  public synchronized void setPosition(Vector3d p) { position = p; }
  public synchronized void setSpeed(Vector3d s) { speed = s; }
  public synchronized void setAcceleration(Vector3d a) { acceleration = a; }

  // --------------------------- Utility ------------------------------
  public String round(double val, int n) {
    return env.round(val, n);
  }
  public final void rest(int time) {
    try { sleep(time); }
    catch (InterruptedException e){ System.err.println(e); }
  }
  public static double test(boolean b) {
    if (b) return 1;
    else return 0;
  }
  public static double signal(double val) {
    if (val<0) return -1;
    else return 1;
  }
  public static boolean probability(double p) {
    return (rnd(0,1)<p);
  }
  public static Vector3d generateRandomVector() {
    Vector3d v = new Vector3d(2*Math.random()-1,
                              2*Math.random()-1,
                              2*Math.random()-1);
    v.normalize();
    return v;
  }
  public static double rnd(double min,double max) {
    return (max-min)*Math.random()+min;
  }
  public static double rnd(int min,int max) {
    return (max-min)*Math.random()+min;
  }
  public final void message(String s) { env.message(s); }
  public final void log(String s) { env.log(s); }
}