package alive;

/**
 * <ALIVE Project>
 * RenderClient class
 * By Rogerio Neves
 * LSI - POLI - USP
 *
 * What does this:
 * Receives  the server MultiCast packet
 * Starts actors with correct shapes, texture, transparency and color
 * Manage actors in run time
 *
 */

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.net.*;
import java.io.PrintStream;
import java.util.Date;
import java.util.Vector;
import javax.media.j3d.*;
import javax.vecmath.*;

public class RenderClient extends Applet implements Runnable {

  // Application related
  boolean isStandalone = false;
  boolean started = false;
  boolean quit = false;
  boolean paused = false;
  Thread tread;
  String[] entry = {" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "};
  double posX = -1,posY = 2,posZ = 33, scale;
  float transp;
  int i, id;

  // Comm related
  String s = new String("");
  String ip = "228.5.6.7";
  int port = 6789;
  double packetConter = 0, lastPacket, lost;
  InetAddress group = null;
  MulticastSocket sock = null;
  byte[] buf = new byte[51200];
  char c;

  // Java3D related
  Vector actors = new Vector();
  VirtualUniverse u = new VirtualUniverse();
  Locale locale = new Locale(u);
  BranchGroup viewGroup = new BranchGroup();
  BranchGroup lightGroup = new BranchGroup();
  BranchGroup actorGroup = new BranchGroup();
  TransformGroup viewTransform = new TransformGroup();
  DirectionalLight light = new DirectionalLight();
  BoundingSphere bounds = new BoundingSphere(new Point3d(), 1000.0);
  Transform3D t = new Transform3D();
  ViewWindow v = new ViewWindow("ALIVE RenderClient");
  Vector3d pos = new Vector3d(), dir = new Vector3d();
  Color3f color = new Color3f();
  Actor a = null, ac = null;

  // UI related
  Panel panTop = new Panel();
  Panel panBottom = new Panel();
  Panel panMain = new Panel();
  Panel panPresets = new Panel();
  Panel panSets = new Panel();
  Panel panAddresses = new Panel();
  Panel panCam = new Panel();
  Button bStart = new Button("Start");
  Button bPause = new Button(" Pause ");
  Button bQuit = new Button("Quit");
  Button bFront = new Button("Front");
  Button bRight = new Button("Right");
  Button bLeft = new Button("Left");
  Button bBack = new Button("Back");
  Button bFloor = new Button("Floor");
  Label l1 = new Label("Presets");
  Label lb1 = new Label(" ");
  Label lb2 = new Label(" ");
  Label lb3 = new Label(" ");
  Label lc = new Label("IP:PORT");
  Label lrx = new Label("Cam RotX  ");
  Label lry = new Label("Cam RotY  ");
  Label lrz = new Label("Cam RotZ  ");
  TextField fip = new TextField(new String(ip + ":" + port));
  TextField rx = new TextField("0");
  TextField ry = new TextField("0");
  TextField rz = new TextField("0");
  TextField sep = new TextField("0.0066");
  TextField field = new TextField("1.57");
  Label lField = new Label("Field  ");
  Label lSep = new Label("Separat  ");
  Checkbox fullScreen = new Checkbox("Fullscreen", false);
  Checkbox stereo = new Checkbox("Stereo", false);
  Checkbox cave = new Checkbox("Cave", false);
  Label lbx1 = new Label("Packet                               ");
  Label lbx2 = new Label("Lost                                 ");

  public RenderClient() {
  }

