/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.bigwall;

import java.util.ArrayList;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LightingColorFilter;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.TextView;

/*
import org.anddev.andengine.extension.physics.box2d.PhysicsFactory;
import org.anddev.andengine.extension.physics.box2d.PhysicsWorld;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.joints.RevoluteJointDef;
*/

/**
 * View that draws, takes touches, etc. for BigWall game.
 *
 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
 * current world physics. All x/y etc. are measured with (0,0) at the lower left.
 * updatePhysics() advances the physics based on real time. draw() renders the
 * world, 
 */
class BigWallGameView extends SurfaceView implements SurfaceHolder.Callback {
	
	class BigWallGameThread extends Thread {

		// UI event info

		boolean touchDown = false;   // is the user "pressing" the screen?
		float touch_x, touch_y;
		float touch_dx = 0f;
		float touch_dy = 0f;
		float touch_pressure;
		MotionEvent eTouch = null;

		// the "holds"

		ArrayList<PointF> Hold;
		ArrayList<Integer>HoldVisit;
		int num_holds_visited = 0;
		float x_last_hold, y_last_hold;
		int level_num_holds = 20;

		static final int HOLD_TEXTSIZE = 24;
		static final int HOLD_RADIUS = 3*HOLD_TEXTSIZE/4;
		static final float HOLD_GRAB_RADIUS = 30f;
		static final float MIN_HOLD_SEPARATION = 50f;
		static final int NOT_VISITED = -1;
		static final int NO_HOLD = -1;
		static final float HOLD_DRAW_RADIUS = 2f*HOLD_GRAB_RADIUS;

		// the "climber"

		static final int NUM_LIMBS =  4;
		static final int NUM_HANDS =  2;
		static final int NO_LIMB   = -1;  // when nothing is selected
		static final int LEFT_HAND =  0;
		static final int RIGHT_HAND = 1;
		static final int LEFT_FOOT =  2;
		static final int RIGHT_FOOT = 3;
		static final float MAX_LIMB_SEPARATION = 150f;
		static final float MAX_LIMB_TOUCH_DISTANCE = 75f;
		static final float LIMB_SIZE = 30f;
		static final float LIMB_SIZE_HALF = 0.5f*LIMB_SIZE;

		PointF[] Limb; 
		int limb_index = NO_LIMB; // currently selected limb

		// physics
		
//		PhysicsWorld mPhysicsWorld;
		
		// sounds

		SoundPool SP;
		MediaPlayer MP;

		int sound_spin_jump;
		int sound_scratch_rewind;

		// miscellaneous

		public static final String TAG = "BigWall";

		public static final int STATE_LOSE = 1;
		public static final int STATE_PAUSE = 2;
		public static final int STATE_READY = 3;
		public static final int STATE_RUNNING = 4;
		public static final int STATE_WIN = 5;

		/*
		 * Member (state) fields
		 */
		/** The drawable to use as the background of the animation canvas */
		private Bitmap mBackgroundImage;

		/**
		 * Current height of the surface/canvas.
		 */
		private int mCanvasHeight = 1;

		/**
		 * Current width of the surface/canvas.
		 */
		private int mCanvasWidth = 1;

		/** Message handler used by thread to interact with TextView */
		private Handler mHandler;

		/** Used to figure out elapsed time between frames */
		private long mLastTime;

		/** Paint to draw limbs  */
		private Paint[] LimbPaint;

		/** Paint to draw the lines on screen. */
		private Paint mLinePaint;

		private Paint mBlackPaint;
		private Paint mWhitePaint;
		private Paint mGreenPaint;

		/** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
		private int mMode;

		/** Indicate whether the surface has been created & is ready to draw */
		private boolean mRun = false;

		/** Scratch rect object. */
		private RectF mScratchRect;

		/** Handle to the surface manager object we interact with */
		private SurfaceHolder mSurfaceHolder;


		public void stopMusic() {
			MP.stop();
			MP.release();
		}

		public void startRandomMusic() {

			int num_tracks = 2;
			int track_num = (int) (Math.random() * (double) num_tracks);

			switch (track_num) {
			case 0:
				MP = MediaPlayer.create(mContext, R.raw.acidy_arp);
				break;
			case 1:
				MP = MediaPlayer.create(mContext, R.raw.tr_eight_fade);
				break;
			}

			try {
				MP.prepare();
			} catch (Exception e) { }

			MP.start();
			MP.setVolume(.25f, .25f);
			MP.setLooping(true);
		}

		// constructor for thread

