//    File:  CMBubble.java  -  drawing using an off-screen buffer

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
///// F. Permadi 1998
/////
///// Updated by Bary W Pollack; April 8, 2004
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

public class CMBubble extends Applet implements Runnable {
    private Thread mThread = null;
    private int mAppletWidth, mAppletHeight;
    private Image mOffScrImage;
    private Graphics mOffScrGraphics;
    private boolean mStillDrawingOffScreen;

    // status code
    private int mStatus;
    private static final int PLAYING = 2;
    private static final int LOADING = 3;

    // background image
    private Image mBgrImage;

    // FONTS
    private static final Font mFont1 = new Font("Helvetica", Font.BOLD, 12);
    private CBub mBubbles[];
    private int mNumOfBubbles;
    private int mCurrentBubble;
    private int mMouseX, mMouseY, mDir;
    private boolean mUserHasMoved;

    /////////////////////////////////////////////////////////////////////
    // Create background image
    /////////////////////////////////////////////////////////////////////

    public void createBackground() {
        String s = getParameter("bgrImage");
        if (s!= null)
            mBgrImage = loadImage(s);
        else {
            mBgrImage = createImage(mAppletWidth, mAppletHeight);
            Graphics g = mBgrImage.getGraphics();
            g.setColor(Color.red);
            g.fillRect(0, 0, mAppletWidth, mAppletHeight);
        }
    }

    /////////////////////////////////////////////////////////////////////
    // default constructor
    /////////////////////////////////////////////////////////////////////

    public CMBubble() { }

    /////////////////////////////////////////////////////////////////////
    // load image
    /////////////////////////////////////////////////////////////////////

    public Image loadImage(String imageName) {
        MediaTracker tracker = new MediaTracker(this);

        //Load image
        Image i = getImage(getCodeBase(), imageName);
        tracker.addImage(i, 0);
        try {
            tracker.waitForID(0);
        } catch (InterruptedException e) {
            System.out.println(e);
        }
        return i;
    }

    /////////////////////////////////////////////////////////////////////
    // for painting the applet
    /////////////////////////////////////////////////////////////////////

    public void update(Graphics g) {
        if (!mStillDrawingOffScreen)
            g.drawImage(mOffScrImage, 0, 0, this);
    }

    public void drawOffScreen() {
        mStillDrawingOffScreen = true;
        if (mStatus == PLAYING) {
            // draw background
            mOffScrGraphics.drawImage(mBgrImage, 0, 0, this);
            for (int i = 0; i < mNumOfBubbles; i++) {
                mBubbles[i].draw();
                mBubbles[i].move();
            }
        } else if (mStatus == LOADING) {
            mOffScrGraphics.setColor(Color.black);
            mOffScrGraphics.fillRect(0, 0, getSize().width, getSize().height);
            mOffScrGraphics.setFont(mFont1);
            drawTextCentered(0, mAppletHeight/2, mAppletWidth, "LOADING...", mOffScrGraphics);
            showStatus("Please move the mouse...");
        }
        mStillDrawingOffScreen = false;
    }

    /////////////////////////////////////////////////////////////////////
    // standard applet startup
    /////////////////////////////////////////////////////////////////////

    public void start() {
        if (mThread == null) {
            mThread = new Thread(this);
            mThread.start();
        }
        requestFocus();
    }

    /////////////////////////////////////////////////////////////////////
    // this will be called when user leave the applet page
    /////////////////////////////////////////////////////////////////////

    public void stop() {
        if (mThread != null) {
            //mThread.stop();    deprecated method; removed...
            mThread = null;
        }
    }

    /////////////////////////////////////////////////////////////////////
    // create the game image/bitmap/objects, etc.
    // this should only be called once
    /////////////////////////////////////////////////////////////////////

    public void createGame() {
        // create offscreen buffer
        mAppletWidth = getSize().width;
        mAppletHeight = getSize().height;
        mOffScrImage = createImage(mAppletWidth, mAppletHeight);
        mOffScrGraphics = mOffScrImage.getGraphics();

        // show "LOADING" message
        mStatus = LOADING;
        drawOffScreen();
        repaint();

        // create background
        mBgrImage = createImage(mAppletWidth, mAppletHeight);
        createBackground();

        CBub.setupStatic(mOffScrGraphics, mAppletWidth, mAppletHeight, this);
        mNumOfBubbles = 30;
        mBubbles = new CBub[mNumOfBubbles];
        for (int i = 0; i < mNumOfBubbles; i++)
            mBubbles[i] = new CBub();
    }

    /////////////////////////////////////////////////////////////////////
    // applet thread runner
    /////////////////////////////////////////////////////////////////////

    public void run() {
        int delay = 50;
        createGame();
        mStatus = PLAYING;
        mUserHasMoved = false;
        mMouseX = mAppletWidth / 2;
        mMouseY = mAppletHeight - 20;

        while (true) {
            drawOffScreen();
            repaint();

            if (!mUserHasMoved) {
                mDir = ((int)(Math.random() * 2) == 1) ? CBub.RIGHT : CBub.LEFT;
                mMouseX = mAppletWidth / 2 - (int)(Math.random() * 20);
            }

            if (mCurrentBubble < mNumOfBubbles)
                mBubbles[mCurrentBubble].deploy(mMouseX-(mBubbles[0].mWidth>>1), 
                                                mMouseY-(mBubbles[0].mHeight>>1), mDir);
            mCurrentBubble++;
            if (mCurrentBubble >= mNumOfBubbles)
                mCurrentBubble = 0;

            try {
                Thread.sleep(delay);
            } catch (InterruptedException e) {
                stop();
            }
        }
    }

