/*
 * @(#)BallCanvas.java	1.5 04/03/01
 *
 * Copyright (c) 1999-2004 Sun Microsystems, Inc. All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms
 */

package example.audiodemo;

import javax.microedition.lcdui.*;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.InputStream;

public class BallCanvas extends Canvas implements CommandListener {

    private BBall midlet;
    private Player player;

    private int[] notes = {68, 72, 70, 74, 76, 80, 56, 58};
    private int[] colors = {0xff, 0xff00, 0xff0000, 0xffff,
                            0xff00ff, 0xff8080, 0x80ff80, 0x8080ff};

    // a set of free roaming balls
    private SmallBall[] balls;
    private int numBalls;
    private int width, height;
    private boolean paused;

    private Command backCommand = new Command("Back", Command.BACK, 1);
    private Command pauseCommand = new Command("Pause", Command.BACK, 1);
    private Command playCommand = new Command("Play", Command.BACK, 1);

    private boolean playerCreated;
    private int currBackground = -1;

    public BallCanvas(BBall parentMidlet) {
	midlet = parentMidlet;

	width = getWidth();
	height = getHeight();

	balls = null;
	numBalls = 0;
	paused = true;

	this.addCommand(backCommand);
	this.addCommand(pauseCommand);
	setCommandListener(this);

    }

    public synchronized void init(int maxBalls, int bg) {
    	if (maxBalls < 1) {
	    maxBalls = 1;
	} else if (maxBalls > notes.length) {
	    maxBalls = notes.length;
	}
	if (balls == null
	    || player == null
	    || balls.length != maxBalls
	    || currBackground != bg) {

	    destroy();
	    // initialize the array of balls
	    balls = new SmallBall[maxBalls];

	    currBackground = bg;
	    playerCreated = initPlayer(bg);
	}

	numBalls = 0;
	pause();

	// Start with 2 balls
	makeNumberOfBalls(2);
    }

    boolean needAlert() {
	return (!playerCreated);
    }

    private static String guessContentType(String url) throws Exception {
	String ctype;
	// some simple test for the content type
	if (url.endsWith("wav")) {
	    ctype = "audio/x-wav";
	} else if (url.endsWith("jts")) {
	    ctype = "audio/x-tone-seq";
	} else if (url.endsWith("mid")) {
	    ctype = "audio/midi";
	} else {
	    throw new Exception("Cannot guess content type from URL: "+url);
	}
	return ctype;
    }

    private void createPlayer(String url) throws Exception {
	if (url.startsWith("resource")) {
	    int idx = url.indexOf(':');
	    String loc = url.substring(idx+1);
	    InputStream is = getClass().getResourceAsStream(loc);
	    String ctype = guessContentType(url);
	    player = Manager.createPlayer(is, ctype);
	} else {
	    player = Manager.createPlayer(url);
	}
    }


    private boolean initPlayer(int bg) {
	try {
	    switch (bg) {
	    case 1: // wave bg
	        createPlayer(midlet.wavbgUrl);
		break;
	    case 2: // tone seq bg
		{
		    byte d = 8;
		    byte C4 = ToneControl.C4;
		    byte D4 = ToneControl.C4 + 2; // a whole step
		    byte E4 = ToneControl.C4 + 4; // a major third
		    byte G4 = ToneControl.C4 + 7; // a fifth
		    byte rest = ToneControl.SILENCE; // eighth-note rest

		    byte[] mySequence = new byte[] {
			ToneControl.VERSION, 1,
			ToneControl.TEMPO, 30,
			ToneControl.BLOCK_START, 0,
			E4,d,D4,d,C4,d,D4,d,E4,d,E4,d,E4,d,rest,d,
			ToneControl.BLOCK_END,0,
			ToneControl.PLAY_BLOCK,0,
			D4,d,D4,d,D4,d,rest,d,E4,d,G4,d,G4,d,rest,d,//play "B" section
			ToneControl.PLAY_BLOCK,0, // content of "A" section
			D4,d,D4,d,E4,d,D4,d,C4,d,rest,d// play "C" section
		    };
		    player = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
		    player.realize();
		    ToneControl c = (ToneControl)player.getControl("ToneControl");
		    c.setSequence(mySequence);
		}
		break;
	    case 3: // MIDI bg
	        createPlayer(midlet.midbgUrl);
	        break;
	    default:
		player = null;
	    }

	    if (player != null) {
		player.setLoopCount(-1);
		player.start();
	    }
	} catch (Exception ex) {
	    ex.printStackTrace();
	    if (player != null)
		player.close();
	    player = null;
	    return false;
	}

	return true;
    }