		public BigWallGameThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {

			// get handles to some important objects
			mSurfaceHolder = surfaceHolder;
			mHandler = handler;
			mContext = context;

			Resources res = context.getResources();
			// cache handles to our key sprites & other drawables
			// ???

			SP = new SoundPool(16, AudioManager.STREAM_MUSIC, 0);
			sound_spin_jump = SP.load(context, R.raw.spin_jump, 1);
			sound_scratch_rewind = SP.load(context, R.raw.scratch_rewind, 1);

			startRandomMusic();  // start mediaplayer for random background music

			// load background image as a Bitmap instead of a Drawable b/c
			// we don't need to transform it and it's faster to draw this way
			mBackgroundImage = BitmapFactory.decodeResource(res,R.drawable.rock);

			LimbPaint = new Paint[NUM_LIMBS];
			LimbPaint[LEFT_HAND] = new Paint();
			LimbPaint[LEFT_HAND].setAntiAlias(true);
			LimbPaint[LEFT_HAND].setARGB(255, 0, 255, 0);

			LimbPaint[RIGHT_HAND] = new Paint();
			LimbPaint[RIGHT_HAND].setAntiAlias(true);
			LimbPaint[RIGHT_HAND].setARGB(255, 255, 0, 0);
 
			mLinePaint = new Paint();
			mLinePaint.setAntiAlias(true);
			//mLinePaint.setARGB(255, 128, 128, 128);
			mLinePaint.setARGB(255, 80, 80, 80);
			mLinePaint.setStyle(Paint.Style.STROKE);
			mLinePaint.setStrokeWidth(5);

			mGreenPaint = new Paint();
			mGreenPaint.setAntiAlias(true);
			mGreenPaint.setARGB(255, 0, 80, 0);
			mGreenPaint.setStyle(Paint.Style.STROKE);
			mGreenPaint.setStrokeWidth(5);

			mBlackPaint = new Paint();
			mBlackPaint.setAntiAlias(true);
			mBlackPaint.setARGB(255, 0, 0, 0);

			mWhitePaint = new Paint();
			mWhitePaint.setAntiAlias(true);
			mWhitePaint.setARGB(255, 255, 255, 255);
			mWhitePaint.setTextAlign(Paint.Align.CENTER);
			mWhitePaint.setTextSize(HOLD_TEXTSIZE);

			mScratchRect = new RectF(0, 0, 0, 0);

			initializeLimbs();
			initializeHolds(level_num_holds);
			initialize_box2d();
		}

		void initializeLimbs() {
			Limb = new PointF[NUM_LIMBS];
			Limb[LEFT_HAND] = new PointF();
			Limb[LEFT_HAND].x = 400 - 0.5f*MAX_LIMB_SEPARATION;
			Limb[LEFT_HAND].y = 320;
			Limb[RIGHT_HAND] = new PointF();
			Limb[RIGHT_HAND].x = 400 + 0.5f*MAX_LIMB_SEPARATION;
			Limb[RIGHT_HAND].y = 320;
		}

		/**
		 * Starts the game, setting parameters for the current difficulty.
		 */
		public void doStart() {
			synchronized (mSurfaceHolder) {

				mLastTime = System.currentTimeMillis() + 100;
				setState(STATE_RUNNING);
			}
		}

		/**
		 * Pauses the physics update & animation.
		 */
		public void pause() {
			synchronized (mSurfaceHolder) {
				if (mMode == STATE_RUNNING) {
					MP.stop();
					//         				  MP.release();  // force close if this is uncommented
					Log.i(TAG, "Paused");
				}
			}
		}

		@Override
		public void run() {
			while (mRun) {
				Canvas c = null;
				try {
					c = mSurfaceHolder.lockCanvas(null);
					synchronized (mSurfaceHolder) {
						if (mMode == STATE_RUNNING) 
							updatePhysics();
						doDraw(c);
					}
				} finally {
					// do this in a finally so that if an exception is thrown
					// during the above, we don't leave the Surface in an
					// inconsistent state
					if (c != null) {
						mSurfaceHolder.unlockCanvasAndPost(c);
					}
				}
			}
		}

		/**
		 * Used to signal the thread whether it should be running or not.
		 * Passing true allows the thread to run; passing false will shut it
		 * down if it's already running. Calling start() after this was most
		 * recently called with false will result in an immediate shutdown.
		 *
		 * @param b true to run, false to shut down
		 */
		public void setRunning(boolean b) {
			mRun = b;
		}