  // Starts up User Interface
  public void init() {
    try { jbInit(); } catch(Exception e) { e.printStackTrace(); }
  }
  private void jbInit() throws Exception {
    this.setLayout(new BorderLayout());
    fip.setColumns(15);
    panMain.setLocale(java.util.Locale.getDefault());
    panMain.setLayout(new GridLayout(1,2));
    l1.setAlignment(Label.CENTER);
    lb1.setAlignment(Label.CENTER);
    lb1.setText("");
    lb2.setAlignment(Label.CENTER);
    lb3.setAlignment(Label.CENTER);
    lb3.setText("");
    lrx.setAlignment(Label.RIGHT);
    lry.setAlignment(Label.RIGHT);
    lrz.setAlignment(Label.RIGHT);
    panPresets.setLayout(new GridLayout(3,1));
    panSets.setLayout(new GridLayout(2,1));
    panCam.setLayout(new GridLayout(5,2));
    lField.setAlignment(Label.RIGHT);
    lSep.setAlignment(Label.RIGHT);
    bStart.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    bPause.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    bQuit.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    bFront.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    bRight.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    bLeft.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    bBack.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    bFloor.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent e) {eval(e);}});
    this.add(panTop,  BorderLayout.NORTH);
    this.add(panBottom,  BorderLayout.SOUTH);
    this.add(panMain,  BorderLayout.CENTER);
    panMain.add(panPresets);
    panMain.add(panSets);
    panPresets.add(l1);
    panPresets.add(bFront);
    panPresets.add(lb1);
    panPresets.add(bLeft);
    panPresets.add(bFloor);
    panPresets.add(bRight);
    panPresets.add(lb2);
    panPresets.add(bBack);
    panPresets.add(lb3);
    panSets.add(panAddresses);
    panSets.add(panCam);
    panAddresses.add(lc);
    panAddresses.add(fip);
    panAddresses.add(fullScreen);
    panAddresses.add(stereo);
    panAddresses.add(cave);
    panAddresses.add(lbx1);
    panAddresses.add(lbx2);
    panCam.add(lrx);
    panCam.add(rx);
    panCam.add(lry);
    panCam.add(ry);
    panCam.add(lrz);
    panCam.add(rz);
    panCam.add(lField);
    panCam.add(field);
    panCam.add(lSep);
    panCam.add(sep);
    panBottom.add(bStart);
    panBottom.add(bPause);
    panBottom.add(bQuit);
  }
  // Process UI events
  public void eval(ActionEvent e) {
    if (e.getSource() == bStart) {
      if (started==true) {
        tell("Already Started!");
      } else {
        tell("Starting Up!");
        tread = new Thread(this);
        tread.start();
        started = true;
      }
    }
    else if (e.getSource() == bPause) {
      paused =!paused;
      if (paused) bPause.setLabel("Resume");
      else bPause.setLabel(" Pause ");
    }
    else if (e.getSource() == bQuit) {
      destroy();
    }
    else if (e.getSource() == bFront) {
      rx.setText("0"); ry.setText("0"); rz.setText("0");
    }
    else if (e.getSource() == bLeft) {
      rx.setText("0"); ry.setText("90"); rz.setText("0");
    }
    else if (e.getSource() == bRight) {
      rx.setText("0"); ry.setText("-90"); rz.setText("0");
    }
    else if (e.getSource() == bFloor) {
      rx.setText("-90"); ry.setText("0"); rz.setText("0");
    }
    else if (e.getSource() == bBack) {
      rx.setText("0"); ry.setText("180"); rz.setText("0");
    }
  }

  // Thread main loop
  public void run() {
    int i,separator = fip.getText().indexOf(":");
    ip = fip.getText().substring(0,separator).trim();
    port = getInt(fip.getText().substring(separator+1, fip.getText().length()).trim());
    createContent();
    tell("Joining multicast group in IP " + ip + ", PORT " + port);
    try {   group = InetAddress.getByName(ip);    } catch (Exception e)  { tell(e); }
    try {   sock = new MulticastSocket(port);     } catch (Exception e)  { tell(e); }
    try {   sock.joinGroup(group);                } catch (Exception e)  { tell(e); }
    DatagramPacket recv = new DatagramPacket(buf, buf.length, group, port);
    while (!quit) {
      try { sock.receive(recv);                   } catch (Exception e)  { tell(e);  }
      for (i = 0;i<recv.getLength();i++) {
        c = (char) buf[i];
        if (c==10) {
          processLine(s);
          s = "";
        } else s = s + c;
      }
      packetConter++;
      for (i=0;i<actors.size();i++)  {
        ac = getActor(i);
        ac.latency++;
        if (ac.latency>5) {
          ac.detach();
          actors.remove(ac);
          i--;
        }
      }
    }
  }

  // Process received commands
  void processLine(String s) {
    entry[0] = new String();
    int i, idx = 0;
    for (i = 0;i<s.length();i++) {
      if (s.charAt(i)==',') {
        idx++;
        entry[idx] = new String();
      } else entry[idx] = entry[idx] + s.charAt(i);
    }
    //for (i=0;i<idx;i++) tell("   " + entry[i]);
    // Packet info line
    if (entry[0].equals("Packet")) {
      double pk = getDouble(entry[1]);
      if (pk<lastPacket) { // Reset all
        tell("\nRestarting...\n");
        this.packetConter = 0;
        this.lost = 0;
        this.lastPacket = 0;
        lbx2.setText("Lost " + (int)(lost));
        if (!actors.isEmpty())
          while (!actors.isEmpty()) {
            getActor(0).detach();
            actors.remove(getActor(0));
          }
      }
      else if (pk-lastPacket>1) {
        lost = lost + pk - lastPacket - 1;
        tell("Lost " + (int)(pk - lastPacket - 1) + " packets!");
        lbx2.setText("Lost " + (int)(lost));
      }
      lastPacket = pk;
      lbx1.setText("Pack " + (int)pk);
    } // Cam position adjust line
    else if (entry[0].equals("Cam")) {
      double posXn = getDouble(entry[1])
          ,posYn = getDouble(entry[2])
          ,posZn = getDouble(entry[3]);
      if (posX!=posXn||posY!=posYn||posZ!=posZn) {
        posX = posXn; posY = posYn; posZ = posZn;
        t.set(t.IDENTITY);
        t.set(new Vector3d(posX, posY, posZ));
        viewTransform.setTransform(t);
        tell("New Cam position: " + posX + "," + posY + "," + posZ);
      }
    } // Start new experiment
    else if (getInt(entry[0])>0) {
      id = getInt(entry[0]);
      ac = findActor(id);
      if (ac==null) ac = createAcrtor(id, entry[1], entry[2], getFloat(entry[3]));
      else if (ac.id!=id) ac = createAcrtor(id, entry[1], entry[2], getFloat(entry[3]));
      transp = getFloat(entry[3]);
      scale = getDouble(entry[4]);
      color.set(getFloat(entry[5]),getFloat(entry[6]),getFloat(entry[7]));
      pos.set(getDouble(entry[8]),getDouble(entry[9]),getDouble(entry[10]));
      dir.set(getDouble(entry[11]),getDouble(entry[12]),getDouble(entry[13]));
      if (transp!=ac.transparency) ac.setTransparency(transp);
      if (!color.equals(ac.emissiveColor)) ac.setColor(color);
      ac.set(pos,dir,scale);
      ac.latency = 0;
    }
  }

  // Initialize visualization
  void createContent() {
    tell("Starting view...");
    v.field = getDouble(field.getText());
    v.separation = getDouble(sep.getText());
    v.stereo = stereo.getState();
    v.fullScreen = fullScreen.getState();
    v.cave = cave.getState();
    v.construct();
    v.setLocation(0,0);
    t = makeTransform(getDouble(rx.getText()),getDouble(ry.getText()),getDouble(rz.getText()));
    v.setTransform(t);
    t.set(new Vector3d(posX, posY, posZ));
    viewTransform.setTransform(t); // Add a global viwing TG and
    viewTransform.setCapability(viewTransform.ALLOW_TRANSFORM_WRITE);
    viewTransform.setCapability(viewTransform.ALLOW_TRANSFORM_READ);
    viewTransform.addChild(v.getTransformGroup());
    viewGroup.addChild(viewTransform); // Operate all cams toguether
    viewGroup.compile();
    locale.addBranchGraph(viewGroup);
    createLights();
    actorGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
    actorGroup.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
    actorGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
    locale.addBranchGraph(actorGroup);
    v.setVisible(true);
  }
  public void createLights() {
    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);
    newGroup.addChild(light);
    newGroup.compile();
    locale.addBranchGraph(newGroup);
  }
  Actor createAcrtor(int id, String model, String texturefile, float transparency) {
    Actor newActor = null;
    if (model.startsWith("ABB:")) newActor = new Actor_BillBoard(model.substring(4));
    else if (model.startsWith("LWO:")) {
      newActor = new Actor_LwsFile(model.substring(4));
      // ((Actor_LwsFile)newActor).rotY(3*Math.PI/2); // Default rotation
    }
    else if (model.startsWith("OBJ:")) {
      newActor = new Actor_ObjFile(model.substring(4));
      ((Actor_ObjFile)newActor).rotY(3*Math.PI/2); // Default Rotation
    }
    else if (model.startsWith("VRML:")) {
      newActor = new Actor_VRML(model.substring(5));
    }
    else if (model.equals("Actor_Fish")) newActor = new Actor_Fish();
    else if (model.equals("Actor_Gull")) newActor = new Actor_Gull();
    else newActor = new Actor_Sphere();
    if (!texturefile.equals("")) newActor.setTexture(texturefile);
    if (transparency!=0) newActor.setTransparency(transparency);
    newActor.id = id;
    actorGroup.addChild(newActor);
    actors.add(newActor);
    return newActor;
  }
  private final Transform3D makeTransform(double rotX, double rotY, double rotZ) {
    Transform3D tr = new Transform3D();
    tr.set(tr.IDENTITY);
    Transform3D t1 = new Transform3D();
    Transform3D t2 = new Transform3D();
    Transform3D t3 = new Transform3D();
    Transform3D t4 = new Transform3D();
    if (rotX!=0) { t1.rotX(rotX*Math.PI/180); tr.mul(t1); }
    if (rotY!=0) { t2.rotY(rotY*Math.PI/180); tr.mul(t2); }
    if (rotZ!=0) { t3.rotZ(rotZ*Math.PI/180); tr.mul(t3); }
    tr.setScale(1f); // Default Scale
    return tr;
  }
  public void removeActor(BranchGroup actor) {
    actor.detach();
  }
  public synchronized Actor getActor(int i) {
    a = null;
    if (i<actors.size()&&i>=0) a = ((Actor)actors.get(i));
    return a;
  }
  public Actor findActor(int id) {
    /* Returns the agent with the specified id
     * or the next identified living agent,
     * in this case, (id!=ag.id)
     */
    a = null;
    int i = 0;
    while (i<actors.size()) {
      a = getActor(i);
      if (a.id>=id) break;
      i++;
    }
    return a;
  }
  public void destroy() {
    quit = true;
    if (started) {
      tell("Exitting!");
      try {  sock.leaveGroup(group); } catch (Exception e) { tell(e); }
    }
    System.exit(0);
  }
  // Text output
  void tell(Exception e) {
    // Text output for exception
    e.printStackTrace();
  }
  void tell(String s) {
    System.out.println(s);
  }

  // Number conversion tools
  int getInt(String f) {
    // Text output for string
    int i = 0;
    try { i = (int)Integer.parseInt(f.trim()); }
    catch(NumberFormatException e) { }
    return i;
  }
  double getDouble(String f) {
    double d = 0;
    try { d = (double)Double.parseDouble(f.trim()); }
    catch(NumberFormatException e) { }
    if (d-d!=0) d = 0;
    return d;
  }
  float getFloat(String f) {
    float v = 0;
    try { v = (float)Float.parseFloat(f.trim());}
    catch(NumberFormatException e) { }
    return v;
  }

  // ---------------------------- Default --------------------------------

  //Get Applet information
  public String getAppletInfo() {
    return "ALIVE Project RenderClient";
  }
  //Main method
  public static void main(String[] args) {
    RenderClient applet = new RenderClient();
    applet.isStandalone = true;
    Frame frame = new Frame() {
      protected void processWindowEvent(WindowEvent e) {
        super.processWindowEvent(e);
        if (e.getID() == WindowEvent.WINDOW_CLOSING) { System.exit(0);}}
      public synchronized void setTitle(String title) {
        super.setTitle(title); enableEvents(AWTEvent.WINDOW_EVENT_MASK);}};
    frame.setTitle("ALIVE RenderClient");
    frame.add(applet, BorderLayout.CENTER);
    applet.init();
    applet.start();
    frame.setSize(450,320);
    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
    frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
    frame.setVisible(true);
  }
}
