import java.awt.*;
import java.awt.event.*;


public abstract class AnimatedSprite extends Sprite
{
  /*
   * Constants
   */
  private static final long DEFAULT_COUNTER   = 120;
  private static final int  MAX_COUNTERS      = 3;
  private static final int  MAX_SPRITE_FRAMES = 20;
  private static final int  MAX_TRACKS        = 20;
    
  /*
   * Member variables 
   */ 
  private long m_counter;
  private long m_counterInterval;
  private int  m_currentFrame;
  private int  m_currentTrack;
  private int  m_frameHeight;
  private int  m_frameWidth;
  private int  m_numTracks;
  private int  m_tracks[][];

  /*
   * Class constructor.  Takes an image strip, and divides it
   * up into numFrames.
   */
  public AnimatedSprite(Image image, int numFrames)
  {
    super(image);
    m_counter         = System.currentTimeMillis() + DEFAULT_COUNTER;
    m_counterInterval = DEFAULT_COUNTER;
    m_currentFrame    = 0;
    m_currentTrack    = 0;
    m_frameHeight     = getHeight();
    m_frameWidth      = getWidth() / numFrames;
    m_numTracks       = 0;
    m_tracks          = new int[MAX_TRACKS][];
  }

  /*
   * Returns the current frame of the current track, and
   * increments the frame counter to point to the next frame
   * of the current track
   */
  public int advanceFrame()
  {
    //
    // Only increment the frame counter if enough time
    // has passed since the last increment
    //
    if (System.currentTimeMillis() > m_counter)
      {     
	int length;

	// Get the length of the current track
	length = m_tracks[m_currentTrack].length;
	
	// Update current frame
	m_currentFrame++;
	m_currentFrame %= length;

	// Update counter
	m_counter += m_counterInterval;

	return m_tracks[m_currentTrack][m_currentFrame];
      }
    else
      {
	return m_tracks[m_currentTrack][m_currentFrame];
      }
  }

  /* 
   * Adds the specified track to this AnimatedSprite's list 
   * of tracks.  If the Sprite already has too many tracks,
   * returns false, and the track is not added.  Otherwise 
   * returns true.
   */
  public boolean addTrack(int[] track)
  {
    boolean result = false;

    if (m_numTracks < MAX_TRACKS)
      {
	m_tracks[m_numTracks] = new int[track.length];
	System.arraycopy(track, 0, m_tracks[m_numTracks++], 0, track.length);
	result = true;
      }

    return result;
  }


