/*
Copyright (C) 2005 Chr. Clemens Lee <clemens@kclee.de>.

This file is part of DLA
(http://www.klee.de/clemens/java/dla/).

Dla is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

Dla is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Dla; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

package de.kclee.dla;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

/**
 * Diffusion Limited Aggregation (DLA) applet.
 *
 * @author    Chr. Clemens Lee (http://www.kclee.de/clemens/java/dla/)
 * @version   $Revision: 1.8 $, $Date: 2005/03/26 11:29:45 $
 */
public class DLAApplet extends Applet implements Runnable
{
    private static final int _appletWidth  = 640;
    private static final int _appletHeight = 480;
    private static final int POINTS = 40000;
    private int _dlaPoints = 0;
    private Random _random = new Random();
    private static final int RED = 255;
    private static final int BLUE = 0;
    private static final int INITIAL_GREEN = 0;
    private static final int INITIAL_COLOR_DIRECTION = 1;
    private int[] _aX = new int[ POINTS ];
    private int[] _aY = new int[ POINTS ];
    private int[][] _aScreen = new int[ _appletWidth ][ _appletHeight ];
    private static final int EMPTY = -1;
    private static final int POINT = -2;
    private static final int DLA = 0;
    private Thread _thread = null;
    private boolean _active = false;
    private static final int UP = 0;
    private static final int RIGHT = 1;
    private static final int DOWN = 2;
    private static final int LEFT = 3;

    private int _increaseX( int x )
    {
        return _increase( x, _appletWidth );
    }

    private int _increaseY( int y )
    {
        return _increase( y, _appletHeight );
    }

    private int _increase( int x, int limit )
    {
        int retVal = x + 1;
        if ( retVal >= limit )
            {
                retVal = 0;
            }
        
        return retVal;
    }

    private int _decreaseX( int x )
    {
        return _decrease( x, _appletWidth );
    }

    private int _decreaseY( int y )
    {
        return _decrease( y, _appletHeight );
    }

    private int _decrease( int x, int limit )
    {
        int retVal = x - 1;
        if ( retVal < 0 )
            {
                retVal = limit - 1;
            }
        
        return retVal;
    }

    private int nextGreen( int green, int direction )
    {
        int retVal = green + direction;
        if ( retVal < 0 || retVal >= 256 )
            {
                return nextGreen( retVal, -direction );
            }

        return retVal;
    }

    private int nextDirection( int green, int direction )
    {
        int newGreen = green + direction;
        if ( newGreen < 0 || newGreen >= 256 )
            {
                return -direction;
            }

        return direction;
    }

    private int colorDirectionOffset( int direction )
    {
        return 512 + direction * 256;
    }

    public void init()
    {
        setBackground ( Color.black );

        for( int x = 0; x < _appletWidth; x++ )
            {
                for( int y = 0; y < _appletHeight; y++ )
                    {
                        _aScreen[ x ][ y ] = EMPTY;
                    }
            }

        int middleX = _appletWidth / 2;
        int middleY = _appletHeight / 2;
        _aX[ _dlaPoints ] = middleX;
        _aY[ _dlaPoints ] = middleY;
        _aScreen[ _aX[ _dlaPoints ] ][ _aY[ _dlaPoints ] ] = INITIAL_GREEN + colorDirectionOffset( INITIAL_COLOR_DIRECTION );
        _dlaPoints++;

        for( int i = _dlaPoints; i < POINTS; i++ )
            {
                do
                    {
                        _aX[ i ] = _random.nextInt( _appletWidth );
                        _aY[ i ] = _random.nextInt( _appletHeight );
                    }
                while( _aScreen[ _aX[ i ] ][ _aY[ i ] ] != EMPTY );
                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = POINT;
            }

        _thread = new Thread( this );
    }

    private int getGreen( int point )
    {
        return _aScreen[ _aX[ point ] ][ _aY[ point ] ] % 256;
    }

    synchronized public void paint(Graphics g)
    {
        // print dla
        for( int i = 0; i < _dlaPoints; i++ )
            {
                g.setColor( new Color( RED, getGreen( i ), BLUE ) );
                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
            }

        // draw initial points
        g.setColor( Color.yellow );
        for( int i = _dlaPoints; i < POINTS; i++ )
            {
                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
            }
    }

