/*

Freely Distributable Java Applets

Copyright (c) 1996-2001 The University of Texas
All Rights Reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

The GNU Public License is available in the file LICENSE, or you
can write to the Free Software Foundation, Inc., 59 Temple Place -
Suite 330, Boston, MA 02111-1307, USA, or you can find it on the
World Wide Web at http://www.fsf.org.

Author: Dogu Arifler, Brian Evans, Jun Huang and Saleem Marwat
Version: @(#)Simulator.java	1.30  04/17/01

Department of Electrical and Computer Engineering
The University of Texas, Austin, TX 78712-1084

Interface to a server providing access to command-line simulators.
*/

package utexas.espl.weds.ui;

import java.net.*;
import java.io.*;
import java.util.Vector;

public class Simulator {
    private boolean simulatorFlag = false;
    private int simulatorId = -1;
    private boolean connectedFlag = false;
    private boolean unnormal_terminate_flag=false;
    private BufferedReader inputStream = null;
    private PrintStream outputStream = null;

    private Socket connection = null;

    private static String internetProtocolAddress = null;
    private Vector history = null;

    private boolean _DEBUG = false;
    final String IN_USE = "Boards Not Available";
    final int END_OF_STRING_CHAR = '`';
    int SOCKET_NUMBER = 0; // SOCKET_NUMBER will be read from the config file

    final static String NO_NEED_CONFIG = "donot send config";
    final static String CONFIG_REQUEST = "send config please";

    final int RETURN_NUMBER_OF_SIMULATORS = 0;
    final int RETURN_PROCESSOR_NAMES = 1;
    final int RETURN_SIMULATOR_TYPES = 2;
    final int RETURN_COMMANDS_COMMAND = 3;
    final int RETURN_CYCLE_COMMAND = 4;
    final int RETURN_ENABLE_DEBUGGING = 5;
    final int RETURN_EXIT_COMMAND = 6;
    final int RETURN_LOAD_COMMAND = 7;
    final int RETURN_HELP_COMMAND = 8;
    final int RETURN_PROCESSOR_STATE_COMMAND = 9;
    final int RETURN_QUIT_COMMAND = 10;
    final int RETURN_SIMULATOR_COMMAND = 11;
    final int RETURN_STEP_COMMAND = 12;
    final int RETURN_VERSION_COMMAND = 13;
    final int RETURN_USER_PROG_DIR = 14;

    // Configuration information specific to the simulator being used
    // retrieved from the server.
    private static int numSimulators = 0;
    private static String[] processorNames =        null;
    private static String[] simulatorType =         null;
    private static String[] commandsCommand =       null;
    private static String[] cycleCommand =          null;
    private static String[] enableDebugCommand =    null;
    private static String[] exitCommand =           null;
    private static String[] loadCommand =           null;
    private static String[] helpCommand =           null;
    private static String[] procStateCommand =      null;
    private static String[] quitCommand =           null;
    private static String[] simulatorStateCommand = null;
    private static String[] stepCommand =           null;
    private static String[] versionCommand =        null;

    // Define the name of the subdirectory for user files.
    private static String UserProgramDirectory = null;

    // Commands for the server which begin with a '#' character
    final String LOAD_CMD = "#LOAD";
    final String DELETE_CMD = "#DELETE";
    final String FILE_EXIST_CMD = "#EXIST";
    final String TERMINATE_CMD = "#BYE";

    final int    CONFIG_COMMAND_NUM = 15;
    final String FILE_CMD = "file";
    final String SIMULATOR_CMD = "simulator";

    final int    HISTORY_SIZE_INITIAL = 200;
    final int    HISTORY_SIZE_UPDATE = 100;

    private boolean fileCommandFlag = false;

    // FIXME: Should cmdCode be static?
    static char commandCode;

    // Constructor that retrieves configuration information from the
    // server such as the number of simulators and names of simulators
    public Simulator( String ipAddress, String baseURL ) {
        WedsConfig config = new WedsConfig(baseURL);
        SOCKET_NUMBER = config.getPort();
        internetProtocolAddress = new String(ipAddress);
        establishServerConnection();
        outputStream.println(CONFIG_REQUEST);
        outputStream.flush();
        FetchConfig();
    }

