import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.lang.*;
import java.io.*;

/** One-Dimension Cellular Automata
 * This applet implements One Dimensional Cellular Automata,
 * first researched by Stephen Wolfram.
 * The set of CA's implemented are those with a radix of 1.
 * @author J Scott Cameron
 */
public class OneDCA extends Applet implements ActionListener,Runnable{
    
    /* awt components */
    Button Draw = new Button("Draw");
    TextField zoomText = new TextField("1");
    Label zoomLabel = new Label("Zoom");
    Label initTypeLabel = new Label("Init Type:");
    Dimension size;
    Image buffer;
    Graphics bufferGraphics;
    Image buffer2;
    Image line;
    Graphics lineGraphics;
    Graphics bufferGraphics2;
    Checkbox states[] = new Checkbox[8];
    Checkbox scroll, seedInit, randomInit;
    CheckboxGroup initType;
    
    /* random object for Random initialization*/
    Random random;
    
    
    /* ints to control size and zoom of CA/graphics */
    int xMin, xMax, yMin, yMax;
    int width,height,zoom;
    
    /* Vector that contains the most recent state of the CA */
    Vector lastLine;
    
    /* lattice to record the state of the CA as it progresses*/
    boolean board[][];
    
    /* keeps track of how many generations have  passed*/
    int generation;
    
    /* Thread to control animation */
    Thread animator;
    
