import java.util.Vector;
import java.awt.Dialog;
import java.awt.Button;
import java.awt.Event;
import java.awt.List;
import java.awt.Frame;
import java.awt.BorderLayout;

import vrml.*;
import vrml.node.*;
import vrml.field.*;

// MainScript
//
//
public class MainScript extends Script {

  // eventOut
  private SFVec3f arrow_position_changed;
  private SFRotation arrow_orientation_changed;
  private SFRotation last_orientation;
  private SFVec3f last_position;
  private String currentRoom;
  private String destRoom;
  private Node building;
  
  private Browser browser;
  private float targetCoords[];  
  private Vector rooms;
  private Vector doors;
  
  public void initialize() {
    try {
      arrow_position_changed = (SFVec3f)getEventOut( "arrow_position_changed" );
      arrow_orientation_changed = (SFRotation)getEventOut( "arrow_orientation_changed" );
      last_position = new SFVec3f();
      last_orientation = new SFRotation();
	  building = (Node)((SFNode)getField( "buildingnode" )).getValue();
      browser = this.getBrowser();
	  targetCoords = new float[3]; // need to initialize it with the starting door position
      rooms = new Vector();
      doors = new Vector();
	  currentRoom = "";
// The destination room is 'Canny's Office' and is hardcoded. Refer to README for details
      destRoom = "Canny's Office";
	  
      buildRoomGraph( building );
      // Debug code
      System.out.println( "\nFound #" + rooms.size() + " rooms:" );
      for( int i=0; i < rooms.size(); i++ )
		System.out.println( rooms.elementAt(i));
      System.out.println( "\nFound #" + doors.size() + " doors:" );
	  for( int i=0; i < doors.size(); i++ )
		System.out.println( doors.elementAt(i));
      
    }
    catch( Exception e ) {
	  e.printStackTrace();
    }
  }
  
// Build a graph of all the rooms and doors in the building
  protected void buildRoomGraph( Node bnode ) {
    rooms.removeAllElements();
    doors.removeAllElements();
    findRooms( bnode );

    for( int i=0; i<doors.size(); i++ )
      for( int j=0; j<((Door)doors.elementAt(i)).roomnames.length; j++ ) {
	Room t = getRoom( (String)((Door)doors.elementAt(i)).roomnames[j] );
	if( t == null ) {
	  System.err.println( "Can't find room matching: " + ((Door)doors.elementAt(i)).roomnames[j] +
			      "; the door is " + doors.elementAt(i) );
	}
	else {
	  t.doors.addElement( doors.elementAt(i) );
	  ((Door)doors.elementAt(i)).rooms[j] = t;
	}	
      }
  }

  private boolean internalFindPath( Room from, Room to, Vector path ) {

	  
	if( from == null ) {
      System.out.println( "ifp called with null from" );
	}
	if( to == null ) {
      System.out.println( "ifp called with null to" );
	}

    if( from.touched )
      return false;
    from.touched = true;

    if( from == to )
      return true;

    for(int i=0; i<from.doors.size(); i++ ) {
      Room next;

      if( from == ((Door)from.doors.elementAt(i)).rooms[0] )
		next = ((Door)from.doors.elementAt(i)).rooms[1];
      else
		next = ((Door)from.doors.elementAt(i)).rooms[0];

      if( internalFindPath( next, to, path )) {
	path.addElement( from.doors.elementAt(i));
	return true;
      }
    }

    return false;
  }

   // When the user enters a new room, the door and the corresponding target
   // coordinates change. This function finds the new target coordinates
   // given the new and old room as arguments
  public float[] getNextCoord( String fromroom, String toroom ) {
    Vector vec = findPath( fromroom, toroom );
	if( vec.size() >= 1 )
	  return ((Door)vec.elementAt( vec.size()-1 )).center;
    else
      return getRoom( toroom ).center;
  }
   // Determine the path between two rooms
  public Vector findPath( String fromroom, String toroom ) {
    
    for( int i=0; i<rooms.size(); i++ )
      ((Room)rooms.elementAt(i)).touched = false;

    Room froom = getRoom( fromroom );
    if( froom == null )
      System.out.println( "could not find " + fromroom );
	Room troom = getRoom( toroom );
    if( troom == null )
      System.out.println( "could not find " + toroom );
	Vector ret = new Vector();

    internalFindPath( froom, troom, ret );

    return ret;
  } 

