import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;

/**
 * This demo program uses a thread to compute an image "in the background".
 * As rows of pixels in the image are computed, they are copied to the
 * screen.  (The image is a small piece of the famous Mandelbrot set, which
 * is used just because it takes some time to compute.  There is no need
 * to understand what the image means.)  The user starts the computation by
 * clicking a "Start" button.  A separate thread is created and is run at
 * a lower priority, which will make sure that the GUI thread will get a
 * chance to run to repaint the display as necessary.
 * 
 * This is a version of BackgroundComputationDemo that uses 
 * SwingUtilities.invokeAndWait() instead of synchronization to handle
 * coordination between the computation thread and the GUI thread.
 */
public class BackgroundCompWithInvoke extends JPanel {

    /**
     * This main routine just shows a panel of type BackgroundComputationDemo.
     */
    public static void main(String[] args) {
        JFrame window = new JFrame("Demo: Background Computation in a Thread");
        BackgroundCompWithInvoke content = new BackgroundCompWithInvoke();
        window.setContentPane(content);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.pack();
        window.setResizable(false);
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        if (window.getWidth() > screenSize.width-100 || window.getHeight() > screenSize.height-100) {
            double scale1 = (double)(screenSize.width-100)/window.getWidth();
            double scale2 = (double)(screenSize.height-100)/window.getHeight();
            double scale = Math.min(scale1,scale2);
            window.setSize( (int)(scale*window.getWidth()), (int)(scale*window.getHeight()) );
        }
        window.setLocation( (screenSize.width - window.getWidth()) / 2,
                (screenSize.height - window.getHeight()) / 2 );
        window.setVisible(true);
    }


    private Runner runner;  // the thread that computes the image

    private volatile boolean running;  // used to signal the thread to abort

    private JButton startButton; // button the user can click to start or abort the thread

    private BufferedImage image; // contains the image that is computed by this program


    /**
     * The display is a JPanel that shows the image.  The part of the image that has
     * not yet been computed is gray.  If the image has not yet been created, the
     * entire display is filled with gray.
     */
    private JPanel display = new JPanel() {
        protected void paintComponent(Graphics g) {
            if (image == null)
                super.paintComponent(g);  // fill with background color, gray
            else {
                    /* Copy the image onto the display.  This is synchronized because
                     * there are two threads that compete for access to the image:
                     * the thread that computes the image and the thread that does the
                     * painting.  The two threads both synchronize on the image object,
                     * although any object could be used.
                     */
                g.drawImage(image,0,0,null);
            }
        }
    };


