/*
  -- This file is  free  software, which  comes  along  with  SmartEiffel. This
  -- software  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. You can modify it as you want, provided
  -- this header is kept unaltered, and a notification of the changes is added.
  -- You  are  allowed  to  redistribute  it and sell it, alone or as a part of 
  -- another product.
  --          Copyright (C) 1994-98 LORIA - UHP - CRIN - INRIA - FRANCE
  --            Dominique COLNET and Suzanne COLLIN - colnet@loria.fr 
  --                       http://SmartEiffel.loria.fr
  --
*/

import java.io.*;
import java.util.*;
import java.lang.*;

/**
 * The SmartEiffelRuntime class implements some external "SmartEiffel" 
 * features. This class must be present to execute the Java byte-code
 * generated by command `compile_to_jvm'.
 * Do not forget to add this class in your CLASSPATH system
 * environment variable.
 * You can also move this class file in an appropriate directory.
 */
public final class SmartEiffelRuntime {
    
    public static Object basic_getenv (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	String value = System.getProperty(name);
	if (value == null) {
	    return null;
	}
	else {
	    return StringToNullTerminatedBytes(value);
	}
    }
    
    public static void basic_putenv (Object name, Object value) {
	String name_string = NullTerminatedBytesToString(name);
	String value_string = NullTerminatedBytesToString(value);
	// *** Dominique Colnet to Greg Lee:
	//     On my linux box, I have to replace:
	// String result = System.setProperty(name_string, value_string);
	//     with:
	Properties props = System.getProperties();
	props.put(name_string,value_string);
	System.setProperties(props); 
	// *** But I may have an old Java on my machine ?
	// ***
	return;
    }
    
    public static void print_run_time_stack () {
	Throwable stack = new Throwable();
	System.err.println("Printing the JVM stack:");
	stack.printStackTrace();
    }
    
    public static void die_with_code (int code) {
	throw( new Error( "die with code: " + code ) );
    }
    
    public static int basic_system_execute_command (Object system_command_line) {
	String scl = NullTerminatedBytesToString(system_command_line);
	Process process;
	try {
	    process = Runtime.getRuntime().exec(scl);
	    process.waitFor();
	}
	catch (Exception e) {
	    System.err.print("SmartEiffelRuntime: basic_system_execute_command(\"");
	    System.err.print(scl);
	    System.err.println("\") failed.");
	    print_run_time_stack();
	    System.err.println("Try to continue execution.");
	}
	return 0;
    }
    
    public static int basic_fstat_st_size (Object path) {
	String name = NullTerminatedBytesToString(path);
	File file = new File(name);
	return (int) file.length();
    }
  
    public static double basic_fstat_st_mtime (Object path) {
	String name = NullTerminatedBytesToString(path);
	File file = new File(name);
	return Double.longBitsToDouble( file.lastModified() );
    }
    
    public static Object basic_io_stdin() {
	return System.in;
    }
    
    public static Object basic_io_stdout() {
	return System.out;
    }
    
    public static Object basic_io_stderr() {
	return System.err;
    }
    
    public static int basic_io_eof() {
	return -1;
    }
    
    public static Object basic_io_binary_file_read_open (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	File file = new File(name);
	try {
	    return new FileInputStream (file);
	} catch (FileNotFoundException e) {
	    return null;
	}
    }
    
    public static Object basic_io_binary_file_write_open (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	File file = new File(name);
	try {
	    return new FileOutputStream (file);
	} catch (IOException e) {
	    return null;
	}
    }
    
    public static Object basic_io_binary_file_write_append (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	try {
	    return new FileOutputStream (name, true);
	} catch (IOException e) {
	    return null;
	}
    }
    
    public static Object basic_io_text_file_read_open (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	File file = new File(name);
	try {
	    return new FileInputStream (file);
	} catch (FileNotFoundException e) {
	    return null;
	}
    }
    
    public static Object basic_io_text_file_write_open (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	File file = new File(name);
	try {
	    return new FileOutputStream (file);
	} catch (IOException e) {
	    return null;
	}
    }
    
    public static Object basic_io_text_file_write_append (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	try {
	    return new FileOutputStream (name, true);
	} catch (IOException e) {
	    return null;
	}
    }
    
