/*
 * Land Sea Air -- an interactive music game
 * demonstrating the "secret yanni" techique, with key changes
 * design, animation, audio, and code by peter "pdx" drescher
 * twittering machine
 * 
 */
package com.twittering.landseaair;

import java.util.Random;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.Gravity;
import android.widget.Toast;

public class AirAnimation {

    private Bitmap mDaySky;
    private Bitmap mNightSky;
    private Bitmap mDay2Night;
    private Bitmap mNight2Day;
	private Bitmap bottomBackground;
	private Bitmap topBackground;

    private Bitmap mSprite[] = new Bitmap[24];
    private Bitmap mBugs[] = new Bitmap[2];
    private Bitmap mBlueBF[] = new Bitmap[2];
    private Bitmap mGreenBF[] = new Bitmap[2];
    private Bitmap mRedBF[] = new Bitmap[2];
	private Bitmap mButterfly[] = new Bitmap[2];

    private Random mRnd = new Random();
	FMODaudio mFMODaudio = new FMODaudio();
    public static final float KEY_OF_C  = 0f; // parameter value
    public static final float KEY_OF_E  = 1f; 
    public static final float KEY_OF_Ab = 2f;
    public static final float KEY_OF_Cmin  = 3f;
    public static final float KEY_OF_Emin  = 4f; 
    public static final float KEY_OF_Abmin = 5f;
    private Canvas mCanvas;
    private Context mContext;
	
    public static final int FPS = 30; // milliseconds
    public static final int BUG_RELEASE = 5000; // milliseconds
    public static final int BF_RELEASE = 7000; // milliseconds
    public static final int DRUNKARDS_WALK_AMT = 50; // pixels
    public static final int BUGY_SPEED = 6; // pixels
    public static final int BUGX_SPEED = 3; // pixels
    public static final int BFY_SPEED = 6; // pixels
    public static final int BFX_SPEED = 6; // pixels

    private float mSpriteX = 0;
    private float mSpriteY = 0;
    private static final float SPRITE_SIZE = 64; //pixels
    private static final int SPRITE_DEGREES = 15;
    private static final int BUG_SIZE = 16; //pixels
    private static final int BF_SIZE = 32; //pixels
    private static int backgroundHeight;
    
    private int topBackgroundY = 0;
	private int bottomBackgroundY = 0;
    private int nextBugX[] = {0,0,0}; // tracking three bugs
    private int mBugX[] = {0,0,0};
    private int mBugY[] = {0,0,0};
    private int mBugFrame[] = {0,0,0};
    private int nextBFX = 0;
    private int mBFX = 0;
    private int mBFY = 0;
    private int mBFFrame = 0;
	private boolean bugSwallowed[] = {false, true, true}; // start with first bug uneaten
	private boolean bfSwallowed = false;
	private boolean bfOffScreen = true;
	private boolean daylight = true;
	private boolean BGtransition = false;
	private boolean countBugs = true;
	private int bugSwallowedCtr = 0;
    private int mSpriteDirection = 0;
    private float prevX = 0;
    private float prevY = 0;
    private float keyChange = 0;
    private int prevKey, currKey = 0;
	private Activity mActivity;
    
    public long mGameTimer;
	private long mBugFrameTimer[] = new long[3];
    private long mBugTimer[] = new long[3];
	private long mBFFrameTimer;
    private long mBFTimer;

    public AirAnimation() { }
	
