// ComPhys File: Surface.java
// Chapter 14 Section 4: Eden Model of Surface Growth

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import comphys.*;

public class Surface extends Applet implements ActionListener, AdjustmentListener, ItemListener, Runnable {

    int maxL = 256;         // maximum sites L in x direction
    int L = 20;             // default value of L
    
    static final int EMPTY = 0;
    static final int PERIMETER = EMPTY + 1;
    static final int OCCUPIED = PERIMETER + 1;
    int[][] lattice;

    // object to hold (x,y) coordinates of perimeter sites
    class Site {

        int x;
        int y;

        // constructor
        Site (int x, int y) {

            this.x = x;
            this.y = y;

        }

    }
    
    Vector perimeter;           // list of perimeter sites

    void initial () {

        if (lattice == null || lattice.length != L)
            lattice = new int[L][L];

        if (perimeter == null)
            perimeter = new Vector();
        else
            perimeter.removeAllElements();
        
        for (int x = 0; x < L; x++) {

            lattice[x][0] = OCCUPIED;       // substrate occupied
            lattice[x][1] = PERIMETER;      // perimeter sites
            
            // add nearest neighbor sites to perimeter list
            perimeter.addElement(new Site(x, 1));
                                 
            for (int y = 2; y < L; y++)        
                lattice[x][y] = EMPTY;      // all others empty

        }

        computeProperties();

    }

    static final int EDEN = 0;
    static final int RANDOM = EDEN + 1;
    static final int BALLISTIC = RANDOM + 1;
    int model = EDEN;

    boolean siteAdded () {

        int x = 0;
        int y = 0;

        if (model == EDEN) {

            if (perimeter.size() == 0)   // no more perimeter sites
                return false;
            else {

                // choose a random perimeter site and occupy it
                int i = (int) (Math.random() * perimeter.size());
                Site s = (Site) perimeter.elementAt(i);
                x = s.x;
                y = s.y;
                lattice[x][y] = OCCUPIED;
                perimeter.removeElementAt(i);

            }

        } else {
            
            // check for un-occupied sites in top layer
            boolean topLayerOccupied = true;
            for (x = 0; x < L; x++)
                if (lattice[x][L - 1] != OCCUPIED) {
                    topLayerOccupied = false;
                    break;
                }
            if (topLayerOccupied)
                return false;

            // choose a column at random
            x = (int) (Math.random() * L);
            if (lattice[x][L - 1] == OCCUPIED)
                return true;

            if (model == RANDOM) {
                
                for (y = L - 1; y > 0; y--) {
                    if (lattice[x][y - 1] == OCCUPIED) {
                        if (lattice[x][y] == PERIMETER)
                            removeFromPerimeterList(x, y);
                        lattice[x][y] = OCCUPIED;
                        break;
                    }
                }

            } else if (model == BALLISTIC) {

                for (y = L - 1; y > 0; y--) {
                    if (lattice[x][y] == PERIMETER) {
                        removeFromPerimeterList(x, y);
                        lattice[x][y] = OCCUPIED;
                        break;
                    }
                }

            } else return false;
                
        }

        updatePerimeterSites(x, y);
        return true;

    }

    void updatePerimeterSites (int x, int y) {

        // add neighbors of (x,y) to the perimeter list

        int xLeft = x - 1;
        if (xLeft == -1)
            xLeft += L;
        if (lattice[xLeft][y] == EMPTY) {
            lattice[xLeft][y] = PERIMETER;
            perimeter.addElement(new Site(xLeft, y));
        }

        int xRight = x + 1;
        if (xRight == L)
            xRight -= L;
        if (lattice[xRight][y] == EMPTY) {
            lattice[xRight][y] = PERIMETER;
            perimeter.addElement(new Site(xRight, y));
        }

        int yTop = y + 1;
        if (yTop < L && lattice[x][yTop] == EMPTY) {
            lattice[x][yTop] = PERIMETER;
            perimeter.addElement(new Site(x, yTop));
        }

        int yBottom = y - 1;
        if (yBottom > 1 && lattice[x][yBottom] == EMPTY) {
            lattice[x][yBottom] = PERIMETER;
            perimeter.addElement(new Site(x, yBottom));
        }

    }