    public static Object basic_io_text_file_read_write_open (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	RandomAccessFile rfile;
	try {
	    try {
		rfile = new RandomAccessFile(name, "rw");
	    } catch (FileNotFoundException e) {
		return null;
	    }
	} catch (IOException e) {
	    // *** Dominique Colnet to Greg Lee:
	    //     On my linux box, I have to catch this exception to avoid 
            //     an error at compile time (once again, I may have a too old version
	    //     of java on my machine.
	    // ***
	    return null;
	}
	return rfile;
    }
    
    public static Object basic_io_text_file_read_write_append (Object bytes) {
	String name = NullTerminatedBytesToString(bytes);
	RandomAccessFile rfile;
	try {
	    try {
		rfile = new RandomAccessFile(name, "rw");
	    } catch (FileNotFoundException e) {
		return null;
	    }
	} catch (IOException e) {
	    return null;
	}
	try {
	    long len = rfile.length();
	    rfile.seek( len );
	} catch (IOException e) {
	}   
	return rfile;
    }
  
    public static boolean basic_io_feof (Object stream) {
	boolean result;
	result = false;
	try {
	    long fpointer = ((RandomAccessFile)stream).getFilePointer();
	    long len =  ((RandomAccessFile)stream).length();
	    if ( fpointer == len ) {
		result = true;
	    }
	} catch (IOException e) {
	}
	return result;
    }
    
    public static void basic_io_rename (Object old_path, Object new_path) {
	String oldPath = NullTerminatedBytesToString(old_path);
	String newPath = NullTerminatedBytesToString(new_path);
	File oldFile = new File(oldPath);
	File newFile = new File(newPath);
	oldFile.renameTo(newFile);
    }
    
    public static void basic_io_remove (Object path) {
	String name = NullTerminatedBytesToString(path);
	File file = new File(name);
	file.delete();
    }
    
    public static void basic_io_fclose (Object stream) {
	try { ((InputStream)stream).close(); return ; } 
	catch (Exception e) {
	    try {((OutputStream)stream).close(); return ; }
	    catch (Exception f) {
	    }
	}
    }
    
    public static int basic_io_getc (Object stream) {
	try {
	    return ((InputStream)stream).read();
	} catch (IOException e) {
	    return -1;
	} catch (ClassCastException cce) {
	    try {
		return ((RandomAccessFile)stream).read();
	    } catch (IOException e) {
		return -1;
	    }
	}
    }
    
    public static void basic_io_putc (byte b, Object stream) {
	try {
	    ((OutputStream)stream).write(b);
	} catch (IOException e) {
	} catch (ClassCastException cce) {
	    try {
		((RandomAccessFile)stream).write(b);
	    } catch (IOException e) {
	    }
	}
    }
    
    public static void basic_io_flush(Object dirstream_pointer) {
	try {
	    ((PrintStream)dirstream_pointer).flush();
	} catch (ClassCastException cce) {
	}
    }
  
    public static int basic_io_read_stdin (byte[] bytes, int max_count) {
	try {
	    return (System.in).read(bytes,0,max_count);
	} catch (IOException e) {
	    return -1;
	}
    }

    public static int basic_io_fread (byte[] bytes, int max_count, Object stream) {
	try {
	    return ((InputStream)stream).read(bytes, 0, max_count);
	} catch (IOException e) {
	    return -1;
	} catch (ClassCastException cce) {
	    try {
		return ((RandomAccessFile)stream).read(bytes, 0, max_count);
	    } catch (IOException e) {
		return -1;
	    }
	}
    }
    
    public static void basic_io_fwrite (byte[] bytes, int count, Object stream) {
	try {
	    ((OutputStream)stream).write(bytes,0,count);
	} catch (IOException e) {
	} catch (ClassCastException cce) {
	    try {
		((RandomAccessFile)stream).write(bytes,0,count);
	    } catch (IOException e) {
	    }
	}
    }
    
    public static int basic_clock() {
	return (int) ( System.currentTimeMillis() & 0xFFFFFFFFL );
    }
    
    public static int basic_clock_per_sec() {
	return 1000;
    }
  
    public static long last_millis;
    
    public static void basic_microsecond_update() {
	last_millis = System.currentTimeMillis();
	return;
    }
  
