package alive;

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

import java.net.*;
import java.util.*;
import javax.media.j3d.*;
import javax.media.j3d.Locale;
import javax.vecmath.*;
import com.sun.j3d.utils.behaviors.keyboard.*;
import com.sun.j3d.utils.behaviors.mouse.*;

public class Environment extends Thread {

  // Comm related
  MulticastSocket socket;
  DatagramPacket packet;
  InetAddress address;
  String ip;
  int port;

  // Java3D related
  private VirtualUniverse u = new VirtualUniverse();
  public Locale locale = new Locale(u);
  public BranchGroup viewGroup = new BranchGroup();
  public BranchGroup lightGroup = new BranchGroup();
  public BranchGroup actorGroup = new BranchGroup();
  public TransformGroup viewTransform = new TransformGroup();
  public DirectionalLight light = new DirectionalLight();
  public DirectionalLight light2 = new DirectionalLight();
  public BoundingSphere bounds = new BoundingSphere(new Point3d(), 1000.0);
  private Transform3D t = new Transform3D();

  // Env related
  Runtime runtime = Runtime.getRuntime();
  public EnvConfig config = null;
  public Vector agents = new Vector();
  public ViewWindow[] v = new ViewWindow[6];
  public UserInterface control = null;
  public double[] param =  null;
  public String[] paramName = null;
  public String path = null;
  public boolean renderEnabled = true;
  public boolean PAUSED = false;
  public boolean alive = true;
  public double MAX_SPEED = 0.1;
  public int LOOP_COUNT = 0;
  public double elapsed = 0;
  public Date startDate, date;
  int i, xPos = 0, yPos = 0, nViews, nAgents = 0, index = 1, ref = -1, fc = 0;
  Agent a = null, ag = null;
  Actor ac;

  public final void configureEnvironment(EnvConfig cfg) {
    // Starts up the environment windows and behaviors
    config = cfg;
    path = config.codeBase;
    message("\n(1) Processing Configuration Parameters...  ");
    if (config.RENDER) {
      for (i = 0; i < 6; i++) if (config.view[i]) { nViews++; ref = i; }
      message("\n(2) Starting View Windows...  ");
      message("\n -Number of Cams = " + nViews + " on " + new Vector3d(config.xPos, config.yPos, config.zPos));
      // Starts view windows
      startView(0, "Left View", makeTransform(0, 90));
      startView(1, "Front View", makeTransform(0, 0));
      startView(2, "Right View", makeTransform(0, -90));
      startView(3, "Rear View", makeTransform(0, 180));
      startView(4, "Ground View", makeTransform( -90, 0));
      startView(5, "Top View", makeTransform(90, 0));
      t.set(new Vector3d(config.xPos, config.yPos, config.zPos));
      viewTransform.setTransform(t); // Add a global viwing TG and
      viewTransform.setCapability(viewTransform.ALLOW_TRANSFORM_WRITE);
      viewTransform.setCapability(viewTransform.ALLOW_TRANSFORM_READ);
      viewGroup.addChild(viewTransform); // Operate all cams toguether
      // Starts navigation behaviors
      if (config.MOUSE_NAVIGATION) {
        MouseRotate mRotate = new MouseRotate(MouseBehavior.INVERT_INPUT);
        mRotate.setTransformGroup(viewTransform);
        mRotate.setSchedulingBounds(bounds);
        viewGroup.addChild(mRotate);
        MouseTranslate mTranslate = new MouseTranslate(MouseBehavior.INVERT_INPUT);
        mTranslate.setTransformGroup(viewTransform);
        mTranslate.setSchedulingBounds(bounds);
        viewGroup.addChild(mTranslate);
        MouseZoom mZoom = new MouseZoom(MouseBehavior.INVERT_INPUT);
        mZoom.setTransformGroup(viewTransform);
        mZoom.setSchedulingBounds(bounds);
        viewGroup.addChild(mZoom);
      }
      if (config.KEY_NAVIGATION) {
        KeyNavigatorBehavior keyNav = new KeyNavigatorBehavior(viewTransform);
        keyNav.setSchedulingBounds(bounds);
        viewGroup.addChild(keyNav);
      }
      viewGroup.compile();
      locale.addBranchGraph(viewGroup);
      message("\n -DoubleBufferAvailable " + v[ref].GetCanvas().getDoubleBufferAvailable());
      message("\n -StereoAvailable " + v[ref].GetCanvas().getStereoAvailable());
      message("\n -ScreenSize " + v[ref].GetCanvas().getScreen3D().getSize().toString());
      message("\n -WindowSize " + v[ref].getSize().toString());
      createLights();
      actorGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
      actorGroup.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
      actorGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
      locale.addBranchGraph(actorGroup);
      // Sets up network multicast
      if (config.NET_CAST) {
        int separator = config.ip.indexOf(":");
        ip = config.ip.substring(0,separator).trim();
        port = getInt(config.ip.substring(separator+1, config.ip.length()).trim());
        try { address = InetAddress.getByName(ip); } catch(Exception e) { message("\n" + e.getMessage() + "\n"); }
        try { socket = new MulticastSocket(port); }  catch(Exception e) { message("\n" + e.getMessage() + "\n"); }
        try { socket.joinGroup(address); }           catch(Exception e) { message("\n" + e.getMessage() + "\n"); }
        message("\nMultiCasting to IP " + ip + " PORT " + port);
      }
    }
    // Starts UI
    control = new UserInterface(this);
    if (ref>-1) control.setLocation(v[ref].xScreenSize - 400, v[ref].yScreenSize - 440);
    createCustomUI();
    control.setVisible(true);
  }