    void removeFromPerimeterList (int x, int y) {

        Enumeration e = perimeter.elements();
        while (e.hasMoreElements()) {

            Site s = (Site) e.nextElement();
            if (s.x == x && s.y == y)
                perimeter.removeElement(s);

        }

    }

    double hAverage;            // average height of surface
    double w;                   // rms width of surface
    int perimeterSites;         // number of perimeter sites
    double density;             // density of occupied sites

    void computeProperties () {

        double hSum = 0;
        double hSquaredSum = 0;

        // surface sites are defined as the subset of perimeter sites
        // with the largest values of y for each given x
        for (int x = 0; x < L; x++)
            for (int y = L; y > 0; y--)
                if (lattice[x][y - 1] == PERIMETER) {

                    hSum += y;
                    hSquaredSum += y * y;
                    break;
                    
                }

        hAverage = hSum / L;
        w = Math.sqrt(hSquaredSum / L - hAverage * hAverage);

        // compute number of perimeter sites and density of occupied sites
        perimeterSites = 0;
        density = 0;
        for (int x = 0; x < L; x++) {
            for (int y = 0; y < L; y++) {
                if (lattice[x][y] == OCCUPIED)
                    density += 1;
                else if (lattice[x][y] == PERIMETER)
                    ++perimeterSites;
            }
        }
        density /= (L * L);

    }

    boolean clear;
    
    class Picture extends Canvas {

        int pixels = 400;

        Picture () {

            setSize(pixels, pixels);
            setBackground(Color.white);

        }

        Image offScreen;
        Graphics osg;

        public void paint (Graphics g) {

            update(g);

        }

        public void update (Graphics g) {

            if (offScreen == null) {

                offScreen = createImage(pixels, pixels);
                osg = offScreen.getGraphics();

            }

            if (clear) {
                
                osg.clearRect(0, 0, pixels, pixels);
                clear = false;

            }

            int d = (int) (pixels / (double) L) - 1;
            if (d < 2)
                d = 2;

            for (int y = 0; y < L; y++) {
                for (int x = 0; x < L; x++) {

                    if (lattice[x][y] == OCCUPIED)
                        osg.setColor(Color.red);
                    else if (lattice[x][y] == PERIMETER)
                        osg.setColor(Color.yellow);
                    else
                        continue;
                    
                    int ix = (int) (x / (double) L * pixels);
                    int iy = (int) ((L - 1 - y) / (double) L * pixels);
                    osg.fillRect(ix, iy, d, d);

                }
            }

            g.drawImage(offScreen, 0, 0, null);

        }

    }

    class DataWindow extends Canvas {

        int xPixels = 280;
        int yPixels = 70;

        DataWindow () {

            setSize(xPixels, yPixels);
            setBackground(new Color(255, 250, 245));

        }

        public void paint (Graphics g) {

            update(g);

        }

        Image offScreen;
        Graphics osg;

        public void update (Graphics g) {

            if (offScreen == null) {

                offScreen = createImage(xPixels, yPixels);
                osg = offScreen.getGraphics();

            }

            osg.clearRect(0, 0, xPixels, yPixels);
            osg.setColor(Color.blue);

            int x1 = 10;
            int x2 = xPixels / 2;
            int dy = 15;
            int y = dy;

            osg.drawString("Average h = ", x1, y);
            osg.drawString(CP.format(hAverage, 4), x2, y);
            osg.drawString("Roughness w = ", x1, y += dy);
            osg.drawString(CP.format(w, 4), x2, y);
            osg.drawString("Perimeter sites = ", x1, y += dy);
            osg.drawString("" + perimeterSites, x2, y);
            osg.drawString("Density = ", x1, y += dy);
            osg.drawString("" + CP.format(density, 4), x2, y);

            g.drawImage(offScreen, 0, 0, null);
            
        }

    }
    
