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

/*
 * WormPit.java
 *
 * Created on March 30, 2001, 16:15
 * @version 1.5
 */

package example.wormgame;

import java.lang.System;
import java.lang.Runnable;
import java.lang.InterruptedException;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;

import javax.microedition.media.Player;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.control.ToneControl;

/**
 * The WormPit contains a Worm and some WormFood. The Worm will
 * slither around the pit in search of WormFood. If the Worm eats
 * it will grow in length. The Worm will be killed if it attempts
 * to slither past the edge of the pit or if it eats itself.
 */
public class WormPit extends Canvas implements Runnable {

    /** Handle to target food. */
    private WormFood myFood;
    /** Handle to current worm object. */
    private Worm     myWorm;
    /** Flag to indicate when the game is finished. */
    private boolean gameOver = false;
    /** Flag to indicate if current game is paused. */
    private boolean gamePaused = false;
    /** Flag to indicate game is restarted. */
    private boolean gameRestart = false;
    /** Flag to indicate forced repaint is needed. */
    private boolean forceRedraw = true;
    /** Flag to indicate the game is to be completely destroyed. */
    private boolean gameDestroyed = false;
    /** Current game score. */
    private int score = 0;
    /** Current game level. */
    private int level = 5;
    /** Number of pieces of food eaten since the last level change. */
    private int foodEaten = 0;
    /** Current screen width in pixels. */
    private int width;
    /** Current screen height in pixels. */
    private int height;
    /** Audio player for background tune. */
    private Player audioPlayer;
    /** Tone player. */
    private Player tonePlayer;
    /** Current screen size in cells. */
    static int CellWidth;
    /** Current screen height in cells. */
    static int CellHeight;
    /** Initial offset for screen rendering. */
    private static final int START_POS = 3;
    /** Font height for rendering the score message. */
    private static final int SCORE_CHAR_HEIGHT;
    /** Font width for rendering the score message. */
    private static final int SCORE_CHAR_WIDTH;
    /** Height for two lines of score message text. */
    private static final int SCORE_HEIGHT;
    /** Default delay between worm repaints. (400 milliseconds) */
    private static final int DEFAULT_WAIT = 400;
    /** Maximum number of difficulty levels. (10) */
    static final byte MAX_LEVELS = 10;
    /** Color for target food object. (0x00ff00) */
    static final int FOOD_COLOUR  = 0x0000ff00;
    /** Color for text objects. (0xff0000) */
    static final int TEXT_COLOUR  = 0x00ff0000;
    /** Color for erasing worm cells. (0xffffff) */
    static final int ERASE_COLOUR = 0x00ffffff;
    /** Color for drawing worm cells. (0x000000) */
    static final int DRAW_COLOUR  = 0x00000000;
    /** Size of a cell for worm rendering. */
    public  static final int CELL_SIZE = 5;

    static {
    Font defaultFont  = Font.getDefaultFont();
    SCORE_CHAR_WIDTH  = defaultFont.charWidth('S');
    SCORE_CHAR_HEIGHT = defaultFont.getHeight();
    SCORE_HEIGHT      = SCORE_CHAR_HEIGHT * 2;
    }

    /**
     * Default constructor for worm pit.
     * Initialized the food and worm objects.
     */
    public WormPit() {
    width  = round(getWidth());
    height = round(getHeight()-SCORE_HEIGHT);
    WormPit.CellWidth  = (width-(START_POS*2)) / WormPit.CELL_SIZE;
    WormPit.CellHeight = (height-(START_POS*2)) / WormPit.CELL_SIZE;
    myWorm = new Worm(this);

    /* Generate food for worm to eat */
    myFood  = new WormFood(this);
    int x = myFood.getX();
    int y = myFood.getY();
    while (myWorm.contains(x, y)) {
        myFood.regenerate();        // regenerate if food placed under worm
        x = myFood.getX();  y = myFood.getY();
    }
    }