  public final void run() {
    /**
     * Environment Engine
     * Custom procedures and environment dinamycs
     * must be implemented in update() method.
     */
    message("\n(3) Starting Environment.run() Thread!  ");
    fc = getInt(control.fc.getText());
    createContent();
    startDate = new Date();
    message("\nStarted: " + startDate);
    startUp();
    if (config.RENDER)
      for (i = 0; i < 6; i++) if (config.view[i]) v[i].setVisible(true);
    rest(2000);  // Give time to screens to come up
    while(alive) {
      do rest(config.SLEEP); while (PAUSED);
      update(); elapsed ++;
      if (getInt(control.fc.getText())>0) {
        fc--;
        if (fc<1) {
          renderEnabled = true;
          fc = getInt(control.fc.getText());
        } else renderEnabled = false;
      }
      if (config.SEQUENTIAL) for (int i=0; i<agents.size();i++) ((Agent)agents.get(i)).execute();
      if (config.NET_CAST&&renderEnabled) {
        String s = new String( "Packet," + elapsed +
            "\nCam," + config.xPos + "," + config.yPos + "," + config.zPos + "\n");
        for (int i=0; i<agents.size();i++) {
          a = ((Agent)agents.get(i)); ac = a.actor;
          s = s + a.id + "," + ac.model + "," + ac.texturefile + "," + ac.transparency + "," + round(ac.scale,4) +
              "," + round(ac.emissiveColor.x,4) + "," + round(ac.emissiveColor.y,4) + "," + round(ac.emissiveColor.z,4) +
              "," + round(ac.pos.x,4) + "," + round(ac.pos.y,4) + "," + round(ac.pos.z,4) +
              "," + round(ac.dir.x,4) + "," + round(ac.dir.y,4) + "," + round(ac.dir.z,4) + ",\n";
          if (s.length()>49152) {
            sendString(s);
            s = new String();
          }
        }
        sendString(s);
      }
    }
    if (config.SEQUENTIAL) for (int i=0; i<agents.size();i++) ((Agent)agents.get(i)).cleanUp();
    cleanUp();
    agents.removeAllElements();
    message("\nFinished: " + new Date());
  }
  public final void resetView() {
    // Resets camera location and angle
    t.set(new Vector3d(config.xPos, config.yPos, config.zPos));
    viewTransform.setTransform(t);
  }
  public final void sendString(String s) {
    // Multicast to socket
    packet = new DatagramPacket(s.getBytes(), s.length(), address, port);
    try { socket.send(packet); } catch(Exception e) { message("\n" + e.getMessage() + "\n"); }
  }
  public final void rest(int time) {
    // Sleep for given milisseconds
    try { sleep(time); }
    catch (InterruptedException e){ System.err.println(e); }
  }
  public final void destroy() {
    // Finish the experiment
    renderEnabled = false;
    alive = false;
    control.removeAll();
    control.dispose();
    control = null;
     if (config.RENDER) for(int i = 0; i < 6; i++) if (config.view[i]) { v[i].close(); }
    u.removeAllLocales();
    if (config.STAND_ALONE) System.exit(0);
  }