    /**
     * Constructor creates a panel to hold the display, with a "Start" button below it.
     */
    public BackgroundCompWithInvoke() {
        display.setPreferredSize(new Dimension(1600,1200));
        display.setBackground(Color.LIGHT_GRAY);
        setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
        setLayout(new BorderLayout());
        add(display, BorderLayout.CENTER);
        startButton = new JButton("Start");
        JPanel bottom = new JPanel();
        bottom.add(startButton);
        bottom.setBackground(Color.WHITE);
        add(bottom,BorderLayout.SOUTH);
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (running)
                    stop();
                else
                    start();
            }
        });
    }


    /**
     * This method is called when the user clicks the Start button,
     * while no thread is running.  It starts a new thread and
     * sets the signaling variable, running, to true;  Also changes
     * the text on the Start button to "Abort".
     * Note that the priority of the thread is set to be one less
     * than the priority of the thread that calls this method, that
     * is of Swing's event-handling thread.  This means that the event-handling
     * thread is run in preference to the computation thread.  When there is an
     * event to be handled, such as painting the display or reacting to a
     * button click, the event-handling thread will wake up to handle the
     * event.
     */
    private void start() {
        startButton.setEnabled(false);
        int width = display.getWidth() + 2;
        int height = display.getHeight() + 2;
        if (image == null)
            image = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics(); // fill image with gray
        g.setColor(Color.LIGHT_GRAY);
        g.fillRect(0,0,width,height);
        g.dispose();
        display.repaint();
        startButton.setText("Abort");
        runner = new Runner();
        try {
            runner.setPriority( Thread.currentThread().getPriority() - 1 );
        }
        catch (Exception e) {
        }
        running = true;  // Set the signal before starting the thread!
        runner.start();
    }


    /**
     * This method is called when the user clicks the button while
     * a thread is running.  A signal is sent to the thread to terminate,
     * by setting the value of the signaling variable, running, to false.
     */
    private void stop() {
        startButton.setEnabled(false);
        running = false;
        runner = null;
    }


    /**
     * This class defines the thread that does the computation.  The
     * run method computes the image one pixel at a time.  After computing
     * the colors for each row of pixels, the colors are copied into the
     * image, and the part of the displays that shows that row is repainted.
     * (Since the thread runs in the background, at lower priority than
     * the event-handling thread, the event-handling thread wakes up
     * immediately to repaint the display.)
     */
    private class Runner extends Thread {
        double xmin, xmax, ymin, ymax;
        int maxIterations;
        int[] rgb;
        int[] palette;
        int width, height;
        Runner() {
            width = image.getWidth();
            height = image.getHeight();
            rgb = new int[width];
            palette = new int[256];
            for (int i = 0; i < 256; i++)
                palette[i] = Color.getHSBColor(i/255F, 1, 1).getRGB();
            xmin = -1.6744096740931858;
            xmax = -1.674409674093473;
            ymin = 4.716540768697223E-5;
            ymax = 4.716540790246652E-5;
            maxIterations = 10000;
        }
        public void run() {
            try {
                startButton.setEnabled(true);
                double x, y;
                double dx, dy;
                dx = (xmax-xmin)/(width-1);
                dy = (ymax-ymin)/(height-1);
                for (int row = 0; row < height; row++) {  // Compute one row of pixels.
                    y = ymax - dy*row;
                    for (int col = 0; col < width; col++) {
                        x = xmin + dx*col;
                        int count = 0;
                        double xx = x;
                        double yy = y;
                        while (count < maxIterations && (xx*xx + yy*yy) < 4) {
                            count++;
                            double newxx = xx*xx - yy*yy + x;
                            yy = 2*xx*yy + y;
                            xx = newxx; 
                        }
                        if (count == maxIterations)
                            rgb[col] = 0;
                        else
                            rgb[col] = palette[count%palette.length];
                    }
                    if (! running) {  // Check for the signal to abort the computation.
                        return;
                    }
                    
                    final int r = row; // A local variable used in an inner class must be
                                       // final, so we can't use row itself in the following.
                    
                    /* SwingUtilities.invokeLater schedules a Runnable to be executed in the
                     * GUI thread, which also executes the display's paintComponent method.
                     * So, all accesses to the image will be in the GUI thread, eliminating
                     * the need for synchronization.  This can improve performance.
                     */
                    
                    SwingUtilities.invokeLater( new Runnable() {
                        public void run() {
                            image.setRGB(0, r, width, 1, rgb, 0, width);
                            display.repaint(0,r,width,1); // Repaint just the newly computed row.
                        }
                    } );
                    
                    /* The following is an alternative to using SwingUtilities.invokeLater.
                     * SwingUtilities.invokeAndWait schedules a Runnable to be run in the
                     * GUI thread, then waits until the Runnable has finished executing.
                     * This might be necessary if it is impossible for this thread to proceed
                     * until the Runnable has run, or if Runnables could be generated faster
                     * than they can be handled by the GUI thread.
                     */

//                    try {
//                        SwingUtilities.invokeAndWait( new Runnable() {
//                            public void run() {
//                                image.setRGB(0, r, width, 1, rgb, 0, width);
//                                display.repaint(0,r,width,1); // Repaint just the newly computed row.
//                            }
//                        });
//                    } 
//                    catch (InterruptedException e) {
//                    } 
//                    catch (InvocationTargetException e) {
//                          // An error occurred in the run() method of the invoked Runnable.
//                          // This shouldn't be possible here.
//                        System.out.println("Unexpected error in invoked Runnable: " + e.getCause());
//                    }

                    
                }
            }
            finally {
                startButton.setText("Start Again");
                startButton.setEnabled(true);
                running = false; // Make sure running is false after the thread ends.
            }
        }
    }

}
