// (c) MIT 2003.  All rights reserved.

// Author: Christine Alvarado <calvarad@ai.mit.edu> 
// Maintainer: Christine Alvarado <calvarad@ai.mit.edu> 
// Created: <Thu Mar 18 12:10:44 1999> 
package rec.simulate;

import java.io.*;
import java.io.PrintWriter;
import java.lang.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.util.*;
import rec.core.widgets.*;
import rec.geo.*;
import rec.simulate.widgets.*;
import rec.simulate.widgets.wm.*;
import rec.sys.*;
import rec.util.*;
import rec.util.ClassFinder;
import java.net.UnknownHostException;

/**
 * based on SimulateRegistry.  Has all the funcitonality of the Simulate Reg
 * but it can also print out to a WM file.
 * $Id: WMSimulatorRegistryNetwork.java,v 1.5 2003/03/06 00:40:41 moltmans Exp $
 **/
public class WMSimulatorRegistryNetwork extends WMSimulatorRegistryBase
{
  
  private final boolean m_debug = true;
  
  private PrintWriter m_fileout;
  private String m_host;
  private int m_hostPort;
  private PrintWriter m_output;
  private Socket m_socket;
  
  private String m_WMAnimationStep = "$$WM_ANIMATION_STEP$$";
  private String m_WMBasicDir = "$$WM_BASIC_DIR$$";

  private void connect( Surface s )
  {
    SimulateSurfaceNetwork netSurface = (SimulateSurfaceNetwork)s;

    if( m_socket != null &&
	m_output != null &&
	m_host.equals( netSurface.getServer() ) )
    {
      // Make sure we can still write to the sccket
      send( "RESET" );
      if( ! m_output.checkError() ) {
	return;
      }
    }

    // otherwise (re)establish the connection
    reconnect( s );
    // and initialize it
    send( "RESET" );
  }

  private void reconnect( Surface s ) 
  {
    SimulateSurfaceNetwork netSurface = (SimulateSurfaceNetwork)s;
    m_host = netSurface.getServer();
    m_hostPort = netSurface.getPort();

    InetAddress hostAddress = null;
    // lookup the host
    try {
      hostAddress = InetAddress.getByName( m_host );
    }
    catch( UnknownHostException e ) {
      System.err.println( "Invalid host specified: " + m_host );
      return;
    }

    // Open the socket and get a stream to write to
    try {
      // clean up old connections first
      shutdown();
      // then make new ones
      m_socket = new Socket( hostAddress, m_hostPort );
      m_output = new PrintWriter( m_socket.getOutputStream(), true );
    } catch( IOException e ) {
      System.err.println( "Trouble opening socket on: " +
			  hostAddress + ": " + m_hostPort );
    }
  }
  

  protected void finalize() throws Throwable
  {
    shutdown();
  }
  

  // load all the header, then all the simulatable objects then the footer
  // into the WM file.
  public void load( Surface s )
  {
    // make sure we have a socket to talk to
    connect( s );

    // no just send $$WM_BASIC_DIR$$ and let the server fill it in...
    //    m_WMBasicDir = s.getSystemProperty( "WM-server-basic-dir" );

    send( "$wmstart$" );
    
    Console.writeln( "loading..." );

    if ( m_debug ) {
      try {
	m_fileout = new PrintWriter( new FileWriter( "WMDebugInput.wbs" ), true );
      }
      catch ( IOException e ) {
	System.out.println( e );
      }
    }
        
    // print the header stuff to the file
    printHeader();

    // step through the widgets on the surface.  For each visible widget 
    // which is on the surface and also in this registry, print it to 
    // the file.
    // for now the base simulatable class is WSimBody.  This only
    // handles polygons.  Obviously i will need to abstract one more time
    // in the future.
    Iterator it = s.getSurfacePoolIterator();
    String object_message;
    // load the bodies first
    while ( it.hasNext() ) {
      Widget w = (Widget)it.next();
      // check to see if we have this one
      if ( w instanceof WSimBody ) {
	object_message = ((WSimBody)w).load( this );
	send( object_message );
      }
    }

    Iterator it2 = s.getSurfacePoolIterator();
    while( it2.hasNext() ) {
      Widget w = (Widget)it2.next();
      if ( w instanceof WSimConstraint ) {
	object_message = ((WSimConstraint)w).load( this );
	send( object_message );
      }      
    }

    // run the simulation for a few timesteps
    send( "doc.run" );
    
    // print the footer stuff to the file
    printFooter();

    if ( m_fileout != null ) {
      m_fileout.close();
    }

    send( "$wmend$" );

  }

