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

// $Id: RecIniFile.java,v 1.43 2003/03/06 00:40:39 moltmans Exp $
package rec.ui;


import rec.speech.*;
import java.io.*;
import java.util.*;
import rec.util.*;
import rec.sys.*;
import rec.geo.*;
import rec.simulate.*;
import rec.annotate.rules.*;
import javax.swing.JFrame;
import rec.util.ClassFinder;
import java.net.URL;

/**
 * Ini file format:
 * It can contain:
 *   # comments
 *   Console [ EnableOutput | EnablePrettyPrinting ]
 *   Display EnableOutput
 *   Surface <surface class>
 *   Color <property> <color>
 *   Part
 *   MajorMode <mode name> must appear before any recognizers
 *             specifies that the following mode is a major mode
 *   EditMode <mode name>  must appear before any recognizers
 *   Module <module name>[.mod]
 *   Widget <name> [x] [y]  [args]
 *   Grammar <name>[.gram]
 *   CleanUp <surface mutator> # add a clean up button
 *   Done <surface mutator> # add a done button
 *   Recognizer <recognizer class name>
 *   <recognizer class name that ends with "Recognizer">
 *   Reasoner <reasoner class name>
 *   <reasoner class name that ends with "Reasoner">
 *   SystemProperty <key-string> "<string-value">
 *   Rule <rule's class>
 **/
public class RecIniFile
{
  private boolean m_ModuleOnly = false;

  private BufferedReader m_Reader;
  private String m_Name;
  private URL m_Base;

  // this is the recognizer module built by the ini file.
  BasicModule m_Module;

  // the edit mode in which this module is active
  HashSet m_EditMode;

  HashSet m_dependentModules;

  Surface m_Surface;

  public RecIniFile( URL url, String editMode )
    throws IOException
  {
    m_Reader = grabStream( url );
    m_Name = url.getFile();
    m_Base = url;

    m_EditMode = new HashSet();
    if ( editMode != null ) {
      m_EditMode.add( editMode );
    }
    m_dependentModules = new HashSet();
  }

  public static BufferedReader grabStream( URL url ) throws IOException
  {
    InputStream in = url.openStream();

    if ( in == null )
      throw( new IOException( "File "
                              + url + " is not found in classpath." ) );
    
    BufferedReader rd = new BufferedReader( new InputStreamReader( in ) );

    return rd;
  }
  

  public static BufferedReader grabStream( String filename )
    throws IOException
  {
    URL url = grabFile( filename );
    if ( url != null ) {
      return grabStream( url );
    }
    return null;
  }
  
  public static URL grabFile( String filename )
       throws IOException
  {
    // switch packaging to directory name scheme.

    filename = filename.replace( '.', File.separatorChar );
    int ind = filename.lastIndexOf( File.separatorChar );
    if ( ind != -1 )
      {
	StringBuffer sb = new StringBuffer( filename );
	sb.setCharAt( ind, '.' );
	filename = sb.toString();
      }

    URL url = ClassFinder.getResource(filename);
    if ( url == null ) {
      url = ClassFinder.getResource("mod/" + filename);
    }
    
    return url;
  }

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

  public Surface initializeSurface( RecFrame f, Surface s )
  {
    String nextLine = null;

    m_Surface = s;
    if ( m_Surface != null )
      m_Surface.stopRecording();

    try {
      while( (nextLine = m_Reader.readLine()) != null ) {
	parseLine( f, nextLine );
      }
    }
    catch( IOException e ) {
      e.printStackTrace();
      Console.writeln( "I/O error occurred while processing the ini file" );

      if ( m_Surface != null )
	m_Surface.startRecording();

      return null;
    }

    makeModuleIfNeeded();

    m_Surface.addModule( m_Module, m_EditMode );
    if ( ! m_dependentModules.isEmpty() ) {
      m_Module.setDependentModules( m_dependentModules );
    }

//      DebugWindow.addLine( "MO",
//  			 "Mod = " + m_Name +
//  			 " has dependents: " + m_dependentModules );

    m_Surface.startRecording();

    return m_Surface;
  }


  /**
   * Used to just get the module defined by the file.
   */
  public Module getModule( )
  {
    m_ModuleOnly = true;

    String nextLine = null;
    try
      {
	while( (nextLine = m_Reader.readLine()) != null ) 
	  {
	    parseLine( null, nextLine );
	  }
      }
    catch( IOException e ) 
      {
	e.printStackTrace();
	Console.writeln( "I/O error occurred while processing the ini file" );
      }

    m_ModuleOnly = false;

    return m_Module;
  }


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

