/************************************************
* Columbia University CS Department
* Systems and Security Group
*
* Open and Survivable Embedded Systems (OASES)
*
* Michael E. Locasto
*
* Protocol For Code Exchange
*  in Survivable Embedded Systems (PCXSES)
*
* This software is provided as is without any
* warranty or guarantee of merchantibility
* or fitness for a particular purpose. This
* software is released under the terms of the
* GNU General Public License (GPL).
*
* Last updated: December 11, 2002
*
***********************************************/

package locasto.pcxses.ac;

import java.net.*;
import locasto.logging.*;
import locasto.xml.XMLConfigReader;
import locasto.pcxses.protocol.*;

import javax.naming.directory.*;
import javax.naming.*;
import java.io.*;
import java.util.*;


/**
 * XXX - Essentially, we'd like to do this:
 *   -drop a jar in your classpath
 *   -mark an object with interface 'Transportable' or 'Migratable' or 'Survivable'
 *   -have the developer write the method that identifies state
 *   -our JAR + changes to JVM allow a computation to be moved via the pcx protocol
 *
 * <h3>AreaContoller</h3>
 * <p>
 *  The AreaController provides the server environment.
 * </p>
 * - note, just have the socket put stuff in a FifoBuffer
 *  that is big enough to accomodate two signals/Bottles
 *  from every device on the net. Have a different worker
 *  class actually read from the buffer and update our
 *  table of variables as needed

 *   Device      heardFromThisTick
     -----------------------------------------------------
 *
 *
 *
 *
 * Also need to create a Timer class (maybe sleep will work)
 *
 * Have to come up with some meta-language describing state
 *  for each computation (some little XML document that
 *  details the Main() class, a Runnable class, other entry points,
 *  and how to capture "essential state"
 *
 *
 * @author locasto@cs.columbia.edu
*/
public class AreaController implements Runnable{

	private String name;


	private RepositoryManager repositoryManager;

	private PolicyEngine policyEngine;

	private ACConsole console;

	private DeviceManager deviceManager;

	private Logger logger=null;

	static int debug;

	private ServerSocket server;
	private int port;

	private int defaultHelloInterval;
	private int defaultDeadInterval;


	private Thread listener;
	private boolean stopListening=false;


	public static final String AC_HOME = System.getProperty( "ac.home" );
	private static String configFile;


	/**
	 * Constructs the AreaController after main is called:  interprets and
	 * executes the command line arguments, creates the various server subcomponents,
	 * configures them, writes the keyfile info, and initializes all components.
	*/
	public AreaController( String [] args ) throws Exception {

		execute( args );

		console = new ACConsole();
		console.setParent( this );

		policyEngine = new PolicyEngine();

		repositoryManager = new RepositoryManager();

		deviceManager = new DeviceManager();

		configure();

		writeKeyFile();

		init();

		//could have a useful loop here in the context of the "Main" thread

	}

	/**
	 * Read the configuration file and configure all objects in
	 * the AreaController.
	 * <p>
	 * PRECONDITION: the command line arguments have been executed
	 * POSTCONDITION: the configuration file has been read and the
	 *   appropriate callbacks made.
	 * @throws Exception - if the XML parsing does not succeed or the
	 *   file reading does not succeed or the callback methods do not
	 *   succeed.
	*/
	public void configure() throws Exception{

		if( configFile==null || configFile.equals( "" ) ){
			configFile = AC_HOME+File.separatorChar+"conf"+File.separatorChar+"ac.xml";
		}

		XMLConfigReader xmlConfigReader = new ACXMLConfigReader();

		xmlConfigReader.setCallBackObject( this );
		xmlConfigReader.readConfig( configFile );

		xmlConfigReader = null;
	}

	private void writeKeyFile() throws Exception{

		FileWriter fout =
		  new FileWriter( AC_HOME+File.separatorChar+"conf"+File.separatorChar+"ac.properties", false );

		fout.write( "#Temporary file with key information\n" );
		fout.write( "\nsecret = "+ new String( console.getSecret() ) );
		fout.write( "\nport = "+console.getPort() );

		fout.close();
		fout=null;

	}


	public void init() throws Exception{

		policyEngine.init();

		repositoryManager.init();

		deviceManager.init();

		console.init();
		logger.log( "Console started..." );

		server = new ServerSocket( port, ((int)(1.5*(deviceManager.totalDevices+1))) );

		listener = new Thread( this );
		listener.start();

		logger.log( "AreaController "+name+" started..." );

	}

	//--------------------- interface Runnable

