// joi/10/juno/Juno.java                         
//                                                            
//                                                            
// Copyright 2003 Bill Campbell and Ethan Bolker                         
                                                            
import java.io.*;
import java.util.*;
import java.lang.*;

/** 
 * Juno (Juno's Unix NOt) mimics a command line operating system 
 * such as Unix.  
 * <p>
 * A Juno system has a name, a set of Users, a JFile system,
 * a login process and a set of shell commands.
 * 
 * @see User
 * @see JFile
 * @see ShellCommand
 *
 * @version 10
 **/

public class Juno 
    implements Serializable
{
    private final static String OS      = "Juno";
    private final static String VERSION = "10";

    private String    hostName;   // host machine name
    private Map       users;      // lookup table for Users
    private transient OutputInterface  console;  

    private Directory slash;      // root of JFile system
    private Directory userHomes;  // for home directories

    private ShellCommandTable commandTable; // shell commands

    // file containing Juno state
    private transient String fileName = null;

    // port used by Juno server for remote login
    private int junoPort = 2001; 

    /**
     * Construct a Juno (operating system) object.
     *
     * @param hostName  the name of the host on which it's running.
     * @param echoInput should all input be echoed as output?
     * @param isGUI graphical user interface?
     * @param isRemote running as a server?
     */

    public Juno( String hostName, boolean echoInput, 
                 boolean isGUI, boolean isRemote )
    {
        // Initialize the Juno environment ...

        this.hostName     = hostName;
        users             = new TreeMap();
        commandTable      = new ShellCommandTable();

        // the file system

        slash = new Directory( "", null, null );
        User root = new User( "root", "swordfish", slash, 
                              "Rick Martin" );
        users.put( "root", root );
        slash.setOwner(root);
        userHomes = new Directory( "users", root, slash );
    }

    // Set up the correct console:
    // command line (default), graphical or remote.

    private void setupConsole( boolean echoInput, boolean isGUI, 
                               boolean isRemote )
    {
        LoginInterpreter interpreter 
            = new LoginInterpreter( this, null );

        if (isGUI) {
            console = new GUILoginConsole( hostName, 
                                       this, interpreter, echoInput);
        }
        else if (isRemote) {
            console = new RemoteConsole( this, echoInput, junoPort );
        }
        else {
            console = new JunoTerminal( echoInput );
        }

        // Tell the interpreter about the console
        interpreter.setConsole( console );

        // If we're using a simple command line interface,
        // start that.  (Constructing a GUI starts the GUI.)
        // Shut down Juno when done

        if (!isGUI && !isRemote) {
            interpreter.CLILogin();
            shutDown();
        }
    }

    /** 
     * Shut down this Juno system.
     *
     * Save state if required.
     */

    public void shutDown( )
    {
        if (fileName != null) { 
            writeJuno( );
        }
    }

    /**
     * Set the name of file in which system state is kept.
     *
     * @param fileName the file name.
     */

    public void setFileName(String fileName)
    {
        this.fileName = fileName;
    }

    /**
     * The name of the host computer on which this system
     * is running.
     *
     * @return the host computer name.
     */

    public String getHostName()
    {
        return hostName;
    }

    /**
     * The name of this operating system.
     *
     * @return the operating system name.
     */

    public String getOS()
    {
        return OS;
    }

    /**
     * The version number for this system.
     *
     * @return the version number.
     */

    public String getVersion()
    {
        return VERSION;
    }

    /**
     * The directory containing all user homes for this system.
     *
     * @return the directory containing user homes.
     */

    public Directory getUserHomes()
    {
        return userHomes;
    }

    /**
     * The shell command table for this system.
     *
     * @return the shell command table.
     */

    public ShellCommandTable getCommandTable()
    {
        return commandTable;
    }

    /**
     * Look up a user by user name.
     *
     * @param username the user's name.
     * @return the appropriate User object. 
     */

    public User lookupUser( String username )  
    {
        return (User) users.get( username );
    }

    /**
     * Create a new User.
     *
     * @param userName the User's login name.
     * @param home her home Directory.
     * @param password her password.
     * @param realName her real name.
     * @return newly created User.
     */

    public User createUser( String userName, Directory home,
                            String password, String realName )
    {
        User newUser = new User( userName, password,
                                 home, realName );
        users.put( userName, newUser );
        return newUser;
    }

    /**
     * The Juno system may be given the following command line
     * arguments:
     *
     * -e:         Echo all input (useful for testing).
     *
     * -version:   Report the version number and exit.
     *
     * -g:         Support a GUI console.
     *
     * -remote     Start Juno server.
     *
     * -f filename File to read/write system state from/to
     *
     * [hostname]: The name of the host on which
     *             Juno is running (optional).
     */

    public static void main( String[] args )
    {
        // Parse command line options

        boolean echoInput    = false;
        boolean versionQuery = false;
        boolean isGUI        = false;
        boolean isRemote     = false;
        String  hostName     = "mars";
        String  junoFileName = null;

        for (int i=0; i < args.length; i++) {
            if (args[i].equals("-e")) {
                echoInput = true;
            }
            else if (args[i].equals("-version")) {
                versionQuery = true;
            }
            else if (args[i].equals("-g")) {
                isGUI = true;
            }
            else if (args[i].equals( "-remote" )) {
                isRemote = true;
            }
            else if (args[i].equals("-f")) {
                junoFileName = args[++i];
            }
            else {
                hostName = args[i];
            }
        }

        // If it's  a version query give the version and exit
        if ( versionQuery ) {
            System.out.println( OS + " version " + VERSION );
            System.exit(0);
        }

        // Create a new Juno or read one from a file.
        Juno junoSystem = null;
        if (junoFileName != null) {
            junoSystem = readJuno( junoFileName );
        }
        if (junoSystem == null) {
            junoSystem = new Juno( hostName, echoInput, 
                                   isGUI, isRemote );
        }
        junoSystem.setFileName( junoFileName );
        junoSystem.setupConsole( echoInput, isGUI, isRemote );
    }

    // Read Juno state from a file.
    //
    // @param junoFileName the name of the file containing the system.
    // @return the system, null if file does not exist.

    private static Juno readJuno(String junoFileName)
    {
        File file = new File( junoFileName );
        if (!file.exists()) {
            return null;
        }
        ObjectInputStream inStream = null;
        try {
            inStream = new ObjectInputStream( 
                           new FileInputStream( file ) );
            Juno juno = (Juno)inStream.readObject();
            System.out.println( 
               "Juno state read from file " + junoFileName);
            return juno;
        }
        catch (Exception e ) {
            System.err.println("Problem reading " + junoFileName );
            System.err.println(e);
            System.exit(1);
        }
        finally {
            try {
                inStream.close();
            }
            catch (Exception e) {
            }
        }
        return null; // you can never get here
    }
    
    // Write Juno state to a file.

    private void writeJuno() 
    {
        ObjectOutputStream outStream = null;
        try {
            outStream = new ObjectOutputStream( 
                           new FileOutputStream( fileName ) );
            outStream.writeObject( this );
            System.out.println( 
               "Juno state written to file " + fileName);
        }
        catch (Exception e ) {
            System.err.println("Problem writing " + fileName);
            System.err.println(e);
        }
        finally {
            try {
                outStream.close();
            }
            catch (Exception e ) {
            }
        }
    }
}