    Picture picture;
    DataWindow dataWindow;
    Checkbox EdenBox, randomBox, ballisticBox;
    Label LLabel;
    Scrollbar LSlider;
    Button runButton;
    Button resetButton;
    int skip;
    TextField skipTextField;
    
    public void init () {

        initial();

        add(picture = new Picture());

        Panel rightPanel = new Panel();
        rightPanel.setLayout(new BorderLayout(2, 30));

        rightPanel.add(dataWindow = new DataWindow(), "North");

        Panel panel = new Panel();
        panel.setLayout(new GridLayout(0, 2));

        panel.add(new Label("Model Type"));
        CheckboxGroup cg = new CheckboxGroup();
        panel.add(EdenBox = new Checkbox("Eden", cg, true));
        EdenBox.addItemListener(this);
        panel.add(randomBox = new Checkbox("Random", cg, false));
        randomBox.addItemListener(this);
        panel.add(ballisticBox = new Checkbox("Ballistic", cg, false));
        ballisticBox.addItemListener(this);

        panel.add(LLabel = new Label("Lattice L = " + L));
        panel.add(LSlider = new Scrollbar(Scrollbar.HORIZONTAL,
                                          10, 0, 2, 257));
        LSlider.setValue(L);
        LSlider.addAdjustmentListener(this);

        panel.add(runButton = new Button("Grow"));
        runButton.addActionListener(this);
        panel.add(resetButton = new Button("Reset"));
        resetButton.addActionListener(this);

        panel.add(new Label("Steps to skip"));
        panel.add(skipTextField = new TextField("" + skip));
        skipTextField.addActionListener(this);

        rightPanel.add(panel, "South");
        add(rightPanel);

    }

    Thread runThread;
    boolean running;

    public void actionPerformed (ActionEvent event) {

        if (event.getSource() == runButton) {

            if (runThread == null) {

                running = true;
                runThread = new Thread(this);
                runThread.start();
                runButton.setLabel("Stop");

            } else {

                running = false;
                runButton.setLabel("Grow");

            }

        } else if (event.getSource() == resetButton) {

            running = false;
            runButton.setLabel("Grow");
            initial();
            clear = true;
            repaint();

        } else if (event.getSource() == skipTextField) {
            
            try {
                
                skip = Integer.parseInt(event.getActionCommand());
                if (skip < 0)
                    skip = 0;
                
            } catch (NumberFormatException nfe) { }
            
            skipTextField.setText("" + skip);

        }
        
    }

    public void adjustmentValueChanged (AdjustmentEvent event) {

        if (event.getSource() == LSlider) {

            running = false;
            runButton.setLabel("Grow");
            L = LSlider.getValue();
            LLabel.setText("Lattice L = " + L);
            initial();
            clear = true;
            repaint();

        } 

    }

    public void itemStateChanged (ItemEvent event) {

        if (EdenBox.getState())
            model = EDEN;
        else if (randomBox.getState())
            model = RANDOM;
        else
            model = BALLISTIC;

    }

    public void run () {

        runThread.setPriority(Thread.MIN_PRIORITY);

        while (running) {

            running = siteAdded();
            for (int s = 0; s < skip; s++)
                if (!(running = siteAdded()))
                    break;
            computeProperties();
            repaint();

            try {
                runThread.sleep(30);
            } catch (InterruptedException ie) { }

        }

        runThread = null;

    }

    public void stop () {

        running = false;
        runButton.setLabel("Grow");

    }

    public void paint (Graphics g) {

        picture.repaint();
        dataWindow.repaint();

    }

    public static void main (String[] args) {

        Surface surface = new Surface();
        CPFrame aFrame = new CPFrame("Diffusion-Limited Aggregation");
        
        aFrame.add(surface);
        surface.init();
        aFrame.setSize(700, 450);
        aFrame.setLocation(50, 50);
        aFrame.setVisible(true);

    }

}

