package com.example.helloandroid;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.FontMetrics;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.TextView;

/**
 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
 * 
 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
 * updatePhysics() advances the physics based on realtime. draw() renders the
 * ship, and does an invalidate() to prompt another draw() as soon as possible
 * by the system.
 */
class GameView extends SurfaceView implements SurfaceHolder.Callback {
    class DrawingThread extends Thread {
        /*
         * State-tracking constants
         */
        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;

        /*
         * UI constants (i.e. the speed & fuel bars)
         */
        public static final int UI_BAR = 100; // width of the bar(s)
        public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)

        /*
         * Member (state) fields
         */

        private int mCanvasHeight = 1;
        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 the lines on screen. */
        private Paint mLinePaint, mTextPaint;

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

        /** Handle to the surface manager object we interact with */
        private SurfaceHolder mSurfaceHolder;
        
        public Object planetsLock, fleetsLock;
        
        public ArrayList<Planet> planets;
        public ArrayList<Fleet> fleets;
        public Planet planetSelected;
        
        int mFleetSize;

        public DrawingThread(SurfaceHolder surfaceHolder, Context context,
                Handler handler) {
            // get handles to some important objects
            mSurfaceHolder = surfaceHolder;
            mHandler = handler;
            mContext = context;

            // Initialize paints for speedometer
            mLinePaint = new Paint();
            mLinePaint.setAntiAlias(true);
            mLinePaint.setARGB(255, 0, 255, 0);

            mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);
            mTextPaint.setARGB(255, 255, 255, 255);
        
            planetsLock = new Object();
            fleetsLock = new Object();
            
            planets = new ArrayList<Planet>();
            planetSelected = null;
            
            fleets = new ArrayList<Fleet>();
            