  private final void startView(int n, String name, Transform3D t) {
    // Initialize render windows
    if (config.view[n]) {
      int divs = 2;
      if (nViews>2) divs = 3;
      v[n] = new ViewWindow(this, name, divs);
      v[n].setTransform(t);
      viewTransform.addChild(v[n].getTransformGroup());
      v[n].setLocation(xPos,yPos);
      if (!config.AUTOSIZE) v[n].setSize(config.xSize, config.ySize);
      xPos = xPos + v[n].getSize().width;
      if (nViews>4&&n==2) {
        xPos = 0;
        yPos = yPos + v[n].getSize().height;
      }
      if (1.1*xPos>=v[n].xScreenSize) {
        xPos = 0;
        yPos = yPos + v[n].getSize().height;
      }
    }
  }
  private final Transform3D makeTransform(double rotX, double rotY) {
    t = new Transform3D();
    Transform3D t1 = new Transform3D();
    Transform3D t2 = new Transform3D();
    Transform3D t3 = new Transform3D();
    t1.rotX(rotX*Math.PI/180);
    t2.rotY(rotY*Math.PI/180);
    t.mul(t1); t.mul(t2);
    t.setScale(1f); // Default Scale
    return t;
  }
  public synchronized void setParm(String name, double val) {
    try {
      boolean done = set(name, val);
      if (done) message("\nenv.set(\"" + name + "\"," + val + ")");
    }
    catch(NullPointerException e) { message("\nsetAgentParam(Err)"); }
  }

