package cellularautomata;

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

/**
 * Simple 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.3
 */

public class SimpleApplet extends Applet
{

    public static final String VERSION = "1.3";
    private ImageCanvas        _imageCanvas;
    private StateComponentMap  _stateMap;
    private Label              _status;
    private Button             _drawButton;
    private TextField          _maxIterField;
    private TextField          _zoomField;
    private ScrollPane         _scrollPane;
    private StateComponent[]   _stateComponents;
    private byte[]             _colorMap;
    
    public void init()
    {
        super.init();
        Color background = getColorParameter("background", Color.white);
        Color foreground = getColorParameter("foreground", Color.black);
        _colorMap        = new byte[] {(byte)background.getRed(),
                                       (byte)background.getGreen(),
                                       (byte)background.getBlue(),
                                       (byte)foreground.getRed(),
                                       (byte)foreground.getGreen(),
                                       (byte)foreground.getBlue()};
        _stateMap        = new StateComponentMap(2);
        _stateComponents = new StateComponent[8];
        
        initStateComponents();
        
        setBackground(background);

        setForeground(foreground);
        setLayout(new BorderLayout());
        Panel topPanel    = new Panel(new GridLayout(2,1));
        Panel heightPanel = new Panel(new FlowLayout());
        Panel statePanel  = new Panel(new FlowLayout());
        int maxIter       = getWidth()/2;
        topPanel.add(heightPanel);
        topPanel.add(statePanel);
        _imageCanvas      = new ImageCanvas(maxIter,1);
        _scrollPane       = new ScrollPane();
        _scrollPane.add(_imageCanvas);
        _maxIterField     = new TextField(new Integer(maxIter).toString());
        _zoomField        = new TextField("1.0");
        _drawButton       = new Button("Redraw!");
        heightPanel.add(new Label("zoom:"));
        heightPanel.add(_zoomField);
        heightPanel.add(
            new Label("number of iterations:"));
        heightPanel.add(_maxIterField);
        heightPanel.add(_drawButton);
        statePanel.add(
            new Label("automata states (click a state to change):"));
        for (int i=0; i<_stateComponents.length; i++)
        {
            StateComponent component = _stateComponents[i];
            statePanel.add(component);
            _stateMap.put(component);
        }
        _status = new Label(
            "copyright 2003 (c) Fabien Le Floc'h <fabien@31416.org>");
        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);
                _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;
    }
    
    private void initStateComponents()
    {
        byte w = 0;
        byte b = 1;

        _stateComponents[0] = new StateComponent(b,b,b,b);
        _stateComponents[1] = new StateComponent(b,b,w,b);
        _stateComponents[2] = new StateComponent(b,w,b,w);
        _stateComponents[3] = new StateComponent(b,w,w,b);
        _stateComponents[4] = new StateComponent(w,b,b,b);
        _stateComponents[5] = new StateComponent(w,b,w,w);
        _stateComponents[6] = new StateComponent(w,w,b,b);
        _stateComponents[7] = new StateComponent(w,w,w,w);
        
    }
    
    public String getAppletInfo()
    {
        return "Simple 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"}
    };

    private class ImageCanvas extends Canvas
    {
        
        private Image _image;
        private int   _maxIter;
        private float _zoom;
        // uses _stateMap and _colorTable...
        
        public ImageCanvas(int maxIter, float zoom)
        {
            this.setBounds(0,0,maxIter*2,maxIter);
            _maxIter = maxIter;
            _zoom    = zoom;
        }

        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];
            int       index = 0;
            
            // init
            for (;index<maxX;index++)
            {
                pixels[index] = 0;
            }
            pixels[_maxIter] = 1;
            // loop
            for (int y=1; y<_maxIter; y++)
            {
                pixels[index] = 0;
                pixels[index+maxX-1] = 0;
                index++;
                for (int x=1; x<maxX-1; x++)
                {
                    pixels[index] = _stateMap.get(pixels,index-maxX);
                    index++;
                }
                index++;
            }

            MemoryImageSource mis = new MemoryImageSource(
                maxX,_maxIter,
                new IndexColorModel(8,2,_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;
        }
    }

    private static class StateComponent extends Canvas
    {
        Image _initialState;
        byte  _result;
        int   _width, _height;
        byte  _left, _middle, _right;

        public StateComponent(byte left, byte middle, byte right, byte result)
        {
            _initialState = null;
            _result       = result;
            _width        = 9;
            _height       = 9;
            _left         = left;
            _middle       = middle;
            _right        = right;
            this.setBounds(0,0,3*(_width+1),2*(_height+1));
        }

        public Image createImage(int width, int height)
        {
            Image image = super.createImage(width,height);
            Graphics g  = image.getGraphics();
            
            drawState(g,0,0,_left);
            drawState(g,_width,0,_middle);
            drawState(g,2*_width,0,_right);
            
            return image;
        }

        protected void drawState(Graphics g, int x, int y, byte state)
        {
            g.drawRect(x,y,_width,_height);
            if (state > 0)
            {
                g.fillRect(x,y,_width,_height);
            }
            
        }

        public byte getLeft()
        {
            return _left;
        }

        public byte getMiddle()
        {
            return _middle;
        }

        public byte getRight()
        {
            return _right;
        }

        public byte getResult()
        {
            return _result;
        }
        
        public void paint(Graphics g)
        {
            if (_initialState == null)
            {
                _initialState = this.createImage(3*_width+1,2*_height+1);
            }
            g.drawImage(_initialState,0,0,this);
            drawState(g,_width,_height,_result);
        }
        
        public boolean mouseDown(Event e, int x, int y)
        {
            if (_result == 0) _result =1;
            else _result = 0;
            repaint();
            return true;
        }
    }

    public static class StateComponentMap
    {
        private int                  _nbColors;
        private int                  _nbStates;
        private StateComponent[][][] _stateTable;

        /**
         */
    
        public StateComponentMap(int nbColors)
        {
            _nbColors   = nbColors;
            _stateTable = new StateComponent[nbColors][nbColors][nbColors];
        }

        public int getSize()
        {
            return _stateTable.length;
        }
        
        public void put(StateComponent component)
        {
            _stateTable[component.getLeft()][component.getMiddle()]
                [component.getRight()] = component;
        }
        
        public byte get(int left, int middle, int right)
        {
            return _stateTable[left][middle][right].getResult();
        }

        public byte get(byte[] lastLine, int i)
        {
            return get(lastLine[i-1],lastLine[i],lastLine[i+1]);
        }
        
    }

}