    public static double basic_microsecond_time() {
	return Double.longBitsToDouble( last_millis );
    }
  
    public static int basic_microsecond_microsecond() {
	return (int) ( last_millis % 1000 )*1000;
    }
    
    public static double basic_time_time() {
	return Double.longBitsToDouble( System.currentTimeMillis() );
    }
    
    public static int basic_time_difftime( double t2, double t1 ) {
	return (int) ( ( Double.doubleToLongBits( t2 ) - Double.doubleToLongBits( t1) )/1000 );
    }
    
    public static int basic_time_getyear( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
	return aCalendar.get( java.util.Calendar.YEAR );
    }
    
    public static int basic_time_getmonth( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
	return ( aCalendar.get( java.util.Calendar.MONTH ) + 1 );
    }
  
    public static int basic_time_getday( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
	return aCalendar.get( java.util.Calendar.DAY_OF_MONTH );
    }
     
    public static int basic_time_gethour( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
	return aCalendar.get( java.util.Calendar.HOUR );
    }
   
    public static int basic_time_getminute( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
	return aCalendar.get( java.util.Calendar.MINUTE );
    }
  
    public static int basic_time_getsecond( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
	return aCalendar.get( java.util.Calendar.SECOND );
    }
    
    public static boolean basic_time_is_summer_time_used( double t ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.TimeZone zone = java.util.TimeZone.getDefault();
	return zone.inDaylightTime( aDate );
    }
  
    public static int basic_time_getyday( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
	return aCalendar.get( java.util.Calendar.DAY_OF_YEAR );
    }
    
    public static int basic_time_getwday( double t, int mode ) {
	java.util.Date aDate = new java.util.Date( Double.doubleToLongBits( t ) );
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.setTime( aDate );
	if ( mode == 1 ) {
	    java.util.TimeZone zone = java.util.TimeZone.getTimeZone( "GMT" );
	    aCalendar.setTimeZone( zone );
	}
      return ( aCalendar.get( java.util.Calendar.DAY_OF_WEEK ) - 1 );
    }
  
    public static double basic_time_mktime( int a_year, int a_mon, int a_day, 
					    int a_hour, int a_min, int a_sec ) {
	java.util.Calendar aCalendar = java.util.Calendar.getInstance();
	aCalendar.set( a_year, (a_mon - 1), a_day, a_hour, a_min, a_sec );
	return Double.longBitsToDouble( aCalendar.getTime().getTime() );
    }
    
    public static double basic_time_add_second( double t2, double t1 ) {
      return Double.longBitsToDouble( Double.doubleToLongBits( t2 ) - Double.doubleToLongBits( t1) );
    }
    
    public static String NullTerminatedBytesToString (Object bytes) {
	int i = 0;
	byte b [] = ((byte[])bytes);
	while ( b[i] != 0 ) {
	    i++;
	}
	return (new String(b,0,i));
    }
    
    /**
     * Convert <code>string</code> as a C like null terminated 
     * <code>byte[]</code> array.
     */
    public static Object StringToNullTerminatedBytes (String string) {
	byte result [] = new byte[string.length() + 1];
	int i = 0;
	for (; i < string.length() ; i++) {
	    result[i] = (byte)string.charAt(i);
	}
	result[result.length - 1] = 0;
	return result;
    }
    
    public static int internal_exception_number = 3;
    
    public static void runtime_error(int line, int column, String path, String type,
				     String message) throws Exception {
	StringBuffer s = new StringBuffer( 128 );
	
	s.append( "Run-time Eiffel error.\n" );
	if (message != null) {
	    s.append( message );
	    s.append( "\n" );
	}
	if (type != null) {
	    s.append( "Eiffel type: \"" );
	    s.append( type );
	    s.append( "\".\n" );
	}
	if (line != 0) {
	    s.append("Error occurs at line ");
	    s.append(line);
	    s.append(" column ");
	    s.append(column);
	    s.append(" in \"");
	    s.append(path);
	    s.append("\".\n");
	}
	String str = s.toString();
	Exception e = new Exception( str );
	throw( e );
    }
    
