/************************************************
 * 6.837 F98 Project #1 - ANIMATED SPRITES
 * @author Copyright (c) Hugo Botelho Barra Fall 1998
 * AlphaApplet.java
 * 
 * Entry point for the applet.
 */

import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import java.awt.event.MouseMotionListener;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;


/************************************************
 * This class reads PARAM tags from its HTML host page and sets
 * the color and label properties of the applet. Program execution
 * begins with the init() method. 
 */


public class AlphaApplet extends Applet 
						 implements Runnable,
									MouseMotionListener,
									KeyListener
{

	
    ///////////////////////// Variables ////////////////////////////	
	
	
	public Playfield playfield;	
	
	int[] filteredPixels;		//array used for temporary storage during filtering operations
	
	int[] magnGlass;			//array used for temporary storage during filtering operations
	
	Image mag;					//image obtained by the "magnifying glass"
	
	Image magnFrame;			//image for the magnifying glass frame
	
	Point glassCoords;			//coordinates for the center of the magnifying glass
	
	int glassDim;				//dimension of the magnifying glass (width = height)
	
	int glassColor;				//current color of the magnifying glass lens
	
	public Image offscreen;		//image for offscreen painting
	
	private Thread animator;	//animation control thread

	
	
    ///////////////////////// Methods ////////////////////////////	
	
	
	
	public void init()
	{
		
		//add event listeners
		addMouseMotionListener(this);
		addKeyListener(this);

		glassCoords = new Point(0, 0);
		glassColor = 0;
		
		//Get magnifying glass dimensions (it must be square)
		glassDim = ( (new Integer(0)).decode(getParameter("glassDimension")) ).intValue();
		
		//Setup playfield
		playfield = new Playfield(getImage(getDocumentBase(), getParameter("background")), this);
		offscreen = playfield.toImage(this);
		

		//Decode image filenames from HTML file.
		//Because this method has been implemented in a generalized fashion,
		//it allows for any number of sprites, each one with any number
		//of frames and respective "alpha-channel characterizer" images.
		int numSprites = ( (new Integer(0)).decode(getParameter("numberOfSprites")) ).intValue();
		int numFrames = 0;
		
		for (int i = 0; i < numSprites; i++) 
		{
			numFrames = ( (new Integer(0)).decode(getParameter("sprite"+(i+1)+"NumberOfFrames")) ).intValue();
			AlphaRaster[] frames = new AlphaRaster[numFrames];
			for (int j = 0; j < numFrames; j++)
			{
				frames[j] = new AlphaRaster(getImage(getDocumentBase(), getParameter("sprite"+(i+1)+"Frame"+(j+1)) ), 
											getImage(getDocumentBase(), getParameter("sprite"+(i+1)+"Frame"+(j+1)+"Alpha") ));
				
				
			}
			playfield.addSprite(new AnimatedSprite (frames,
													(new Integer(0)).decode(																			
																			getParameter("sprite"+(i+1)+"XPos")).intValue(),
													(new Integer(0)).decode(
																			getParameter("sprite"+(i+1)+"YPos")).intValue(),
													playfield.width,
													playfield.height,
													(new Integer(0)).decode(
																			getParameter("sprite"+(i+1)+"XVel")).intValue(),
													(new Integer(0)).decode(
																			getParameter("sprite"+(i+1)+"YVel")).intValue()));
			
			
		}
		
		//System.out.println("numSprites: "+numSprites);
		//System.out.println("numFrames: "+numFrames);
													
		
		mag = this.createImage(100, 100);
		magnFrame = this.createImage(100, 154);
		
		//filteredPixels = Filters.addAlpha(filteredPixels, getImage(getDocumentBase(), getParameter("glassAlpha")));
		filteredPixels = Filters.grabPixels(mag);
		filteredPixels = Filters.addAlpha(filteredPixels, getImage(getDocumentBase(), getParameter("glassAlpha")));
		
		magnGlass = Filters.grabPixels(getImage(getDocumentBase(), getParameter("glassFrame")));
		magnGlass = Filters.addAlpha(magnGlass, getImage(getDocumentBase(), getParameter("glassFrameAlpha")));
		
		
	}

	/************************************************
	*
	*/
	public void start()
	{
		animator = new Thread(this);
		animator.start();
	}
	

	/************************************************
	*
	*/
	public void stop()
	{
		animator = null;
	}
	
	
	/************************************************
	* At each cycle of the animation cycle, animate one of the sprites
	* and recalculate/redraw the magnifying glass at its current position
	*/
	public void run()
	{
		while (true) {
			
			tick();
			
			updateGlass();
			
			try {
				animator.sleep(50);
			} catch (InterruptedException e) {
			}
		}
	}
	

	/************************************************
	* Recalculate the image seen through the magnifying glass
	* and paints it.
	*/
	public void updateGlass()
	{


		if ( (glassCoords.y >= -glassDim/4) & (glassCoords.y <= (playfield.height - glassDim)) & (glassCoords.x >= -glassDim) & (glassCoords.x <= 320) ) {
			filteredPixels = Filters.colorComposite(filteredPixels, 
													Filters.magnify(playfield.getBuffer(glassCoords.x + glassDim/4, glassCoords.y + glassDim/4, glassDim/2, glassDim/2), glassDim/2), 
													(glassColor == 1), (glassColor == 2), (glassColor == 3));
			filteredPixels = Filters.colorComposite(magnGlass, filteredPixels, false, false, false);
		}
		
	
		mag = this.createImage(new MemoryImageSource(glassDim, glassDim, filteredPixels, 0, glassDim));	
		
		myPaint();

	}
	
	
	/************************************************
	* Main paint method. This will only get called by unsolicited
	* repainting operations (those called by the operating system).
	* 
	* Method is synchronized to avoid multiple calls to it
	* resulting in exceptions (this should not happen unless
	* there is another application thread).
	*/
	public synchronized void paint(Graphics g)
    {		
		
		g.drawImage(offscreen, 0, 0, this);
		g.drawImage(mag, glassCoords.x, glassCoords.y, this);
	
    }
	

	/************************************************
	* To avoid flickering, this function should be empty.
	*/
	public void update (Graphics g)
	{
	}
	

	/************************************************
	* Does offscreen painting and then pastes image onto the screen.
	* One of the KEY POINTS of this method is that, besides doing offscreen painting,
	* it also uses CLIPPING for repaiting operations. This means that 
	* ONLY THE PARTS OF THE SCREEN THAT HAVE CHANGED get repainted,
	* which should save a bit of time.
	* 
	* One drawback here is the hack to paint to the screen
	* without using the paint method. This may cause synchronization
	* problems if the operation calls the paint method at the same time
	* this method is running.
	*/
	public void myPaint()
    {	
		offscreen = playfield.toImage(this);
				
		this.getGraphics().setClip(playfield.coords.x, playfield.coords.y, playfield.coords.width, playfield.coords.height);
		this.getGraphics().drawImage(offscreen, 0, 0, this);

		this.getGraphics().setClip(glassCoords.x, glassCoords.y, glassDim, glassDim);
		this.getGraphics().drawImage(mag, glassCoords.x, glassCoords.y, this);
    }
	
	
	/************************************************
	* This will update the playfield for one "clock tick".
	*/
	public void tick() {
		playfield.tick();
		playfield.render();
	}

	
	/************************************************
	* When mouse is dragged, magnifying glass should move and be updated.
	*/
	public void mouseDragged(MouseEvent e) {
		showStatus("Mouse: "+e.getPoint().x+", "+e.getPoint().y);
		glassCoords = new Point (e.getPoint().x - glassDim/2, e.getPoint().y - glassDim/2);
    }
	

	/************************************************
	* When a key is released, the color of the magnifying glass lens changes.
	*/	
	public void keyReleased(KeyEvent e) {
		glassColor++;
		glassColor &= 3;
		
    }
	
	
	/************************************************
	* Unused event handling methods.
	*/	
	
	public void mouseMoved(MouseEvent e) {
    }
	
	public void keyPressed(KeyEvent e) {
    }
	
	public void keyTyped(KeyEvent e) {
    }
	
	


}
