// Copyright 2001-2003, Danger, Inc.  All Rights Reserved.
// This file is subject to the Danger, Inc. Sample Code License,
// which is provided in the file SAMPLE_CODE_LICENSE.
// Copies are also available from http://developer.danger.com/
/* Asteroids! */
package com.danger.rocks;

import danger.app.*;
import danger.audio.*;
import danger.system.*;
import danger.ui.*;
import danger.util.*;
import danger.util.format.StringFormat;
import java.util.Random;

import java.util.LinkedList;
import java.util.ListIterator;

/**
 * DOCUMENT ME!
 *
 * @author $author$
 * @version $Revision$
 */
public class rocks extends Application implements Resources
{
	//~ Static fields/initializers ---------------------------------------------

	/* constants */
	static final int MAX_LIVES	    = 6;
	static final int STARTING_LIVES = 3;
	static final int EXTRA_LIFE_AT  = 15000;
	static final int ALT_AT		    = 25000;

	/* strings */
	static final int kCmd_ResumeGame    = 1;
	static final int kCmd_SoundtrackOn  = 2;
	static final int kCmd_SoundtrackOff = 3;
	static final int kCmd_EndGame	    = 5;

	// score values
	static final int kSMALL_ROCK_VALUE = 150;
	static final int kMED_ROCK_VALUE   = 100;
	static final int kLARGE_ROCK_VALUE = 50;
	static final int kANTAGONIST_VALUE = 25;

	//*	--------------------------------	Class Variables
	static RocksStore mDS;
	static boolean    mDEBUG	  = false;
	static boolean    mGOD		  = false;
	static boolean    mPause	  = false;
	static boolean    sIsColor;
	static RockWindow mWindow;
	static Random     mRandom;
	static rocks	  mMe;
	static int		  mGameFrame;
	static Timer	  mFrameTimer;

	//~ Instance fields --------------------------------------------------------

	private SplashScreen  mSplashScreen;
	private StaticTextBox mNumRemaining;

	//~ Constructors -----------------------------------------------------------

	//*	--------------------	rocks
	public rocks()
	{
		mMe			 = this;
		mRandom		 = new Random();
		sIsColor     = Screen.isColor();

		RockStrings.setup();

		//*	Create our app's main window
		mWindow = new RockWindow();
		mWindow.setTitle(RockStrings.kAppTitle);

		mDS     = new RocksStore();

		mSplashScreen     = getResources().getSplashScreen(AppResources.ID_SPLASH_SCREEN_RESOURCE);
		mNumRemaining     = (StaticTextBox) mSplashScreen.getDescendantWithID(ID_HIGH_SCORE);
		mNumRemaining.setVisible(true);

		updatePreviewScreen();
	}

	//~ Methods ----------------------------------------------------------------