    ///////////////////////////////////////////////////////////////
    // The user has clicked in the applet
    ///////////////////////////////////////////////////////////////

    public boolean mouseDown(Event evt, int x, int y) {
        mCurrentBubble = 0;
        return true;
    }

    public boolean mouseDrag(Event  evt, int  x, int  y) {
        mUserHasMoved = true;
        mDir = (mMouseX > x) ? CBub.RIGHT : CBub.LEFT;
        mMouseX = x;
        mMouseY = y;
        return true;
    }

    public boolean mouseMove(Event  evt, int  x, int  y) {
        mUserHasMoved = true;
        mDir = (mMouseX > x) ? CBub.RIGHT : CBub.LEFT;
        mMouseX = x;
        mMouseY = y;
        return true;
    }

    ///////////////////////////////////////////////////////////////
    // draw raised text on the center of the screen
    ///////////////////////////////////////////////////////////////

    public void drawTextCentered(int left, int top, int right, String s, Graphics g) {
        FontMetrics fm = g.getFontMetrics();
        int x = left + (((right - left) - fm.stringWidth(s)) >> 1);
        g.setColor(Color.white);
        g.drawString(s, x, top);
    }
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

class CBub {
    private static CMBubble mGame;
    private static Graphics mOffScrGraphics;
    private static int mAppletWidth, mAppletHeight;

    // status code
    private int mStatus;
    private int mVDir, mHDir;
    private static final int DEAD = 3;
    private static final int UP = 1;
    private static final int DOWN = 2;
    static final int LEFT = 3;
    static final int RIGHT = 4;

    // PLAYER'S STUFF
    private int mStartX, mStartY;
    private int mLeftX, mTopY;
    private int mYSpeed, mXSpeed;
    static int mWidth, mHeight;
    private static Image mImage;
    private int mLifeTime;

    /////////////////////////////////////////////////////////////////////
    // standard constructor
    /////////////////////////////////////////////////////////////////////

    public CBub() {
        mStatus = DEAD;
    }

    static void setupStatic(Graphics g, int gameAreaWidth, int gameAreaHeight, CMBubble game) {
        mOffScrGraphics = g;
        mAppletWidth  = gameAreaWidth;
        mAppletHeight = gameAreaHeight;
        mGame = game;
        String s = game.getParameter("bubbleImage");
        if (s != null)
            mImage = mGame.loadImage(s);
        else {
            int width = 20;
            mImage = game.createImage(width, width);
            Graphics gr = mImage.getGraphics();
            int transColorValue = 0xFF000000;
            Color transparentColor = new Color(transColorValue);
            gr.setColor(transparentColor);
            gr.fillRect(0, 0, width, width);
            gr.setColor(Color.white);
            gr.fillOval(0, 0, width-1, width-1);
            gr.setColor(Color.blue);
            gr.drawOval(0, 0, width-1, width-1);

            // MAKE TRANSPARENT
            //Allocate buffer to hold the image's pixels
            int pixels[] = new int[width * width];

            PixelGrabber pg = new PixelGrabber(mImage, 0, 0, width, width, pixels, 0, width);
            try {
                pg.grabPixels();
            } catch (InterruptedException e) {
            }
            final int width2 = width * width;
            for (int i = 0; i < width2; i++) {
                if (pixels[i] == transColorValue)
                    pixels[i] = 0x00000000;
            }
            mImage = game.createImage(new MemoryImageSource(width, width, pixels, 0, width));
        }

        mWidth  = mImage.getWidth(null);
        mHeight = mImage.getHeight(null);
    }

    public void deploy(int x, int y, int dir) {
        mLeftX = x;
        mTopY = y;
        mStatus = DOWN;
        mVDir = DOWN;
        mLifeTime = 0;
        mHDir = dir;
    }

    public void move() {
        if (mStatus == DEAD)
            return;

        mLifeTime++;

        if (mHDir == LEFT)
            mLeftX -= mXSpeed;
        else
            mLeftX += mXSpeed;

        if ((mLifeTime % 3) == 0) {
            mXSpeed++;
            if (mXSpeed > 10)
                mXSpeed = 10;
        }

        if (mVDir == DOWN) {
            mTopY += mYSpeed;
            mYSpeed++;
            if (mTopY + mHeight >= mAppletHeight) {
                mTopY = mAppletHeight - mHeight;
                mVDir = UP;
                mYSpeed = 3;
                mXSpeed = 3;
            }
        } else if (mVDir == UP) {
            mTopY -= mYSpeed;
            mYSpeed++;
            if (mTopY <= 0) {
                mTopY = 0;
                mVDir = DOWN;
                mYSpeed = 3;
                mXSpeed = 3;
            }
        }
    }

    public void draw() {
        if (mStatus != DEAD)
            mOffScrGraphics.drawImage(mImage, mLeftX, mTopY, null);
    }
}