	public void run(){

		Socket client=null;
		ACWorker acWorker = new ACWorker( this, deviceManager );
		long clientCounter=0;

		try{

			listener.yield();

			while( !stopListening ){

				try{

					listener.yield();
					if( debug>6 )
						log( "AreaController waiting for connection #"+clientCounter+" ..." );
					client = server.accept();
					//( new Thread( ((Runnable)(new ACWorker( this, client, clientCounter, deviceManager ))) ) ).start();
					acWorker.process( client, clientCounter );
					//if( debug>8 )
						//log( "AreaController got connection #"+clientCounter+" ..." );

					if( clientCounter<Long.MAX_VALUE ){
						clientCounter++;
					}else{
						//note in the log file that we have cycled
						logger.log( "...........We have cycled request id numbers..........." );
						clientCounter=0;
					}

				}catch( OutOfMemoryError oomem ){
					System.err.println( "Could not create another worker, out of memory: "+oomem.getMessage() );
					//clean up thread references?
					//force logger to flush
					//clean up device stubs?
					//clean up repository manager?
					//clean up Device Manager?
					//kill XML Configreader?
				}catch( Exception ex ){
					logger.log( "AC: Had problem with client #"+clientCounter+": "+ex.getMessage() );
				}

			}

		}catch( Exception e ){
			logger.log( "Major problem running AreaController accept() thread: "+e.getMessage() );
		}



	}


	//----------- configure callbacks

	void setName( String name ){
		this.name = name;
	}

	String getName(){
		return name;
	}

	void setPort( int port ){
		this.port = port;
	}

	int getPort(){
		return port;
	}

	void setDefaultHelloInterval( int defaultHelloInterval ){
		this.defaultHelloInterval = defaultHelloInterval;
	}

	void setDefaultDeadInterval( int defaultDeadInterval ){
		this.defaultDeadInterval = defaultDeadInterval;
	}

	int getDefaultHelloInterval(){
		return defaultHelloInterval;
	}

	int getDefaultDeadInterval(){
		return defaultDeadInterval;
	}

	RepositoryManager getRepositoryManager(){
		return repositoryManager;
	}

	void setCommandPort( int commandPort ){
		console.setPort( commandPort );
	}

	void setShutDownSecret( char [] shutDownSecret ){
		console.setSecret( shutDownSecret );
	}

	void configurePolicyEngine() throws Exception{
		policyEngine.configure( this );
	}

	void configureDeviceManager( String deviceListFileName, int sleepTime ) throws Exception{
		deviceManager.configure( deviceListFileName, sleepTime, this );
	}

	void configureRepositoryManager( String initialContextFactory ) throws NamingException{
		Hashtable env = new Hashtable();
		env.put( Context.INITIAL_CONTEXT_FACTORY, initialContextFactory );
		repositoryManager.configure( env, this );
	}


	void setDebug( int d ){
		debug = d;
	}

	void setLogger( Logger l ){
		logger = l;
	}

	Logger createLogger( String filename, String logPathConnector, int logSleepTime, boolean useFullDate ) throws FileNotFoundException{

		StandardLogger tempSlogger = null;
		StandardLogger slogger = new StandardLogger();

		slogger.setApplicationHome( AC_HOME, false );
		slogger.setLogPathConnector( logPathConnector );
		slogger.setLogFileName( filename );
		slogger.setUseFullDateFormat( useFullDate );
        slogger.setSleepTime( logSleepTime );

		slogger.init();
		return ((Logger)slogger);
	}





	//----------- package methods

	synchronized void log( String message ){

		logger.log( message );

	}

	/** this should only be called by the ACConsole */
	void shutDown(){

		stopListening = true;

		System.out.println( "Area Controller shutting down..." );
		try{

			console.shutDown();
			policyEngine.shutDown();
			repositoryManager.shutDown();
			deviceManager.shutDown();

			logger.shutDown();

		}catch( Exception e ){
			System.err.println( "AreaController had a problem shutting down: "+e.getMessage() );
		}finally{
			System.exit( 0 );
		}

	}


	/**
	 * Print the following usage information and quit.
	 * java locasto.pcxses.ac.AreaController -config [configfile].xml
	 * java locasto.pcxses.ac.AreaController -stop
	 *
	 *
	*/
	private void doUsage(){

		System.out.println( "\n USAGE INFORMATION:\n--------------------" );
		System.out.println( "\t java -Dac.home=$AC_HOME locasto.pcxses.ac.AreaController -config [configfile].xml" );
		System.out.println( "\tjava -Dac.home=$AC_HOME locasto.pcxses.ac.AreaController -stop" );
		System.exit( 0 );
	}

	/**
	 * executes the command line arguments.
	 * @param String[] args - the arguments
	*/
	private void execute( String [] args ) throws Exception{

		if( AC_HOME==null || AC_HOME.equals( "" ) ){
			throw new Exception( "Please define (via the -D command line switch) the \"ac.home\" system property." );
		}

		if( args.length==1 ){

			if( args[0].equals( "-stop" ) ){
				new ShutDownTask();
			}else{
				doUsage();
			}
			System.exit( 0 );

		}else if( args.length==2 ){

			if( args[0].equals( "-config" ) && args[1].endsWith( ".xml" ) ){

				configFile = args[1];

			}else{
				doUsage();
			}

		}else{
			doUsage();
		}

	}


	public static void main( String [] args ){

		try{

			new AreaController( args );

			System.out.println( "AreaController bound and started..." );

		}catch( Exception e ){
			e.printStackTrace();
			System.err.println( "Area Controller failed to start: "+e.getMessage() );
			System.exit( -1 );
		}


	}

}