  private void parseLine( RecFrame f,
			  String line ) throws IOException
  {
    String command = line.trim();

    if( isEmpty( command ) )
      return;

    if( isComment( command ) )
      return;

    if( executeCommand( command ) )
      return;

    
    // if m_ModuleOnly is true, we are just building a basic module from
    // a file and should not carry out any commands which change/affect
    // the surface.
    if ( !m_ModuleOnly )
    {
	//System.err.println( "dealing with \"" + line + "\""  );
	if ( command.startsWith( "Surface" ) )
	  {
	    Console.writeln( "Making surface." );
	    
	    Assert.note( m_Surface == null,
			  "Surface is already defined" );

	    StringTokenizer st = new StringTokenizer( command, " \t" );
	    st.nextToken(); // cut out the module thing.
	    String mname = st.nextToken();
		
	    try  
	      {
		Console.writeln( "Creating surface of type " + mname );
		m_Surface = (Surface)DynamicConstructor.create(mname).create();
	      }
	    catch( Exception e ) 
	      {
		e.printStackTrace();
		Assert.error("Error creating surface");
	      }

	    if ( m_Surface != null )
	      {
		// hack counting
		m_Surface.stopRecording();
	      }
	    else
	      {
		Assert.error( "Surface call " + command + " failed" );
	      }

	    return;
	  }
	
	if ( command.startsWith( "Color" ) )
	  {
	    StringTokenizer st = new StringTokenizer( command, " \t" );
	    st.nextToken();
	    String mname = st.nextToken();
	    String cname = st.nextToken();

	    Colors.setColor( mname, cname );

	    return;
	  }

	/** SURFACE DEPENDENT ACTIONS **/

	// make surface if none
	if ( m_Surface == null )
	  {
	    Console.writeln( "Making default Surface" );
	    m_Surface = new Surface();
	    m_Surface.stopRecording();
	  }

	if ( command.startsWith( "Grammar" ) ) {
	  loadGrammar( f, command, m_Surface );
	  return;
	}

	if( command.startsWith( "Rule" ) ) {
	  loadRule( command, m_Surface );
	  return;
	}
	
	if ( command.startsWith( "Part" ) )
	{
	  StringTokenizer st = new StringTokenizer( command, " \t" );
	  st.nextToken();
	  String mname = st.nextToken();
	  m_Surface.addWidgetOrdering( mname );

	  return;
	}

	if( command.startsWith( "Module" ) ) 
	{
	  loadModule( f, command );
	  return;
	}
	
	if ( command.startsWith( "Widget" ) )
	{
	  loadWidget( f, command );
	  return;
	}

	if ( command.startsWith( "EditMode" ) ) {
	  setEditMode( command, false );
	  return;
	}

	if ( command.startsWith( "MajorEditMode" ) ) {
	  setEditMode( command, true );
	  return;
	}
	

	if ( command.startsWith( "SystemProperty" ) ) {
	  addProperterty( command );
	  return;
	}

	if( command.startsWith( "SimServer" ) ) {
	  addServer( f, command );
	  return;
	}

	if( command.startsWith( "CleanUp" )
	    || command.startsWith( "Done" ) ) 
	  {
	    Console.writeln( "loading " + command );
	    Console.indent( 5 );
	  
	    StringTokenizer st = new StringTokenizer( command, " \t" );
	    st.nextToken(); // cut out the command tag thing.
	    String tname = st.nextToken();
	  
	    Object o = null;
	    if( tname == null )
	      return;
	    else
	      o = DynamicConstructor.create( tname ).create();
	  
	    if( o == null ) {
	      Console.writeln( "unknown surface mutator: " + tname );
	      return;
	    }
	  
	    if( command.startsWith( "CleanUp" ) )
	      f.addButton( "CLEAN UP", (SurfaceMutator)o );
	    else // the "Done" case
	      f.addButton( "DONE", (SurfaceMutator)o );
	  
	    Console.outdent( 5 );
	  
	    return;
	  }
    } // end if !m_ModuleOnly

    // Otherwise it must be a recognizer or reasoner
    loadRecognizerOrReasoner( command );
  }