    /* bool for animation control */
    boolean restart;
    
/** initializes fields of applet on start-up
 */
    public void init() {
        
        /* initialize awt components */
        setLayout(null);           
        initType = new CheckboxGroup();        
        scroll = new Checkbox("scroll", false);
        seedInit = new Checkbox("seed", true , initType);
        randomInit = new Checkbox("random",false, initType);
        random = new Random();        
        Draw.setBounds(405,5,80,20);
        Draw.addActionListener(this);
        add(Draw);
        zoomLabel.setBounds(415,30,70,20);
        add(zoomLabel);
        zoomText.setBounds(415,55,70,20);
        add(zoomText);
        scroll.setBounds(415,80,70,20);
        add(scroll);
        initTypeLabel.setBounds(415,105,70,20);
        add(initTypeLabel);
        seedInit.setBounds(415,130,70,20);
        add(seedInit);
        randomInit.setBounds(415,155,70,20);
        add(randomInit);
        
        /* initializes all the rules the "false"*/
        for(int i=0;i<8;i++)
        {
            states[i] = new Checkbox("",false);
            states[i].setBounds(523,20+i*40,15,15);
            add(states[i]);
        }
        
        size = new Dimension(800,410);
        buffer = createImage(400, 400);
        bufferGraphics = buffer.getGraphics();
        buffer2 = createImage(60,20);
        bufferGraphics2 = buffer2.getGraphics();
        
        
        width=400;
        height=400;
        zoom=1;
        line = createImage(width,1);
        lineGraphics = line.getGraphics();
        board = new boolean[width/zoom][height/zoom];
        
        /* initializes the board as empty */
        for(int i = 0;i<(width/zoom) ; i++)
        {
            for(int j = 0 ; j<(height/zoom) ; j++)
            {
                board[i][j]=false;
            }
        }
    }
    
    
/** overload of update(Graphics) function
 * calls paint
 * @param g Graphics object for applet window.
 */
    public synchronized void update(Graphics g)
    {
        paint(g);
    }
    
    
/** Updates all the graphics for the applet using a buffer swapping technique
 * for smooth animation.
 * @param g Graphics object to be painted to,
 * usually the Graphics object of the main window
 */
    public void paint(Graphics g)
    {
        /* clear drawing area and draw outline */
        bufferGraphics.setColor(this.getBackground());
        bufferGraphics.fillRect(0,0, 399, 399);
        bufferGraphics.setColor(Color.black);
        bufferGraphics.drawRect(0,0,399,399);
        
        
        int k;
        /* this for loops draws the graphics
         * for the rules boxes */
        for(int i=0;i<8;i++)
        {
            
            bufferGraphics2.setColor(this.getBackground());
            bufferGraphics2.fillRect(0,0, 59, 19);
            bufferGraphics2.setColor(Color.black);
            
            k=i;
            for(int j=0;j<3;j++)
            {
                if( (k%2) == 0)
                {
                    bufferGraphics2.drawRect(45-(j*20),5,10,10);
                }
                
                else
                {
                    bufferGraphics2.fillRect(45-(j*20),5,10,10);
                }
                
                k=k/2;
            }
            g.drawImage(buffer2,500,i*40,this);
        }
        
        bufferGraphics.setColor(Color.blue);
        /* draw the board */
        for(int i =0 ; i< (width/zoom) ; i++)
        {
            for(int j=0; j<(height/zoom);j++)
            {
                if( board[i][j])
                {
                    bufferGraphics.fillRect(i*zoom,j*zoom,zoom,zoom);
                }
            }
        }
        
        /* draw the buffer to the actual screen */
        g.drawImage(buffer,5,5,this);
        
    }
    
    
/** updates the CA vector for one generation
 */
    public synchronized void updateCA()
    {
        /* iterates through each cell and checks the
         * rules to determine the state of the cell
         * in the next generation */
        for(int j=0;j<((width/zoom));j++)
        {
            int k=0;
            if(board[((j-1)+(width/zoom))%(width/zoom)]
            [(generation-1)%(height/zoom)])
            {
                k+=4;
            }
            if(board[j][(generation-1)%(height/zoom)])
            {
                
                k+=2;
            }
            if(board[(j+1)%(width/zoom)][(generation-1)%(height/zoom)])
            {
                k+=1;
            }
            board[j][generation%(height/zoom)] = states[k].getState();
        }
    }
    
    
/** catches the actionEvents (pressed buttons)
 *  and runs the appropriate functions.
 * @param evt object with information on which button was pressed
 */
    public void actionPerformed(ActionEvent evt)
    {
        Button theItem = (Button) evt.getSource();
        if (theItem == Draw) DrawPressed();
        
    }
    
/** Starts redrawing CA with new paramaters.
 */
    public void DrawPressed()
    {
        /* tell the any animation threads to stop running */
        if(animator != null)
        {
            if(animator.isAlive())
            {
                restart = true;
            }
        }
        
        while(restart)
        {;}
        generation = 1;
        
        /* get the zoom factor*/
        zoom = Integer.parseInt(zoomText.getText());
        
        /* reset the animator with a new Thread */
        animator = new Thread(this);
        
        /* create a buffered image */
        line = createImage(width,zoom);
        lineGraphics = line.getGraphics();
        
        /* make a new board with the correct dimensions
         * and initilize it to empty */
        board = new boolean[width/zoom][height/zoom];
        for(int i = 0 ; i< (width/zoom); i++)
        {
            for(int j = 0; j < (height/zoom); j++)
            {
                board[i][j]=false;
            }
        }
        
        /* clear the screen */
        repaint(5,5,width,height);
        
        /*if the "seed" is selected then have the initial vector
         * be empty except for the center cell */
        if(seedInit.getState())
        {
            board[(width/zoom)/2][0] = true;
        }
        /* otherwise fill it randomly */
        else
        {
            for(int i = 0 ; i<(width/zoom) ; i++)
            {
                board[i][0] = random.nextBoolean();
            }
        }
        animator.start();
        
        
    }
    
/** controls the main animator thread
 */
    public void run()
    {
        /* this loop continually unpdates the CA
         * until another configuration is started
         * or it hit the bottom of the screen and is
         * not suppose to scroll
         */
        while(!restart && (scroll.getState() ||
        (generation%(height/zoom) != 0 )))
        {
            
            updateCA();
            
            /* repaint only the part of the screen affected by the update */
            repaint(5,(generation%(height/zoom))*zoom,width,zoom);
            
            generation++;
            try
            {
                Thread.sleep(1);
            }
            catch(InterruptedException ie)
            {;
            }
            
        }
        
        restart = false;
        
    }
}