/* SlotMachine.java */

/* 
 * Copyright (C) 1995 Mark Boyns <boyns@sdsu.edu>
 *
 * SlotMachine
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.*;
import java.net.URL;
import java.net.MalformedURLException;

class Wheel implements Runnable
{
    private SlotMachine parent;
    private Thread thread;
    private boolean spinning = false;
    private int nspins = 0;
    private int max = 0;
    private int current = 0;

    /* Create a new wheel with 0..max-1 slots. */
    Wheel (int max, SlotMachine parent)
    {
	this.max = max;
	this.parent = parent;
    }
    
    public void run ()
    {
	int delay = 10;

	/* Start the wheel spinning. */
	spinning = true;
	for (int i = 0; i < nspins; i++)
	{
	    current--;
	    if (current < 0)
	    {
		current = max - 1;
	    }

	    parent.repaint ();

	    try
	    {
		Thread.sleep (delay);
		delay += 10;
	    }
	    catch (InterruptedException e)
	    {
	    }
	}
	spinning = false;

	parent.repaint ();
	parent.wheelStopped ();
    }

    public void spin (int nspins)
    {
	this.nspins = nspins;
	thread = new Thread (this);
	thread.start ();
    }

    public int currentSlot ()
    {
	return current;
    }
    
    public void stop ()
    {
	thread = null;
    }
}

public class SlotMachine extends java.applet.Applet implements Runnable
{
    private Thread thread;
    private Image images[];
    private int maxImages = 0;
    private AudioClip winner = null;
    private AudioClip loser = null;
    private int maxWidth = 0;
    private int maxHeight = 0;
    private Wheel wheels[] = new Wheel[3];
    private int wheelsSpinning = 0;

    public synchronized boolean imageUpdate (Image image, int infoFlags,
					     int x, int y,
					     int width, int height)
    {
	notifyAll ();
	return true;
    }

    synchronized Dimension getImageDimensions (Image image)
    {
	int width;
	int height;
	
	while ((width = image.getWidth (this)) < 0)
	{
	    try
	    {
		wait ();
	    }
	    catch (InterruptedException e)
	    {
	    }
	}

	while ((height = image.getHeight (this)) < 0)
	{
	    try
	    {
		wait ();
	    }
	    catch (InterruptedException e)
	    {
	    }
	}
	
	return new Dimension (width, height);
    }
    
    void fetchImages () throws MalformedURLException
    {
	String param;
	URL source;

	param = getParameter ("IMAGESOURCE");
	source = (param == null) ? getDocumentBase () : new URL (getDocumentBase (), param + "/");

	param = getParameter ("IMAGES");
	for (int i = 0; i < param.length (); )
	{
	    int next = param.indexOf ('|', i);
	    if (next == -1)
	    {
		next = param.length ();
	    }
	    String s = param.substring (i, next);
	    URL url = new URL (source, s);
	    images[maxImages] = getImage (url);
	    Dimension dim = getImageDimensions (images[maxImages]);
	    maxWidth = Math.max (maxWidth, dim.width);
	    maxHeight = Math.max (maxHeight, dim.height);
	    maxImages++;
	    i = next + 1;
	}
    }

    void fetchSounds () throws MalformedURLException
    {
	String param;
	URL source;

	param = getParameter ("SOUNDSOURCE");
	source = (param == null) ? getDocumentBase () : new URL (getDocumentBase (), param + "/");

	param = getParameter ("WINNER");
	winner = getAudioClip (new URL (source, param));

	param = getParameter ("LOSER");
	loser = getAudioClip (new URL (source, param));
    }
    
    public void init ()
    {
	images = new Image[32];
	
	try
	{
	    fetchImages ();
	}
	catch (Exception e)
	{
	    e.printStackTrace ();
	}

	try
	{
	    fetchSounds ();
	}
	catch (Exception e)
	{
	    e.printStackTrace ();
	}

	for (int i = 0; i < 3; i++)
	{
	    wheels[i] = new Wheel (maxImages, this);
	}

	resize (maxWidth*3 + 40, maxHeight*3 + 20);
    }

    public void update (Graphics g)
    {
	paint (g);
    }
    
    public void paint (Graphics g)
    {
	int x = 10;
	int y = 10;
	
	for (int i = 0; i < 3; i++)
	{
	    g.setColor (Color.black);
	    g.drawRect (x - 1, y - 1, maxWidth+1, 3*maxHeight + 1);

	    if (wheels[i] != null)
	    {
		int previousImage, nextImage, currentImage;

		currentImage = wheels[i].currentSlot ();
		if (currentImage == 0)
		{
		    previousImage = maxImages - 1;
		    nextImage = currentImage + 1;
		}
		else if (currentImage == maxImages-1)
		{
		    previousImage = currentImage - 1;
		    nextImage = 0;
		}
		else
		{
		    previousImage = currentImage - 1;
		    nextImage = currentImage + 1;
		}

		if (images[previousImage] != null)
		{
		    g.drawImage (images[previousImage], x, y, this);
		}
		if (images[currentImage] != null)
		{
		    g.drawImage (images[currentImage], x, y + maxHeight, this);
		}
		if (images[nextImage] != null)
		{
		    g.drawImage (images[nextImage], x, y + 2*maxHeight, this);
		}
	    }

	    x += maxWidth + 10;
	}
    }

    public synchronized void wheelStopped ()
    {
	wheelsSpinning--;
	notify ();
    }

    public synchronized void waitForWheels ()
    {
	while (wheelsSpinning > 0)
	{
	    try
	    {
		wait ();
	    }
	    catch (Exception e)
	    {
	    }
	}
    }
    
    public void run ()
    {
	wheelsSpinning = 3;
	
	for (int i = 0; i < 3; i++)
	{
	    wheels[i].spin (20 + (int) (maxImages * Math.random ()));
	}

	waitForWheels ();

	if (wheels[0].currentSlot () == wheels[1].currentSlot ())
	{
	    if (winner != null)
	    {
		winner.play ();
	    }
	}
	else
	{
	    if (loser != null)
	    {
		loser.play ();
	    }
	}
    }

    public boolean handleEvent (Event e)
    {
	if (e.id == Event.MOUSE_DOWN)
	{
	    thread = new Thread (this);
	    thread.start ();
	    return true;
	}
	else
	{
	    return super.handleEvent (e);
	}
    }
}
