// (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 com.neva.Jddeml;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

/**
 * A server that listens on a port and then sends messages to Working
 * Model 2D via DDE.
 *
 * @version $Id: WMSimulatorServerDDE.java,v 1.4 2003/03/06 00:40:41 moltmans Exp $
 **/
public class WMSimulatorServerDDE 
{

  private Jddeml m_output;
  private PrintWriter m_fileout;
  private final boolean m_debug = true;

  // parameters to get from property file
  private String m_WindowTitle;
  private String m_WMExe;
  private String m_WMAnimStep;
  private String m_WMBasicDir;
  private int m_timeout;
  
  public WMSimulatorServerDDE( Properties properties )
  {
    m_output = new Jddeml();

    m_WindowTitle = getProperty( properties, "window-title" );
    m_WMExe = getProperty( properties, "simulator" );
    m_timeout = getIntProperty( properties, "dde-timeout" );
    m_WMBasicDir = getProperty( properties, "WM-basic-dir" );
    m_WMAnimStep = getProperty( properties, "WM-animation-step" );

//      try {
//        m_fileout = new PrintWriter( new FileWriter( "WMInput-from-server.wbs" ) );
//      } catch( IOException e ) {
//        System.err.println( "Error opening output file" );
//      }

  }

  private void startup()
  {
    // connect to working model
    if ( !m_output.ddeConnect( "WM", "WMBasic" ) ) {
      System.err.println( "Unable to establish connection with WM" );

      try {
	Runtime.getRuntime().exec( "\"" + m_WMExe + "\"" );
	boolean is_connected = false;

	// connect to working model
	for ( int i=0; i < 5 && !is_connected; i++ ) {
	  if ( m_output.ddeConnect( "WM", "WMBasic" ) ) {
	    is_connected = true;
	  }
	  else {
	    Thread.sleep( 1000 );
	  }
	}

	if ( ! is_connected  ) {
	  System.err.println( "Unable to establish connection with WM" );
	  return;
	}

      }	catch ( Exception e ) {
	System.err.println( e );
	return;
      }
    }
    restoreWindow();
  }

  private void shutdown() 
  {
    System.err.println( "Shuting down" );
    m_output.ddeDisconnect();
  }

  public void restoreWindow() 
  {
    // activate the working model application
    process( "WinRestore \""+ m_WindowTitle + "\"" );
    process( "WinActivate \"Working Model 2D\"" );
  }

  private String replaceArg( String command, String arg, String val ) 
  {
    int pos = command.indexOf( "$$" + arg + "$$" );
    if( pos >= 0 ) {
      String result = ( command.substring( 0, pos ) + val +
			command.substring( pos + 4 + arg.length() ) );
      return result;
    }
    return command;
  }
  
  
  private boolean process( String command ) 
  {
    command = replaceArg( command, "WM_BASIC_DIR", m_WMBasicDir );
    command = replaceArg( command, "WM_ANIMATION_STEP", m_WMAnimStep );
    
    //    System.err.println( "msg: " + command );
    //    m_fileout.println( command );
    return m_output.ddeExecute( command, m_timeout );
  }

  private static void checkConnection( InetAddress client, List addresses )
  {
    // if addresses is null then we are being promiscuous and
    // accepting everyone (kinky or practical, you decide).
    if( addresses == null ) {
      return;
    }
    
    Iterator it = addresses.iterator();
    while( it.hasNext() ) {
      InetAddress addr = (InetAddress)it.next();
      if( addr.equals( client ) ) {
	return;
      }
    }
    System.err.println( "*** Invader alert ***" );
    System.err.println( "Connection refused from: " + client );

    // if someone is playing games lets just shutdown
    System.exit( 1 );
  }

  private static String getProperty( Properties properties, String key ) 
  {
    String val = properties.getProperty( key );
    if( val == null ) {
      System.err.println( "No value given in property file for: " + key );
      System.exit( 1 );
    }
    return val;
  }
  
  private static int getIntProperty( Properties properties, String key ) 
  {
    String val = properties.getProperty( key );
    if( val == null ) {
      System.err.println( "No port property defined in property file" );
    }
    
    try {
      return Integer.parseInt( val );
    } catch( NumberFormatException e ) {
      System.err.println("Couldn't parse port number: " + val);
    }
    System.exit(1);
    return Integer.MAX_VALUE;
  }

  // returns null to allow all clients: indicated by "all"
  private static List getClients( Properties properties )
  {
    String val = properties.getProperty( "clients" );
    if( val == null ) {
      System.err.println( "No clients property defined in property file" );
      System.exit(1);
    }

    // return empty list indicating accept everyone
    if( val.equals( "all" ) ) {
      return null;
    }
    
    List list = new LinkedList();
    StringTokenizer tokenizer = new StringTokenizer( val, "," );

    while( tokenizer.hasMoreElements() ) {
      String addr = tokenizer.nextToken();
      try {
	list.add( InetAddress.getByName( addr ) );
      } catch( UnknownHostException e ) {
	System.err.println("Couldn't find allowed host: " + addr );
      }
    }
    
    return list;    
  }
  

  public static void main(String[] args) throws IOException {
    Properties properties = new Properties();

    if( args.length != 1 ) {
      System.err.println( "must give path to properties file as only argument" );
      System.exit(1);
    }

    try {
      InputStream is = new FileInputStream( args[0] );
      properties.load( is );
    } catch( IOException e ) {
      System.err.println( "Couldn't open properties file: " + args[0] );
      System.exit(1);
    }
    
    
    WMSimulatorServerDDE simDDE = new WMSimulatorServerDDE( properties );
    ServerSocket serverSocket = null;

    int port = getIntProperty( properties, "port" );
    
    try {
      serverSocket = new ServerSocket( port );
      System.err.println("server started on port: " + port);
    }
    catch (IOException e) {
      System.err.println("Could not listen on port: " + port );
      System.exit(1);
    }

    try {
      // accept one connection at a time...
      while( true ) {
	Socket clientSocket = null;
	try {
	  // listen for a connection
	  clientSocket = serverSocket.accept();

	  InetAddress address = clientSocket.getInetAddress();
	  System.err.println( "Got connection from: " + address );
      
	  // XXX: this is crap security...  HELP!
	  checkConnection( address, getClients( properties ) );
	} catch (IOException e) {
	  System.err.println("Accept failed.");
	  System.exit(1);
	}

	// prepare to read from the port
	InputStreamReader isr = new InputStreamReader( clientSocket.getInputStream() );
	BufferedReader in = new BufferedReader( isr );
			
	String inputLine;

	// start up the DDE stuff and Working Model
	simDDE.startup();
    
	while ((inputLine = in.readLine()) != null) {
	  if( inputLine.equals( "RESET" ) ) {
	    simDDE.startup();
	  }

	  simDDE.process( inputLine );
	}
	System.err.println( "Closed connection: " + clientSocket );

	// clean up after ourselves
	in.close();
	clientSocket.close();
      }
    }
    finally {
      serverSocket.close();
    }
  }

}


