/*
 * StdChannel.java --
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 * 
 * RCS: @(#) $Id: StdChannel.java,v 1.1.1.1 1998/10/14 21:09:21 cvsadmin Exp $
 *
 */

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

/**
 * Subclass of the abstract class Channel.  It implements all of the 
 * methods to perform read, write, open, close, etc on system stdio channels.
 */

class StdChannel extends Channel {
  
    /**
     * System.in is wrapped around this class to access a better API 
     */
    private static BufferedReader in = null;

    /**
     * stdType store which type, of the three below, this StdChannel is.
     */
    private int stdType;

    /**
     * Flags indicating the type of this StdChannel.
     */
    static final int STDIN    = 0;
    static final int STDOUT   = 1; 
    static final int STDERR   = 2; 

    /**
     * The buffer size when reading in large files
     */
    private static final int BUF_SIZE = 1024;


    /**
     * Constructor that does nothing.  Open() must be called before
     * any of the subsequent read, write, etc calls can be made.
     */

    StdChannel() {
        in      = null;
	stdType = -1;
    }


    /**
     * Constructor that will automatically call open.
     *
     * @param stdName name of the stdio channel; stdin, stderr or stdout.
     */

    StdChannel(String stdName) {
	if (stdName.equals("stdin")) {
	    open(STDIN);
	} else if (stdName.equals("stdout")) {
	    open(STDOUT);
	} else if (stdName.equals("stderr")) {
	    open(STDERR);
	} else {
	    throw new TclRuntimeError(
                    "Error: unexpected type for StdChannel");
	}
    }


    StdChannel(int type) {
        open(type);
    }


    /**
     * Set the channel type to one of the three stdio types.  Throw a 
     * tclRuntimeEerror if the stdName is not one of the three types.  If
     * it is a stdin channel, initialize the "in" data member.  Since "in"
     * is static it may have already be initialized, test for this case 
     * first.  Set the names to fileX, this will be the key in the chanTable 
     * hashtable to access this object.  Note: it is not put into the hash 
     * table in this function.  The calling function is responsible for that.
     *
     * @param stdName String that equals stdin, stdout, stderr
     * @return The name of the channelId
     */

    String open(int type) {

        switch (type) {
	    case STDIN:
	        mode = TclIO.RDONLY;
		if (in == null) {
		    in = new BufferedReader(new InputStreamReader(System.in)); 
		}
		break;
	    case STDOUT:
	        mode = TclIO.WRONLY;
		break;
	    case STDERR:
	        mode = TclIO.WRONLY;
		break;
	}

        stdType = type;
	setChanName("file" + type);
	
	return getChanName();
    }


    /**
     * Read data from a stdio channel. The read can be for the entire buffer,
     * line or a specified number of bytes.
     *
     * @param interp The currrent interpreter.
     * @param readType Specifies if the read should read the entire buffer, 
     *            the next line, or a specified number of bytes.  See the 
     *            TclIO class for more information on read types.
     * @param numBytes Number of bytes to read.  Only used when the readType
     *            is TclIO.READ_N_BYTES.
     * @return String of data that was read from file.
     * @exception TclException is thrown if read occurs on WRONLY channel.
     * @exception IOException is thrown when an IO error occurs that was not
     *                correctly tested for.  Most cases should be caught.
     */

    String read(Interp interp, int readType, int numBytes) 
            throws IOException, TclException {

        if (stdType != STDIN) {
	      throw new TclException(interp, "channel \"" +
	        getChanName() + "\" wasn't opened for reading");
	}

	switch (readType) {
	    case TclIO.READ_ALL: {
	        int  byteRead = 0;
		char[] charArr = new char[BUF_SIZE];
		StringBuffer sbuf = new StringBuffer();
		    
		while((in.read(charArr, 0, BUF_SIZE)) != -1) {
		    sbuf.append(charArr);
		}
		return sbuf.toString();
	    } 
	    case TclIO.READ_LINE: {
	        return(in.readLine());
	    }
	    case TclIO.READ_N_BYTES: {
		char[] charArr = new char[numBytes];
		in.read(charArr, 0, numBytes);
		return( new String(charArr) );
	    }
	    default : {
	        throw new TclRuntimeError(
                        "StdChannel.read: Incorrect read mode.");
	    }
	}
    }


    /**
     * Write to stdout or stderr.  If the stdType is not set to 
     * STDOUT or STDERR this is an error; either the stdType wasnt
     * correctly initialized, or this was called on a STDIN channel.
     *
     * @param interp the current interpreter.
     * @param s the string to write 
     */

    void write(Interp interp, String s) 
            throws IOException, TclException {

	if (stdType != STDOUT && stdType != STDERR) {
	    throw new TclException(interp, "channel \"" +
	        getChanName() + "\" wasn't opened for writing");
	}

	if (stdType == STDOUT) {
	    System.out.print(s);
	} else {
	    System.err.print(s);
	}
      
    }


    /**
     * Close the stdio channel.
     */

    void close() throws IOException {
        switch (stdType) {
            case STDIN: {
	        if(in != null) {
	            in.close();
		    in = null;
		}  
		break;
	    }
            case STDOUT: {
	        System.out.close();
		break;
	    } 
	    case STDERR: {
	        System.err.close();
		break;
	    }
	    default: {
	        throw new TclRuntimeError(
                        "Error: unexpected stdType for StdChannel");
	    }
	}
    }


    /**
     * Flush all data from the StdChannel.  This is an error if called
     * on a STDIN channel.
     */

    void flush(Interp interp) throws IOException, TclException  {

        switch (stdType) {
            case STDIN: {
	        throw new TclException(interp, "channel \"" +
                        getChanName() + "\" wasn't opened for writing");
	    }
            case STDOUT: {
	        System.out.flush();
		break;
	    } 
	    case STDERR: {
	        System.err.flush();
		break;
	    }
	    default: {
	        throw new TclRuntimeError(
                        "Error: unexpected stdType for StdChannel");
	    }
	}
    }


    /**
     * Not sure what this means on stdio channels???
     *
     * @param offset The number of bytes to move the stream pointer.
     * @param where to begin incrementing the stream pointer; beginning,
     * current, or end of the file.
     */

    void seek(long offset, int mode)  throws IOException {
      
        switch(mode) {

	    case TclIO.SEEK_SET: {
		break;
	    }
	    case TclIO.SEEK_CUR: {
		break;
	    }
	    case TclIO.SEEK_END: {
		break;
	    }
	}
    }


    /**
     * Not sure what this means on stdio channels???
     * 
     * @return For now just return -1
     */
    long tell()  throws IOException {
        return((long)-1);
    }


    /**
     * Not sure what this means on stdio channels???
     * 
     * @return For now just return false
     */
    boolean eof() {
        return false;
    }
}