  /*
   * Draws the next frame of this AnimatedSprite to the
   * specified Raster object
   */
  public void draw(Raster raster)
  {
    int   clippedHeight;
    int   clippedWidth;
    int   clippedX;
    int   clippedY;
    int   frame;
    int   height;
    int   pixel;
    int[] pixels;
    int   rasterHeight;
    int[] rasterPixels;
    int   rasterWidth;
    int   thisHeight;
    int   thisWidth;
    int   width;
    int   x, y;
    int   pf_x, pf_y;
    
    thisHeight = getHeight();
    thisWidth = getWidth();

    clippedHeight = m_frameHeight;
    clippedWidth  = m_frameWidth;
    clippedX      = 0;
    clippedY      = 0;
    height        = m_frameHeight;
    pixels        = getPixels();
    rasterHeight  = raster.getHeight();
    rasterPixels  = raster.getPixels();
    rasterWidth   = raster.getWidth();
    width         = m_frameWidth;

    if (hasFocus())
      {
	x = getX();
	y = getY();
      }
    else
      { 
	pf_x = getPlayfield().getX();
	pf_y = getPlayfield().getY();
	x    = getX() - pf_x;
	y    = getY() - pf_y;
      }
    
    // If the object is not on the screen, dont draw it!
    if ((x >= rasterWidth) ||
	((x + width) <= 0) ||
	(y >= rasterHeight) ||
	((y + height) <= 0))
      {
	return;
      }
    else
      {
	//
	// Horizontal clipping:
	//

	//
	// Left edge is off of the screen
	if (x < 0)
	  {
	    clippedX     = -x;
	    clippedWidth = width + x;
	  }
	//
	// Right edge is off of the screen
	else if ((x + width) >= rasterWidth)
	  {
	    clippedWidth = rasterWidth - x;
	  }
	
	//
	// Vertical clipping:
	//
	
	//
	// Top edge is off of the screen
	if (y < 0)
	  {
	    clippedY      = -y;
	    clippedHeight = height + y;
	  }
	else if ((y + height) >= rasterHeight)
	  {
	    clippedHeight = rasterHeight - y;
	  }

	//
	// Get frame to display:
	frame = advanceFrame();

	//
	// Calculate constants to minimize the amount of 
	// computation done copying pixels from the Sprite,
	// to the Raster
	//
	int frameOffset;
	int x_end = clippedX + clippedWidth;
	int y_end = clippedY + clippedHeight;
	int y_read, y_write;

	frameOffset = m_frameWidth * frame;
	y_read = clippedY * thisWidth;
	y_write = (clippedY + y) * rasterWidth;

	for (int y_offset = clippedY; y_offset < y_end; y_offset++)
	  { 

	    for (int x_offset = clippedX; x_offset < x_end; x_offset++)
	      {
		pixel = pixels[y_read + (x_offset + frameOffset)];
	        if (!Ps1Defs.IsPixelTransparent(pixel))
		  {
		    rasterPixels[y_write + (x + x_offset)] = pixel;
		  }
	      }
	    y_read += thisWidth;
	    y_write += rasterWidth;
	  }
	
      }	
  }

  /*
   * Returns the height of a frame in this AnimatedSprite's
   * image strip
   */
  public final int getFrameHeight()
  {
    return m_frameHeight;
  }

  /*
   * Returns the width of a frame in this AnimatedSprite's
   * image strip
   */
  public final int getFrameWidth()
  {
    return m_frameWidth;
  }


  /*
   * Returns true if the specified coordinates fall upon
   * a non-transparent pixel in this AnimatedSprite.
   * If this AnimatedSprite does NOT have focus, the supplied 
   * x-y coordinates are considered to be relative
   * to the current position of the playfield.  If the 
   * sprite DOES have focus, the coordinates are considered
   * to be the location of the sprite on the screen.
   */
  public boolean hitTest(int x, int y)
  {
    int frameOffset;
    int thisX, thisY;
    int pf_x, pf_y;
    int pixel;

    if (hasFocus())
      {
	thisX = getX();
	thisY = getY();
      }
    else
      {
	pf_x = getPlayfield().getX();
	pf_y = getPlayfield().getY();
	thisX = getX() - pf_x;
	thisY = getY() - pf_y;
      }

    if ((x >= thisX) && 
	(x < (thisX + getWidth())) &&
	(y >= thisY) &&
	(y < (thisY + getHeight())))
      {
	x -= thisX;
	y -= thisY;
	frameOffset = m_tracks[m_currentTrack][m_currentFrame] * m_frameWidth;

	return (!Ps1Defs.IsPixelTransparent(getPixel(x + frameOffset, y)));
      }
    return false;
  }
	

  /*
   * Sets the number of milliseconds this AnimatedSprite will wait
   * before incrementing the frame counter to the next frame.
   */
  public boolean setCounterInterval(int millis)
  {
    m_counterInterval = millis;
    return true;
  }


  /*
   * Sets the current track of this AnimatedSprite.
   * Returns true if successful, false otherwise.
   */
  public boolean setTrack(int track)
  {
    boolean result = false;

    if ((track >= 0) &&
	(track < MAX_TRACKS))
    {
      m_currentTrack = track;
      m_currentFrame = 0;
      result = true;
    }

    return result;
  }    
}

		     