		/**
		 * Sets the game mode. That is, whether we are running, paused, in the
		 * failure state, in the victory state, etc.
		 *
		 * @see #setState(int, CharSequence)
		 * @param mode one of the STATE_* constants
		 */
		public void setState(int mode) {
			synchronized (mSurfaceHolder) {
				setState(mode, null);
			}
		}

		/**
		 * Sets the game mode. That is, whether we are running, paused, in the
		 * failure state, in the victory state, etc.
		 *
		 * @param mode one of the STATE_* constants
		 * @param message string to add to screen or null
		 */
		public void setState(int mode, CharSequence message) {
			/*
			 * This method optionally can cause a text message to be displayed
			 * to the user when the mode changes. Since the View that actually
			 * renders that text is part of the main View hierarchy and not
			 * owned by this thread, we can't touch the state of that View.
			 * Instead we use a Message + Handler to relay commands to the main
			 * thread, which updates the user-text View.
			 */
			synchronized (mSurfaceHolder) {
				mMode = mode;

				if (mMode == STATE_RUNNING) {
					Message msg = mHandler.obtainMessage();
					Bundle b = new Bundle();
					b.putString("text", "");
					b.putInt("viz", View.INVISIBLE);
					msg.setData(b);
					mHandler.sendMessage(msg);
				} else {
					Resources res = mContext.getResources();
					CharSequence str = "";
					if (mMode == STATE_READY)
						str = res.getText(R.string.mode_ready);
					else if (mMode == STATE_PAUSE)
						str = res.getText(R.string.mode_pause);
					else if (mMode == STATE_LOSE)
						str = res.getText(R.string.mode_lose);
					else if (mMode == STATE_WIN)
						str = res.getString(R.string.mode_win_prefix) + " " 
								+ res.getString(R.string.mode_win_suffix);

					if (message != null) {
						str = message + "\n" + str;
					}

					Message msg = mHandler.obtainMessage();
					Bundle b = new Bundle();
					b.putString("text", str.toString());
					b.putInt("viz", View.VISIBLE);
					msg.setData(b);
					mHandler.sendMessage(msg);

				}
			}
		}  // setState


		/* Callback invoked when the surface dimensions change. */
		public void setSurfaceSize(int width, int height) {
			// synchronized to make sure these all change atomically
			synchronized (mSurfaceHolder) {
				mCanvasWidth = width;
				mCanvasHeight = height;

				// don't forget to resize the background image
				mBackgroundImage = Bitmap.createScaledBitmap(
						mBackgroundImage, width, height, true);
			}
		}

		/**
		 * Resumes from a pause.
		 */
		public void unpause() {
			// Move the real time clock up to now
			synchronized (mSurfaceHolder) {
				mLastTime = System.currentTimeMillis() + 100;
			}
			setState(STATE_RUNNING);
		}

		// what hold is (x, y) within reach of, if any?

		int on_hold(float x, float y) {
			for (int i = 0; i < Hold.size(); i++) {
				float dx = x - Hold.get(i).x;
				float dy = y - Hold.get(i).y;
				if (Math.sqrt(dx*dx + dy*dy) <= HOLD_GRAB_RADIUS) 
					return i;
			}
			return NO_HOLD;
		}

		// user released limb at (x, y) -- which hold is that, if any?

		boolean select_hold(float x, float y) {
			for (int i = 0; i < Hold.size(); i++) {
				float dx = x - Hold.get(i).x;
				float dy = y - Hold.get(i).y;
				if (Math.sqrt(dx*dx + dy*dy) <= HOLD_GRAB_RADIUS) {
					if (HoldVisit.get(i) == NOT_VISITED) 
						HoldVisit.set(i, num_holds_visited++);
					return true;
				}
			}
			return false;
		}

		// user touched (x, y) -- which limb is that?

		void select_limb(float x, float y) {
			double min_dist = 1000000f;
			double dist;
			float dx, dy;

			for (int i = 0; i < NUM_HANDS; i++) {
				dx = x - Limb[i].x;
				dy = y - Limb[i].y;
				dist = Math.sqrt(dx * dx + dy * dy);
				if (dist < MAX_LIMB_TOUCH_DISTANCE && dist < min_dist) {
					limb_index = i;
					min_dist = dist;
					touch_dx = dx;
					touch_dy = dy;
				}
			}
		}

		// user is trying to stretch a limb to (x, y) -- is that allowable given rest of body?