  //////////////////////////////////////////////////////////////////////

  // there are some variable name issues to consider here, like the name 
  // of the doccument, and the names of any objects created.  They should
  // all be controlled by this function, and passed in to the loading
  // function.  Also, when we start loading libraries to handle more 
  // complex shapes (that is, WMBasic libraries which already exist), into
  // the file that we are creating we will need to somehow keep track of 
  // them to be used at the beginning of printHeader()
  private void loadTypes()
  {
    // point type
    send( "Type point" );
    send( "X as double" );
    send( "Y as double" );
    send( "end Type" );
  }

  private void loadVariables()
  {
    send( "Dim center as point" );
    send( "Dim p() as point" );
    send( "Dim t1 as point" );
    send( "Dim t2 as point" );
  }
  
  private void loadLibs()
  {
    send( "WM.LoadWMBLibrary \"" + m_WMBasicDir + "\\draw_bodies.wbs\"" );
    send( "WM.LoadWMBLibrary \"" + m_WMBasicDir + "\\draw_constraints.wbs\"" );
  }
  
  private void unloadLibs()
  {
    send( "WM.UnloadWMBLibrary \"" + m_WMBasicDir + "\\draw_bodies.wbs\"" );
    send( "WM.UnloadWMBLibrary \"" + m_WMBasicDir + "\\draw_constraints.wbs\"" );
  }

  private void loadDeclare()
  {
    send( "Declare function Draw_Rect(h, w, c as point)" );
    send( "Declare function Draw_Poly(n, p() as point, c as point)" );
    send( "Declare function Draw_Spring(l as point, r as point)" );
    send( "Declare function Draw_Pulley(n, a() as point)" );
    send( "Declare function Draw_Damper(l as point, r as point)" );
    send( "Declare function Draw_SpringDamper(l as point, r as point)");
    send( "Declare function Draw_Circle(r, c as point)" );
    send( "Declare function Draw_Force(c as point, fx, fy)" );
    send( "Declare function Draw_Rod(l as point, r as point)");

  }

  private void printHeader()
  {

    loadTypes();
    loadDeclare();

    Console.writeln( "writing to file" );
    send( "Sub Main()" );
    
    loadLibs();

    send("dim doc as WMDocument");
    send("set doc = WM.ActiveDocument");

    // reset the document 
    send("doc.Reset");

    // clear everything
    send("doc.SelectAll");
    send("doc.Delete");

    if ( m_WMAnimationStep != null ) {
      send("doc.AutoAnimationStep = False");
      send("doc.AnimationStep = " + m_WMAnimationStep );
      System.err.println( m_WMAnimationStep );
      
    }
    else {
      send("doc.AutoAnimationStep = True");
    }
    

    // declare variables
    loadVariables();

    // set gravity appropriately 
    if ( !m_gravity ) {
      send("doc.gravity = \"none\"");
    } 
    else {
      send("doc.gravity = \"linear\"");
    }
  }

  private void printFooter()
  {
    send("doc.SelectAll False");
    unloadLibs();
    send("end Sub");

  }

  /** Plop the command into the file. **/
  private void send( String command ) 
  {
    if ( m_fileout != null ) {
      m_fileout.println( command );
    }

    m_output.println( command );
  }

  public void shutdown()
  {
    if( m_output != null ) {
      m_output.close(); 
    }
    if( m_socket != null ) {
      try {
	m_socket.close();   
      } catch( IOException e ) {
	System.err.println( "Trouble closing socket: " + m_socket );
	e.printStackTrace();
      }
    }
  }
}


