/* $Id: Disk.java,v 1.3 1998/11/17 22:04:22 thain Exp thain $ */

/** A software simulation of a Disk.
 * <p>
 * <b>You may not change this class.</b>
 * <p>
 * This disk is slow and ornery.
 * It contains a number of blocks, all BLOCK_SIZE bytes long.
 * All operations occur on individual blocks.
 * You can't modify any more or any less data at a time.
 * <p>
 * To read or write from the disk, call beginRead() or beginWrite().
 * Both of these functions will start the action and return immediately.
 * When the action has been completed, a call to Kernel.interrupt() will
 * occur to let you know the Disk is ready for more.
 * <p>
 * It may take a while for the disk to seek from one block to another.
 * Seek time is proportional to the difference in block number.
 * <p>
 * <b>Warning:</b> Don't call beginRead() or beginWrite() while the 
 * disk is busy! If you don't treat
 * the Disk gently, the system will crash! (Just like a real machine!)
 * @author Douglas Thain (thain@cs.wisc.edu)
 * @see Kernel
 */

public class Disk implements Runnable {

    /* The size of a disk block in bytes. */
    public static final int BLOCK_SIZE=512;

    private int blocksPerTrack=10;
    private int delayPerTrack=1;    // delay per track change, in ms
    private int transferTime=20;    // minimum delay, in ms
    private int currentBlock=0; // what block am I currently at?

    private int blocks;     // # of blocks on this disk
    private byte    data[];     // contents of this disk

    private int targetBlock;    // the request location
    private byte    target[];   // the request target or source
    private boolean busy = false;   // is the disk seeking or transferring?
    private boolean write = false;  // is this request a write?

    private boolean request = false;

    /** Create a new Disk.
    * @param b The number of blocks on the disk.
    */
    public Disk( int b ) {
        if( b<1 ) throw new DiskException("Bad block count: "+b);

        blocks = b;
        data = new byte[blocks*BLOCK_SIZE];
    }

    /** Get the size of the disk
    * @return The number of blocks in this disk.
    */

    public int getSize() {
        return blocks;
    }

    /** Start a new read operation.
    * @param b The block number to read from.
    * @param d The data area to write to.
    */

    synchronized public void beginRead( int b, byte d[] ) {

        if((b<0)||(b>=blocks)||(d==null)||(d.length<BLOCK_SIZE) ) {
            throw new DiskException( "Illegal disk request: "+
                " read block "+b+" destination size "+d );
        }

        if( busy ) {
            throw new DiskException( "Disk read attempted "+
                " while the disk was still busy.");
        }

        write = false;
        target = d;
        targetBlock = b;
        request = true;

        notify();
    }

    /** Start a new write operation.
    * @param b The block to write to.
    * @param d The data area to read from.
    */

    synchronized public void beginWrite( int b, byte d[] ) {

        if((b<0)||(b>=blocks)||(d==null)||(d.length<BLOCK_SIZE) ) {
            throw new DiskException( "Illegal disk request: "+
                " write block "+b+" destination size "+d );
        }

        if( busy ) {
            throw new DiskException( "Disk write attempted "+
                " while the disk was still busy.");
        }

        write = true;
        target = d;
        targetBlock = b;
        request = true;

        notify();
    }

    /** Do not call this function.  */

    public void run() {
        while(true) {

            // Wait for a request to be submitted.

            synchronized(this) {
                while(!request) {
                    try {
                        wait();
                    } catch( Exception e ) {
                    }
                }
                busy = true;
            }

            // Delay for a while.
            // Take into account a base access time,
            // and the distance to travel.

            int target_track = targetBlock/blocksPerTrack;
            int current_track = currentBlock/blocksPerTrack;

            int seek_time = delayPerTrack*
                            Math.abs(current_track-target_track);

            try {
                Thread.sleep( transferTime+seek_time);
            } catch( Exception e ) {
            }

            // Move the data.

            if( write ) {
                System.arraycopy(
                    target, 0,
                    data, targetBlock*BLOCK_SIZE,
                    BLOCK_SIZE);
            } else {
                System.arraycopy(
                    data, targetBlock*BLOCK_SIZE,
                    target, 0,
                    BLOCK_SIZE);
            }

            // Complete the transaction.

            synchronized(this) {
                busy = false;
                request = false;
                currentBlock = targetBlock;
            }

            // Inform the kernel.

            Kernel.interrupt(Kernel.INTERRUPT_DISK,
                0,0,null,null,null);
        }
    }
    static private class DiskException extends RuntimeException {
            public DiskException( String s ) {
            super("*** YOU CRASHED THE DISK: "+s);
        }
    }
}