    /**
     * Round the given value to next lowest number divisible by the
     * cell size.
     * @param val number to be rounded down
     * @return rounded down value
     */
    private int round(int val) {
    int delta = (val-(START_POS*2)) % CELL_SIZE;
    return (val - delta);
    }

    /**
     * Set the difficulty level. If the new level is different from the
     * current level, any game currently in progress is terminated and
     * the score is lost.
     * @param level value of new level requested
     * @see #getLevel
     */
    public void setLevel(int level) {
    if (this.level != level) {
        this.level = level;
        gameOver = true;
        gamePaused = false;
        foodEaten = 0;
        score = 0;
    }
    }

    /**
     * Get the difficulty level.
     * @return current game level
     * @see #setLevel
     */
    public int getLevel() {
    return level;
    }

    /**
     * Get the score of this game.
     * @return current score
     */
    public int getScore() {
    return score;
    }

    /**
     * Returns true if the given point is within the bounds of the
     * worm pit.
     * @param x x coordinate of point to check (0 - CellWidth)
     * @param y y coordinate of point to check (0 - CellHeight)
     * @return true, if coordinate is in the worm pit
     */
    static boolean isInBounds(int x, int y) {
    if ((x < 0) || (x >= WormPit.CellWidth)) {
        return false;
    }
    if ((y < 0) || (y >= WormPit.CellHeight)) {
        return false;
    }
    return true;
    }

    /**
     * Restart the game.
     */
    void restart() {
        if (gamePaused) {
        gamePaused = false;
        } else {
        myWorm.regenerate();
        myFood.regenerate();
                int x = myFood.getX();
                int y = myFood.getY();

                while (myWorm.contains(x, y)) {
                    myFood.regenerate(); // regenerate if food placed under worm
                    x = myFood.getX();
                    y = myFood.getY();
                }
        foodEaten = 0;
        score = 0;
        gameOver = false;

        if ((audioPlayer != null) &&
            (audioPlayer.getState() == Player.PREFETCHED)) {
            try {
            audioPlayer.setMediaTime(0);
            audioPlayer.start();
            } catch (MediaException me) { }
        }
        }
        forceRedraw = true;

        synchronized (myWorm) {
        myWorm.notifyAll();
        }
    }

    /**
     * Handle keyboard input. This is used for worm movement.
     * @param keyCode pressed key is either Canvas arrow key (UP,
     *  DOWN, LEFT, RIGHT) or simulated with KEY_NUM (2, 8, 4,6).
     */
    public void keyPressed(int keyCode) {
    switch (getGameAction(keyCode)) {
    case Canvas.UP:
        myWorm.setDirection(Worm.UP);
        break;
    case Canvas.DOWN:
        myWorm.setDirection(Worm.DOWN);
        break;
    case Canvas.LEFT:
        myWorm.setDirection(Worm.LEFT);
        break;
    case Canvas.RIGHT:
        myWorm.setDirection(Worm.RIGHT);
        break;
    case 0:
        // There is no game action.. Use keypad constants instead
        switch (keyCode) {
        case Canvas.KEY_NUM2:
        myWorm.setDirection(Worm.UP);
        break;
        case Canvas.KEY_NUM8:
        myWorm.setDirection(Worm.DOWN);
        break;
        case Canvas.KEY_NUM4:
        myWorm.setDirection(Worm.LEFT);
        break;
        case Canvas.KEY_NUM6:
        myWorm.setDirection(Worm.RIGHT);
        break;
        }
        break;
    }
    }

