/* $Id$
 *
 * Copyright (c) 1999 Xenonsoft Limited. All Rights Reserved.
 *
 * This software is protected by copyright. You are hereby notified from
 * now by reading this message. This software is also the confidential 
 * and proprietary information of Xenonsoft Limited.  ("Confidential
 * Information"). 
 *
 * This software is distributed under the Xenonsoft Public end user 
 * License ("XPeL"), where the machine-readable source code is provided 
 * under the "Open Source" model.
 * For more information, please read the file "LICENSE-XPL.txt" 
 */

// Filename:`Landscape.java'
//
// Proof of concept.
// Humanoid
//

package xenon.humanoid;

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

import xenon.gamekit.*;				// for GameEngine, WorldObject, etc
import xenon.soundkit.*;			// for SoundEngine, PPMCAudioPlayer, etc

public class Landscape extends Canvas 
implements Runnable, KeyListener 
{
    public static int WORLD_WIDTH = 2400;
    public static int WORLD_HEIGHT = 280;
    public static int MAXIMUM_VELOCITY = 16;	// per Frame    
    public static int STARSHIP_LEFT_DIRECTION=0;
    public static int STARSHIP_RIGHT_DIRECTION=1;
    
    public static Point[] peaks = {
	new Point( 0, 20 ),
	new Point( 20, 40 ),
	new Point( 40, 20 ),
	new Point( 60, 20 ),
	new Point( 140,40 ),
	new Point( 180,20 ),
	new Point( 220, 20 ),
	new Point( 260, 60 ),
	new Point( 280, 42 ),
	new Point( 340, 65 ),
	new Point( 400, 20 ),
	new Point( 460, 22 ),
	new Point( 500, 73 ),
	new Point( 530, 61 ),
	new Point( 580, 20 ),
	new Point( 600, 20 ),
	new Point( 640, 30 ),
	new Point( 680, 60 ),
	new Point( 690, 80 ),
	new Point( 710, 80 ),
	new Point( 720, 60 ),
	new Point( 745, 40 ),
	new Point( 800, 20 ),
	new Point( 840, 20 ),
	new Point( 880, 51 ),
	new Point( 920, 24 ),
	new Point( 940, 20 ),
	new Point( 970, 53 ),
	new Point( 1000, 23 ),
	new Point( 1040, 20 ),
	new Point( 1080, 38 ),
	new Point( 1120, 51 ),
	new Point( 1160, 43 ),
	new Point( 1200, 20 ),
	// **** MID WORLD ****
	new Point( 1200, 20 ),
	new Point( 1300, 10 ),
	new Point( 1360, 11 ),
	new Point( 1400, 32 ),
	new Point( 1440, 10 ),
	new Point( 1500, 10 ),
	new Point( 1520, 33 ),
	new Point( 1560, 21 ),
	new Point( 1580, 10 ),
	new Point( 1620, 11 ),
	new Point( 1720, 33 ),
	new Point( 1800, 11 ),
	new Point( 1860, 42 ),
	new Point( 1900, 21 ),
	new Point( 1980, 45 ),
	new Point( 2030, 10 ),
	new Point( 2140, 21 ),
	new Point( 2200, 39 ),
	new Point( 2240, 41 ),
	new Point( 2280, 20 ),
	new Point( 2340, 20 ),
	new Point( 2360, 40 ),
	new Point( 2380, 20 ),
	new Point( 2400, 20 ),
    };
    
    int  	world_width, world_height;
    int  	screen_width, screen_height;
    int  	wx, wy;				// world position
    int  	sx, sy;				// ship position
    int  	sx_vel, sy_vel;			// velocity
    int  	sx_acc, sy_acc;			// acceleration;
    
    int		direction;
    
    boolean	up_key_pressed;			// Pressed status of up key
    boolean     down_key_pressed;		// Pressed status of down key;
    boolean	thrust_key_pressed;		// pressed status of thrust key;
    boolean	fire_key_pressed;		// pressed status of fire key;
    boolean	cod_key_pressed;		// pressed status of change
						// of direction key;
    boolean	hyperspace_key_pressed;		// pressed status of hyperspace key;
    
    Image	starship_left_image, starship_right_image;
    
    Image	offScreenImage;
    Graphics	offScreenGC;

    boolean	needDoubleBufferReshape=true;
    
    public Landscape( int screen_width, int screen_height )
    {
	// Screen Dimensions
	this.screen_width = screen_width;
	this.screen_height = screen_height;

	// World Map Dimension
	world_width = WORLD_WIDTH;
	world_height = screen_height;
	
	wx=0; wy=0;
	sx=48; sy=screen_height/2;
	sx_vel=0; sy_vel=0;
	sx_acc=0; sy_acc=0;
	direction = STARSHIP_RIGHT_DIRECTION;

	setSize( screen_width, screen_height );

	addKeyListener( this );
	
	setBackground( Color.black );

	// Get the images
	loadImages();
	
	// The animation by firing up the animation thread.
	new Thread(this).start();
    }
    
    public void loadImages()
    {
	starship_left_image = 
	    Toolkit.getDefaultToolkit().getImage( 
		getClass().getResource( "images/starship-left.gif" ) );
	starship_right_image = 
	    Toolkit.getDefaultToolkit().getImage( 
		getClass().getResource( "images/starship-right.gif" ) );

	// use a media tracker to load the image.
	int track_id = 0;
	MediaTracker mt = new MediaTracker(this);
	
	mt.addImage( starship_left_image, track_id );
	mt.addImage( starship_right_image, track_id );

	try { mt.waitForAll(); }
	catch (InterruptedException e ) { /*error*/ }

	// From the Swing `ImageIcon.java' source code remove image
	// after loading it.
	mt.removeImage( starship_left_image, track_id );
	mt.removeImage( starship_right_image, track_id );

    }
    
    
    public synchronized void paint( Graphics g )
    {
	int sw = getSize().width;
	int sh = getSize().height;

	// Paint the canvas in the default background color.
	g.setColor( getBackground() );
	g.fillRect( 0, 0, sw, sh );
	
	// g.setColor( Color.magenta );
	// Acornsoft's Planetoid `Red' is the chosen
	g.setColor( Color.red );

	// FIXME: Could do with a optimisation draw here.
	for (int k=0; k<peaks.length-1 ; ++k )
	{
	    int displacement = wx - ( WORLD_WIDTH - screen_width );
	    
	    int x1 = peaks[k].x - wx;
	    int y1 = screen_height - peaks[k].y;
	    int x2 = peaks[k+1].x - wx;
	    int y2 = screen_height - peaks[k+1].y;

	    // Detect where the world has wrapped, and translate
	    // those x coordinates back to the screen to complete
	    // the illusion of scrolling wraparound landscape.
	    if ( wx > WORLD_WIDTH - screen_width &&
		 peaks[k].x < screen_width )
	    {
		// Remap "Humanoid!" here we come
		x1 += WORLD_WIDTH;
		x2 += WORLD_WIDTH;
	    }
	    g.drawLine( x1, y1, x2, y2 );
	}

	int xpos = sx - wx;
	if ( sx < wx ) 
	    xpos = (sx + WORLD_WIDTH - wx ) % WORLD_WIDTH;
	    
	if ( (xpos) < 0 || sx > WORLD_WIDTH ) {
	    System.out.print( "ERROR ");
	    System.out.print( "sx:" + sx + ",sy:" + sy + ")  " );
	    System.out.print( "wx:" + wx + ",wy:" + sy + ")  " );
	    System.out.println( "xpos:" + xpos );
	}
	
	// paint Starship
	if (direction == STARSHIP_RIGHT_DIRECTION )
	    g.drawImage( starship_right_image, xpos, screen_height - sy, this );
	else
	    g.drawImage( starship_left_image, xpos, screen_height - sy, this );

	// Draw aliens here
    }
    
    public void update( Graphics g )
    {
	// Override the update for our `double buffering' technique
	if (offScreenImage == null || needDoubleBufferReshape == true ) {
	    // create off screen image & graphics context
	    // Use a critical section to mutually exclude
	    // the creation of the double buffer
	    synchronized (this) {
		offScreenImage = createImage( getSize().width, getSize().height );
		offScreenGC = offScreenImage.getGraphics();
		needDoubleBufferReshape = false;	/* ! */
	    }
	}
	
	paint( offScreenGC );
	g.drawImage( offScreenImage, 0, 0, this );
    }


    public void run()
    {
	// Animation thread
	int		counter=0;
	int		cod_counter=0;		// change of direction counter
	int		displacement=64;	
	boolean		disable_thrust=false;

	while (true) {
	    // Counter
	    ++counter;
	    if ( cod_counter > 0) --cod_counter;

	    if (counter % 4 == 0 ) {
		if ( sx_vel > 0 ) 
		    --sx_vel;
		else if ( sx_vel < 0 )
		    ++sx_vel;
	    }
	    
	    if ( direction == STARSHIP_RIGHT_DIRECTION &&
		 displacement > 64 )
		displacement -= MAXIMUM_VELOCITY;
	    else if ( direction == STARSHIP_LEFT_DIRECTION &&
		      displacement < screen_width - 96 )
		displacement += MAXIMUM_VELOCITY;

	    // Scroll to the right
	    // wx = ( wx + WORLD_WIDTH + 4 ) % WORLD_WIDTH;

	    // Redraw the landscape
	    if ( down_key_pressed  && sy > 4 )
		sy = sy - 4;
	    else if ( up_key_pressed && sy < screen_height-2 )
		sy = sy + 4;

	    if ( cod_key_pressed && cod_counter == 0 ) {
		cod_counter = 10;
		if ( direction == STARSHIP_RIGHT_DIRECTION ) {
		    // FIXME: could set maximum velocity here
		    // sx_vel = -2;
		    sx_vel -= 6;		    
		    direction = STARSHIP_LEFT_DIRECTION;
		}
		else {
		    // sx_vel = 2;
		    sx_vel += 6;		    
		    direction = STARSHIP_RIGHT_DIRECTION;
		}
	    }

	    if ( thrust_key_pressed ) {
		if ( direction == STARSHIP_RIGHT_DIRECTION ) {
		    // FIXME: could set maximum velocity here
		    if ( sx_vel < MAXIMUM_VELOCITY ) {
			sx_acc = 2 ;
			sx_vel += 2;
		    }
		}
		else {
		    if ( sx_vel > -MAXIMUM_VELOCITY ) {
			sx_acc = -2;
			sx_vel -= 2;
		    }
		}
	    }
	    

	    sx = ( sx + sx_vel + WORLD_WIDTH ) % WORLD_WIDTH;
	    wx = ( sx - displacement + WORLD_WIDTH ) % WORLD_WIDTH;
	    

	    // repaint();
	    repaint( 1024/24, 0, 0, screen_width, screen_height );

	    try {
		// Sleep for a bit.
		Thread.sleep( 1000/24 );
	    }
	    catch ( InterruptedException e ){ ; /* die? */  }

	}
    }
    
    // Handle Event Driven Input
    public synchronized void keyTyped( KeyEvent ke )
    {
    }

    public void report( String msg, KeyEvent ke )
    {
	int keyCode  = ke.getKeyCode();
	char keyChar = ke.getKeyChar();
	String mods  = ke.getKeyModifiersText( keyCode );
	String txt   = ke.getKeyText( keyCode );

	System.out.println( msg );
	if ( keyCode != -1 /*KeyEvent.KEY_UNDEFINED*/ )
	    System.out.println( "code:  "+keyCode );
	if ( keyCode != KeyEvent.CHAR_UNDEFINED )
	    System.out.println( "char:  "+keyChar );
	System.out.println( "Modif: "+mods );
	System.out.println( "Text:  "+txt );
	if ( ke.isActionKey() ) System.out.println("ACTION");
	System.out.println("----------");
    }
    
    public synchronized void keyPressed( KeyEvent ke )
    {
	report("KEY_PRESSED",ke);
	
	int keycode = ke.getKeyCode();
	int mods = ke.getModifiers();
	char keychar = ke.getKeyChar();
	
	// JDK 1.1.7 KeyEvent.getModifiers() and generally modifiered
	// are fucked:
	// ----------
	// 
	// Holding down the SHIFT key and press the Z key only once, 
	// immediately releasing it produces:
	// ----------
	// KEY_PRESSED
	// code:  90
	// char:  Z
	// Modif: Ctrl+Alt
	// Text:  Z
	// ----------
	// KEY_RELEASED
	// code:  90
	// char:  Z
	// Modif: Ctrl+Alt
	// Text:  Z
	// 
	// Where as Holding down the SHIFT key and pressing the A key only once, 
	// immediately releasing it produces:
	// ----------
	// KEY_PRESSED
	// code:  65
	// char:  A
	// Modif: Shift
	// Text:  A
	// ----------
	// KEY_RELEASED
	// code:  65
	// char:  A
	// Modif: Shift
	// Text:  A
	// 
	// Look closely at modifier text and you see why the theoretical scheme
	// does not wrong. This is problem a bug the JDK 1.1.7.
	//

	// Here is a work around
	if ( keychar == 'a' ) {
	    up_key_pressed = true;
	    thrust_key_pressed = false;
	}
	else if ( keychar == 'z' ) {
	    down_key_pressed = true;
	    thrust_key_pressed = false;
	}
	else if ( keychar == 'A' ) {
	    up_key_pressed = true;
	    thrust_key_pressed = true;
	}
	else if ( keychar == 'Z' ) {
	    down_key_pressed = true;
	    thrust_key_pressed = true;
	}

	/*
	if ( keycode == KeyEvent.VK_A )
	    up_key_pressed = true;
	else if ( keycode == KeyEvent.VK_Z )
	    down_key_pressed = true;
	else 
	*/

	if ( keycode == KeyEvent.VK_SHIFT )
	    thrust_key_pressed = true;
	else if ( keycode == KeyEvent.VK_ENTER )
	    fire_key_pressed = true;
	else if ( keycode == KeyEvent.VK_SPACE )
	    cod_key_pressed = true;
	else if ( keycode == KeyEvent.VK_H )
	    hyperspace_key_pressed = true;
		
	// Special case of shift key
	if ( (mods & InputEvent.SHIFT_MASK ) != 0 )
	    thrust_key_pressed = true;
    }
    
    public synchronized void keyReleased( KeyEvent ke )
    {
	report("KEY_RELEASED",ke);

	int keycode = ke.getKeyCode();
	int mods = ke.getModifiers();
	char keychar = ke.getKeyChar();
	
	if ( keychar == 'a' ) {
	    up_key_pressed = false;
	    thrust_key_pressed = false;
	}
	else if ( keychar == 'z' ) {
	    down_key_pressed = false;
	    thrust_key_pressed = false;
	}
	else if ( keychar == 'A' ) {
	    up_key_pressed = false;
	    thrust_key_pressed = true;
	}
	else if ( keychar == 'Z' ) {
	    down_key_pressed = false;
	    thrust_key_pressed = true;
	}
	   
	/*
	if ( keycode == KeyEvent.VK_A )
	    up_key_pressed = false;
	else if ( keycode == KeyEvent.VK_Z )
	    down_key_pressed = false;
	else 
	*/
	if ( keycode == KeyEvent.VK_SHIFT )
	    thrust_key_pressed = false;
	else if ( keycode == KeyEvent.VK_ENTER )
	    fire_key_pressed = false;
	else if ( keycode == KeyEvent.VK_SPACE )
	    cod_key_pressed = false;
	else if ( keycode == KeyEvent.VK_H )
	    hyperspace_key_pressed = false;
	
	if ( keycode != KeyEvent.VK_DIVIDE )
	    thrust_key_pressed = false;
	// Special case of shift key
	///if ( (mods & InputEvent.SHIFT_MASK ) != 0 )
	///    thrust_key_pressed = false;
    }
    
    public synchronized boolean imageUpdate(Image img, int infoflags, int x,
					    int y, int width, int height) {
	if( (infoflags & (ImageObserver.ALLBITS | ImageObserver.FRAMEBITS)) != 0 )
	    // image is ready
	    repaint();
	return true;
    }
    
    public static void main( String[] args )
    {
	Frame frame = new Frame();
	
	Landscape landscape = new Landscape( 600, 320 );
	
	frame.add( landscape );
	
	frame.pack();
	frame.show();
    }
}

// fini
