package cellularautomata;

import java.applet.*;
import java.awt.*;
import java.awt.image.*;

/**
 * Continuous cellular automata applet
 * showing S. Wolfram (A New Kind Of Science) ideas.
 * This applet is compatible with Java 1.1
 * (1.0.2 without ScrollPane and inner classes)
 * for maximum browser compatibility.
 *
 * @author Fabien Le Floc'h <fabien@31416.org>
 * @version 1.0
 */

public class ContinuousApplet extends Applet
{

    public static final String VERSION = "1.0";
    private ImageCanvas        _imageCanvas;
    private Label              _status;
    private Button             _drawButton;
    private TextField          _maxIterField;
    private TextField          _zoomField;
    private ScrollPane         _scrollPane;
    private StateComponent     _stateComponent;
    
    public void init()
    {
        super.init();
        Color background = getColorParameter("background", Color.white);
        Color foreground = getColorParameter("foreground", Color.black);
        int maxIter      = getWidth()/2;
        byte[] colorMap  = new byte[256*3];

        for (int i=0; i<256;i++)
        {
            int n = 3*i; 
            colorMap[n] = (byte) (255-i);
            //(background.getRed()*(1-i/255)+foreground.getRed()*i/255);
            colorMap[n+1] = (byte) (255-i);
            //(background.getGreen()*(1-i/255)+foreground.getGreen()*i/255);
            colorMap[n+2] = (byte) (255-i);
            //(background.getBlue()*(1-i/255)+foreground.getBlue()*i/255);
        }
        
        _stateComponent = new StateComponent(colorMap,1,0);
        _imageCanvas    = new ImageCanvas(_stateComponent,
                                          colorMap, maxIter,1);
        _scrollPane     = new ScrollPane();
        _scrollPane.add(_imageCanvas);
        _maxIterField   = new TextField(new Integer(maxIter).toString());
        _zoomField      = new TextField("1.0");
        _drawButton     = new Button("Redraw!");
        _status         = new Label(
            "copyright 2003 (c) Fabien Le Floc'h <fabien@31416.org>");
        
        setBackground(background);
        setForeground(foreground);
        setLayout(new BorderLayout(0,0));
        
        Panel topPanel    = new Panel(new GridLayout(2,1,0,0));
        Panel heightPanel = new Panel(new FlowLayout(FlowLayout.CENTER,5,2));
        topPanel.add(heightPanel);
        topPanel.add(_stateComponent);
        heightPanel.add(new Label("zoom:"));
        heightPanel.add(_zoomField);
        heightPanel.add(new Label("number of iterations:"));
        heightPanel.add(_maxIterField);
        heightPanel.add(_drawButton);
        add(BorderLayout.NORTH,topPanel);
        add(BorderLayout.CENTER,_scrollPane);
        add(BorderLayout.SOUTH,_status);
    }

