import weiss.util.Random;

import weiss.nonstandard.PriorityQueue;
import weiss.nonstandard.BinaryHeap;

// ModemSim clas interface: run a simulation
//
// CONSTRUCTION: with three parameters: the number of
//     modems, the average connect time, and the
//     interarrival time
//
// ******************PUBLIC OPERATIONS*********************
// void runSim( )       --> Run a simulation

/**
 * The ModemSim class.
 */
public class ModemSim
{
    /**
     * The event class.
     * Implements the Comparable interface
     * to arrange events by time of occurrence.
     */
    private static class Event implements Comparable
    {
        static final int DIAL_IN = 1;
        static final int HANG_UP = 2;

        public Event( )
        {
            this( 0, 0, DIAL_IN );
        }

        public Event( int name, int tm, int type )
        {
            who  = name;
            time = tm;
            what = type;
        }

        public int compareTo( Object rhs )
        {
            Event other = (Event) rhs;
            return time - other.time;
        }
      
        int who;        // the number of the user
        int time;       // when the event will occur
        int what;       // DIAL_IN or HANG_UP
    }

    /**
     * Constructor.
     * @param modem number of modems.
     * @param avgLen averge length of a call.
     * @param callIntrvl the average time between calls.
     */
    public ModemSim( int modems, double avgLen, int callIntrvl )
    {
        eventSet    = new BinaryHeap( );
        freeModems  = modems;
        avgCallLen  = avgLen;
        freqOfCalls = callIntrvl;
        r           = new Random( );
        nextCall( freqOfCalls );  // Schedule first call
    }

    private Random r;                 // A random source
    private PriorityQueue eventSet;   // Pending events

        // Basic parameters of the simulation
    private int freeModems;           // Number of modems unused
    private double avgCallLen;        // Length of a call
    private int freqOfCalls;          // Interval between calls

        // Used by nextCall only
    private int userNum = 0;
    private int nextCallTime = 0;

    /**
     * Place a new DIAL_IN event into the event queue.
     * Then advance the time when next DIAL_IN event will occur.
     * In practice, we would use a random number to set the time.
     */
    private void nextCall( int delta )
    {
        Event ev = new Event( userNum++, nextCallTime, Event.DIAL_IN );
        eventSet.insert( ev );
        nextCallTime += delta;
    }


    /**
     * Run the simulation until stoppingTime occurs.
     * Print output as in Figure 4.
     */
    public void runSim( long stoppingTime )
    {
        Event e = null;
        int howLong;

        while( !eventSet.isEmpty( ) )
        {
            e = (Event) eventSet.deleteMin( );

            if( e.time > stoppingTime )
                break;

            if( e.what == Event.HANG_UP )    // HANG_UP
            {
                freeModems++;
                System.out.println( "User " + e.who +
                                    " hangs up at time " + e.time );
            }
            else                      // DIAL_IN
            {
                System.out.print(  "User " + e.who +
                                    " dials in at time " + e.time + " " );
                if( freeModems > 0 )
                {
                    freeModems--;
                    howLong = r.nextPoisson( avgCallLen );
                    System.out.println(  "and connects for "
                                         + howLong + " minutes" );
                    e.time += howLong;
                    e.what = Event.HANG_UP;
                    eventSet.insert( e );
                }
                else
                    System.out.println( "but gets busy signal" );

                nextCall( freqOfCalls );
            }
        }
    }

    /**
     * Quickie main for testing purposes.
     */
    public static void main( String [ ] args )
    {
        ModemSim s = new ModemSim( 3, 5.0, 1 );
        s.runSim( 20 );
    }
}