	public void init (Context context) {
    	mContext = context;
		mActivity = (Activity) context;
    	
        mSprite[0]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow015);
        mSprite[1]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow030);
        mSprite[2]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow045);
        mSprite[3]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow060);
        mSprite[4]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow075);
        mSprite[5]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow090);
        mSprite[6]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow105);
        mSprite[7]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow120);
        mSprite[8]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow135);
        mSprite[9]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow150);
        mSprite[10] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow165);
        mSprite[11] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow180);
        mSprite[12] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow195);
        mSprite[13] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow210);
        mSprite[14] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow225);
        mSprite[15] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow240);
        mSprite[16] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow255);
        mSprite[17] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow270);
        mSprite[18] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow285);
        mSprite[19] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow300);
        mSprite[20] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow315);
        mSprite[21] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow330);
        mSprite[22] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow345);
        mSprite[23] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_swallow000);

        //scale the background bitmaps to fit the screen width
        Bitmap fromRes;
        int screenWidth = (int) GameSurfaceView.mScreenWidth;
        fromRes    = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_dayskylp);
        mDaySky    = Bitmap.createScaledBitmap(fromRes, screenWidth, fromRes.getHeight(), true);
        fromRes    = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_nightsky);
        mNightSky  = Bitmap.createScaledBitmap(fromRes, screenWidth, fromRes.getHeight(), true);
        fromRes    = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_day2night);
        mDay2Night = Bitmap.createScaledBitmap(fromRes, screenWidth, fromRes.getHeight(), true);
        fromRes    = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_night2day);
        mNight2Day = Bitmap.createScaledBitmap(fromRes, screenWidth, fromRes.getHeight(), true);
        backgroundHeight = mDaySky.getHeight();
        
        mBugs[0]    = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_fly1_16);
        mBugs[1]    = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_fly2_16);
        mBlueBF[0]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_bluebf1_32);
        mBlueBF[1]  = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_bluebf2_32);
        mGreenBF[0] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_greenbf1_32);
        mGreenBF[1] = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_greenbf2_32);
        mRedBF[0]   = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_redbf1_32);
        mRedBF[1]   = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.air_redbf2_32);
		mButterfly  = mBlueBF;
        
		mBugTimer[0] = System.currentTimeMillis();
		mBugTimer[1] = System.currentTimeMillis();
		mBugTimer[2] = System.currentTimeMillis();
		mBFTimer  = System.currentTimeMillis();
	    startAnimation();
    }

	public void startAnimation() {
	    topBackgroundY = 0;
    	bottomBackground = mDaySky;
    	topBackground = mDaySky;
		BGtransition = false;
    	daylight = true;
		bugSwallowed[0] = false;
		bugSwallowed[1] = true;
		bugSwallowed[2] = true;
		bfSwallowed = false;
		bfOffScreen = true;
		daylight = true;
		countBugs = true;
		bugSwallowedCtr = 0;
	    keyChange = 0; prevKey = 0; currKey = 0;
        showKeyToast();
	}
	
	public void updatePhysics(float mSensorX[], float mSensorY[]) {	
		int length = mSensorX.length;
		int i = 0;
		float totalSensorX = 0f;
		float totalSensorY = 0f;
		while (i < length){
			totalSensorX += mSensorX[i] * 5f;
			totalSensorY += mSensorY[i] * 5f;
			i += 1;
		}
		float averageSensorX = totalSensorX / length;
		float averageSensorY = totalSensorY / length;
		
		//infinite field: if sprite hits edge, transport to other side
		mSpriteX = mSpriteX - averageSensorX;
		if (mSpriteX >= GameSurfaceView.mScreenWidth) {
			mSpriteX = -SPRITE_SIZE;
		}
		if (mSpriteX < -SPRITE_SIZE) {
			mSpriteX = GameSurfaceView.mScreenWidth;
		}

		mSpriteY = mSpriteY - averageSensorY;
		if (mSpriteY >= GameSurfaceView.mScreenHeight) {
			mSpriteY = -SPRITE_SIZE;
		}
		if (mSpriteY < -SPRITE_SIZE) {
			mSpriteY = GameSurfaceView.mScreenHeight;
		}
	}
	
	private void checkCollision() {
		//make a rect = half the size of the sprite (in ma belly!)
		Rect spriteRect = new Rect((int) (mSpriteX + SPRITE_SIZE * 0.25),
				 (int) (mSpriteY + SPRITE_SIZE * 0.25),
				 (int) (mSpriteX + SPRITE_SIZE * 0.75),
				 (int) (mSpriteY + SPRITE_SIZE * 0.75));

		//check if it intersects one of the three bugs
		for (int i=0; i<3;i++) {
			if (!bugSwallowed[i]) {
				Rect bugRect = new Rect(mBugX[i], mBugY[i], mBugX[i] + BUG_SIZE, mBugY[i] + BUG_SIZE);	
				if (Rect.intersects(spriteRect, bugRect)) {
					mBugTimer[i] = mGameTimer;
					bugSwallowed[i] = true;
					mFMODaudio.playBonusSound();
					if (countBugs) {
						bugSwallowedCtr++;
						if (bugSwallowedCtr > 6) {
							toggleDaylight();
						}		
					}
				}
			}
		}
		
		//check if it intersects butterfly
		if (!bfSwallowed && !bfOffScreen) {
			Rect bfRect = new Rect(mBFX, mBFY, mBFX + BF_SIZE, mBFY + BF_SIZE);
			if (Rect.intersects(spriteRect, bfRect)) {
				mBFTimer = mGameTimer;
				bfSwallowed = true;
				mFMODaudio.playBFSound();
				mFMODaudio.changeKey(keyChange);
			}
		}
	}

	private void toggleDaylight() {
		if (daylight) {
			daylight = false;
		} else {
			daylight = true;
		}
		BGtransition = true;
		bugSwallowedCtr = 0;
	}
	
	public void doDraw(Canvas canvas) {	
		mCanvas = canvas;
		drawBackground();
		drawBugs();
		drawBFs();
		drawSprite();
		checkCollision();
	}
	
	private void drawBackground() {		
		currKey = mFMODaudio.checkAirKey();
		if (currKey != prevKey) {
			showKeyToast();
		}

		//scroll background up, wrapping from below with the correct background 
	    topBackgroundY -= 3;
	    bottomBackgroundY = topBackgroundY + backgroundHeight;
	    if (bottomBackgroundY <= 0) {
	    	bottomBackgroundY = backgroundHeight;
	    	topBackgroundY = 0;
			topBackground = bottomBackground;
			if (daylight) {
				if (BGtransition) {  		//transition to daytime background
					bottomBackground = mNight2Day;
					BGtransition = false;
					countBugs = false;
					mFMODaudio.setMode(FMODaudio.TRANS_TO_DAY);
				} else {				    //daytime
					bottomBackground = mDaySky;
					countBugs = true;
					mFMODaudio.setMode(FMODaudio.DAYTIME);
				}
			} else {
				if (BGtransition) {  		//transition to nighttime background
					bottomBackground = mDay2Night;
					BGtransition = false;
					countBugs = false;
					mFMODaudio.setMode(FMODaudio.TRANS_TO_NIGHT);
				} else {				    //nighttime
					bottomBackground = mNightSky;
					countBugs = true;
					mFMODaudio.setMode(FMODaudio.NIGHTTIME);
				}
			}
	    }
        mCanvas.drawBitmap(topBackground,    0, topBackgroundY, null);
        mCanvas.drawBitmap(bottomBackground, 0, bottomBackgroundY, null);
	}
	
	private void showKeyToast () {
		mActivity.runOnUiThread(new Runnable() {
		    public void run() {
				CharSequence keyText;
				switch (currKey) {
				case 0:keyText = "Key of C Major";  break;
				case 1:keyText = "Key of E Major";  break;
				case 2:keyText = "Key of Ab Major"; break;
				case 3:keyText = "Key of C minor";  break;
				case 4:keyText = "Key of E minor";  break;
				case 5:keyText = "Key of Ab minor"; break;
		    	default: keyText = "???"; break;
		    	}
				Toast keyToast = Toast.makeText(mActivity, keyText, Toast.LENGTH_SHORT);
		    	keyToast.setGravity(Gravity.TOP|Gravity.LEFT, 0, 0);
		    	keyToast.show();
		    }
		});
		prevKey = currKey;
	}
	
	private void drawBugs() {
		for (int i=0;i<3;i++) {
			drawABug(i);
		}
	}
	
	private void drawABug(int bugNumber) {
		// when a bug gets swallowed, wait an increasing amount of time per bug, then re-release the bug
		if (bugSwallowed[bugNumber]) {
			if (mGameTimer > mBugTimer[bugNumber] + (BUG_RELEASE + (bugNumber*7000))) {
				bugSwallowed[bugNumber] = false;
				mBugY[bugNumber] = (int) (GameSurfaceView.mScreenHeight + BUG_SIZE);
			} else {
				return;  // don't draw a bug
			}
		}
		
    	// for each frame, get the next sprite of the current bug, and move down the Y axis
		if( mGameTimer > mBugFrameTimer[bugNumber] + FPS) {
			mBugFrameTimer[bugNumber] = mGameTimer;
	        mBugFrame[bugNumber]++;
	        if (mBugFrame[bugNumber] >= 2) {
	        	mBugFrame[bugNumber] = 0;
	        }
	        
    		mBugY[bugNumber] -= BUGY_SPEED + bugNumber; // vary the speed per bug
    		if (mBugY[bugNumber] < -BUG_SIZE) {
    			mBugY[bugNumber] = (int) (GameSurfaceView.mScreenHeight + BUG_SIZE);
    		}
    		
    		// determine the X axis using a "drunkard's walk" (brownian motion) algorithm:
    		// flip a coin to determine which direction to move a small random amount, then move to that point incrementally
    		if (mBugX[bugNumber] < nextBugX[bugNumber]) {
    			mBugX[bugNumber] += BUGX_SPEED + bugNumber; // vary the speed per bug
    			if (mBugX[bugNumber] >= nextBugX[bugNumber]) {
    				mBugX[bugNumber] = nextBugX[bugNumber];
    			}
    			
    		} else
    		if (mBugX[bugNumber] > nextBugX[bugNumber]) {
    			mBugX[bugNumber] -= BUGX_SPEED + bugNumber; // vary the speed per bug
    			if (mBugX[bugNumber] <= nextBugX[bugNumber]) {
    				mBugX[bugNumber] = nextBugX[bugNumber];
    			}
    			
    		} else
    		if (mBugX[bugNumber] == nextBugX[bugNumber]) {
	    		int flipCoin = mRnd.nextInt(2);
	    		if (flipCoin == 0) {
	    			nextBugX[bugNumber] += mRnd.nextInt(DRUNKARDS_WALK_AMT);
	    		} else {
	    			nextBugX[bugNumber] -= mRnd.nextInt(DRUNKARDS_WALK_AMT);
	    		}
	    		
	    		// bounce off screen edges
	    		if (nextBugX[bugNumber] > GameSurfaceView.mScreenWidth - BUG_SIZE) {
	    			nextBugX[bugNumber] -= DRUNKARDS_WALK_AMT;
	    		} else if (nextBugX[bugNumber] < 0) {
	    			nextBugX[bugNumber] += DRUNKARDS_WALK_AMT;
	    		}
    		}
		}
        mCanvas.drawBitmap(mBugs[mBugFrame[bugNumber]], mBugX[bugNumber], mBugY[bugNumber], null);
	}
	
	private void drawBFs() {
		// when a butterfly gets swallowed or goes offscreen, wait 10 seconds, and release another
		if (bfSwallowed || bfOffScreen) {
			if (mGameTimer > mBFTimer + BF_RELEASE) {
				bfSwallowed = false;
				bfOffScreen = false;
				mBFY = (int) (GameSurfaceView.mScreenHeight + BF_SIZE);
				setBFcolor();
			} else {
				return;  // don't draw a butterfly
			}
		}
		
    	// for each frame, get the next sprite, and move down the Y axis (butterflies flap their wings half as fast as bugs)  
		if( mGameTimer > mBFFrameTimer + (FPS*2)) {
			mBFFrameTimer = mGameTimer;
	        mBFFrame++;
	        if (mBFFrame >= mButterfly.length) {
	        	mBFFrame = 0;
	        }
	        
    		mBFY -= BFY_SPEED;
    		if (mBFY < -BF_SIZE) {
				mBFTimer = mGameTimer;
				bfOffScreen = true;
    		}
    		
    		// determine the X axis using a "drunkard's walk" (brownian motion) algorithm:
    		// flip a coin to determine which direction to move a small random amount, then move to that point incrementally
    		if (mBFX < nextBFX) {
    			mBFX += BFX_SPEED;
    			if (mBFX >= nextBFX) {
    				mBFX = nextBFX;
    			}
    			
    		} else
    		if (mBFX > nextBFX) {
    			mBFX -= BFX_SPEED;
    			if (mBFX <= nextBFX) {
    				mBFX = nextBFX;
    			}
    			
    		} else
    		if (mBFX == nextBFX) {
	    		int flipCoin = mRnd.nextInt(2);
	    		if (flipCoin == 0) {
	    			nextBFX += mRnd.nextInt(DRUNKARDS_WALK_AMT);
	    		} else {
	    			nextBFX -= mRnd.nextInt(DRUNKARDS_WALK_AMT);
	    		}
	    		
	    		// bounce off screen edges
	    		if (nextBFX > GameSurfaceView.mScreenWidth - BF_SIZE) {
	    			nextBFX -= DRUNKARDS_WALK_AMT;
	    		} else if (nextBFX < 0) {
	    			nextBFX += DRUNKARDS_WALK_AMT;
	    		}
    		}
		}
        mCanvas.drawBitmap(mButterfly[mBFFrame], mBFX, mBFY, null);
	}
	
	private void setBFcolor() {
		
		// check current key (Major or minor) and set color of butterfly to one of the other keys
		int whatColor;
		if (currKey == KEY_OF_C ||
			currKey == KEY_OF_Cmin) { // blue butterfly
			whatColor = mRnd.nextInt(2);
			switch (whatColor) {
			case 0:
				mButterfly = mRedBF;
				keyChange = KEY_OF_Ab;
				break;
			case 1:
				mButterfly = mGreenBF;
				keyChange = KEY_OF_E;
				break;
			}
		} else
		if (currKey == KEY_OF_E ||
			currKey == KEY_OF_Emin	) { // green butterfly
			whatColor = mRnd.nextInt(2);
			switch (whatColor) {
			case 0:
				mButterfly = mBlueBF;
				keyChange = KEY_OF_C;
				break;
			case 1:
				mButterfly = mRedBF;
				keyChange = KEY_OF_Ab;
				break;
			}
		} else
		if (currKey == KEY_OF_Ab ||
			currKey == KEY_OF_Abmin) { //red butterfly
			whatColor = mRnd.nextInt(2);
			switch (whatColor) {
			case 0:
				mButterfly = mBlueBF;
				keyChange = KEY_OF_C;
				break;
			case 1:
				mButterfly = mGreenBF;
				keyChange = KEY_OF_E;
				break;
			}
		}
	}
	
	private void drawSprite() {
        final float x = mSpriteX;
        final float y = mSpriteY;
        final double dblX = x;
        final double dblY = y;
        final double dblPX = prevX;
        final double dblPY = prevY; 
        final double distY = dblPY - dblY;
        final double distX = dblPX - dblX;

        if (Math.abs(distY) > 1d || Math.abs(distX) > 1d) { // prevents jitter when floating
            int angle = (int) Math.toDegrees(Math.atan2(distY, distX));
            if (angle >= 0){
            	mSpriteDirection = angle / SPRITE_DEGREES;	
            } else {
            	mSpriteDirection = ((angle + 180) + 180) / SPRITE_DEGREES;
            }
        }
        
        mCanvas.drawBitmap(mSprite[mSpriteDirection], mSpriteX, mSpriteY, null);
        
        prevX = x;
        prevY = y;
	}
	
	public void cleanAir() {
		int i;
	    mDaySky.recycle(); mDaySky = null;
	    mNightSky.recycle(); mNightSky = null;
	    mDay2Night.recycle(); mDay2Night = null;
	    mNight2Day.recycle(); mNight2Day = null;
		bottomBackground.recycle(); bottomBackground = null;
		topBackground.recycle();  topBackground = null;

		for (i=0;i<24;i++){
			mSprite[i].recycle(); mSprite[i] = null;
		}
		for (i=0;i<2;i++) {
			mBugs[i].recycle(); mBugs[i] = null;
			mBlueBF[i].recycle(); mBlueBF[i] = null;
			mGreenBF[i].recycle(); mGreenBF[i] = null;
			mRedBF[i].recycle(); mRedBF[i] = null;
		}
		System.gc();
	}
}