    /**
     * Paint anything currently in the pit.
     * Overrides Canvas.paint.
     * @param g graphics object to be rendered
     */
    private void paintPitContents(Graphics g) {
    try {
        myWorm.update(g);    // update worm position
        if (myFood.isAt(myWorm.getX(), myWorm.getY())) {
        myWorm.eat();
        score += level;

        foodEaten++;

        if (foodEaten > (level << 1)) {
            /* Increase difficulty level */
            forceRedraw = true;
            foodEaten = 0;
            level++;

                    /*
                     * End of game - no more levels.
                     */
                    if (level == MAX_LEVELS) {
                        level--;
                        gameOver = true;
                    }

            if (tonePlayer != null) {
            try {
                tonePlayer.setMediaTime(0);
                tonePlayer.start();
            } catch (MediaException me) { }
            }
        } else {
            if (audioPlayer != null) {
            try {
                Manager.playTone(69, 50, 100);   // Play audio
            } catch (MediaException me) { }
            }
        }

        g.setColor(WormPit.ERASE_COLOUR);
        g.fillRect((width - (SCORE_CHAR_WIDTH * 3))-START_POS,
               height-START_POS,
               (SCORE_CHAR_WIDTH * 3),
               SCORE_CHAR_HEIGHT);
        g.setColor(WormPit.DRAW_COLOUR);

        // Display new score
        g.drawString("" + score,
                 width - (SCORE_CHAR_WIDTH * 3) - START_POS,
                 height - START_POS, g.TOP|g.LEFT);

        myFood.regenerate();
        int x = myFood.getX();
        int y = myFood.getY();
        while (myWorm.contains(x, y)) {
        // generate again if food placed under worm..
            myFood.regenerate();
            x = myFood.getX();  y = myFood.getY();
        }
        }

        myFood.paint(g);
    } catch (WormException se) {
        gameOver = true;
    }
    }

    /**
     * Paint the worm pit and its components
     * @param g graphics object to be rendered
     */
    public void paint(Graphics g) {
    if (forceRedraw) {
        // Redraw the entire screen
        forceRedraw = false;

        // Clear background
        g.setColor(WormPit.ERASE_COLOUR);
        g.fillRect(0, 0, getWidth(),
              getHeight());

        // Draw pit border
        g.setColor(WormPit.DRAW_COLOUR);
        g.drawRect(1, 1, (width - START_POS), (height - START_POS));

        // Display current level
        g.drawString("L: " + level, START_POS, height, g.TOP|g.LEFT);
        g.drawString("" + score,
             (width - (SCORE_CHAR_WIDTH * 3)),
             height, g.TOP|g.LEFT);

        // Display current score
        g.drawString("S: ",
             (width - (SCORE_CHAR_WIDTH * 4)),
             height, g.TOP|g.RIGHT);
        g.drawString("" + score,
             (width - (SCORE_CHAR_WIDTH * 3)),
             height, g.TOP|g.LEFT);

        // Display highest score for this level
        g.drawString("H: ",
             (width - (SCORE_CHAR_WIDTH * 4)),
             (height + SCORE_CHAR_HEIGHT),
             g.TOP|g.RIGHT);
        g.drawString("" + WormScore.getHighScore(level),
             (width - (SCORE_CHAR_WIDTH * 3)),
             (height + SCORE_CHAR_HEIGHT),
             g.TOP|g.LEFT);

        // Draw worm & food
        g.translate(START_POS, START_POS);
        g.setClip(0, 0, CellWidth*CELL_SIZE, CellHeight*CELL_SIZE);
        myWorm.paint(g);
        myFood.paint(g);
    } else {
        // Draw worm & food
        g.translate(START_POS, START_POS);
    }

    if (gamePaused) {
        Font pauseFont = g.getFont();
        int fontH = pauseFont.getHeight();
        int fontW = pauseFont.stringWidth("Paused");
        g.setColor(WormPit.ERASE_COLOUR);
        g.fillRect((width-fontW)/2 - 1, (height-fontH)/2,
               fontW + 2, fontH);
        g.setColor(WormPit.TEXT_COLOUR);
        g.setFont(pauseFont);
        g.drawString("Paused", (width-fontW)/2, (height-fontH)/2,
             g.TOP|g.LEFT);
    } else if (gameOver) {
        Font overFont = g.getFont();
        int fontH = overFont.getHeight();
        int fontW = overFont.stringWidth("Game Over");
        g.setColor(WormPit.ERASE_COLOUR);
        g.fillRect((width-fontW)/2 - 1, (height-fontH)/2,
               fontW + 2, fontH);
        g.setColor(WormPit.TEXT_COLOUR);
        g.setFont(overFont);
        g.drawString("Game Over", (width-fontW)/2, (height-fontH)/2,
             g.TOP|g.LEFT);
    } else {
        paintPitContents(g);
    }
    g.translate(-START_POS, -START_POS);
    }

