   package mars.util;
   import mars.*;
   import java.io.*;
   import javax.swing.*;
   import java.util.*;
	
	/*
Copyright (c) 2003-2013,  Pete Sanderson and Kenneth Vollmar

Developed by Pete Sanderson (psanderson@otterbein.edu)
and Kenneth Vollmar (kenvollmar@missouristate.edu)

Permission is hereby granted, free of charge, to any person obtaining 
a copy of this software and associated documentation files (the 
"Software"), to deal in the Software without restriction, including 
without limitation the rights to use, copy, modify, merge, publish, 
distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject 
to the following conditions:

The above copyright notice and this permission notice shall be 
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

(MIT license, http://www.opensource.org/licenses/mit-license.html)
 */
	
/**
 * Provides standard i/o services needed to simulate the MIPS syscall
 * routines.  These methods will detect whether the simulator is being
 * run from the command line or through the GUI, then do I/O to
 * System.in and System.out in the former situation, and interact with
 * the GUI in the latter.
 *
 * @author Pete Sanderson and Ken Vollmar
 * @version August 2003-2005
 */
    public class SystemIO
   {
    /** Buffer size for syscalls for file I/O */
      public static final int SYSCALL_BUFSIZE = 128;
    /** Maximum number of files that can be open */
      public static final int SYSCALL_MAXFILES = 32;
    /** String used for description of file error */
      public static String fileErrorString = new String("File operation OK");
   
      private static final int O_RDONLY = 0x00000000;
      private static final int O_WRONLY = 0x00000001;
      private static final int O_RDWR   = 0x00000002;
      private static final int O_APPEND = 0x00000008;
      private static final int O_CREAT  = 0x00000200; // 512
      private static final int O_TRUNC  = 0x00000400; // 1024
      private static final int O_EXCL   = 0x00000800; // 2048
   	
   	// standard I/O channels
      private static final int STDIN  = 0;
      private static final int STDOUT = 1;
      private static final int STDERR = 2;
   
      // Will use one buffered reader for all keyboard/redirected/piped input.
   	// Added by DPS 28 Feb 2008.  See getInputReader() below.
      private static BufferedReader inputReader = null;
   
    /**
     * Implements syscall to read an integer value.  
     * Client is responsible for catching NumberFormatException.
     * @param serviceNumber the number assigned to Read Int syscall (default 5)
     * @return int value corresponding to user input
     */
   
       public static int readInteger(int serviceNumber)
      {
         String input = "0";
         if (Globals.getGui() == null)
         {
            try
            {  
               input = getInputReader().readLine();
            } 
                catch (IOException e)
               {}
         } 
         else
         {
            if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
               input = Globals.getGui().getMessagesPane().getInputString(
                    "Enter an integer value (syscall "+serviceNumber+")");
            } 
            else {
               input = Globals.getGui().getMessagesPane().getInputString(-1);
            }
         }
      
        // Client is responsible for catching NumberFormatException
         return new Integer(input.trim()).intValue();
      }
   	
   	
    /**
     * Implements syscall to read a float value.
     * Client is responsible for catching NumberFormatException.
     * @param serviceNumber the number assigned to Read Float syscall (default 6)
     * @return float value corresponding to user input
     * Feb 14 2005 Ken Vollmar
     */
       public static float readFloat(int serviceNumber)
      {
         String input = "0";
         if (Globals.getGui() == null)
         {
            try
            {
               input = getInputReader().readLine();
            } 
                catch (IOException e)
               {}
         } 
         else
         {
            if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
               input = Globals.getGui().getMessagesPane().getInputString(
                    "Enter a float value (syscall "+serviceNumber+")");
            } 
            else {
               input = Globals.getGui().getMessagesPane().getInputString(-1);
            }
         }
         return new Float(input.trim()).floatValue();
      
      }
    
    
    /**
     * Implements syscall to read a double value.
     * Client is responsible for catching NumberFormatException.
     * @param serviceNumber the number assigned to Read Double syscall (default 7)
     * @return double value corresponding to user input
     * 1 Aug 2005 DPS, based on Ken Vollmar's readFloat
     */
       public static double readDouble(int serviceNumber)
      {
         String input = "0";
         if (Globals.getGui() == null)
         {
            try
            {
               input = getInputReader().readLine();
            } 
                catch (IOException e)
               {}
         } 
         else
         {
            if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
               input = Globals.getGui().getMessagesPane().getInputString(
                    "Enter a double value (syscall "+serviceNumber+")");
            } 
            else {
               input = Globals.getGui().getMessagesPane().getInputString(-1);
            }
         }
         return new Double(input.trim()).doubleValue();
      
      }
    
    
    /**
     * Implements syscall having 4 in $v0, to print a string.
     */
       public static void printString(String string)
      {
         if (Globals.getGui() == null)
         {
            System.out.print(string);
         } 
         else
         {
            Globals.getGui().getMessagesPane().postRunMessage(string);
         }
      
      }
   	
   	
    /**
     * Implements syscall to read a string.
     * @param serviceNumber the number assigned to Read String syscall (default 8)
     * @param maxLength the maximum string length
     * @return the entered string, truncated to maximum length if necessary
     */
       public static String readString(int serviceNumber, int maxLength)
      {
         String input = "";
         if (Globals.getGui() == null)
         {
            try
            {
               input = getInputReader().readLine();
            } 
                catch (IOException e)
               {}
         } 
         else
         {
            if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
               input = Globals.getGui().getMessagesPane().getInputString(
                    "Enter a string of maximum length " + maxLength
                    + " (syscall "+serviceNumber+")");            
            } 
            else {
               input = Globals.getGui().getMessagesPane().getInputString(maxLength);
               if (input.endsWith("\n")) {
                  input = input.substring(0, input.length() - 1);
               }
            }
         }
      	
         if (input.length() > maxLength) {
            // Modified DPS 13-July-2011.  Originally: return input.substring(0, maxLength);
            return (maxLength <= 0) ? "" : input.substring(0, maxLength);
         } 
         else {
            return  input;
         }
      }
   
   
    /** Implements syscall having 12 in $v0, to read a char value.
     *
     * @param serviceNumber the number assigned to Read Char syscall (default 12)
     * @return int value with lowest byte corresponding to user input
     */
       public static int readChar(int serviceNumber)
      {
         String input = "0";
         int returnValue = 0;
         if (Globals.getGui() == null)
         {
            try
            {
               input = getInputReader().readLine();
            } 
                catch (IOException e)
               {}
         } 
         else
         {
            if (Globals.getSettings().getBooleanSetting(Settings.POPUP_SYSCALL_INPUT)) {
               input = Globals.getGui().getMessagesPane().getInputString(
                    "Enter a character value (syscall "+serviceNumber+")");				
            } 
            else {			
               input = Globals.getGui().getMessagesPane().getInputString(1);
            }
         }
         // The whole try-catch is not really necessary in this case since I'm
      	// just propagating the runtime exception (the default behavior), but 
      	// I want to make it explicit.  The client needs to catch it.
         try
         {
            returnValue = (int) (input.charAt(0)); // first character input
         } 
             catch (IndexOutOfBoundsException e) // no chars present
            {
               throw e;  // was: returnValue = 0;
            }
      
         return returnValue;
      
      }
   
   
    /** Write bytes to file.
     *
     * @param fd file descriptor
     * @param myBuffer byte array containing characters to write
     * @param lengthRequested number of bytes to write
     * @return number of bytes written, or -1 on error
     */
   
       public static int writeToFile(int fd, byte[] myBuffer, int lengthRequested)
      {
       /////////////// DPS 8-Jan-2013  ////////////////////////////////////////////////////
       /// Write to STDOUT or STDERR file descriptor while using IDE - write to Messages pane. 
         if ((fd==STDOUT || fd==STDERR) && Globals.getGui() != null) {
            String data = new String(myBuffer);
            Globals.getGui().getMessagesPane().postRunMessage(data);
            return data.length();
         }
       ///////////////////////////////////////////////////////////////////////////////////
       //// When running in command mode, code below works for either regular file or STDOUT/STDERR
      
         if (!FileIOData.fdInUse(fd, 1)) // Check the existence of the "write" fd
         {
            fileErrorString = new String(
                    "File descriptor " + fd + " is not open for writing");
            return -1;
         }
         // retrieve FileOutputStream from storage
         OutputStream outputStream = (OutputStream) FileIOData.getStreamInUse(fd);
         try
         {
            // Oct. 9 2005 Ken Vollmar
            // Observation: made a call to outputStream.write(myBuffer, 0, lengthRequested)
            //     with myBuffer containing 6(ten) 32-bit-words <---> 24(ten) bytes, where the
            //     words are MIPS integers with values such that many of the bytes are ZEROES.
            //     The effect is apparently that the write stops after encountering a zero-valued
            //     byte. (The method write does not return a value and so this can't be verified
            //     by the return value.)
            // Writes up to lengthRequested bytes of data to this output stream from an array of bytes.
            // outputStream.write(myBuffer, 0, lengthRequested); // write is a void method -- no verification value returned
            
            // Oct. 9 2005 Ken Vollmar  Force the write statement to write exactly
            // the number of bytes requested, even though those bytes include many ZERO values.
            for (int ii = 0; ii < lengthRequested; ii++)
            {
               outputStream.write(myBuffer[ii]); 
            }
            outputStream.flush();// DPS 7-Jan-2013
         } 
             catch (IOException e)
            {
               fileErrorString = new String(
                    "IO Exception on write of file with fd " + fd);
               return -1;
            } 
             catch (IndexOutOfBoundsException e)
            {
               fileErrorString = new String(
                    "IndexOutOfBoundsException on write of file with fd" + fd);
               return -1;
            }
      
         return lengthRequested;
      
      } // end writeToFile
   
   
     /** Read bytes from file.
     *
     * @param fd file descriptor
     * @param myBuffer byte array to contain bytes read
     * @param lengthRequested number of bytes to read
     * @return number of bytes read, 0 on EOF, or -1 on error
     */
       public static int readFromFile(int fd, byte[] myBuffer, int lengthRequested)
      {
         int retValue = -1;  
       /////////////// DPS 8-Jan-2013  //////////////////////////////////////////////////
       /// Read from STDIN file descriptor while using IDE - get input from Messages pane. 
         if (fd==STDIN && Globals.getGui() != null) {
            String input = Globals.getGui().getMessagesPane().getInputString(lengthRequested);
            byte[] bytesRead = input.getBytes();;
            for (int i=0; i < myBuffer.length; i++) {
               myBuffer[i] = (i < bytesRead.length) ? bytesRead[i] : 0 ;
            }
            return Math.min(myBuffer.length, bytesRead.length);
         }
       ////////////////////////////////////////////////////////////////////////////////////
       //// When running in command mode, code below works for either regular file or STDIN
       
         if (!FileIOData.fdInUse(fd, 0)) // Check the existence of the "read" fd
         {
            fileErrorString = new String(
                    "File descriptor " + fd + " is not open for reading");
            return -1;
         }
        // retrieve FileInputStream from storage
         InputStream InputStream = (InputStream) FileIOData.getStreamInUse(fd);
         try
         {
            // Reads up to lengthRequested bytes of data from this Input stream into an array of bytes.
            retValue = InputStream.read(myBuffer, 0, lengthRequested);
         	// This method will return -1 upon EOF, but our spec says that negative
         	// value represents an error, so we return 0 for EOF.  DPS 10-July-2008.
            if (retValue == -1) {
               retValue = 0;
            }
         } 
             catch (IOException e)
            {
               fileErrorString = new String(
                    "IO Exception on read of file with fd " + fd);
               return -1;
            } 
             catch (IndexOutOfBoundsException e)
            {
               fileErrorString = new String(
                    "IndexOutOfBoundsException on read of file with fd" + fd);
               return -1;
            }
         return retValue;
      
      } // end readFromFile
   
   
   /**
    * Open a file for either reading or writing. Note that read/write flag is NOT
    * IMPLEMENTED.  Also note that file permission modes are also NOT IMPLEMENTED. 
    *
    * @param filename string containing filename
    * @param flag 0 for read, 1 for write
    * @return file descriptor in the range 0 to SYSCALL_MAXFILES-1, or -1 if error
    * @author Ken Vollmar
    */
       public static int openFile(String filename, int flags)
      {
        // Internally, a "file descriptor" is an index into a table
        // of the filename, flag, and the File???putStream associated with
        // that file descriptor.
      
         int retValue = -1;
         char ch[] = { ' '}; // Need an array to convert to String
         FileInputStream inputStream;
         FileOutputStream outputStream;
         int fdToUse;
      
        // Check internal plausibility of opening this file
         fdToUse = FileIOData.nowOpening(filename, flags);
         retValue = fdToUse; // return value is the fd
         if (fdToUse < 0)
         { 
            return -1;
         }   // fileErrorString would have been set
         
      	
         if (flags == O_RDONLY) // Open for reading only
         {
            try
            {
                // Set up input stream from disk file
               inputStream = new FileInputStream(filename);
               FileIOData.setStreamInUse(fdToUse, inputStream); // Save stream for later use
            } 
                catch (FileNotFoundException e)
               {
                  fileErrorString = new String(
                        "File " + filename + " not found, open for input.");
                  retValue = -1;
               }
         } 
         else if ( (flags & O_WRONLY) != 0 ) // Open for writing only
         {
            // Set up output stream to disk file
            try
            { 
               outputStream = new FileOutputStream(filename, ((flags & O_APPEND) != 0) );
               FileIOData.setStreamInUse(fdToUse, outputStream); // Save stream for later use
            } 
                catch (FileNotFoundException e)
               {
                  fileErrorString = new String(
                        "File " + filename + " not found, open for output.");
                  retValue = -1;
               }
         }
         return retValue; // return the "file descriptor"
      
      }
   
    /** Close the file with specified file descriptor 
     *
     * @param fd the file descriptor of an open file
     */
       public static void closeFile(int fd)
      {
         FileIOData.close(fd);
      }
   
    /** 
     * Reset all files -- clears out the file descriptor table.
     */
       public static void resetFiles()
      {
         FileIOData.resetFiles();
      }
   
     /**
      *  Retrieve file operation or error message
   	*
   	*  @return string containing message
   	*/
       public static String getFileErrorMessage()
      {
         return fileErrorString;
      }
   
      ///////////////////////////////////////////////////////////////////////
      // Private method to simply return the BufferedReader used for
   	// keyboard input, redirected input, or piped input.
   	// These are all equivalent in the eyes of the program because they are 
   	// transparent to it.  Lazy instantiation.  DPS.  28 Feb 2008
   	
       private static BufferedReader getInputReader() {
         if (inputReader == null) {
            inputReader = new BufferedReader(new InputStreamReader(System.in));  
         }
         return inputReader;
      }
   	
   	
    // //////////////////////////////////////////////////////////////////////////////
    // Maintain information on files in use. The index to the arrays is the "file descriptor."
    // Ken Vollmar, August 2005
    
       private static class FileIOData
      {
         private static String[] fileNames = new String[ SYSCALL_MAXFILES ]; // The filenames in use. Null if file descriptor i is not in use.
         private static int[] fileFlags = new int[ SYSCALL_MAXFILES ]; // The flags of this file, 0=READ, 1=WRITE. Invalid if this file descriptor is not in use.
         private static Object[] streams = new Object[SYSCALL_MAXFILES]; // The streams in use, associated with the filenames
      
        // Reset all file information. Closes any open files and resets the arrays
          private static void resetFiles()
         {
            for (int i = 0; i < SYSCALL_MAXFILES; i++)
            {
               close(i);
            }
            setupStdio();
         }
      	// DPS 8-Jan-2013
          private static void setupStdio() {
            fileNames[STDIN]  = "STDIN";
            fileNames[STDOUT] = "STDOUT";
            fileNames[STDERR] = "STDERR";
            fileFlags[STDIN]  = SystemIO.O_RDONLY;
            fileFlags[STDOUT] = SystemIO.O_WRONLY;
            fileFlags[STDERR] = SystemIO.O_WRONLY;
            streams[STDIN]  = System.in;
            streams[STDOUT] = System.out;
            streams[STDERR] = System.err;
            System.out.flush();
            System.err.flush();
         }
      
        // Preserve a stream that is in use
          private static void setStreamInUse(int fd, Object s)
         {
            streams[fd] = s;
         
         }
      
        // Retrieve a stream for use
          private static Object getStreamInUse(int fd)
         {
            return streams[fd];
         
         }
      
        // Determine whether a given filename is already in use.
          private static boolean filenameInUse(String requestedFilename)
         {
            for (int i = 0; i < SYSCALL_MAXFILES; i++)
            {
               if (fileNames[i] != null
                        && fileNames[i].equals(requestedFilename))
               {
                    // System.out.println("Mars.SystemIO.FileIOData.filenameInUse: rtng TRUE for " + requestedFilename);
                  return true;
               }
            }
         
            // System.out.println("Mars.SystemIO.FileIOData.filenameInUse: rtng TRUE for " + requestedFilename);
            return false;
         
         }
      
        // Determine whether a given fd is already in use with the given flag.
          private static boolean fdInUse(int fd, int flag)
         {
            if (fd < 0 || fd >= SYSCALL_MAXFILES)
            {
               return false;
            } 
            else if (fileNames[fd] != null && fileFlags[fd] == 0 && flag == 0)
            {  // O_RDONLY read-only
               return true;
            }
            else if (fileNames[fd] != null && ((fileFlags[fd] & flag & O_WRONLY) == O_WRONLY) )
            {  // O_WRONLY write-only
               return true;
            }         
            return false;
         
         }
      
        // Close the file with file descriptor fd. No errors are recoverable -- if the user's
        // made an error in the call, it will come back to him.
          private static void close(int fd)
         {
            // Can't close STDIN, STDOUT, STDERR, or invalid fd
            if (fd <= STDERR || fd >= SYSCALL_MAXFILES) 
               return;
               
            fileNames[fd] = null;
         	// All this code will be executed only if the descriptor is open.
            if (streams[fd] != null)
            {
               int keepFlag = fileFlags[fd];
               Object keepStream = streams[fd];
               fileFlags[fd] = -1;
               streams[fd] = null;
               try {
                  if (keepFlag == O_RDONLY)
                     ((FileInputStream)keepStream).close();
                  else
                     ((FileOutputStream)keepStream).close();
               } 
                   catch (IOException ioe) {
                  // not concerned with this exception
                  }
            } 
            else {
               fileFlags[fd] = -1; // just to be sure... streams[fd] known to be null
            }
         }
      
        // Attempt to open a new file with the given flag, using the lowest available file descriptor.
        // Check that filename is not in use, flag is reasonable, and there is an available file descriptor.
        // Return: file descriptor in 0...(SYSCALL_MAXFILES-1), or -1 if error
          private static int nowOpening(String filename, int flag)
         {
            int i = 0;
            if (filenameInUse(filename))
            {
               fileErrorString = new String(
                        "File name " + filename + " is already open.");
               return -1;
            }
         
            if (flag != O_RDONLY && flag != O_WRONLY && flag != (O_WRONLY | O_APPEND) ) // Only read and write are implemented
            {
               fileErrorString = new String(
                        "File name " + filename
                        + " has unknown requested opening flag");
               return -1;
            }
         
            while (fileNames[i] != null && i < SYSCALL_MAXFILES)
            {
               i++;
            } // Attempt to find available file descriptor
         
            if (i >= SYSCALL_MAXFILES) // no available file descriptors
            {
               fileErrorString = new String(
                        "File name " + filename
                        + " exceeds maximum open file limit of "
                        + SYSCALL_MAXFILES);
               return -1;
            }    
         
            // Must be OK -- put filename in table
            fileNames[i] = new String(filename); // our table has its own copy of filename
            fileFlags[i] = flag;
            fileErrorString = new String("File operation OK");
            return i;
         
         }
      
      } // end private class FileIOData
    ////////////////////////////////////////////////////////////////////////////////
   
   
   }

