// --------------------------------------------------------------------
//    csc 148f, 1997, Assignment 0: The Game of Life
//    name:            >> Insert your name here <<
//    student number:  >> Insert your student number here <<
//    lecturer:        >> Insert your lecturer's name here <<
//    lecture time:    >> Insert your lecture time here <<
//    tutor:           >> Insert your tutor's name here <<
// --------------------------------------------------------------------


// Author of the starter code: Paul Gries, with small revisions by Diane Horton
// Written: 27 June 1997
// Last revised: 08 September 1997

// A standalone program that executes the Game of Life.
// The user must supply one command line argument, which indicates where input
// is to come from: 'standard' means take it from the standard input;
// 'file' means take it from a file, in which case a dialog box will ask the
// user to specify the file name.
//      A second command-line argument is optional.  It indicates what sort
// of output is desired: 'text' causes text output only to be given 
// (no graphical output); 'pretty' causes graphical output only to be given 
// (no text output).  If this second argument is omitted, both text and 
// graphical output are used.


import java.io.*;
import java.awt.*;


// The class Life contains all the methods and instance variables
// neccessary to keep track of and run the Game of Life.
// ----------------------------------------------------------------------
public class Life extends Frame {

    // Below are the instance variables.  
    // Every variable of type Life has its own copy of these, just like 
    // with records in other programming languages.
    
    // The grid containing the Cells.
    Grid theGrid;
    
    // The buffer in which to draw the image; used for double buffering.
    Image backBuffer;
    
    // The graphics context to use when double buffering.
    Graphics backG;
    
    // The number of generations to run the simulation.
    int numGenerations;
    
    // Whether to print text to the standard output.
    static boolean textOutput;
    
    // Whether to draw a pretty picture in a Frame.
    static boolean prettyOutput;
    
    // Where to read the input from.  Should be either "file" or "standard".
    static String inputPlace;
    
    // How long to pause between Grid displays.
    static int delay = 1000;
    
    
    // Update the display; show my copy of theGrid.
    // (Note the somewhat odd comment!  Since each instance of class Life
    // has it's own set of methods and variables, "my" and "I" are used.
    // It's a style of commenting that is unique to object-oriented
    // programming.)
    // ------------------------------------------------------------------
    public void paint(Graphics g) {
        update(g);
    }


    // Update the display; prepare to show my copy of theGrid.
    // ------------------------------------------------------------------
    public void update(Graphics g) {
    
        // Get my width and height.
        int w = bounds().width;
        int h = bounds().height;
    
        // If we don't yet have an Image, create one.
        if (backBuffer == null
            || backBuffer.getWidth(null) != w
            || backBuffer.getHeight(null) != h) {
            backBuffer = createImage(w, h);
            if (backBuffer != null) {
            
                // If we have a backG, it belonged to an old Image.
                // Get rid of it.
                if (backG != null) {
                    backG.dispose();
                }
                backG = backBuffer.getGraphics();
            }
        }
        
        if (backBuffer != null) {
            
            // Fill in the Graphics context backG.
            backG.setColor(Color.black);
            backG.fillRect(0, 0, w, h);
            theGrid.display(backG);
            
            // Now copy the new image to g.
            g.drawImage(backBuffer, 0, 0, null);
        }
            
    }


    // Read from dStream the number of generations for which to run the 
    // simulation, and store it in my copy of numGenerations.
    // ------------------------------------------------------------------
    public void getNumGenerations (StreamTokenizer dStream)
        throws EOFException, IOException {
    
        // Get the number of generations to simulate.
        int ttype = dStream.nextToken();
        if (ttype != StreamTokenizer.TT_NUMBER) {

            // The input was not a number.  Complain.
            System.err.print ("Bad input; input must be a number.");
            System.err.println ("Using 0 for the number of generations.");
            numGenerations = 0;
            
        } else {
            // The input was a number.  Run with it.
            numGenerations = (int) dStream.nval;
        }        
    }
    
    
    // Read from dStream the positions of the live cells and insert then
    // into theGrid.
    // ------------------------------------------------------------------
    public void populateGrid (StreamTokenizer dStream)
            throws IOException {

        // Read in the list of row and column numbers from data stream dStream.
        try {

            int ttype = dStream.nextToken();
            while (ttype != StreamTokenizer.TT_EOF) {

                // Read the row, then the column.
                int row = (int) dStream.nval;
                ttype = dStream.nextToken();
                int col = (int) dStream.nval;
                
                // Add a LiveCell to theGrid at row,col.
                theGrid.addLiveCell(row, col);

                ttype = dStream.nextToken();
            }
            
        } catch (EOFException e) {
        
            // The end of this pair of numbers has been reached.  This isn't
            // a problem, so nothing is done.
        }
    }