    /**
     * Notification handler when canvas hidden.
     * Forces a redraw when canvas is shown again.
     * Signals that the game is paused while the canvas is obscured.
     */
    protected void hideNotify() {
    super.hideNotify();
    forceRedraw = true;
    if (!gameOver) {
        gamePaused = true;
    }
    }

    /**
     * The main execution loop.
     */
    public void run() {
    while (!gameDestroyed) {
        try {
        synchronized (myWorm) {
            if (gameOver) {
            if (WormScore.getHighScore(level) < score) {
                /* Display score screen */
                WormScore.setHighScore(level, score, "me");
            }
            if ((audioPlayer != null) &&
                (audioPlayer.getState() == Player.STARTED)) {
                try {
                audioPlayer.stop();
                Manager.playTone(60, 400, 100);
                } catch (Exception ex) { }
            }
            repaint();
            // serviceRepaints(); // Draw immediately
            myWorm.wait();    // Wait until user presses 'restart'
            } else if (gamePaused) {
            repaint();
            // serviceRepaints(); // Draw immediately
            myWorm.wait();    // Wait until user presses 'restart'
            } else {
            myWorm.moveOnUpdate();
            repaint();
            // serviceRepaints(); // Draw immediately
            myWorm.wait(DEFAULT_WAIT-(level*40));
            }
        }
        } catch (java.lang.InterruptedException ie) {
        }
    }
    }

    /**
     * Destroy the entire game. Call this prior to destroying the MIDlet.
     */
    public void destroyGame() {
    synchronized (myWorm) {
        gameDestroyed = true;
        //myWorm.notifyAll();
        myWorm.notifyAll();
    }
    }

    /**
     * Destroy the audio player. Call this destroy the audio player.
     */
    public void destroyAudioPlayer() {
    synchronized (myWorm) {
        if (audioPlayer != null) {
        audioPlayer.close();
        audioPlayer = null;
        }
        if (tonePlayer != null) {
        tonePlayer.close();
        tonePlayer = null;
        }
        myWorm.notifyAll();
    }
    }

    /**
     * Create an audio player.
     */
    public void createAudioPlayer() {
    byte[] bkmelody = {
            ToneControl.VERSION, 1,
            ToneControl.SET_VOLUME, 40,
            76, 8,
            81, 24,
            76, 8,
            88, 24,
            81, 8,
            85, 16,
            83, 8,
            85, 8,
            81, 16,
            76, 16,
            81, 16,
            90, 16,
            88, 16,
            85, 8,
            86, 8,
            88, 48,
            ToneControl.SILENCE, 8,
            76, 8,
            81, 16,
            90, 16,
            88, 16,
            85, 8,
            86, 8,
            88, 16,
            83, 8,
            85, 8,
            81, 16,
            76, 16,
            81, 16,
            85, 8,
            86, 8,
            83, 24,
            81, 8,
            81, 32
    };

    byte[] tseq = {
            ToneControl.VERSION, 1,
            64, 4,
            65, 4,
            66, 4
    };

    if (audioPlayer != null) {
        audioPlayer.close();
        audioPlayer = null;
        try {
        Thread.sleep(200);
        } catch (Exception ex) { }
    }

    try {
        ToneControl tControl;

        tonePlayer = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
        tonePlayer.realize();
        tControl = (ToneControl)tonePlayer.getControl("ToneControl");
        tControl.setSequence(tseq);

        audioPlayer = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
        audioPlayer.setLoopCount(-1);
        audioPlayer.realize();
        tControl = (ToneControl)audioPlayer.getControl("ToneControl");
        tControl.setSequence(bkmelody);
        audioPlayer.start();


    } catch (Exception ex) {
        ex.printStackTrace();
    }
    }

}