    public static void original_runtime_error(int line,
					      int column,
					      String path,
					      String type,
					      String message) {
	System.err.println("Run-time Eiffel error.");
	if (message != null) {
	    System.err.println(message);
	}
	if (type != null) {
	    System.err.print("Eiffel type: \"");
	    System.err.print(type);
	    System.err.println("\".");
	}
	if (line != 0) {
	    System.err.print("Error occurs at line ");
	    System.err.print(line);
	    System.err.print(" column ");
	    System.err.print(column);
	    System.err.print(" in \"");
	    System.err.print(path);
	    System.err.println("\".");
	}
	print_run_time_stack();
	System.exit(1);
    }
    
    public static void runtime_error_bad_target(Object target, int line, int column,
						String path, String type,
						String message) {
	if (target == null) {
	    System.err.println("Void (null) target for a call.");
	}
	else {
	    System.err.println("Bad target for a call.");
	    System.err.print("Target is \"");
	    System.err.print(target.toString());
	    System.err.println("\"");
	}
	original_runtime_error(line,column,path,type,message);
    }
    
    public static int runtime_error_inspect(int inspect_value, int line, int column,
					    String path) {
	System.err.print("Bad inspect value = ");
	System.err.println(inspect_value);
	original_runtime_error(line,column,path,null,
			       "Invalid inspect (nothing selected).");
	return inspect_value;
    }
    
    public static int runtime_check_loop_variant(int loop_counter, int prev_variant,
						 int next_variant, int line,
						 int column, String path) {
	if (next_variant < 0) {
	    bad_loop_variant(loop_counter,prev_variant,next_variant,line,column,path);
	    return 0;
	}
	else if (loop_counter == 0) {
	    return next_variant;
	}
	else if (next_variant < prev_variant) {
	    return next_variant;
	}
	else {
	    bad_loop_variant(loop_counter,prev_variant,next_variant,line,column,path);
	    return 0;
	}
    }
    
    private static void bad_loop_variant(int loop_counter,int prev_variant,
					 int next_variant,int line,int column,
					 String path) {
	System.err.print("Loop body counter = ");
	System.err.print(loop_counter);
	System.err.println(" (done)");
	if (loop_counter > 0) {
	    System.err.print("Previous variant = ");
	    System.err.println(prev_variant);
	}
	System.err.print("Current variant = ");
	System.err.print(next_variant);
	if (next_variant < 0) {
	    System.err.println(" (Bad Negative value)");
	}
	else {
	    System.err.println(" (must be less than previous)");
	}
	original_runtime_error(line,column,path,null,"Loop variant violation.");
    }
    
    public static void se_trace(Object target, int line, int column, String path) {
	se_trace_show(line,column,path);
    }
    
    public static void se_trace(boolean target,int line,int column,String path) {
	if (target) {
	    System.out.print("Current = true ");
	}
	else {
	    System.out.print("Current = false ");
	}
	se_trace_show(line,column,path);
    }
    
    public static void se_trace(byte target,int line,int column,String path) {
	System.out.print("Current = ");
	System.out.print(target);
	System.out.print(" ");
	se_trace_show(line,column,path);
    }
    
    public static void se_trace(int target,int line,int column,String path) {
	System.out.print("Current = ");
	System.out.print(target);
	System.out.print(" ");
	se_trace_show(line,column,path);
    }
    
    public static void se_trace_show(int line,int column,String path) {
	System.out.print(path);
	System.out.print(" l=");
	System.out.print(line);
	System.out.print(" c=");
	System.out.print(column);
	System.out.println();
	System.out.flush();
    }
    
    class DirectoryStream {
	/* To simulate a BASIC_DIRECTORY stream. */
	File directory;
	String[] list;
	int index;
	
	public DirectoryStream read_entry() {
	    if (++index < list.length) {
		return this;
	    }
	    else {
		return null;
	    } 
	}
	
	public Object get_entry_name() {
	    String entry;
	    if (index == -2) {
		entry = ".";
	    }
	    else if (index == -1) {
		entry = "..";
	    }
	    else {
		entry = list[index];
	    }
	    return StringToNullTerminatedBytes(entry);
	}
    }