    /**
     * Draws the drawing frame (which also contains the ball) and the
     * controls.
     */
    protected void paint(Graphics g) {
	// Draw the frame
	g.setColor(0xffffff);
	g.fillRect(0,0, width-1, height-1);
	g.setColor(0);
	g.drawRect(0+1,0+1,width-2,height-2);

	// Draw each ball
	for(int i = 0; i < numBalls; i++) {
	    if (balls[i] != null) {
		balls[i].paint(g);
	    }
	}
    }

    private void makeNumberOfBalls(int newNum) {
	if (balls != null) {
	    if (newNum > balls.length) {
		newNum = balls.length;
	    } else if (newNum < 1) {
		newNum = 1;
	    }
	    if (newNum != numBalls) {
		// temporarily disable painting
		numBalls = 0;
		// first create newNum balls, if necessary
		for(int i = 0; i < newNum; i++) {
		    if (balls[i] == null) {
			balls[i] = new SmallBall(this, 0, 0, width,height-12);
			balls[i].setNote(notes[i]);
			balls[i].setColor(colors[i]);
		    }
		    if (!paused && balls[i].stop) {
			balls[i].stop = false;
			(new Thread(balls[i])).start();
		    }

		}
		// then destroy any other balls
		for(int i = newNum; i < balls.length; i++) {
		    if (balls[i] != null) {
			// stop the thread and remove the reference to it
			balls[i].stop = true;
			balls[i] = null;
		    }
		}
		// enable painting
		numBalls = newNum;
		if (newNum > 0) {
		    balls[0].doRepaint = true;
		}
	    }
	}
    }


    /**
     * Destroy
     */
    synchronized void destroy() {
	// kill all the balls and terminate
	numBalls = 0;
	pause();
	balls = null;
	if (player != null) {
	    player.close();
	    player = null;
	}
    }


    /*
     * Return whether the canvas is paused or not.
     */
    boolean isPaused() {
	return paused;
    }

    /**
     * Pause the balls by signaling each of them to stop.
     * The ball object still exists and holds the current position
     * of the ball.  It may be restarted later.
     * the current thread will be terminated.
     */
    void pause() {
	if (!paused) {
	    synchronized(this) {
		paused = true;
		for (int i = 0; i < balls.length; i++) {
		    if (balls[i] != null) {
			balls[i].stop = true;
		    }
		}

		try {
		    if (player != null)
			player.stop();
		} catch (MediaException e) {
		    // There's nothing much we can do here.
		}
	    }
	    repaint();
	}
    }

    /*
     * Start creates a new thread for each ball and start it.
     */
    void start() {
	if (paused) {
	    synchronized(this) {
		paused = false;
		if (balls != null) {
		    for (int i = 0; i < balls.length; i++) {
		    	if (balls[i] != null) {
			    balls[i].stop = false;
			    (new Thread(balls[i])).start();
			}
		    }
		}

		if (player != null) {
		    try {
			player.start();
		    } catch (Exception ex) {}
		}
	    }
	    repaint();
	}
    }

    public void commandAction(Command c, Displayable s) {
	if (c == backCommand) {
	    destroy();
	    midlet.displayList();
	} else if (c == pauseCommand) {
	    pause();
	    removeCommand(pauseCommand);
	    addCommand(playCommand);
	} else if (c == playCommand) {
	    removeCommand(playCommand);
	    addCommand(pauseCommand);
	    start();
	}
    }

    /**
     * Handle a pen down event.
     */
    public void keyPressed( int keyCode) {

	int action = getGameAction(keyCode);

	switch (action) {
	case LEFT:
	    // Reduce the number of threads
	    makeNumberOfBalls(numBalls - 1);
	    break;

	case RIGHT:
	    // Increase the number of threads
	    makeNumberOfBalls(numBalls + 1);
	    break;

	case UP:
	    // Make them move faster
	    SmallBall.faster();
	    break;

	case DOWN:
	    // Make them move slower
	    SmallBall.slower();
	    break;
	}
	repaint();
    }

}