		boolean is_reachable(float x, float y) {
			double dist;
			int other_limb_index = NUM_HANDS - limb_index - 1;
			dist = Math.sqrt((x - Limb[other_limb_index].x) * (x - Limb[other_limb_index].x) + 
					(y - Limb[other_limb_index].y) * (y - Limb[other_limb_index].y));
			return (dist <= MAX_LIMB_SEPARATION);
		}

		void doTouchDown(float x, float y, float pressure, int count) {
			synchronized (mSurfaceHolder) {

				Log.i(TAG, "touch down");

				if (mMode != STATE_RUNNING) {
					setState(STATE_RUNNING);
				}

				touchDown = true;

				touch_x = x;
				touch_y = y;
				touch_pressure = pressure;

				select_limb(touch_x, touch_y);

				if (limb_index != NO_LIMB) {
					if (!is_reachable(touch_x - touch_dx, touch_y - touch_dy))
						limb_index = NO_LIMB;
					else {
						x_last_hold = Limb[limb_index].x;
						y_last_hold = Limb[limb_index].y;
					}
				}
			}
		}

		void doTouchUp(float x, float y, int count) {
			synchronized (mSurfaceHolder) {

				Log.i(TAG, "touch up");

				if (limb_index != NO_LIMB) {
					boolean result = select_hold(Limb[limb_index].x, Limb[limb_index].y);
					if (!result) {
						Limb[limb_index].x = x_last_hold;
						Limb[limb_index].y = y_last_hold;
					}
					else {    
						// done with level?
						if (num_holds_visited >= Hold.size()) {
							stopMusic();
							SP.play(sound_scratch_rewind, .5f, .5f, 0, 0, 1f);
							startRandomMusic();
							initializeLimbs();
							initializeHolds(--level_num_holds);
							num_holds_visited = 0;
						}
						// not done--just another hold
						else
							SP.play(sound_spin_jump, 1f, 1f, 0, 0, 1.5f);
					}
				}

				touchDown = false; 
				limb_index = NO_LIMB;	 
			}
		}

		void doTouchMove(float x, float y, float dx, float dy, float pressure, int count) {

			synchronized (mSurfaceHolder) {

				//        		Log.i(TAG, "touch move");

				touchDown = true;

				if (limb_index != NO_LIMB && is_reachable(x - touch_dx, y - touch_dy)) {
					touch_x = x;
					touch_y = y;
					touch_pressure = pressure;
				}
			}
		}

		// is this potential hold too close to another?
		
		private boolean checkHoldTooClose(int i, PointF p) {
			double dist;

			for (int j = 0; j < i; j++) {
				dist = Math.sqrt((p.x - Hold.get(j).x) * (p.x - Hold.get(j).x) + 
						(p.y - Hold.get(j).y) * (p.y - Hold.get(j).y));
				if (dist < MIN_HOLD_SEPARATION)
					return true;
			}
			return false;

		}

		private void update_box2d() {
			//mPhysicsWorld.step(0.1f, 3, 8);	
		}

		private void initialize_box2d() {

			/*
			mPhysicsWorld = new PhysicsWorld(new Vector2(0, SensorManager.GRAVITY_EARTH), false);

			final float CAMERA_HEIGHT = 100;
			final float CAMERA_WIDTH = 100;

			final FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0, 0.5f, 0.5f);

			PhysicsFactory.createBoxBody(mPhysicsWorld, (float) 0, (float) (CAMERA_HEIGHT - 2), 
					(float) (CAMERA_WIDTH), (float) 2, 
					(float) 0, BodyType.StaticBody, wallFixtureDef);
			PhysicsFactory.createBoxBody(mPhysicsWorld, (float) 0, (float) 0, 
					(float) (CAMERA_WIDTH), (float) 2, 
					(float) 0, BodyType.StaticBody, wallFixtureDef);
			PhysicsFactory.createBoxBody(mPhysicsWorld, (float) 0, (float) 0, 
					(float) 2, (float) (CAMERA_HEIGHT), 
					(float) 0, BodyType.StaticBody, wallFixtureDef);
			PhysicsFactory.createBoxBody(mPhysicsWorld, (float) (CAMERA_WIDTH - 2), (float) 0, 
					(float) 2, (float) (CAMERA_HEIGHT),  
					(float) 0, BodyType.StaticBody, wallFixtureDef);

			FixtureDef objectFixtureDef = PhysicsFactory.createFixtureDef(10, 0.2f, 0.5f);

			PhysicsFactory.createCircleBody(mPhysicsWorld, (float) 50, (float) 50, (float) 25, (float) 0,
					BodyType.DynamicBody, objectFixtureDef);


			final RevoluteJointDef revoluteJointDef = new RevoluteJointDef();
			revoluteJointDef.enableMotor = true;
			revoluteJointDef.motorSpeed = 10;
			revoluteJointDef.maxMotorTorque = 200;
			*/
		}