  private void loadRecognizerOrReasoner( String command ) 
  {
    StringTokenizer st = new StringTokenizer( command, " \t" );
    command = st.nextToken();
    if ( command.equals( "Recognizer" ) || command.equals( "Reasoner" ) ) {
      command = st.nextToken();
    }
    String args = null;
    if( st.hasMoreTokens() )
      args = st.nextToken();

    Object o = null;
    try  {
      if( args == null ) {
	o = DynamicConstructor.create( command ).create();
      }
      else {
	Class[] cl = new Class[1];
	cl[0] = Class.forName( "java.lang.String" );
	Object[] arga = new Object[1];
	arga[0] = args;
	o = DynamicConstructor.create( command, cl ).create( arga );
      }
    }
    catch( Exception e ) {
      e.printStackTrace();
    }

    Assert.check( o != null, m_Name + ": Error creating object: " + command );

    // create a module if there isn't one yet
    makeModuleIfNeeded();


    if ( o instanceof Recognizer )
      m_Module.add( (Recognizer)o );
    else if ( o instanceof Reasoner ) {
      m_Module.add( (Reasoner)o );
    }
    else
      Assert.error( "In " + m_Name + " there is an object which is not a Recognizer or Reasoner: " + command );

  }

  /** Create a module if there isn't one yet **/
  private void makeModuleIfNeeded() 
  {
    if ( m_Module == null ) {
      if ( m_EditMode.isEmpty() ) {
	m_Module = new BasicModule( m_Name );
      }
      else {
	m_Module = new EditModeModule( m_Name, m_EditMode );
      }
    }
  }
  

//    /**
//     * Figure out what the name of this module should be.
//     */
//    String generateName( String kind )
//    {
//      String n = m_Name.toString();

//      int i = n.lastIndexOf( ".mod" );

//      if( i != -1 )
//        return( n.substring( 0, i ) + kind );
//      else
//        return( n + kind );
//    }



  private void loadWidget( RecFrame f,
			   String command )
       throws IOException
  {
      Console.writeln( "loading " + command );
      Console.indent();

      StringTokenizer st = new StringTokenizer( command, " \t" );
      st.nextToken(); // cut out the module thing.
      String mname = st.nextToken();
      String xstr = st.nextToken();
      String ystr = st.nextToken();
      int x = Integer.parseInt( xstr );
      int y = Integer.parseInt( ystr );
      
      String args = null;
      if( st.hasMoreTokens() )
	args = st.nextToken();

      Widget wid = null;
      try  
	{
	  if( args == null ) {
	    wid = (Widget)DynamicConstructor.create( mname ).create();
	  }
	  else {
	    Class[] cl = new Class[1];
	    cl[0] = Class.forName( "java.lang.String" );
	    Object[] arga = new Object[1];
	    arga[0] = args;
	    wid = (Widget)DynamicConstructor.create( mname, cl ).create( arga );
	  }
	}
      catch( Exception e ) {
	e.printStackTrace();
      }

      // failed to build widget
      if ( wid == null )
	{
	  Assert.error( "Failed to build widget " + command );
	  return;
	}

      if ( wid instanceof SpatialWidget ) {
	Console.writeln( "Moving widget to " + x + ", " + y );
	((SpatialWidget)wid).move( new GPoint( x, y ) );
      }
      
      //adding widget to surface, as a root
      m_Surface.addStartWidget( wid );

      Console.outdent();
  }


  private void loadModule( RecFrame f, String command )
    throws IOException
  {
      Console.writeln( "loading " + command );
      Console.indent();

      StringTokenizer st = new StringTokenizer( command, " \t" );
      st.nextToken();
      String mname = st.nextToken();

      // add the .mod if it isn't there already.
      if( !mname.endsWith( ".mod" ) )
	mname += ".mod";

      URL url = new URL( m_Base, mname );

      // don't create a second module if one is already present
      if ( !m_Surface.hasModule( url.getFile() ) ) {
	RecIniFile rif = new RecIniFile( url, null );
	rif.initializeSurface( f, m_Surface );
      }
      
      // the specified module is now guaranteed to be installed on the
      // surface.  If it has an edit mode it is not dependant on this
      // module (just on the current edit mode).  Otherwise we make it
      // dependent on this module being active
      Module m = m_Surface.getModule( url.getFile() );
      if ( !(m instanceof EditModeModule) ) {
	m_dependentModules.add( m );
      }

      Console.outdent();
  }