	//*	--------------------	getInstance
	public static final rocks getInstance()
	{
		return mMe;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param inView DOCUMENT ME!
	 * @param inPen DOCUMENT ME!
	 */
	public void renderSplashScreen(View inView, Pen inPen)
	{
		String text = StringFormat.withFormat(RockStrings.kHighScoreText,
				String.valueOf(mDS.GetHighScore()));

		mNumRemaining.setText(text);
		mSplashScreen.paint(inView, inPen);
	}

	//*	--------------------	NewGame
	public void newGame()
	{
		mWindow.mObjectsReady     = false;
		mWindow.mGameState		  = mWindow.BUILDING_SCREEN;
		mWindow.mShowCredits	  = false;
		mWindow.StopAnnouncer();
		mWindow.mPlayfieldID	  = 0;
		mWindow.mPlayfieldOsc     = 0;
		mWindow.NewPlayfield();
		mWindow.ClearMissiles();

		mWindow.mLives		  = STARTING_LIVES;
		mWindow.mAltLives     = 0;
		mWindow.SetScore(0);

		mWindow.invalidate();
		mWindow.mTimeBetweenShots     = 0;
		mWindow.mLastShot			  = mGameFrame;

		mWindow.mRocksList     = null; // kill previous list of rocks
		mWindow.mRocksList     = new LinkedList();

		if (mWindow.mHero != null)
		{
			mWindow.mHero.reset();
		}
		else
		{
			mWindow.mHero = new HeroObject(mWindow.mBounds);
		}

		mWindow.mHero.newLife();

		if (mWindow.mAntagonist != null)
		{
			mWindow.mAntagonist.kill();
		}

		mWindow.mLevel = 0;
		mWindow.BuildWorld();
		mWindow.mGameState = mWindow.IN_GAME_SCREEN;
		mWindow.invalidate();
		RockAudio.resetLevel();
	}

	/**
	 * DOCUMENT ME!
	 */
	public void restartGame()
	{
		Hardware.setLEDHSV(0, 0, 0);
		mWindow.mGameState		  = mWindow.INTRO_SCREEN;
		mWindow.mObjectsReady     = false;
	}

	private void splitRock(MotionObject sourceObject, RockObject rockToSplit,
		int vectorDivisor, boolean addScore, ListIterator iterator)
	{
		// we want the rocks to move faster than the source object
		int vectorSpeed = Fixed.divide(sourceObject.getSpeed(), vectorDivisor);
		int vectorAngle = sourceObject.getAngle();

		if (vectorSpeed < 0x2000)
		{
			vectorSpeed = 0x2000;
		}

		// split a rock, and move the object
		sourceObject.addSpeedFromVector(rockToSplit.mMotion,
			Fixed.FIXED_ONE_HALF);

		// POW!
		RockAudio.hitRock();
		rockToSplit.hit(mWindow.mRocksList);

		if (addScore)
		{
			mWindow.AddToScore(rockToSplit.mRockImage.getValueOfRock());
		}

		// only split if its large enough
		RockImage smallerImage = RockImage.getSmallerRockImage(rockToSplit.mRockImage);

		if (smallerImage != null)
		{
			RockObject newRock0;
			RockObject newRock1;
			int		   angle0;
			int		   angle1;

			angle0     = (vectorAngle - 23 + mRandom.nextInt(21)) - 10;
			angle1     = (vectorAngle + 23 + mRandom.nextInt(21)) - 10;

			if (((angle0 / 90) * 90) == angle0)
			{
				angle0 = angle0 + 3; // don't allow 90 degrees
			}

			if (((angle1 / 90) * 90) == angle1)
			{
				angle1 = angle1 + 3; // don't allow 90 degrees
			}

			newRock0     = new RockObject(rockToSplit.mMotion.getX(),
					rockToSplit.mMotion.getY(), angle0, vectorSpeed,
					mWindow.mBounds, smallerImage);

			newRock1 = new RockObject(rockToSplit.mMotion.getX(),
					rockToSplit.mMotion.getY(), angle1, vectorSpeed,
					mWindow.mBounds, smallerImage);
			newRock1.mMotion.addSpeed(Fixed.FIXED_ONE_HALF);

			iterator.add(newRock0);
			iterator.add(newRock1);
		}
	}

	private void processFrame()
	{
		if (mWindow.mObjectsReady)
		{
			ListIterator iterator;
			RockObject   rock;
			int			 count;

			if (mWindow.mRocksList.size() == 0)
			{
				// no more rocks, time to create some news ones.
				RockAudio.goNextLevel(4);
				mWindow.mLevel++;
				mWindow.BuildWorld();
				mWindow.StartAnnouncer();
				mWindow.NewPlayfield();
			}

			// walk through missles and check for hit
			for (count = 0; count < mWindow.MAX_MISSILES; count++)
			{
				boolean missleHits;

				if (mWindow.mMissiles[count] != null)
				{
					mWindow.mMissiles[count].update(mWindow);

					missleHits = false;

					if (mWindow.IsAntagonistDead() == false)
					{
						if (mWindow.mAntagonist.collidesWith(
								mWindow.mMissiles[count]))
						{
							mWindow.AddToScore(kANTAGONIST_VALUE);
							mWindow.mAntagonist.hit();
							missleHits					 = true;
							mWindow.mMissiles[count]     = null;

							break;
						}
					}

					if (mGOD == false)
					{
						// see if this missile can hurt our hero
						if (mWindow.mHero.collidesWith(mWindow.mMissiles[count]))
						{
							mWindow.mTimeBetweenShots     = 0;
							mWindow.mLastShot			  = mGameFrame;
							mWindow.HitHero();
							missleHits					 = true;
							mWindow.mMissiles[count]     = null;

							break;
						}
					}

					// now walk through all the rocks and check for missle hits and hero hits
					synchronized ( mWindow.mRocksList ) {
						iterator     = mWindow.mRocksList.listIterator();
						while ( iterator.hasNext() )
						{
							rock = (RockObject) iterator.next();
							if (rock.collidesWith(mWindow.mMissiles[count]))
							{
								boolean heroMissile = false;

								missleHits = true;

								if (mWindow.mMissiles[count].mOrigin == mWindow.mHero)
								{
									// only allow score for rock hits if its our hero
									heroMissile = true;
								}

								splitRock(mWindow.mMissiles[count].mMotion,
									rock, Fixed.fromInt(4), heroMissile, iterator);
								mWindow.mMissiles[count] = null;

								break;
							}
						}
					
						if (!missleHits &&
							(mWindow.mMissiles[count].mMotion.getCurrentFrame() == (mWindow.mMissiles[count].mMotion.getMaxFrames() -
							1)))
						{
							mWindow.mMissiles[count].mMotion     = null;
							mWindow.mMissiles[count]			 = null;
						}
					}
				}
			}
			
			// walk through active rocks
			synchronized ( mWindow.mRocksList ) {
				iterator = mWindow.mRocksList.listIterator();
				while ( iterator.hasNext() )
				{
					rock = (RockObject) iterator.next();
					rock.update(mWindow);

					if (mWindow.mAntagonist != null)
					{
						if (mWindow.mAntagonist.collidesWith(rock))
						{
							if (mWindow.mAntagonist.sustainDamage() == false)
							{
								mWindow.mAntagonist.hit();
							}
							else
							{
								splitRock(mWindow.mAntagonist.mMotion, rock,
									Fixed.fromInt(2), false, iterator);
							}
						}
					}

					if (mGOD == false)
					{
						if (mWindow.mHero.collidesWith(rock))
						{
							mWindow.mTimeBetweenShots     = 0;
							mWindow.mLastShot			  = mGameFrame;
							RockAudio.goNextLevel();

							if (mWindow.mHero.sustainDamage() == false)
							{
								mWindow.HitHero();
							}
							else
							{
								splitRock(mWindow.mHero.mMotion, rock,
									Fixed.fromInt(2), true, iterator);
							}
						}
					}
				}
			}
			
			// handle auto fire
			if (mWindow.mAutoFire)
			{
				if (mWindow.mAutoFireTimer == 0)
				{
					mWindow.LaunchMissile();
					mWindow.mAutoFireTimer = 5;
				}
				else
				{
					mWindow.mAutoFireTimer--;
				}
			}

			// handle antagonist logic
			if (mWindow.IsAntagonistDead() == false)
			{
				if (mGOD == false)
				{
					Rect h = mWindow.mHero.getRect();
					Rect a = mWindow.mAntagonist.getRect();
					a.inset(4);

					if (h.intersects(a))
					{
						// hero and antagonist have collided; all is not well with the world.
						mWindow.mAntagonist.hit();
						mWindow.HitHero();
						RockAudio.goNextLevel();
					}
				}

				if (mWindow.mAntagonistAutoFireTimer == 0)
				{
					mWindow.AntagonistLaunchMissile();
					mWindow.mAntagonistAutoFireTimer = 25;
				}
				else
				{
					mWindow.mAntagonistAutoFireTimer--;
				}
			}

			mWindow.mTimeBetweenShots = mGameFrame - mWindow.mLastShot;

			if (mWindow.mTimeBetweenShots > 75)
			{
				if (mWindow.mAntagonistLastTimeOnScreen == 0)
				{
					if (mWindow.IsAntagonistDead() == true)
					{
						mWindow.NewAntagonist();
						mWindow.mTimeBetweenShots = 0;
					}
					else
					{
						mWindow.mAntagonistLastTimeOnScreen = mWindow.WAIT_TIME_ANTAGONIST;
					}
				}
				else
				{
					mWindow.mAntagonistLastTimeOnScreen--;
				}
			}

			// handle end game reset
			if (mWindow.mResetGameTimer != 0)
			{
				mWindow.mResetGameTimer--;

				if (mWindow.mResetGameTimer == 0)
				{
					restartGame();
				}
			}

			mWindow.Update();
			RockAudio.idleAudio();
		}

		mGameFrame++;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return DOCUMENT ME!
	 */
	public static boolean isGamePaused()
	{
		return mPause;
	}

	/**
	 * DOCUMENT ME!
	 */
	public static void pauseGame()
	{
		if (mPause == false)
		{
			mPause = true;
			mWindow.pause();
			RockAudio.pause();
		}
	}

	/**
	 * DOCUMENT ME!
	 */
	public static void resumeGame()
	{
		if (mPause == true)
		{
			mPause = false;
			mWindow.resume();
			RockAudio.resume();
		}
	}

	//*	--------------------	ReceiveEvent
	public boolean receiveEvent(Event e)
	{
		switch (e.type)
		{
			case kCmd_ResumeGame:
				resumeGame();
				break;

			case kCmd_SoundtrackOff:
			case kCmd_SoundtrackOn:
				RockAudio.toggleSoundtrack();
				break;

			case kCmd_EndGame:
				DialogWindow dialog = mMe.getDialog(ID_END_GAME_CONFIRM, this);
				dialog.show();
				break;

			case Events.EVENT_END_GAME:
				restartGame();
				resumeGame();
				break;

			case Event.EVENT_TIMER:

				if ((mWindow.mGameState == mWindow.INTRO_SCREEN) &&
					!Screen.isWindowActive(mWindow))
				{
					return true;
				}

				if (isGamePaused() == false)
				{
					if (!Screen.isWindowActive(mWindow))
					{
						pauseGame();

						return true;
					}
					else
					{
						processFrame();
					}
				}
				//				else
				{
					// we still need to draw even though we're paused
					mWindow.invalidate();
				}
				return true;
		}

		return (super.receiveEvent(e));
	}

	/**
	 * DOCUMENT ME!
	 */
	public void quit()
	{
		suspend();
		unload();
	}

	//*	--------------------	Resume
	public void resume()
	{
		// 100 is 10 frames per second
		// 150 is 7.5 frames per second
		// 200 is 5 frames per second
		if (mFrameTimer == null)
		{
			mFrameTimer = new Timer(100, true, this);
		}

		mFrameTimer.start();
		mWindow.show();
		RockAudio.setup();
	}

	//*	--------------------	Suspend
	public void suspend()
	{
		if (mFrameTimer != null)
		{
			mFrameTimer.stop();
		}

		if (mWindow.mGameState != mWindow.INTRO_SCREEN)
		{
			pauseGame();
		}

		RockAudio.cleanup();
		updatePreviewScreen();
	}
}


class RocksStore extends Listener
{
	//~ Static fields/initializers ---------------------------------------------

	// settingsDB stuff
	static final String kRocksDB	  = "Rocks";
	static final String kHighscoreTag = "hs1";

	//~ Instance fields --------------------------------------------------------

	private SettingsDB mSettings;
	private int		   mLocalHS;

	//~ Constructors -----------------------------------------------------------

	RocksStore()
	{
		mSettings = new SettingsDB(kRocksDB, true);

		if (mSettings != null)
		{
			mSettings.setAutoSyncNotifyee(this);
		}
	}

	//~ Methods ----------------------------------------------------------------

	/**
	 * DOCUMENT ME!
	 *
	 * @return DOCUMENT ME!
	 */
	public int GetHighScore()
	{
		int value = mLocalHS;

		if (mSettings != null)
		{
			int value2 = 0;

			try
			{
				value2 = mSettings.getIntValue(kHighscoreTag);
			}
			catch (SettingsDBException e)
			{
			}

			if (value2 > mLocalHS)
			{
				value		 = value2;
				mLocalHS     = value2;
			}
		}

		return value;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param score DOCUMENT ME!
	 */
	public void SetHighScore(int score)
	{
		if (mSettings != null)
		{
			mSettings.setIntValue(kHighscoreTag, score);
		}

		mLocalHS = score;
	}
}

// EOF
