// ComPhys File: Percolation.java
// Chapter 13, Sections 13.2 and 13.3
// Site percolation on a square lattice

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

public class Percolation extends Applet implements ActionListener, AdjustmentListener, ItemListener {

    int Lmax = 99;
    double[][] rsite = new double[Lmax][Lmax];
    
    int L = 16;
    double p = 0.4;
    
    void initial () {

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

                rsite[x][y] = Math.random();

            }
        }

        HoshenKopelman();
                
    }

    // Hoshen-Kopelman algorithm from pages 426-427

    int[][] site = new int[Lmax + 2][Lmax + 2];
    int[] np = new int[(Lmax + 2) * (Lmax + 2)];

    void HoshenKopelman () {

        // initalize the array site
        for (int y = 1; y <= L; y++) {
            for (int x = 1; x <= L; x++) {
                if (rsite[x-1][y-1] < p)
                    site[x][y] = -1;
                else
                    site[x][y] = 0;
            }
        }

        assign();               // assign cluster numbers
        span();                 // check for spanning cluster
        computeObservables();
        
    }

    void assign () {

        // assign cluster numbers to occupied sites
        for (int i = 0; i < np.length; i++)
            np[i] = 0;
        int ncluster = 0;       // cluster number
        for (int y = 1; y <= L; y++) {
            for (int x = 1; x <= L; x++) {
                if (site[x][y] < 0) {
                    int down = y - 1;
                    int left = x - 1;
                    if (site[x][down] + site[left][y] == 0) {
                        ++ncluster;               // new cluster
                        site[x][y] = ncluster;
                        np[ncluster] = ncluster;  // proper label
                    } else {
                        neighbor(x, y);
                    }
                }
            }
        }

        // assign proper labels to cluster array
        for (int y = 1; y <= L; y++) {
            for (int x = 1; x <= L; x++) {
                site[x][y] = proper(site[x][y]);
            }
        }
        
    }

    void neighbor (int x, int y) {

        // determine occupancy of neighbors
        int down = y - 1;
        int left = x - 1;
        if (site[x][down] * site[left][y] > 0) {
            // both neighbors occupied
            label_min(x, y, left, down);
        } else if (site[x][down] > 0) {
            // down neighbor occupied
            site[x][y] = site[x][down];
        } else {
            site[x][y] = site[left][y];
        }

    }

    void label_min (int x, int y, int left, int down) {

        // both neighbors occupied, determine minimum cluster number
        if (site[x][y] == site[x][down]) {
            // both neighbors have same cluster label
            site[x][y] = site[left][y];
        } else {
            // determine minimum cluster label
            int cl_left = proper(site[left][y]);
            int cl_down = proper(site[x][down]);
            int nmax = cl_left > cl_down ? cl_left : cl_down;
            int nmin = cl_left < cl_down ? cl_left : cl_down;
            site[x][y] = nmin;
            if (nmin != nmax)
                np[nmax] = nmin; // set improper label nmax = nmin
        }

    }

    int proper (int label) {

        // recursive function
        if (np[label] == label)
            return label;
        else
            return proper(np[label]);

    }

    boolean VertSpan = true;
    boolean HorzSpan;
    boolean BothSpan;
    boolean VorHSpan;

    int spanningCluster;
    
    void span () {

        // check for existence of vertically spanning cluster
        int vspan = 0;
        boolean done = false;
        for (int bottom = 1; bottom <= L; bottom++) {
            if (site[bottom][1] > 0) {
                int nbot = site[bottom][1];
                for (int top = 1; top <= L; top++) {
                    if (site[top][L] > 0) {
                        int ntop = site[top][L];
                        if (ntop == nbot) {
                            vspan = proper(ntop);
                            done = true;
                            break;
                        }
                    }
                }
            }
            if (done)
                break;
        }

        // check for existence of horizontally spanning cluster
        int hspan = 0;
        done = false;
        for (int left = 1; left <= L; left++) {
            if (site[1][left] > 0) {
                int nleft = site[1][left];
                for (int right = 1; right <= L; right++) {
                    if (site[L][right] > 0) {
                        int nright = site[L][right];
                        if (nright == nleft) {
                            hspan = proper(nright);
                            done = true;
                            break;
                        }
                    }
                }
            }
            if (done)
                break;
        }

        spanningCluster = 0;
        if (BothSpan && vspan > 0 && hspan > 0)
            spanningCluster = vspan;
        else if (VertSpan && vspan > 0)
            spanningCluster = vspan;
        else if (HorzSpan && hspan > 0)
            spanningCluster = hspan;
        else if (VorHSpan) {
            if (vspan > 0)
                spanningCluster = vspan;
            else if (hspan > 0)
                spanningCluster = hspan;
        }

    }

    int occupiedSites;
    int finiteClusters;
    int nSpanning;
    double P_infinity;
    int[] n_s = new int[Lmax * Lmax];
    int[] m_i = new int[Lmax * Lmax];
    double S;

    void computeObservables () {

        occupiedSites = 0;
        nSpanning = 0;

        for (int s = 0; s < L * L; s++)
            n_s[s] = m_i[s] = 0;

        for (int y = 1; y <= L; y++) {
            for (int x = 1; x <= L; x++) {
                int cluster = proper(site[x][y]);
                if (cluster > 0) {
                    ++m_i[cluster];           // count sites in this cluster
                    ++occupiedSites;          // count occupied sites
                }
            }
        }

        for (int cluster = 1; cluster < L * L; cluster++) {

            if (cluster == spanningCluster)
                nSpanning = m_i[cluster];     // sites in spanning cluster
            else if (m_i[cluster] > 0) 
                ++n_s[m_i[cluster]];          // count clusters of size s
            
        }

        P_infinity = 0;
        if (nSpanning > 0)
            P_infinity = nSpanning / (double) occupiedSites;

        finiteClusters = 0;
        double mSum = 0;
        double mSquaredSum = 0;
        for (int cluster = 1; cluster < L * L; cluster++) {
            if (cluster == spanningCluster)
                continue;
            int m = m_i[cluster];
            if (m > 0) {
                mSum += m;
                mSquaredSum += m * m;
                ++finiteClusters;
            }
        }
        S = mSquaredSum / mSum;

    }

    int mouse_x = -1;
    int mouse_y = -1;
    
    class Picture extends Canvas implements MouseListener, MouseMotionListener {

        int pixels = 400;

        Picture () {

            setSize(pixels, pixels);
            setBackground(Color.white);
            addMouseListener(this);
            addMouseMotionListener(this);

        }

        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();

            }

            osg.clearRect(0, 0, pixels, pixels);

            int d = (int) (pixels / (double) L);

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

                    if (rsite[x][y] >= p)
                        continue;
                    int ix = (int) (x / (double) L * pixels);
                    int iy = (int) ((L - y - 1) / (double) L * pixels);
                    int cluster = proper(site[x+1][y+1]);
                    if (label > 0 && cluster == label)
                        osg.setColor(Color.red);
                    else if (cluster == spanningCluster)
                        osg.setColor(Color.green);
                    else
                        osg.setColor(Color.blue);
                    osg.fillOval(ix, iy, d, d);

                }
            }

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

        }

        int label = 0;
        
        public void mouseClicked (MouseEvent me) { }
        public void mouseEntered (MouseEvent me) { }
        
        public void mouseExited (MouseEvent me) {

            mouse_x = mouse_y = -1;
            label = 0;
            repaint();
            outputWindow.repaint();

        }

        public void mousePressed (MouseEvent me) { }
        public void mouseReleased (MouseEvent me) { }
        public void mouseDragged (MouseEvent me) { }

        public void mouseMoved (MouseEvent me) {

            int x = (int) (me.getX() * L / (double) pixels);
            int y = (int) ((pixels - me.getY()) * L / (double) pixels);
            if (x != mouse_x || y != mouse_y) {
                
                mouse_x = x;
                mouse_y = y;
                label = site[x+1][y+1];
                repaint();
                outputWindow.repaint();
                
            }
            
        }

    }

    class OutputWindow extends Canvas {

        int xPixels = 280;
        int yPixels = 160;
        
        OutputWindow () {

            setSize(xPixels, yPixels);
            setBackground(new Color(255, 254, 244));

        }

        Image offScreen;
        Graphics osg;

        public void paint (Graphics g) {

            if (offScreen == null) {

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

            }

            osg.clearRect(0, 0, xPixels, yPixels);
            osg.setColor(Color.black);
            osg.drawRect(0, 0, xPixels - 1, yPixels - 1);

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

            osg.setColor(Color.cyan);
            osg.drawString("Occupied sites = ", x1, y);
            osg.drawString("" + occupiedSites, x2, y);
            y += dy;
            osg.setColor(Color.blue);
            osg.drawString("Finite clusters = ", x1, y);
            osg.drawString("" + finiteClusters, x2, y);
            y += dy;
            osg.drawString("<Cluster size> S = ", x1, y);
            osg.drawString(CP.format(S, 4), x2, y);
            y += dy;
            
            if (mouse_x >= 0 && mouse_x < L && mouse_y >= 0 && mouse_y < L) {

                osg.setColor(Color.magenta);
                osg.drawString("At position of mouse:", x1, y);
                y += dy;
                osg.drawString("Site x and y = ", x1, y);
                osg.drawString("" + (mouse_x + 1) +
                               ",  " + (mouse_y + 1), x2, y);
                y += dy;
                osg.drawString("Probability  r = ", x1, y);
                osg.drawString(CP.format(rsite[mouse_x][mouse_y], 4), x2, y);
                y += dy;
                int cluster = site[mouse_x+1][mouse_y+1];
                if (cluster > 0) {
                    osg.setColor(Color.red);
                    osg.drawString("Cluster size = ", x1, y);
                    osg.drawString("" + m_i[cluster], x2, y);
                    y += dy;
                    osg.drawString("Cluster Label = ", x1, y);
                    osg.drawString("" + cluster, x2, y);
                    y += dy;
                } else { y += 2 * dy; }
                
            } else { y += 5 * dy; }

            if (nSpanning > 0) {
                
                osg.setColor(Color.green);
                osg.drawString("Spanning cluster = ", x1, y);
                osg.drawString("" + nSpanning, x2, y);
                y += dy;
                osg.drawString("P_infinity = ", x1, y);
                osg.drawString(CP.format(P_infinity, 4), x2, y);
                y += dy;
                
            }

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

        }

    }

    Picture picture;
    OutputWindow outputWindow;
    Label LLabel;
    Scrollbar LSlider;
    Button newSampleButton;
    Checkbox vBox, hBox, bBox, eBox;
    Label pLabel;
    Scrollbar pSlider;

    public void init () {

        initial();

        add(picture = new Picture());

        Panel rightPanel = new Panel();
        rightPanel.setLayout(new BorderLayout(2,20));
        rightPanel.add(outputWindow = new OutputWindow(), "North");

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

        panel.add(LLabel = new Label("Linear size L = " + L));
        panel.add(LSlider = new Scrollbar(Scrollbar.HORIZONTAL,
                                          10, 0, 1, 100));
        LSlider.setValue(L);
        LSlider.addAdjustmentListener(this);

        panel.add(new Label("Spanning Cluster"));
        Panel panel1 = new Panel();
        CheckboxGroup cg = new CheckboxGroup();
        panel1.add(vBox = new Checkbox("Vert", cg, true));
        vBox.addItemListener(this);
        panel1.add(hBox = new Checkbox("Horz", cg, false));
        hBox.addItemListener(this);
        panel.add(panel1);

        panel.add(newSampleButton = new Button("New Sample"));
        newSampleButton.addActionListener(this);

        Panel panel2 = new Panel();
        panel2.add(bBox = new Checkbox("Both", cg, false));
        bBox.addItemListener(this);
        panel2.add(eBox = new Checkbox("VorH", cg, false));
        eBox.addItemListener(this);
        panel.add(panel2);

        panel.add(pLabel = new Label("Parameter p = " + CP.format(p, 2)));
        panel.add(pSlider = new Scrollbar(Scrollbar.HORIZONTAL,
                                          10, 0, 1, 100));
        pSlider.setValue((int)(p * 100) - 1);
        pSlider.addAdjustmentListener(this);

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

    }

    public void actionPerformed (ActionEvent event) {

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

            initial();
            repaint();

        }

    }

    public void adjustmentValueChanged (AdjustmentEvent event) {

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

            L = LSlider.getValue();
            LLabel.setText("Linear size L = " + L);
            initial();

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

            p = (pSlider.getValue() + 1) / 100.0;
            pLabel.setText("Parameter p = " + CP.format(p, 2));
            HoshenKopelman();

        }

        repaint();

    }

    public void itemStateChanged (ItemEvent event) {

        VertSpan = vBox.getState();
        HorzSpan = hBox.getState();
        BothSpan = bBox.getState();
        VorHSpan = eBox.getState();

        HoshenKopelman();
        repaint();

    }

    public void paint (Graphics g) {

        picture.repaint();
        outputWindow.repaint();

    }

    public static void main (String[] args) {

        Percolation percolation = new Percolation();
        CPFrame aFrame = new CPFrame("Site Percolation");
        
        aFrame.add(percolation);
        percolation.init();
        aFrame.setSize(700, 450);
        aFrame.setLocation(50, 50);
        aFrame.setVisible(true);

    }

}