    public boolean action(Event e, Object what)
    {
        // 'redraw' button pushed:
        if (e.target == _drawButton)
        {
            String w = _maxIterField.getText();
            String z = _zoomField.getText();
            try
            {
                int maxIter    = Integer.parseInt(w);
                float zoom     = Float.parseFloat(z);
                int position   = Math.round(maxIter*zoom) -
                    ((int)_scrollPane.getViewportSize().getWidth()/2);
                _stateComponent.renew();
                _imageCanvas.setSize(maxIter,zoom);
                _imageCanvas.repaint();
                _scrollPane.layout(); //recalculate scrollbars size
                _scrollPane.setScrollPosition(position,0); // recenter
                return true;
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
        return false;
    }
    
    
    public String getAppletInfo()
    {
        return "Continuous celullar automata v. " + VERSION +
            "Written by F. Le Floc'h <fabien@31416.org>.";
    }


    // Read the specified parameter.  Interpret it as a hexadecimal
    // number of the form RRGGBB and convert it to a color.
    protected Color getColorParameter(String name, Color defaultColor)
    {
        String value = this.getParameter(name);

        try
        {
            return new Color(Integer.parseInt(value, 16));
        }
        catch (Exception e)
        {
            return defaultColor;
        }
    
    }

    // Return info about the supported parameters.  Web browsers and applet
    // viewers should display this information, and may also allow users to
    // set the parameter values.
    public String[][] getParameterInfo()
    {
        return info;
    }
    // Here's the information that getParameterInfo() returns.
    // It is an array of arrays of strings describing each parameter.
    // Format: parameter name, parameter type, parameter description
    private static final String[][] info =
    {
        {"foreground", "hexadecimal color value", "foreground color"},
        {"background", "hexadecimal color value", "background color"}
    };


    /**
     * Picture of cellular automata after n steps.
     *
     */
    
    private static class ImageCanvas extends Canvas
    {
        
        private Image          _image;
        private int            _maxIter;
        private float          _zoom;
        private ContinuousRule _rule;
        private byte[]         _colorMap;
        
        public ImageCanvas(ContinuousRule rule,
                           byte[] colorMap,
                           int maxIter,
                           float zoom)
        {
            this.setBounds(0,0,maxIter*2,maxIter);
            _maxIter  = maxIter;
            _zoom     = zoom;
            _rule     = rule;
            _colorMap = colorMap;
        }

        public void setSize(int maxIter, float zoom)
        {
            _maxIter = maxIter;
            _zoom    = zoom;
            _image   = null;
            
            int height = Math.round(maxIter*zoom);
            this.setBounds(0,0,2*height,height);
        }
        
        public void paint(Graphics g)
        {
            if (_image == null)
            {
                System.gc();
                int height = Math.round(_maxIter*_zoom);
                _image = this.createImage(height*2,height);
            }
            g.drawImage(_image, 0, 0, this);
        }

        
        public Image createImage(int width, int height)
        {
            int        maxX  = _maxIter*2;
            byte[]   pixels  = new byte[maxX*_maxIter];
            float[] lastLine = new float[maxX*2];
            float[] tempLine = new float[maxX*2];
            int       index  = 0;
            // init
            for (int i=0;i<lastLine.length;i++)
            {
                lastLine[i] = 0;
            }            
            lastLine[maxX] = 1;

            for (int i=_maxIter; i<maxX+_maxIter;i++)
            {
                pixels[index++] = (byte) Math.round(255*lastLine[i]);
            }
            
            // loop
            for (int y=1; y<_maxIter; y++)
            {
                tempLine[0] = 0;
                tempLine[tempLine.length-1] = 0;
                for (int x=y; x<tempLine.length-y; x++)
                {
                    tempLine[x] = _rule.get(lastLine,x);
                }
                for (int x=_maxIter;x<maxX+_maxIter;x++)
                {
                    pixels[index++] = (byte) Math.round(255*tempLine[x]);
                }
                float[] tempRef = lastLine;
                lastLine = tempLine;
                tempLine = tempRef;
                //System.out.println();
            }
            lastLine = null;
            tempLine = null;
            
            MemoryImageSource mis = new MemoryImageSource(
                maxX,_maxIter,
                new IndexColorModel(8,256,_colorMap,0,false,-1),
                pixels,0,maxX);
            mis.setFullBufferUpdates(false);
            Image image = super.createImage(mis);
            if (_zoom != 1)
            {
                image = image.getScaledInstance(
                    width,height,Image.SCALE_SMOOTH);
            }
            return image;
        }
    }


    /**
     * Rule governing a continuous cellular automata.
     *
     */

    public static interface ContinuousRule
    {
        float get(float[] pixels, int i);
        float get(float left, float middle, float right);
    }
    

    /**
     * Description and parametrization of automata states.
     *
     */
    
    public static class StateComponent
        extends    Panel
        implements ContinuousRule
    {
        private Image       _initialState;
        private TextField   _aField, _bField;
        private float       _a, _b;
        private StateCanvas _canvas;
        private byte[]      _colorMap;
        
        public StateComponent(byte[] colorMap, float a, float b)
        {
            _initialState = null;
            _a            = a;
            _b            = b;
            _colorMap     = colorMap;
            _aField       = new TextField(new Float(a).toString());
            _bField       = new TextField(new Float(b).toString());
            _canvas       = new StateCanvas(this, _colorMap, 120, 40);
            
            this.setLayout(new FlowLayout(FlowLayout.CENTER,0,2));
            this.add(new Label("Next state = FractionalPart["));
            this.add(_aField);
            this.add(new Label("x +"));
            this.add(_bField);
            this.add(new Label("]"));
            this.add(_canvas);
            this.setBounds(0,0,this.getWidth(),41);
        }

        public void renew()
        {
            _a = Float.parseFloat(_aField.getText());
            _b = Float.parseFloat(_bField.getText());
            _canvas.renew();
        }       
        
        public float get(float[] pixels, int i)
        {
            return get(pixels[i-1],pixels[i],pixels[i+1]);
        }
        
        public float get(float left, float middle, float right)
        {
            //int temp = ((left & 0xFF) + (middle & 0xFF) +(right & 0xFF)) /3;
            //temp = Math.round(_a*temp+255*_b);
            float temp = _a*(left+middle+right)/3+_b;
            temp -= (float) Math.floor(temp);
            return temp;
        }
    }
    

    /**
     * Pretty pictures describing a given continuous cellular automata
     * as described in Wolfram's "A New Kind Of Science".
     */
    
    public static class StateCanvas extends Canvas
    {
        private int            _height;
        private int            _width;
        private ContinuousRule _rule;
        private Image          _image;
        private byte[]         _colorMap;
        
        public StateCanvas(ContinuousRule rule,
                           byte[] colorMap,
                           int width,
                           int height)
        {
            _height   = height;
            _width    = width;
            _rule     = rule;
            _image    = null;
            _colorMap = colorMap;
            
            this.setBounds(0,0,width,height);
        }

        public void setRule(ContinuousRule rule)
        {
            _rule = rule;
            renew();
        }

        public void renew()
        {
            _image = null;
            repaint();
        }
        
        public void paint(Graphics g)
        {
            if (_image == null)
            {
                System.gc();
                _image = this.createImage(_width, _height);
            }
            g.drawImage(_image, 0, 0, this);
        }

        public Image createImage(int width, int height)
        {
            Image image = super.createImage(width,height);
            Graphics g  = image.getGraphics();

            // left gradients
            int leftWidth = 2*width/3;
            int topHeight = height/2;
            Color    grey;
            
            for (int i=0; i<leftWidth; i++)
            {
                int mapIndice = 3*255*i/(leftWidth-1);

                grey = new Color((int)_colorMap[mapIndice] & 0xFF,
                                 (int)_colorMap[mapIndice+1] & 0xFF,
                                 (int)_colorMap[mapIndice+2] & 0xFF);
                g.setColor(grey);
                g.drawLine(i,0,i,topHeight); //top

                float f = (float)i/(float)(leftWidth-1);
                mapIndice = 3*Math.round(255*_rule.get(f,f,f));
                grey = new Color((int)_colorMap[mapIndice] & 0xFF,
                                 (int)_colorMap[mapIndice+1] & 0xFF,
                                 (int)_colorMap[mapIndice+2] & 0xFF);
                g.setColor(grey);
                g.drawLine(i,topHeight+1,i,height-1); //bottom
            }

            // right part
            g.setColor(getForeground());
            for (int i=leftWidth;i<width;i++)
            {
                float f = (float)(i-leftWidth)/(float)(width-leftWidth-1);
                int   y = Math.round((height-1)*_rule.get(f,f,f));
                g.drawLine(i,height-y-1,i,height-y-1);
            }

            // borders
            g.drawRect(0,0,leftWidth-1,topHeight);
            g.drawRect(0,topHeight,leftWidth-1,height-1-topHeight);
            g.drawRect(leftWidth-1,0,width-leftWidth,height-1);

            return image;
        }
        
    }

}