  protected Room getRoom( String name ) {
    for( int i=0; i<rooms.size(); i++ )
      if( ((Room)rooms.elementAt(i)).name.equals( name ))
	return (Room)rooms.elementAt(i);

    return null;
  }
  
  protected void findRooms( Node bnode ) {

    if( bnode.getType().equals( "Room" )) {
      Room aroom = new Room();
       aroom.name = ((SFString)bnode.getExposedField( "name" )).getValue();
      ((SFVec3f)bnode.getExposedField( "center" )).getValue(aroom.center);
      ((SFVec3f)bnode.getExposedField( "size" )).getValue(aroom.size);
      rooms.addElement( aroom );
    }
    else if( bnode.getType().equals( "Door" )) {
      Door adoor = new Door();
      ((SFVec3f)bnode.getExposedField( "center" )).getValue(adoor.center);
      ((MFString)bnode.getExposedField( "rooms")).getValue(adoor.roomnames);
      doors.addElement( adoor );
    }
    else if(( bnode.getType().equals( "Transform" )) ||
	    ( bnode.getType().equals( "Group" ))) {	
      MFNode children = (MFNode)bnode.getExposedField( "children" );
      int size = children.getSize();
      if( size > 0 ) {
	BaseNode[] nodes = new BaseNode[ size ];
	children.getValue( nodes );
	for( int i=0; i < nodes.length; i++ ) {
	  if( nodes[i] instanceof Node ) {
	    findRooms( (Node)nodes[i]);
	  }
	}
      }
    }
  }
  
  public void processEvent( vrml.Event e ) {
	if( e.getName().equals( "set_viewer_position" )) {
      set_viewer_position((ConstSFVec3f)e.getValue());
    }
    else if( e.getName().equals( "set_viewer_orientation" )) {
      set_viewer_orientation((ConstSFRotation)e.getValue());
    }
	else if( e.getName().equals( "activeRoom" )) {
	  IsNewRoom((ConstSFString)e.getValue());
	}
  }
	// set the viewer(arrow) position using the value from the Proximity sensor  
  public void set_viewer_position( ConstSFVec3f pos ) {
    last_position.setValue( pos );
    set_arrow();
  }
   // set the viewer orientation using the proximity sensor
  public void set_viewer_orientation( ConstSFRotation rot ) {
    last_orientation.setValue( rot );
    set_arrow();
  }
   // updates the position_changed and orientation changed eventOuts.
   // The arrow is rotated so that it always points to the target coordinates
  public void set_arrow() {
    float[] pos = new float[3];
    float[] rot = new float[4];
    last_position.getValue( pos );
    last_orientation.getValue( rot );
	rot[0] = 0;
	rot[1] = 1;
	rot[2] = 0;
    rot[3] = (float)Math.atan((targetCoords[0] - pos[0])/(targetCoords[2] - pos[2])); 
	if( targetCoords[2] - pos[2] < 0)
		rot[3] = (float)(Math.PI) + rot[3];

	arrow_position_changed.setValue( pos );
    arrow_orientation_changed.setValue( rot );
  }
  // Tells whether or not the viewer has entered a new room
  public void IsNewRoom(ConstSFString room) {
	  try {
      System.out.println( "Entered: " + room.getValue() );
      currentRoom = room.getValue();
	  targetCoords = getNextCoord(currentRoom, destRoom );		  
	  System.out.println( "go to: (" + targetCoords[0] + "," 
		                             + targetCoords[1] + ","
									 + targetCoords[2] + ")" );
      }
	  catch( Exception e ) {
        e.printStackTrace();
	  }
	}

}
 
// Simple Room class
class Room {
  public float center[];
  public float size[];
  public String name;

  public boolean touched;

  public Vector doors;

  public Room() {
    center = new float[3];
    size = new float[3];
    doors = new Vector();
  }
  
  public String toString() {
    return "Room{ name=" + name + ";center=" + center + 
      "; size=" + size + "}";
  }
}

// And the Door class
class Door {
  public float center[];
  public String roomnames[];

  public Room rooms[];
  
  public Door() {
    center = new float[3];
    roomnames = new String[2];
    rooms = new Room[2];
  }

  public String toString() {
    return "Door{ center=" + center + 
      "; roomnames[0]=" + roomnames[0] + 
      "; roomnames[1]=" + roomnames[1] + "}";
  }
}