// 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/

package com.danger.chessclock;

import danger.app.Event;
import danger.app.Timer;
import danger.audio.Meta;
import danger.ui.Bitmap;
import danger.ui.ImageView;
import danger.ui.Menu;
import danger.ui.MenuItem;
import danger.ui.ScreenWindow;
import danger.ui.StaticText;

/**
 * The screen that displays the ChessClock. This class handles
 * the bulk of the UI interaction.
 */
public class ClockWindow
	extends ScreenWindow
{
	/** null-ok; the application object; set by {@link #setApp} */
	private ChessClock mApp;

	/** null-ok; the prefs; set by {@link #setApp} */
	private ClockPrefs mPrefs;

	// active state
	private long[] mTimeLeft;
	private long[] mFinishAt;
	private int mWhosRunning;
	private Timer mTimer;

	// screen widgets
	private StaticText mLabel1;
	private ImageView mNeg1;
	private ImageView mHourA1;
	private ImageView mHourB1;
	private ImageView mMinA1;
	private ImageView mMinB1;
	private ImageView mSecA1;
	private ImageView mSecB1;

	private StaticText mLabel2;
	private ImageView mNeg2;
	private ImageView mHourA2;
	private ImageView mHourB2;
	private ImageView mMinA2;
	private ImageView mMinB2;
	private ImageView mSecA2;
	private ImageView mSecB2;

	private Bitmap[] mDigits;
	private Bitmap mDash;

	private String mLabelString1;
	private String mLabelString2;
	private String mLabelStringPause1;
	private String mLabelStringPause2;
	private String mLabelStringRunning1;
	private String mLabelStringRunning2;



	// ------------------------------------------------------------------------
	// constructors

	/**
	 * Construct an instance.
	 */
	public ClockWindow()
	{
		mTimeLeft = new long[3];
		mFinishAt = new long[3];
		mWhosRunning = -1;
		mTimer = new Timer(250, true, this);
	}



	// ------------------------------------------------------------------------
	// public instance methods

	// standard UI method
	public boolean eventWidgetUp(int inWhichWidget, Event event)
	{
		switch (inWhichWidget) {
			case Event.DEVICE_BUTTON_JUMP: {
				if (((event.modifiers & Event.EVENT_MODIFIER_ALT) == 0) &&
					mPrefs.mJumpBackSwitch)
				{
					switchTo(2);
					return true;
				}
			}
			case Hiptop2Events.DEVICE_BUTTON_CANCEL:
			case Event.DEVICE_BUTTON_BACK: {
				if (((event.modifiers & Event.EVENT_MODIFIER_ALT) == 0) &&
					mPrefs.mJumpBackSwitch)
				{
					switchTo(1);
				}
				else {
					mApp.returnToLauncher();
				}
				return true;
			}
			case Event.DEVICE_WHEEL_BUTTON: {
				pauseCurrent();
				return true;
			}
		}

		return super.eventWidgetUp(inWhichWidget, event);
	}

	// standard UI method
	public boolean eventWidgetDown(int inWhichWidget, Event event)
	{
		switch (inWhichWidget) {
			case Event.DEVICE_BUTTON_JUMP: {
				if (((event.modifiers & Event.EVENT_MODIFIER_ALT) == 0) &&
					mPrefs.mJumpBackSwitch)
				{
					// silently swallow the event if in jump-back-switch
					// mode
					return true;
				}
			}
			case Event.DEVICE_BUTTON_BACK: {
				if (((event.modifiers & Event.EVENT_MODIFIER_ALT) == 0) &&
					mPrefs.mJumpBackSwitch)
				{
					// silently swallow the event if in jump-back-switch
					// mode
					return true;
				}
			}
		}

		return super.eventWidgetDown(inWhichWidget, event);
	}

	// standard UI method
	public boolean eventKeyDown(char c, Event e)
	{
		// we wouldn't have to implement this method at all if
		// ScreenWindow were willing to deal with non-MENU shortcuts other
		// than a couple special keys (like delete and arrows)

		adjustActionMenuState();
		Menu menu = getActionMenu();
		boolean handled = menu.matchAndDispatchNonMenuShortcut(c, e);

		if (handled) {
			return true;
		}

		return super.eventKeyDown(c, e);
	}

	// standard UI method
	public boolean receiveEvent(Event e)
	{
		switch (e.type) {
			case Events.CMD_HINTS: {
				mApp.showAlert(Resources.HINTS_WINDOW, null);
				return true;
			}
			case Events.CMD_CONFIGURE: {
				mPrefs.show();
				return true;
			}
			case Events.CMD_RESET: {
				reset();
				return true;
			}
			case Events.CMD_PAUSE: {
				pauseCurrent();
				return true;
			}
			case Events.CMD_SWITCH_1TO2: {
				switchTo(2);
				return true;
			}
			case Events.CMD_SWITCH_2TO1: {
				switchTo(1);
				return true;
			}
			case Events.CMD_JUMP_BACK_SWITCH: {
				mPrefs.mJumpBackSwitch = !mPrefs.mJumpBackSwitch;
				getActionMenu().getItemWithID(Resources.MI_JUMP_BACK_SWITCH)
					.setChecked(mPrefs.mJumpBackSwitch);
				return true;
			}
			case Event.EVENT_TIMER: {
				if (mWhosRunning > 0) {
					if (recalcTimeLeft(mWhosRunning)) {
						setImages();
					}
					// redo the if() cuz recalcTimeLeft() might stop the timer
					if (mWhosRunning > 0) {
						long time = mTimeLeft[mWhosRunning];
						int delayTime = ((int) (time % 1000)) +
							((time > 0) ? -100 : 900);
						if (delayTime < 50) {
							delayTime = 50;
						}
						mTimer.resetDelay(delayTime);
					}
				}
				return true;
			}
		}

		return super.receiveEvent(e);
	}



	// ------------------------------------------------------------------------
	// package instance methods

	/**
	 * Set the application object, and set up the UI elements.
	 *
	 * @param app non-null; the application object
	 */
	/*package*/ void setApp(ChessClock app)
	{
		mApp = app;
		mPrefs = new ClockPrefs(app);

		// disable "pause" and make all of these items usable without
		// the MENU key; also, as a hack until there's a better automatic
		// label, make the shortcut for "pause" read "SPC"
		Menu menu = getActionMenu();
		MenuItem item = menu.getItemWithID(Resources.MI_PAUSE);
		item.setEnabled(false);
		item.setShortcut(item.getShortcut(), 0);
		item.setShortcutLabel("SPC");
		item = menu.getItemWithID(Resources.MI_SWITCH_1TO2);
		item.setShortcut(item.getShortcut(), 0);
		item = menu.getItemWithID(Resources.MI_SWITCH_2TO1);
		item.setShortcut(item.getShortcut(), 0);

		mLabel1 = (StaticText) getDescendantWithID(Resources.LABEL_1);
		mNeg1 = (ImageView) getDescendantWithID(Resources.NEG_1);
		mHourA1 = (ImageView) getDescendantWithID(Resources.HOURA_1);
		mHourB1 = (ImageView) getDescendantWithID(Resources.HOURB_1);
		mMinA1 = (ImageView) getDescendantWithID(Resources.MINA_1);
		mMinB1 = (ImageView) getDescendantWithID(Resources.MINB_1);
		mSecA1 = (ImageView) getDescendantWithID(Resources.SECA_1);
		mSecB1 = (ImageView) getDescendantWithID(Resources.SECB_1);

		mLabel2 = (StaticText) getDescendantWithID(Resources.LABEL_2);
		mNeg2 = (ImageView) getDescendantWithID(Resources.NEG_2);
		mHourA2 = (ImageView) getDescendantWithID(Resources.HOURA_2);
		mHourB2 = (ImageView) getDescendantWithID(Resources.HOURB_2);
		mMinA2 = (ImageView) getDescendantWithID(Resources.MINA_2);
		mMinB2 = (ImageView) getDescendantWithID(Resources.MINB_2);
		mSecA2 = (ImageView) getDescendantWithID(Resources.SECA_2);
		mSecB2 = (ImageView) getDescendantWithID(Resources.SECB_2);

		mLabelString1 = mLabel1.getText();
		mLabelString2 = mLabel2.getText();
		mLabelStringRunning1 = ">>> " + mLabelString1  + " <<<";
		mLabelStringRunning2 = ">>> " + mLabelString2  + " <<<";
		mLabelStringPause1 = "--- " + mLabelString1  + " ---";
		mLabelStringPause2 = "--- " + mLabelString2  + " ---";

		mDigits = new Bitmap[10];
		mDigits[0] = app.getBitmap(Resources.BITMAP_0);
		mDigits[1] = app.getBitmap(Resources.BITMAP_1);
		mDigits[2] = app.getBitmap(Resources.BITMAP_2);
		mDigits[3] = app.getBitmap(Resources.BITMAP_3);
		mDigits[4] = app.getBitmap(Resources.BITMAP_4);
		mDigits[5] = app.getBitmap(Resources.BITMAP_5);
		mDigits[6] = app.getBitmap(Resources.BITMAP_6);
		mDigits[7] = app.getBitmap(Resources.BITMAP_7);
		mDigits[8] = app.getBitmap(Resources.BITMAP_8);
		mDigits[9] = app.getBitmap(Resources.BITMAP_9);
		mDash = app.getBitmap(Resources.BITMAP_DASH);

		setImages();
	}



	// ------------------------------------------------------------------------
	// private instance methods

	/**
	 * Recalc the time left for the given timer.
	 *
	 * @return <code>true</code> iff the displayed time is different
	 * than it was previously
	 */
	private boolean recalcTimeLeft(int which)
	{
		long time = mTimeLeft[which];
		boolean oldNeg = time < 0;
		long oldSecs = time / 1000;
		long now = System.currentTimeMillis();

		time = mFinishAt[which] - now;
		mTimeLeft[which] = time;

		if (time < 0) {
			if ((! oldNeg) && (mPrefs.mExpirationTone != null)) {
				// time just became negative and there's a tone set; boing
				Meta.play(mPrefs.mExpirationTone.getID());
			}

			// halt if we aren't supposed to go negative
			if (! mPrefs.mGoNegative) {
				pauseCurrent();
				mTimeLeft[which] = 0;
				return true;
			}
		}

		return (oldNeg != (time < 0)) || (oldSecs != (time / 1000));
	}

	/**
	 * Pause the current timer, if any is running.
	 */
	private void pauseCurrent()
	{
		if (mWhosRunning < 0) {
			return;
		}

		mTimeLeft[mWhosRunning] =
			mFinishAt[mWhosRunning] - System.currentTimeMillis();
		mWhosRunning = -mWhosRunning;

		setImages();
		mTimer.stop();

		getActionMenu().getItemWithID(Resources.MI_PAUSE)
			.setEnabled(false);
	}

	/**
	 * Start the current timer, if none is running.
	 */
	private void startCurrent()
	{
		if (mWhosRunning > 0) {
			return;
		}

		getActionMenu().getItemWithID(Resources.MI_PAUSE)
			.setEnabled(true);

		mWhosRunning = -mWhosRunning;
		mFinishAt[mWhosRunning] = 
			System.currentTimeMillis() + mTimeLeft[mWhosRunning]; 

		setImages();
		mTimer.resetDelay(100);
	}

	/**
	 * Pause (if not already) and reset all values based on the
	 * configuration.
	 */
	private void reset()
	{
		pauseCurrent();
		mTimeLeft[1] =
			(mPrefs.mInitialSeconds1 +
			 mPrefs.mInitialMinutes1 * 60 +
			 mPrefs.mInitialHours1 * 3600)
			* 1000;
		mTimeLeft[2] =
			(mPrefs.mInitialSeconds2 +
			 mPrefs.mInitialMinutes2 * 60 +
			 mPrefs.mInitialHours2 * 3600)
			* 1000;

		setImages();
	}

	/**
	 * Switch to the given timer and start it.
	 */
	private void switchTo(int which)
	{
		if (mWhosRunning == which) {
			return;
		}

		if (mWhosRunning > 0) {
			pauseCurrent();
		}

		mWhosRunning = -which;
		startCurrent();
	}

	/**
	 * Set the images according to the current timer times.
	 */
	private void setImages()
	{
		setImages0(mTimeLeft[1], mNeg1, mHourA1, mHourB1, mMinA1, mMinB1,
				   mSecA1, mSecB1);
		setImages0(mTimeLeft[2], mNeg2, mHourA2, mHourB2, mMinA2, mMinB2,
				   mSecA2, mSecB2);

		switch (mWhosRunning) {
			case -1: {
				mLabel1.setText(mLabelStringPause1);
				mLabel2.setText(mLabelString2);
				break;
			}
			case -2: {
				mLabel1.setText(mLabelString1);
				mLabel2.setText(mLabelStringPause2);
				break;
			}
			case 1: {
				mLabel1.setText(mLabelStringRunning1);
				mLabel2.setText(mLabelString2);
				break;
			}
			case 2: {
				mLabel1.setText(mLabelString1);
				mLabel2.setText(mLabelStringRunning2);
				break;
			}
		}
	}

	/**
	 * Used by {@link #setImages}.
	 */
	private void setImages0(long time, ImageView neg,
							ImageView hourA, ImageView hourB,
							ImageView minA, ImageView minB,
							ImageView secA, ImageView secB)
	{
		if (time < 0) {
			neg.setImage(mDash);
			time = -time;
		}
		else {
			neg.setImage(null);
		}

		time /= 1000; // secs
		secB.setImage(mDigits[(int) (time % 10)]);
		time /= 10; // 10 secs
		secA.setImage(mDigits[(int) (time % 6)]);

		time /= 6; // mins
		minB.setImage(mDigits[(int) (time % 10)]);
		time /= 10; // 10 mins
		minA.setImage(mDigits[(int) (time % 6)]);

		time /= 6; // hours
		hourB.setImage(mDigits[(int) (time % 10)]);
		time = (time / 10) % 10; // 10 hours
		if (time == 0) {
			hourA.setImage(null);
		}
		else {
			hourA.setImage(mDigits[(int) time]);
		}
	}
}