    // Constructor for a specific simulator
    public Simulator( String ipAddress, String simName, String baseURL ) {
        WedsConfig config = new WedsConfig(baseURL);
        SOCKET_NUMBER = config.getPort();
        int simId;
        for (simId = 0; simId < numSimulators; simId++) {
            if ( simName.equals( processorNames[simId] ) ) break;
        }

        if (simId >= numSimulators) {
            simId = -1;
        }
        else {
            simulatorFlag = true;
        }
        simulatorId = simId;

        // Define a vector of HISTORY_SIZE_INITIAL elements that grows by
	// HISTORY_SIZE_UPDATE elements each time its full to store the
	// evaluated commands
        history = new Vector(HISTORY_SIZE_INITIAL, HISTORY_SIZE_UPDATE);

        internetProtocolAddress = new String(ipAddress);

        if (connectedFlag != true) {
          establishServerConnection();
        }
        outputStream.println(NO_NEED_CONFIG);
        outputStream.write(simulatorId);
        outputStream.flush();
        connectedFlag = true;

        try{
          String is = inputStream.readLine();
          if (is.compareTo(IN_USE)==0){
            connectedFlag = false;
            unnormal_terminate_flag=true;
            terminate();
            unnormal_terminate_flag=false;
          }
          else {
            System.out.println("Boards Available");
          }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean connectFlag(){
      return connectedFlag;
    }

    protected void FetchConfig(){
      int status=0;

      try {
        String ConfigInfo;
        while (((ConfigInfo=inputStream.readLine()) != null) &&
               (status < CONFIG_COMMAND_NUM)) {
          parse(ConfigInfo, status);
          status++;
        }
        connectedFlag = true;
      }
      catch (IOException e) {
        e.printStackTrace();
      }
    }

    private void parse(String configinfo, int status){
      int index0, index1;
      String info = new String(configinfo);

      switch(status) {
      case RETURN_NUMBER_OF_SIMULATORS:
        String t0 = new String(info);
        try {
          numSimulators = Integer.parseInt(t0);
        }
        catch(NumberFormatException e) {
        }
        break;

      case RETURN_PROCESSOR_NAMES:
        index0 = -1;
        processorNames = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1 = info.indexOf('#', index0+1);
          processorNames[i]= new String(info.substring(index0+1,index1));
          index0 = index1;
        }
        break;

      case RETURN_SIMULATOR_TYPES:
        index0 = -1;
        simulatorType = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++){
          index1 = info.indexOf('#',index0+1);
          simulatorType[i] = new String(info.substring(index0+1,index1));
          index0 = index1;
        }
        break;

      case RETURN_COMMANDS_COMMAND:
        index0 = -1;
        commandsCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1 = info.indexOf('#',index0+1);
          commandsCommand[i] = new String(info.substring(index0+1,index1));
          index0 = index1;
        }
        break;

      case RETURN_CYCLE_COMMAND:
        index0 = -1;
        cycleCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1=info.indexOf('#',index0+1);
          cycleCommand[i]= new String(info.substring(index0+1,index1));
          index0=index1;
        }
        break;