  // --------------------- Agent and actor handling ---------------------
  public synchronized void addAgent(Agent newAgent) {
    if (nAgents<config.POP_LIMIT) {
      newAgent.id = index;
      if (config.RENDER)
        newAgent.actor.id = index;
      index++;
      agents.add(newAgent);
      if (!config.SEQUENTIAL)
        newAgent.start();
      nAgents++;
    } else newAgent.die();
  }
  public synchronized void killAgent(Agent a) { // can be overriden
    int i = agents.indexOf(a);
    ((Agent)agents.get(i)).die();
  }
  public synchronized void removeAgent(Agent a) {
    boolean ok = agents.remove(a);
    if (ok) nAgents--;
  }
  public synchronized Agent getAgent(int i) {
    a = null;
    if (i<agents.size()&&i>=0) a = ((Agent)agents.get(i));
    return a;
  }
  public Agent findAgent(int id) {
    /* Returns the agent with the specified id
     * or the next identified living agent
     * in this case, (id!=ag.id)
     */
    ag = null;
    int i = 0;
    while (i<agents.size()) {
      ag = getAgent(i);
      if (ag.id>=id) break;
      i++;
    }
    return ag;
  }
  public synchronized void setAgentParam(int agn, String name, double val) {
    try {
      boolean done = findAgent(agn).set(name, val);
      if (done) message("\nAgent(" + agn + ").set(\"" + name + "\"," + val + ")");
    }
    catch(NullPointerException e) { message("\nsetAgentParam(Err)"); }
  }
  public void addActor(BranchGroup actor) {
    if (config.RENDER) actorGroup.addChild(actor);
  }
  public void removeActor(BranchGroup actor) {
    if (config.RENDER) actor.detach();
  }
  // -------------------- methods "Can be" overriden ----------------------
  public void update() {  }
  public void startUp() { } // Method executes before Environment starts to run
  public void cleanUp() { }    // Method executes defore Environment ends run
  public void createContent() {
    /* Here objects and actors are added to the simulation
     * This method must be overriden to draw the custom world
     * Here, one single bounding cube is created to show emperiment bondings
     */
  }
  public void createCustomUI() {
    control.ui.add(new java.awt.Label("Experiment interface not available"));
  }
  public void createLights() {
    /* Two default blank directional light is created to lit the virtual world
     * This method may be overrided to implement a custom light configuration
     */
    BranchGroup newGroup = new BranchGroup();
    light.setDirection( new Vector3f( 1.0f, -1.0f, -1.0f ) );
    light.setInfluencingBounds(bounds);
    light.setColor(new Color3f(1f, 1f, 1f));
    light.setCapability(light.ALLOW_COLOR_WRITE);
    light2.setDirection( new Vector3f( -1.0f, 1.0f, 1.0f ) );
    light2.setInfluencingBounds(bounds);
    light2.setColor(new Color3f(1f, 1f, 1f));
    light2.setCapability(light.ALLOW_COLOR_WRITE);
    newGroup.addChild(light);
    newGroup.addChild(light2);
    newGroup.compile();
    locale.addBranchGraph(newGroup);
  }
  public String getInfo() {
    String s = new String();
    long freeMem = runtime.freeMemory() / 1024;
    long totalMem = runtime.totalMemory() / 1024;
    s = "Env Info\n" +
        "Memory Free:      " + String.valueOf(freeMem) + " KB\n" +
        "Memory Total:     " + String.valueOf(totalMem) + " KB\n" +
        "Agents Running  : " + String.valueOf(nAgents) + "\n" +
        "Agents Created  : " + String.valueOf(index) + "\n" +
        "Elapsed Time:     " + String.valueOf(elapsed) +
        " cycles\nStarted  " + startDate.toString() +
        "\nToday is " + (new Date()).toString() +
        "\nParameters:\n";

    try {
      for (int i = 0; i < param.length; i++)
        s = s + paramName[i] + " = " + Double.toString(param[i]) + "\n";
    } catch (Exception e) { s = s + "None"; }
    return s;
  }
  public double get(String s) {
    for (int i = 0; i < param.length; i++)
      if (s.equals(paramName[i])) 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);
  }

  // ------------------------- Utility methods ----------------------------
  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;
  }

  // Number conversion tools
  public static int getInt(String f) {
    int i = 0;
    try { i = (int)Integer.parseInt(f.trim()); }
    catch(NumberFormatException e) { }
    return i;
  }
  public static float getFloat(String f) {
    float v = 0;
    try { v = (float)Float.parseFloat(f.trim());}
    catch(NumberFormatException e) { }
    if (v-v!=0) v = 0; // case NaN
    return v;
  }
  public static double getDouble(String f) {
    double d = 0;
    try { d = (double)Double.parseDouble(f.trim()); }
    catch(NumberFormatException e) { }
    if (d-d!=0) d = 0; // case NaN
    return d;
  }  public final void message(String s) {
    // Text output for strings
    if (config.TEXT_OUTPUT) System.out.print(s);
  }

  // Text output related
  public void message(Exception e) {
    // Text output for exceptions
    e.printStackTrace();
  }
  public final void log(String s) {
    // Log to file
    if (config.TEXT_OUTPUT) System.out.print(s);
    if (config.logFile!=null) System.out.print(s);
  }

  public String round(double val, int n) {
    String s = new String(String.valueOf(val));
    int pos = s.indexOf(".");
    int posE = s.indexOf("E");
    if (s.length()>pos+n) {
      String v = s.substring(0, pos + n);
      if (posE > 0) v = v + s.substring(posE);
      return v;
    } else return s;
  }
}