		private void initializeHolds(int n) {

			PointF p;
			int attempts;

			HoldVisit = new ArrayList<Integer>();

			Hold = new ArrayList<PointF>();
			for (int i = 0; i < n; i++) {
				p = new PointF();
				attempts = 0;
				do {
					p.x = 10f + (float) (Math.random() * 780f);
					p.y = 10f + (float) (Math.random() * 380f);
				} while (attempts++ < 100 && checkHoldTooClose(i, p));
				Hold.add(p);
				HoldVisit.add(NOT_VISITED);
			}
		}

		private void drawHolds(Canvas canvas) {

			for (int i = 0; i < Hold.size(); i++)
				drawNumberedDot(HoldVisit.get(i), Hold.get(i).x, Hold.get(i).y, HOLD_RADIUS, canvas);
		}

		private void drawNumberedDot(int number, float x, float y, float r, Canvas canvas) {

			mScratchRect.set(x - r, y - r, x + r, y + r);
			canvas.drawOval(mScratchRect, mBlackPaint);

			if (number != NOT_VISITED)
				canvas.drawText(Integer.toString(number), x, y + HOLD_TEXTSIZE/3, mWhitePaint);
		}

		/**
		 * Draws the ship, fuel/speed bars, and background to the provided
		 * Canvas.
		 */
		private void doDraw(Canvas canvas) {
			// Draw the background image. Operations on the Canvas accumulate
			// so this is like clearing the screen.
			canvas.drawBitmap(mBackgroundImage, 0, 0, null);

			if (touchDown) {

				int which_hold = on_hold(touch_x - touch_dx, touch_y - touch_dy);

				if (which_hold != NO_HOLD) {

					mScratchRect.set(Hold.get(which_hold).x - HOLD_DRAW_RADIUS, Hold.get(which_hold).y - HOLD_DRAW_RADIUS, 
							Hold.get(which_hold).x + HOLD_DRAW_RADIUS, Hold.get(which_hold).y + HOLD_DRAW_RADIUS);
					canvas.drawOval(mScratchRect, mGreenPaint);
				}
			}

			drawHolds(canvas);

			if (limb_index != NO_LIMB) {

				Limb[limb_index].x = touch_x - touch_dx;
				Limb[limb_index].y = touch_y - touch_dy;

				// debug: where did we touch exactly?
						/*
        		mScratchRect.set(touch_x - LIMB_SIZE_HALF, touch_y - LIMB_SIZE_HALF, 
								touch_x + LIMB_SIZE_HALF, touch_y + LIMB_SIZE_HALF);
            canvas.drawOval(mScratchRect, mLinePaint);
						 */

				// debug: how far will the other hand placement let us move this one?

				int other_limb_index = NUM_HANDS - limb_index - 1;
				mScratchRect.set(Limb[other_limb_index].x - MAX_LIMB_SEPARATION, Limb[other_limb_index].y - MAX_LIMB_SEPARATION, 
						Limb[other_limb_index].x + MAX_LIMB_SEPARATION, Limb[other_limb_index].y + MAX_LIMB_SEPARATION);
				canvas.drawOval(mScratchRect, mLinePaint);

				/*
        		float pressure_radius = touch_pressure * 300f;
        		mScratchRect.set(Limb[limb_index].x - pressure_radius, Limb[limb_index].y - pressure_radius, 
        					           Limb[limb_index].x + pressure_radius, Limb[limb_index].y + pressure_radius);
        		canvas.drawOval(mScratchRect, mLinePaint);
				 */
			}

			canvas.drawLine(Limb[LEFT_HAND].x, Limb[LEFT_HAND].y, Limb[RIGHT_HAND].x, Limb[RIGHT_HAND].y, mLinePaint);

			mScratchRect.set(Limb[LEFT_HAND].x - LIMB_SIZE_HALF, Limb[LEFT_HAND].y - LIMB_SIZE_HALF, 
					Limb[LEFT_HAND].x + LIMB_SIZE_HALF, Limb[LEFT_HAND].y + LIMB_SIZE_HALF);
			canvas.drawOval(mScratchRect, LimbPaint[LEFT_HAND]);

			mScratchRect.set(Limb[RIGHT_HAND].x - LIMB_SIZE_HALF, Limb[RIGHT_HAND].y - LIMB_SIZE_HALF, 
					Limb[RIGHT_HAND].x + LIMB_SIZE_HALF, Limb[RIGHT_HAND].y + LIMB_SIZE_HALF);
			canvas.drawOval(mScratchRect, LimbPaint[RIGHT_HAND]);
		}