    public static Object basic_directory_open(Object path_pointer) {
	String path = new String((byte[])path_pointer);
	File directory = new File(path);
	if (directory.isDirectory()) {
	    SmartEiffelRuntime dummy = new SmartEiffelRuntime();
	    DirectoryStream dirstream = dummy. new DirectoryStream();
	    dirstream.directory = directory;
	    dirstream.list = directory.list();
	    dirstream.index = -3;
	    return dirstream;
	}
	else {
	    return null;
	}
    }
    
    public static Object basic_directory_read_entry(Object dirstream_pointer) {
	DirectoryStream dirstream = ((DirectoryStream)dirstream_pointer);
	return dirstream.read_entry();
    }
    
    public static Object basic_directory_get_entry_name(Object dirstream_pointer) {
	DirectoryStream dirstream = ((DirectoryStream)dirstream_pointer);
	return dirstream.get_entry_name();
    }
    
    public static boolean basic_directory_close(Object stream) {
	return true;
    }
    
    public static Object basic_directory_current_working_directory() {
	String cwd = System.getProperty("user.dir");
	return StringToNullTerminatedBytes(cwd);
    }
    
    public static boolean basic_directory_chdir(Object dirpath_pointer) {
	String path = new String((byte[])dirpath_pointer);
	File directory = new File(path);
	if (directory.isDirectory()) {
	    Properties p = System.getProperties();
	    p.put("user.dir",path);
	    return true;
	}
	else {
	    return false;
	}
    }
    
    public static boolean basic_directory_mkdir(Object dirpath_pointer) {
	String name = NullTerminatedBytesToString(dirpath_pointer);
	File directory = new File(name);
	directory.mkdir();
	return true;
    }
    
    public static boolean basic_directory_rmdir(Object dirpath_pointer) {
	String name = NullTerminatedBytesToString(dirpath_pointer);
	File directory = new File(name);
	directory.delete();
	return true;
    }
    
    public static void basic_sprintf_double (byte buffer[], int f, double d) {
	String s = Double.toString(d);
	int ib = 0; int is = 0;	int iE;	int idot; int exp; int i;
	
	if (s.charAt(is) == '-') {
	    buffer[ib++] = '-';
	    s = s.substring(1,s.length());
	}
	iE = s.indexOf('E');
	idot = s.indexOf('.');
	if (iE < 0) {
	    /* ddd.ddd notation. */
	    for (; is < idot ; is++) {
		buffer[ib++] = ((byte)(s.charAt(is)));
	    }
	    if (f > 0) {
		buffer[ib++] = '.';
		is++;
	    }
	    for (; ((f > 0) && (is < s.length())) ; f--) {
		buffer[ib++] = ((byte)(s.charAt(is++)));
	    }
	    for (; f > 0 ; f--) {
		buffer[ib++] = '0';
	    }
	    buffer[ib] = 0;
	}
	else {
	    /* m.ddddE-xx notation. */
	    exp = Integer.parseInt(s.substring(iE+1,s.length()));
	    if (exp >= 0) {
		for (; is < idot ; is++) {
		    buffer[ib++] = ((byte)(s.charAt(is)));
		}
		is++;
		for (; exp > 0 ; exp--) {
		    if (is < iE) {
			buffer[ib++] = ((byte)(s.charAt(is++)));
		    }
		    else {
			buffer[ib++] = '0';
		    }
		}
		if (f > 0) {
		    buffer[ib++] = '.';
		}
		for (; f > 0 ; f--) {
		    if (is < iE) {
			buffer[ib++] = ((byte)(s.charAt(is++)));
		    }
		    else {
			buffer[ib++] = '0';
		    }
		}
		buffer[ib] = 0;
	    }
	    else {
		i = idot + exp;
		if (i <= 0) {
		    buffer[ib++] = '0';
		}
		else {
		    for (; is < i ; is++) {
			buffer[ib++] = ((byte)(s.charAt(is++)));
		    }
		}
		if (f > 0) {
		    buffer[ib++] = '.';
		}
		for (; f > 0 ; f--) {
		    if (i < 0) {
			buffer[ib++] = '0';
			i++;
		    }
		    else if (is < iE) {
			if (is == idot) is++;
			buffer[ib++] = ((byte)(s.charAt(is++)));
		    }
		    else {
			buffer[ib++] = '0';
		    }
		}
		buffer[ib] = 0;
	    }
	}
    }
    
}