      case RETURN_ENABLE_DEBUGGING:
        index0 = -1;
        enableDebugCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1=info.indexOf('#',index0+1);
          enableDebugCommand[i]= new String(info.substring(index0+1,index1));
          index0=index1;
        }
        break;

      case RETURN_EXIT_COMMAND:
        index0 = -1;
        exitCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1 = info.indexOf('#',index0+1);
          exitCommand[i] = new String(info.substring(index0+1,index1));
          index0 = index1;
        }
        break;

      case RETURN_LOAD_COMMAND:
        index0 = -1;
        loadCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1=info.indexOf('#',index0+1);
          loadCommand [i]= new String(info.substring(index0+1,index1));
          index0=index1;
        }
        break;

      case RETURN_HELP_COMMAND:
        index0 = -1;
        helpCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1=info.indexOf('#',index0+1);
          helpCommand[i] = new String(info.substring(index0+1,index1));
          index0 = index1;
        }
        break;

      case RETURN_PROCESSOR_STATE_COMMAND:
        index0 = -1;
        procStateCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1 = info.indexOf('#',index0+1);
          procStateCommand[i] = new String(info.substring(index0+1,index1));
          index0 = index1;
        }
        break;

      case RETURN_QUIT_COMMAND:
        index0 = -1;
        quitCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1=info.indexOf('#',index0+1);
          quitCommand [i]= new String(info.substring(index0+1,index1));
          index0=index1;
        }
        break;

      case RETURN_SIMULATOR_COMMAND:
        index0=-1;
        simulatorStateCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1=info.indexOf('#',index0+1);
          simulatorStateCommand[i]= new String(info.substring(index0+1,index1));
          index0=index1;
        }
        break;

      case RETURN_STEP_COMMAND:
        index0 = -1;
        stepCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1 = info.indexOf('#',index0+1);
          stepCommand[i] = new String(info.substring(index0+1,index1));
          index0 = index1;
        }
        break;

      case RETURN_VERSION_COMMAND:
        index0 = -1;
        versionCommand = new String[numSimulators];
        for (int i = 0; i < numSimulators; i++) {
          index1=info.indexOf('#',index0+1);
          versionCommand [i]= new String(info.substring(index0+1,index1));
          index0=index1;
        }
        break;

      case RETURN_USER_PROG_DIR:
        index0 = -1;
        UserProgramDirectory = new String(info.substring(index0+1));
        break;
      }

      status++;
    }

    // Destructor
    protected void finalize() throws Throwable {
        closeServerConnection();
        super.finalize();
    }

    // Methods for accessing values of data members

    // Return the number of simulators supported
    int numberOfSimulators() {
        return numSimulators;
    }

    // Return the simulator name for a given simulator identification number
    String name(int simId) {
        if ((simId >= 0) && (simId < numSimulators)) {
            System.out.println(simId + "  simId " + processorNames[simId]);
            return new String(processorNames[simId]);
        }
        return "";
    }

    // Return the simulator description for a given simulator id number
    String description(int simId) {
        if ((simId >= 0) && (simId < numSimulators)) {
            return new String(processorNames[simId] + " " +
                              simulatorType[simId] );
        }
        return "";
    }

    // Methods for interacting with a simulator (emulator or debugger)

    // Conditionally log a command and send it to a simulator for evaluation
    public String run( String command ) {
        return runSupport( command, true );
    }

    public String run( String command, boolean logCommandFlag ) {
        return runSupport( command, logCommandFlag );
    }

    private String runSupport( String command, boolean logCommandFlag ) {
        String returnValue = null;
        if (!connectedFlag) {
            returnValue = new String("Error: no connection to server.\n");
        }
        else if (!simulatorFlag) {
            returnValue = new String("Error: no emulator available.\n");
        }
        else {
            if (logCommandFlag) {
                addCommandToHistory(command);
            }
            sendCommandToServer(command);
            if (_DEBUG) {
                System.out.println(command);
            }
            returnValue = new String(receiveResultFromServer());
            if (terminateSimulatorCommand(command)) {
               simulatorFlag = false;
            }
        }
        return returnValue;
    }

    public String displayCycles() {
        return run(cycleCommand[simulatorId]);
    }

    public String enableDebugging() {
        return run(enableDebugCommand[simulatorId]);
    }

    public String helpOnTopic(String topic) {
        String command = helpCommand[simulatorId];
        return run(command + " " + topic);
    }

    public String load(String filename) {
        String command = loadCommand[simulatorId];
        return run(command + " " + filename);
    }

    public String listOfCommands() {
        return run(commandsCommand[simulatorId]);
    }

    public String processorState() {
        return run(procStateCommand[simulatorId]);
    }

    public String simulatorState() {
        return run(simulatorStateCommand[simulatorId]);
    }

    public String step() {
        return run(stepCommand[simulatorId]);
    }

    public String step(int numSteps) {
        String command = stepCommand[simulatorId];
        return run(command + " " + numSteps);
    }

    public boolean terminateSimulatorCommand(String command) {
        boolean isTerminateCommand = false;
        if (!command.equals("")) {
            String trimmed = command.trim();
            isTerminateCommand = trimmed.equals(quitCommand[simulatorId]) ||
                                 trimmed.equals(exitCommand[simulatorId]);
        }
        return isTerminateCommand;
    }

    public String version() {
        String command = versionCommand[simulatorId];
        if (command.length() == 0) {
            return new String(processorNames[simulatorId] +
                              " Simulator, Unknown Version");
        }
        return run(command);
    }

    //
    // Methods for the Graphical User Interface
    //

    public int historySize() {
        return history.size();
    }

    public String[] getHistory() {
        String[] historyArray = new String[ history.size() ];
        history.copyInto(historyArray);
        return historyArray;
    }

    public String getHistory(int element) {
        String command = "";
        if ( (element < 0) || (element >= history.size()) ) {
            command = (String)history.elementAt(element);
        }
        return new String(command);
    }

    private void addCommandToHistory(String command) {
        // Ignore any '\n' at the end of command
        if ( command.charAt( command.length()-1 ) == '\n' )
            history.addElement(
                new String( command.substring( 0, command.length() - 1 ) ) );
        else
            history.addElement(new String(command));
    }

    //
    // Methods for the TCP/IP server
    //

    // Establish a connection to the TCP/IP server
    private void establishServerConnection() {
        if (_DEBUG) {
            System.out.println("About to make connection");
        }
        try {
            connection = new Socket( internetProtocolAddress, SOCKET_NUMBER );
            inputStream = new BufferedReader( new InputStreamReader(connection.getInputStream()) );
            outputStream = new PrintStream(connection.getOutputStream() );
            connectedFlag = true;
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Send a command to the TCP/IP server
    private void sendCommandToServer(String command) {
      if (fileCommandFlag) {
        outputStream.println(FILE_CMD);
      }
      else {
        outputStream.println(SIMULATOR_CMD);
      }

      if (command==null)
        System.out.println("command=" + command + " null");

      if ( command.charAt( command.length() - 1 ) == '\n' ) {
        outputStream.print(command);
      }
      else {
        outputStream.println(command);
      }
      outputStream.flush();
      fileCommandFlag = false;
    }

    // Receive a result from the TCP/IP server
    private String receiveResultFromServer() {
        StringBuffer result = new StringBuffer();
       try {
            // Read and ignore end-of-file markers so that we
            // implement a blocking read
            int c;
            while ( ((c = inputStream.read()) != END_OF_STRING_CHAR) ) {
                result.append((char)c);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return result.toString();
    }

    // Close a connection to the TCP/IP server
    private void closeServerConnection() {
        if (connectedFlag) {
          fileCommandFlag = true;
          if(unnormal_terminate_flag)
            sendCommandToServer(enCrypt(new String(TERMINATE_CMD.concat("#"))));
          else
            sendCommandToServer(enCrypt(TERMINATE_CMD));
            try {
                if (inputStream != null) {
                    inputStream.close();
                    inputStream = null;
                }
                if (outputStream != null) {
                    outputStream.close();
                    outputStream = null;
                }
                if (connection != null) {
                    connection.close();
                    connection = null;
                }
            }
            catch(IOException e) {
                System.err.println("Could not close connection to server. " +
                                   e.getMessage());
            }
        }
        connectedFlag = false;
    }

    // Return the subdirectory for user files
    public String getUserSubDirectory() {
        return new String( UserProgramDirectory );
    }

    // Delete a previously downloaded file
    public String deleteUserFileOnServer() {
        fileCommandFlag = true;
        return run(enCrypt(DELETE_CMD), false);
    }

    // Check to see if a file exists for the current user on the server
    // Also, establishes the name of the file to be stored on the server
    public String checkUserFileOnServer(String filename) {
        fileCommandFlag = true;
        String command =
            new String(FILE_EXIST_CMD + " " + getUserSubDirectory() + filename);
        return run(enCrypt(command), false);
    }

    // Download a user file from given by a URL location to the server
    // Use checkUserFileOnServer to set the filename on the server
    public String loadURLFileToServer(String url) {
        fileCommandFlag = true;
        String command = new String(LOAD_CMD + " " + url);
        return run(enCrypt(command), false);
    }

    // Terminate a connection to the server
    public void terminate() {
        fileCommandFlag = true;
        closeServerConnection();
        simulatorFlag = false;
    }

    public String enCrypt(String command){
      /* This method can be used to encrypt a command. Currently, it does not do anything. */
      return command;
    }
}