		/**
		 * Figures the lander state (x, y, fuel, ...) based on the passage of
		 * realtime. Does not invalidate(). Called at the start of draw().
		 * Detects the end-of-game and sets the UI to the next state.
		 */
		private void updatePhysics() {
			long now = System.currentTimeMillis();

			// nothing right now...

			update_box2d();

			// Do nothing if mLastTime is in the future.
			// This allows the game-start to delay the start of the physics
			// by 100ms or whatever.
			if (mLastTime > now) return;

			double elapsed = (now - mLastTime) / 1000.0;

			mLastTime = now;

		}
	}  // end of BigWallGameThread class definition


	/** Handle to the application context, used to e.g. fetch Drawables. */
	private Context mContext;

	/** Pointer to the text view to display "Paused.." etc. */
	private TextView mStatusText;

	/** The thread that actually draws the animation */
	private BigWallGameThread thread;

	private float x_previous_touch, y_previous_touch;

	// view constructor

	public BigWallGameView(Context context, AttributeSet attrs) {
		super(context, attrs);

		// register our interest in hearing about changes to our surface
		SurfaceHolder holder = getHolder();
		holder.addCallback(this);

		// create thread only; it's started in surfaceCreated()
		// note inline definition of Handler class
		thread = new BigWallGameThread(holder, context, new Handler() {
			@Override
			public void handleMessage(Message m) {
				mStatusText.setVisibility(m.getData().getInt("viz"));
				mStatusText.setText(m.getData().getString("text"));
			}
		});

		setFocusable(true); // make sure we get key events
	}

	/**
	 * Fetches the animation thread corresponding to this BigWallGameView.
	 *
	 * @return the animation thread
	 */
	public BigWallGameThread getThread() {
		return thread;
	}

	/**
	 * Standard window-focus override. Notice focus lost so we can pause on
	 * focus lost. e.g. user switches to take a call.
	 */
	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		if (!hasWindowFocus) thread.pause();
	}

	boolean moved = false;

	// http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-3-understanding-touch-events/1775

	@Override 
	public boolean onTouchEvent(MotionEvent e) {
		// MotionEvent reports input details from the touch screen
		// and other input controls. In this case, you are only
		// interested in events where the touch position changed.

		float x_touch = e.getX();
		float y_touch = e.getY();  	

		int action = e.getAction();
		int ptrIdx;

		switch (action & MotionEvent.ACTION_MASK)
		{

		case MotionEvent.ACTION_MOVE:   			

			float dx_touch = x_touch - x_previous_touch;
			float dy_touch = y_touch - y_previous_touch;

			thread.doTouchMove(x_touch, y_touch, dx_touch, dy_touch, e.getPressure(), e.getPointerCount());
			break;

		case MotionEvent.ACTION_DOWN:
			thread.doTouchDown(x_touch, y_touch, e.getPressure(), e.getPointerCount());
			break;

		case MotionEvent.ACTION_UP:
			thread.doTouchUp(x_touch, y_touch, e.getPointerCount());
			break;
		}

		x_previous_touch = x_touch;
		y_previous_touch = y_touch;

		return true;

	}

	/**
	 * Installs a pointer to the text view used for messages.
	 */
	public void setTextView(TextView textView) {
		mStatusText = textView;
	}

	/* Callback invoked when the surface dimensions change. */
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		thread.setSurfaceSize(width, height);
	}

	/*
	 * Callback invoked when the Surface has been created and is ready to be
	 * used.
	 */
	public void surfaceCreated(SurfaceHolder holder) {
		// start the thread here so that we don't busy-wait in run()
		// waiting for the surface to be created
		thread.setRunning(true);
		thread.start();
	}

	/*
	 * Callback invoked when the Surface has been destroyed and must no longer
	 * be touched. WARNING: after this method returns, the Surface/Canvas must
	 * never be touched again!
	 */
	public void surfaceDestroyed(SurfaceHolder holder) {
		// we have to tell thread to shut down & wait for it to finish, or else
		// it might touch the Surface after we return and explode
		boolean retry = true;
		thread.setRunning(false);
		while (retry) {
			try {
				thread.join();
				retry = false;
			} catch (InterruptedException e) {
			}
		}
	}
}