    synchronized public void update(Graphics g)
    {
        if ( _active && _dlaPoints == POINTS )
            {
                _active = false;

                return;
            }
        
        for( int i = _dlaPoints; i < POINTS; i++ )
            {
                // is there a dla point next to this one?
                int previousGreenAndDirection = EMPTY;
                if ( _aScreen[ _aX[ i ] ][ _decreaseY( _aY[ i ] ) ] >= DLA )
                    {
                        previousGreenAndDirection = _aScreen[ _aX[ i ] ][ _decreaseY( _aY[ i ] ) ];
                    }
                else if ( _aScreen[ _increaseX( _aX[ i ] ) ][ _aY[ i ] ] >= DLA )
                            {
                                previousGreenAndDirection = _aScreen[ _increaseX( _aX[ i ] ) ][ _aY[ i ] ];
                            }
                else if ( _aScreen[ _aX[ i ] ][ _increaseY( _aY[ i ] ) ] >= DLA )
                    {
                        previousGreenAndDirection = _aScreen[ _aX[ i ] ][ _increaseY( _aY[ i ] ) ];
                    }
                else if ( _aScreen[ _decreaseX( _aX[ i ] ) ][ _aY[ i ] ] >= DLA )
                    {
                        previousGreenAndDirection = _aScreen[ _decreaseX( _aX[ i ] ) ][ _aY[ i ] ];
                    }
                if ( previousGreenAndDirection >= DLA )
                    {
                        int previousGreen = previousGreenAndDirection % 256;
                        int previousDirection = (previousGreenAndDirection / 256) - 2;
                        int nextGreen = nextGreen( previousGreen, previousDirection );
                        int nextDirection = nextDirection( previousGreen, previousDirection );
                        _aScreen[ _aX[ i ] ][ _aY[ i ] ] = nextGreen + colorDirectionOffset( nextDirection );
                        g.setColor( new Color( RED, nextGreen, BLUE ) );
                        g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                        int otherX = _aX[ _dlaPoints ];
                        int otherY = _aY[ _dlaPoints ];
                        _aX[ _dlaPoints ] = _aX[ i ];
                        _aY[ _dlaPoints ] = _aY[ i ];
                        _aX[ i ] = otherX;
                        _aY[ i ] = otherY;
                        _dlaPoints ++;
                        
                        continue;
                    }

                // random walk
                int direction = _random.nextInt( 4 );
                if ( direction == UP )
                    {
                        if ( _aScreen[ _aX[ i ] ][ _decreaseY( _aY[ i ] ) ] == EMPTY )
                            {
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = EMPTY;
                                g.setColor( Color.black );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                                _aY[ i ] = _decreaseY( _aY[ i ] );
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = POINT;
                                g.setColor( Color.yellow );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                            }
                    }
                else if ( direction == DOWN )
                    {
                        if ( _aScreen[ _aX[ i ] ][ _increaseY( _aY[ i ] ) ] == EMPTY )
                            {
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = EMPTY;
                                g.setColor( Color.black );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                                _aY[ i ] = _increaseY( _aY[ i ] );
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = POINT;
                                g.setColor( Color.yellow );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                            }
                    }
                else if ( direction == RIGHT )
                    {
                        if ( _aScreen[ _increaseX( _aX[ i ] ) ][ _aY[ i ] ] == EMPTY )
                            {
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = EMPTY;
                                g.setColor( Color.black );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                                _aX[ i ] = _increaseX( _aX[ i ] );
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = POINT;
                                g.setColor( Color.yellow );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                            }
                    }
                else if ( direction == LEFT )
                    {
                        if ( _aScreen[ _decreaseX( _aX[ i ] ) ][ _aY[ i ] ] == EMPTY )
                            {
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = EMPTY;
                                g.setColor( Color.black );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                                _aX[ i ] = _decreaseX( _aX[ i ] );
                                _aScreen[ _aX[ i ] ][ _aY[ i ] ] = POINT;
                                g.setColor( Color.yellow );
                                g.drawLine( _aX[ i ], _aY[ i ], _aX[ i ], _aY[ i ] );
                            }
                    }
            }
    }

    public String getAppletInfo()
    {
        return "DLA Applet by Chr. Clemens Lee [2005-03-25]";
    }

    public void start()
    {
        _active = true;
        _thread.start();
    }

    public void stop()
    {
        _active = false;
    }

    public void run()
    {
        while( _active )
            {
                repaint();
                _thread.yield();
            }
    }
}