    // Read from dStream the number of rows and columns, and create
    // theGrid with those dimensions.
    // ------------------------------------------------------------------
    public void readGridSize (StreamTokenizer dStream)
        throws EOFException, IOException {
    
        // Get the type of the next token from the input stream dStream.
        int ttype = dStream.nextToken ();
        int numRows = (int) dStream.nval;
        
        ttype = dStream.nextToken ();
        int numCols = (int) dStream.nval;
        
        // Now create the grid with the specified dimensions.
        theGrid = new Grid (numRows, numCols);
    }


    // Read the initial Grid information (including the number of
    // generations to compute) from the user, and store the
    // information in theGrid.
    // ------------------------------------------------------------------
    public void buildGrid () {
    
        InputStream inStream = null;
        
        // Figure out where to read the input from.
        if ("file".equals(inputPlace)) {
            // Get the file to read from.
            FileDialog fd = new FileDialog(this, null, FileDialog.LOAD);
            fd.show();
            String file = fd.getDirectory()+"/"+fd.getFile();
            try {
                inStream = new FileInputStream (file);
            } catch (FileNotFoundException e) {
                System.err.println ("Whoops, unable to open file " + inStream);
                System.err.println ("Whoops, unable to open file " + file);
                // System.exit(0);
            }
        } else {
            inStream = System.in;
        }

        StreamTokenizer dStream;
        
        try {
            dStream = new StreamTokenizer (inStream);
            dStream.parseNumbers ();

            // Now dStream is valid; read in the number of generations to run,
            // the size of the grid, and the inital population.  The format
            // should be the number of generations, the size of the grid (rows
            // and columns), and the locations of the LiveCells in the grid.
            
            getNumGenerations (dStream);
            readGridSize(dStream);
            populateGrid(dStream);
            
        } catch (EOFException e) {
            System.err.println ("BOOGERS!  Bad information in file.");
            // System.exit(0);
        } catch (IOException e) {
            System.err.println ("BOOGERS!  Couldn't read from file.");
            // System.exit(0);
        }


    }


    // There must be at least one argument, which is either "file" or
    // "standard".  If there is a second argument, set prettyOutput and
    // textOutput so we know what kind of output to use.
    // ------------------------------------------------------------------
    public static void parseArgs (String[] args) {
        // Find out if user wanted input to come from a file or the
        // standard input.
        if (args.length > 0) {
            inputPlace = args[0];
        }

        switch (args.length) {
            case 1:
                // There were no output-related arguments, so we'll do
                // both kinds of output.
                prettyOutput = true;
                textOutput = true;
                break;
            case 2:
                prettyOutput = (args[1].equals("pretty"));
                textOutput = !prettyOutput;
                break;
            default:
                System.err.println("Bad input!  Expecting 'file' or" +
                " 'standard' followed by text' and/or 'pretty'.  Exiting.");
                // System.exit(0);
        }
    }
    
    
    // Compute numGenerations generations of grid theGrid and display the
    // information either as text or in a graphics Frame, depending on the 
    // values of textOutput and prettyOutput.
    // ------------------------------------------------------------------
    public void computeGenerations () {

        int i;
        for (i = 1; i <= numGenerations; i++) {
        
            if (prettyOutput) {
                // Sleep for delay.
                try {Thread.sleep(delay); }
                catch (InterruptedException e) {}
            }

            theGrid.nextGeneration();

            if (textOutput) {
                System.out.print("Generation number: ");
                System.out.println(i + "\n");
                theGrid.showText();
            }

            if (prettyOutput) {
                repaint ();
            }
        }
    }


    // This is where it all starts.  Since main() is static, there is no
    // need to create an instance of Life in order to call it; note that
    // there are, therefore, no instance variables theGrid or numGenerations
    // yet, since those can *only* exist inside such an instance.
    // ------------------------------------------------------------------
    public static void main (String[] args) {

        parseArgs(args);

        // Create an instance of class Life; we need to do this since we
        // want to use the instance variables and methods.

        Life lifeRunner = new Life ();
        
        lifeRunner.buildGrid();

        if (prettyOutput) {
            // Make the Frame the right size and location, and show it on the computer
            // screen.
            lifeRunner.resize (Cell.size * (3 + lifeRunner.theGrid.numCols ()),
                      Cell.size * (3 + lifeRunner.theGrid.numRows ()));
            lifeRunner.move(100,100);
            lifeRunner.setBackground(Color.black);
            lifeRunner.show();
        }
        
        if (textOutput) {
            System.out.print("Generation number: ");
            System.out.println(0 + "\n");
            lifeRunner.theGrid.showText();
        }

        lifeRunner.computeGenerations ();
        
        // System.exit (0);
    }
}