            mFleetSize = 50;
        }

        /**
         * 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) setState(STATE_PAUSE);
            }
        }

        @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", GameView.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);

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

                    Message msg = mHandler.obtainMessage();
                    Bundle b = new Bundle();
                    b.putString("text", str.toString());
                    b.putInt("viz", GameView.VISIBLE);
                    msg.setData(b);
                    mHandler.sendMessage(msg);
                }
            }
        }
        
        /* 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;
                
                Log.i("Gencon", "width: "+mCanvasWidth+", height: "+mCanvasHeight);
                
                Random rand = new Random();

                synchronized(planetsLock) {
	                for(int x=0; x<15; x++) {
	                	Planet p = new Planet(rand.nextInt(45)+5, rand.nextInt(mCanvasWidth), rand.nextInt(mCanvasHeight));
	                	
	                	if(Planet.collisionDetected(p, planets)) {
	                		x--;
	                	}else if(p.getX()-p.getRadius() < 0 || mCanvasWidth-20<=p.getX()+p.getRadius() ||
	                			 p.getY()-p.getRadius() < 0 || mCanvasHeight-20<=p.getY()+p.getRadius()) {
	                		x--;
	                	}else {
	                		p.setNumShips(rand.nextInt(150));
	                		p.setFaction(rand.nextInt(5));
	                		planets.add(p);
	                	}
	                }
                }
            }
        }

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

        /**
         * Handles a key-down event.
         * 
         * @param keyCode the key that was pressed
         * @param msg the original event object
         * @return true
         */
        boolean doKeyDown(int keyCode, KeyEvent msg) {
            synchronized (mSurfaceHolder) {
                boolean okStart = false;
                if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
                if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
                if (keyCode == KeyEvent.KEYCODE_S) okStart = true;

                if (okStart
                        && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
                    // ready-to-start -> start
                    doStart();
                    return true;
                } else if (mMode == STATE_PAUSE && okStart) {
                    // paused -> running
                    unpause();
                    return true;
                } else if (mMode == STATE_RUNNING) {
                    return true;
                }

                return false;
            }
        }

        /**
         * Handles a key-up event.
         * 
         * @param keyCode the key that was pressed
         * @param msg the original event object
         * @return true if the key was handled and consumed, or else false
         */
        boolean doKeyUp(int keyCode, KeyEvent msg) {
            boolean handled = false;

            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) {
                    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                            || keyCode == KeyEvent.KEYCODE_SPACE) {
                        handled = true;
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
                            || keyCode == KeyEvent.KEYCODE_Q
                            || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
                            || keyCode == KeyEvent.KEYCODE_W) {
                        handled = true;
                    }
                }
            }

            return handled;
        }

        /**
         * Draws the ship, fuel/speed bars, and background to the provided
         * Canvas.
         */
        private void doDraw(Canvas canvas) {
        	canvas.drawColor(Color.BLACK);
        	
        	synchronized(planetsLock) {
        		for(Planet p : planets) {
            		p.draw(canvas, mLinePaint, mTextPaint);
            	}
        	}
            
        	if(planetSelected != null) {
    			planetSelected.drawSelectionCircle(canvas);
    		}
        	
        	synchronized(fleetsLock) {
        		for(Fleet f : fleets) {
            		f.draw(canvas, mLinePaint);
            	}
        	}
        	
        	float textSize = mTextPaint.getTextSize();
        	mTextPaint.setTextSize(24);
        	FontMetrics metrics = mTextPaint.getFontMetrics();
        	mTextPaint.setColor(Color.WHITE);
        	
        	canvas.drawText(mFleetSize+"%", mCanvasWidth-mTextPaint.measureText(mFleetSize+"%"), mCanvasHeight-20-(metrics.ascent+metrics.descent), mTextPaint);
        	
        	mTextPaint.setTextSize(textSize);
        	
        	mLinePaint.setColor(Color.YELLOW);
        	canvas.drawRoundRect(new RectF(70, mCanvasHeight-15, mCanvasWidth-70, mCanvasHeight-5), 5, 5, mLinePaint);
        	
        	mLinePaint.setColor(Color.GREEN);
        	canvas.drawRoundRect(new RectF(70, mCanvasHeight-15, 70+(mCanvasWidth-140)*mFleetSize/100, mCanvasHeight-5), 5, 5, mLinePaint);
        }

        /**
         * 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();

            // 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;
            
            synchronized(planetsLock) {
            	for(Planet p : planets) {
            		p.update();
            	}
            }
            
            synchronized(fleetsLock) {
            	Iterator<Fleet> i = fleets.iterator();
            	Fleet f = null;
            	while(i.hasNext()){
            		f = i.next();
            		if(f.getNumShips() == 0) {
            			i.remove();
            		}else
            			f.update(planets);
            	}
            }

            mLastTime = now+50;
        }
    }

    /** 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 DrawingThread thread;

    public GameView(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()
        thread = new DrawingThread(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
    }

    @Override public boolean onTouchEvent(MotionEvent event) {
    	Log.i("Gencon", "Detected touch event");
    	
    	if(event.getAction() == MotionEvent.ACTION_UP) {
    		if(70 <= event.getX() && event.getX() <= thread.mCanvasWidth-70 &&
    		   thread.mCanvasHeight-15 <= event.getY() && event.getY() <= thread.mCanvasHeight-5) {
    			thread.mFleetSize = ((int)event.getX()-70)*100/(thread.mCanvasWidth-140);
    		}
    	}else if(event.getAction() == MotionEvent.ACTION_DOWN) {
	    	synchronized(thread.planetsLock) {
	    		if(thread.planetSelected != null) {
	    			Planet target = null;
	    			
	    			for(Planet p : thread.planets) {
			        	if(p.contains((int)event.getX(), (int)event.getY())) {
			        		target = p;
			        		break;
			        	}
			        }
	    			
	    			if(target != null && target != thread.planetSelected && thread.planetSelected.getFaction() != 0) {
	    				synchronized(thread.fleetsLock) {
	    	                	Fleet f = new Fleet(thread.planetSelected, target, thread.planetSelected.getNumShips()*thread.mFleetSize/100, thread.planetSelected.getFaction());
	    	                	f.setFaction(thread.planetSelected.getFaction());
	    	                	thread.fleets.add(f);
	                    }
	    			}
	    				
	    			thread.planetSelected.unselect();
	    			thread.planetSelected = null;
	    		}else {
			        for(Planet p : thread.planets) {
			        	if(p.contains((int)event.getX(), (int)event.getY())) {
			        		p.select();
			        		thread.planetSelected = p;
			        		break;
			        	}
			        }
	    		}
	    	}
    	}
        
        return true;
    }
    
    /**
     * Fetches the animation thread corresponding to this LunarView.
     * 
     * @return the animation thread
     */
    public DrawingThread getThread() {
        return thread;
    }

    /**
     * Standard override to get key-press events.
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {
        return thread.doKeyDown(keyCode, msg);
    }

    /**
     * Standard override for key-up. We actually care about these, so we can
     * turn off the engine or stop rotating.
     */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent msg) {
        return thread.doKeyUp(keyCode, msg);
    }

    /**
     * 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();
    }

    /**
     * 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) {
            }
        }
    }
}