  private void loadGrammar( RecFrame f, String command, Surface s )
  {
    if ( ! s.getSystemProperty( "speech", "" ).equalsIgnoreCase("on") ) {
      Console.writeln( "Ignoring grammar: " + command );
      return;
    }
    Console.writeln( "loading " + command );
    Console.indent();

    if (!s.isUtterancePanelEnabled()) {
      UtterancePanel p = new UtterancePanel();
//        JFrame fr = new JFrame("Utterances");
//        fr.getContentPane().add(p);
//        fr.pack();
//        fr.show();
      f.addToolbarComponent(p);
      p.validate();
      f.validate();
      s.setUtterancePanel(p);
    } // end of if ()
    

    StringTokenizer st = new StringTokenizer( command, " \t" );
    st.nextToken();
    String gname = st.nextToken();

    // strip the .gram if it is there
    int index = gname.lastIndexOf( ".gram" );
    if( index != -1 )
      gname = gname.substring( 0, index );

    RecGrammar gram = RecGrammar.makeRecGrammar( gname );

    // create a module if there isn't one yet
    makeModuleIfNeeded();

    m_Module.add( gram, s );

    PhraseIO.setIO( new SpeechPhraseIO() );

    Console.outdent();    
  }

  private void loadRule( String command, Surface s ) 
  {
    StringTokenizer st = new StringTokenizer( command, " \t" );
    command = st.nextToken();
    if ( command.equals( "Rule" ) ) {
      command = st.nextToken();
    }
    Assert.check( !st.hasMoreTokens(),
		  m_Name +
		  ": Rule lines should be: Rule <rule class>" );

    Object o = null;
    try  {
      o = DynamicConstructor.create( command ).create();
    }
    catch( Exception e ) {
      e.printStackTrace();
    }

    Assert.check( o != null, m_Name + ": Error creating object: " + command );

    s.getNotebook().addRule( (AnnotateRule)o );
  }
  

  /**
   **/
  private void setEditMode( String command, boolean isMajor )
  {
    Console.writeln( "Setting the edit mode for this module." );
    Console.indent();

    Assert.check( m_Module == null,
		  "Cannot change modes after instantiating recognizers." );

    StringTokenizer st = new StringTokenizer( command, " \t" );

    // strip off the "EditMode"
    st.nextToken();

    // get the name of the mode
    String editMode = st.nextToken(); 

    if ( isMajor ) {
      m_Surface.addMajorMode( editMode );
    }
    
    m_EditMode.add( editMode );
    Console.outdent();    
  }

  private void addProperterty( String command )
  {
    Console.indent();
    
    StringTokenizer st = new StringTokenizer( command, " \t" );

    // strip off the "SystemProperty" tag
    st.nextToken();
    // get the key string
    String key = st.nextToken();
    // get the value string between quotes

    String value = null;
    try {
      st.nextToken( "\"" );
      value = st.nextToken( "\"" );
    }
    catch ( NoSuchElementException nsee ) {
      Console.writeln( "Bad SystemProperty line " + command );
      Assert.error( "Bad line in mod file: " + m_Name );
    }
    
    m_Surface.setSystemProperty( key, value );
    Console.outdent();
  }

  private void addServer( RecFrame frame, String command )
  {
    // ignore servers if we aren't a Network surface
    if( ! ( m_Surface instanceof SimulateSurfaceNetwork ) ) {
      return;
    }
    
    Console.indent();
    SimulateSurfaceNetwork serverSurface =
      (SimulateSurfaceNetwork)m_Surface;

    StringTokenizer tokenizer = new StringTokenizer( command, " \t" );
    // strip off the "SimServer" tag
    tokenizer.nextToken();
    // get the server
    String server = tokenizer.nextToken();
    Console.writeln( "Adding server: " + server );
    serverSurface.addServer( server, frame );
    Console.outdent();
  }
  
  
  
  private boolean executeCommand( String cmd )
  {
    if( cmd.equals( "Console EnableOutput" ) ) {
      Console.enableOutput();
      Console.writeln( "Console EnableOutput" );
      return( true );
    }

    if( cmd.equals( "Console EnablePrettyPrinting" ) ) {
      Console.enablePrettyPrinting();
      Console.writeln( "Console EnablePrettyPrinting" );
      return( true );
    }

    if( cmd.equals( "Display EnableOutput" ) ) {
      Display.enableOutput();
      Console.writeln( "Display EnableOutput" );
      return( true );
    }
    
    return( false );
  }


  private boolean isEmpty( String command )
  {
    if( command.equals( "" ) )
      return( true );

    return( false );
  }

  private boolean isComment( String command )
  {
    if( isEmpty( command ) )
      return( false );

    if( command.charAt( 0 ) == '#' )
      return( true );

    return( false );
  }

  private boolean isCommand( String command )
  {
    if( isEmpty( command ) )
      return( false );

    if( command.charAt( 0 ) == '!' )
      return( true );

    return( false );
  }
}


