//	Contour - Android drawing teacher application
//	Copyright (C) 2014  Johannes Riedmann
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//	
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//	
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.j0h1.contour;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Locale;

import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;
import org.opencv.core.Size;

import com.j0h1.contour.R;
import com.j0h1.contour.database.ContourDatabase;
import com.j0h1.contour.pojo.Image;
import com.j0h1.contour.serialized.CustomPath;
import com.sun.jersey.core.util.Base64;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

/**
 * This activity contains the whole game logic.
 * @author Johannes Riedmann
 *
 */
public class GameActivity extends Activity {
	
	private DrawingView drawingView;
	
	private Bitmap drawing;
	private Bitmap scoreImage;
	
	private Image currentEdgeImage;
	
	private double currentScore;
	private double overallScore;
	
	private TextView scoreView;
	
	private Toast toast;
	private ProgressDialog progressDialog;
	
	private String sessionType;
	
	private boolean sessionFinished = false;
	
	public static boolean GAME_STARTED = false;
	
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        
        setContentView(R.layout.activity_game);
        
        // request the recent screen orientation
        ContourDatabase db = new ContourDatabase(getApplicationContext());
        int recentScreenOrientation = db.getRecentScreenOrientation();
		db.close();
        
        // before paths are drawn to the canvas, the screen has to be forced to the recent
    	// orientation to avoid rescaling every single path on startup
		// recentScreenOrientation is 0 when the session was not continued
		if (recentScreenOrientation != 0) {
	    	if (recentScreenOrientation == 1) {
	    		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
	    	} else {
	    		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
	    	}
		}
        
        drawingView = (DrawingView) findViewById(R.id.drawingView);        
        scoreView = (TextView) findViewById(R.id.txtScore);
        
        // inflate game controls into a View
        View controlView = new View(getApplicationContext());
        LayoutInflater inflater = (LayoutInflater) getApplicationContext().
        		getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        controlView = inflater.inflate(R.layout.layout_buttonrow, null);        
        
        FrameLayout containerBottom = (FrameLayout) findViewById(R.id.containerBottom);
        containerBottom.addView(controlView);        
        
        // call DrawingView's undo method and give feedback to the user
        // when undo button is clicked
        ImageButton imbUndo = (ImageButton) findViewById(R.id.imbUndo);
        imbUndo.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				if (!drawingView.onClickUndo()) {
					prepareToast(getString(R.string.no_paths_to_undo));
					toast.show();
				}
			}
        });
        
        // call DrawingView's redo method and give feedback to the user
        // when redo button is clicked
        ImageButton imbRedo = (ImageButton) findViewById(R.id.imbRedo);
        imbRedo.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				if (!drawingView.onClickRedo()) {
					prepareToast(getString(R.string.no_undone_paths));
					toast.show();
				}
			}
        });
        
        // when the reset button is clicked, prompt the user and ask if
        // he really wants to delete his drawing - if he accepts call
        // DrawingView's reset method, otherwise do nothing
        ImageButton imbReset = (ImageButton) findViewById(R.id.imbReset);
        imbReset.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				new AlertDialog.Builder(GameActivity.this)
		    	.setTitle(getString(R.string.clear_drawing_dialog_title))
		    	.setMessage(getString(R.string.clear_drawing_dialog_message))
		    	.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
			        public void onClick(DialogInterface dialog, int which) {
			        	drawingView.onClickReset();
			        }
		    	})
		    	.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
			        public void onClick(DialogInterface dialog, int which) {
			        	// do nothing when user decided to continue the session
			        }
			     })
			    .show();
			}
        });
        
        // when the user clicks the next button, either calculate the score image
        // and give feedback or switch to the next image (more information in method
        // description)
        final ImageButton imbNext = (ImageButton) findViewById(R.id.imbNext);
        imbNext.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				if (imbNext.getTag().equals("done")) {
					// disable control buttons, that user is not able to interact with
		    		// them as long as the score image calculation is running
		    		setControlButtonsEnabled(false);
        		}
        
				onClickNext();
			}
        });
        
        // MainMenuActivity passes an extra String to this Activity that
        // declares the session type to be a new session or a existing
        // (continued) session
        Intent sessionIntent = getIntent();
        sessionType = sessionIntent.getStringExtra("NewOrExistingSession");
        
        // set DrawingView's orientation constant (needed to accurately
        // adjust the paths to the DrawingView)
        if (recentScreenOrientation == 1) {
        	DrawingView.IS_PORTRAIT_MODE = true;
        } else if (recentScreenOrientation == 2){
        	DrawingView.IS_PORTRAIT_MODE = false;
        }
        
        // if there is no recent screen orientation (first image displayed)
        if (recentScreenOrientation == 0) {
        	// tell DrawingView which mode is currently displayed
        	if (getResources().getConfiguration().orientation == 
        			Configuration.ORIENTATION_PORTRAIT) {
        		DrawingView.IS_PORTRAIT_MODE = true;
        	} else if (getResources().getConfiguration().orientation == 
        			Configuration.ORIENTATION_LANDSCAPE) {
        		DrawingView.IS_PORTRAIT_MODE = false;
        	}
        }
        
        // set recent screen orientation of DrawingView
		drawingView.setRecentScreenOrientation(recentScreenOrientation);
        
        initializeDrawingSession();
    }
	
	/**
	 * The activity handles the orientation changes itself, so on every orientation
	 * change (portrait to landscape and vice versa) this method is called.
	 */
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
		
		// only react to configuration changes when app is started
	    if (GAME_STARTED) {
		    // the sizeChanged attribute is necessary to decide if a update of
		    // the recent screen orientation in the local database is needed or not
		    if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
		    	DrawingView.IS_PORTRAIT_MODE = true;
		    	drawingView.setSizeChanged(true);
		    } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
		    	DrawingView.IS_PORTRAIT_MODE = false;
		    	drawingView.setSizeChanged(true);
		    }
		    
		    setUpLayoutListenerForDrawingViewRescaling();
	    }
    }
	
	/**
	 * Initialize the drawing session by attaching an edge image to the
	 * DrawingView and setting the header data. If the session is continued
	 * and some paths are persisted locally, these paths are set to the 
	 * DrawingView.
	 */
	private void initializeDrawingSession() {
		if (sessionType.equals("newSession")) {
        	// get the first edge image from the local database
			ContourDatabase db = new ContourDatabase(getApplicationContext());
			currentEdgeImage = db.getImageAtPosition(1);
			db.close();
			
			// set header data (image position, image name and score)
        	setHeaderData(currentEdgeImage);
        } else if (sessionType.equals("existingSession")) {
        	// when session is continued, get the last image that has no drawing
        	// assigned to it
        	ContourDatabase db = new ContourDatabase(getApplicationContext());
        	currentEdgeImage = db.getLastImage();
        	ArrayList<CustomPath> paths = db.getPathListForImage(currentEdgeImage.getImageId());
        	db.close();
        	
        	drawingView.drawPreviousPaths(paths);
        	
        	setHeaderData(currentEdgeImage);
        }
		
		// when getLastImage() returns null, the session is already finished 
		// and the following steps can be skipped
		if (!sessionFinished) {
			final FrameLayout containerTop = (FrameLayout) findViewById(R.id.containerTop);
	        
	        // wait for the layout to be finalized to get the parent's actual width/height
			containerTop.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
				@TargetApi(Build.VERSION_CODES.JELLY_BEAN) 
				@Override
				public void onGlobalLayout() {
		        	// set up DrawingView (scale towards image proportions, set background)
		        	setUpDrawingView(containerTop.getWidth(), containerTop.getHeight());
		        	
		        	// remove this listener again, so it's not called again
		        	containerTop.getViewTreeObserver().removeOnGlobalLayoutListener(this);
		        	
		        	// after drawing the paths, the sensor should be responsible for listening to
                	// orientation changes again
                	setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
                	
                	GAME_STARTED = true;
				}
	    	});
		}
	}
    
	/**
	 * When the user closes the game, the current progress (paths of the drawing)
	 * need to be stored. If the activity is paused (user leaves the game)
	 */
    @Override
    public void onPause() {
    	super.onPause();
    	
    	// only persist paths and recent screen orientation when session is not finished
    	if (!sessionFinished) {
    		// if DrawingView's size did not change, do not update recent screen orientation
    		if (drawingView.isSizeChanged()) {
    			ContourDatabase db = new ContourDatabase(getApplicationContext());
    			db.updateRecentScreenOrientation(getResources().getConfiguration().orientation);
    			db.close();
    		}
    		
        	ImageButton nextButton = (ImageButton) findViewById(R.id.imbNext);        	
        	// if tag equals "next", paths already persisted and DrawingView locked
	    	if (nextButton.getTag().equals("done"))  {
	    		persistPaths();
	    	}
    	}
    }
    
    /**
     * Sets the header data for the session and the current drawing.
     * @param image	Image, whose name and position is shown in the 
     * 				GameActivity header area.
     */
    private void setHeaderData(Image image) {
    	// get the total amount of images of this session and the current score
    	ContourDatabase db = new ContourDatabase(GameActivity.this.getApplicationContext());
		int numberOfImagesInSession = db.getImageCount();
		overallScore = db.getCurrentScore();
		db.close();
    	
		// set data to Views in the header area of GameActivity
		TextView imagenameView = (TextView) findViewById(R.id.txtImageName);
    	imagenameView.setText(image.getPosition() + "/" + 
    			numberOfImagesInSession + " " + image.getImagename());
    	// score should only have two decimal places
    	DecimalFormat scoreFormat = new DecimalFormat("#.##");
    	scoreFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
    	scoreView.setText(" " + Double.valueOf(scoreFormat.format(overallScore)));
    }
    
    /**
     * The DrawingView holds a background image that is either an
     * edge image or the score image. By assigning the scaled background 
     * image to the DrawingView, the DrawingView is scaled towards the 
     * background image's dimensions (because it's LayoutParams are
     * set to wrap_content in height and width)
     * @param width_parent	Width of DrawingView's parent.
     * @param height_parent	Height of DrawingView's parent.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void setUpDrawingView(int width_parent, int height_parent) {
    	// check if scoreImage or edgeImage is shown in background
    	Bitmap backgroundBitmap;
    	
    	if (scoreImage != null) {
    		backgroundBitmap = scoreImage;
    	} else {
    		backgroundBitmap = prepareImageForScaling(currentEdgeImage);
    	}
    	
    	// scale background image towards drawing view dimensions
    	Bitmap scaledBackgroundBitmap = scaleEdgeBitmapToFitParent(backgroundBitmap, width_parent, height_parent);
    	
    	// set the DrawingView's proportions according to the scaled image
    	drawingView.setWidth(scaledBackgroundBitmap.getWidth());
    	drawingView.setHeight(scaledBackgroundBitmap.getHeight());
    	
    	// setting the background does not respect the image proportions when scaling, this is 
    	// why the background image is scaled manually and then set as background
    	drawingView.setBackground(new BitmapDrawable(getResources(), scaledBackgroundBitmap));   	
    }
    
    /**
     * Prepare Image object, returned from local database, for scaling by
     * converting it into a Bitmap.
     * @param image	Image object that needs preparation.
     * @return Bitmap object of extracted image data.
     */
    private Bitmap prepareImageForScaling(Image image) {
    	Bitmap edgeImageBitmap;
    	
    	// imageId = Integer.MAX_VALUE indicates a custom session where 
    	// the image bytes are not base64 encoded
    	if (image.getImageId() != Integer.MAX_VALUE) {
	    	byte[] decodedBytes = Base64.decode(image.getData());
	    	edgeImageBitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
    	} else {
    		edgeImageBitmap = 
    				BitmapFactory.decodeByteArray(
    						currentEdgeImage.getData(), 0, currentEdgeImage.getData().length);
    	}
    	
    	return edgeImageBitmap;
    }
    
    /**
     * Scales the input image towards the dimensions of it's parent.
     * @param edgeImageBitmap Bitmap that needs the be scaled.
     * @param width_parent 	  Width of the parent container.
     * @param height_parent   Height of the parent container.
     * @return Scaled Bitmap that matches the parent in one dimension.
     */
    private Bitmap scaleEdgeBitmapToFitParent(Bitmap edgeImageBitmap, 
    		int width_parent, int height_parent) {
    	// scale rate for DrawingView
    	float rate = 1f;
    	
    	float dx = (float) edgeImageBitmap.getWidth() / (float) width_parent;
    	float dy = (float) edgeImageBitmap.getHeight() / (float) height_parent;
    	
    	// if aspect ratio dx is bigger, width has to be scaled up/down
    	// if aspect ratio dy is bigger, height has to be scaled up/down
    	if (dx > dy) {
    		rate = (float) width_parent / (float) edgeImageBitmap.getWidth();
    	} else {
    		rate = (float) height_parent / (float) edgeImageBitmap.getHeight();
    	}
    	
    	Bitmap scaledEdgeImageBitmap = Bitmap.createScaledBitmap(edgeImageBitmap, 
				(int) ((float) edgeImageBitmap.getWidth() * rate),
				(int) ((float) edgeImageBitmap.getHeight() * rate), false);
    	
    	return scaledEdgeImageBitmap;
    }
    
    /**
     * Persist the currently drawn paths, held by the DrawingView, to
     * the local database.
     */
    private void persistPaths() {
    	ContourDatabase db = new ContourDatabase(getApplicationContext());
		db.updateDrawing(currentEdgeImage.getImageId(), drawingView.getPaths());
		db.close();
    }
    
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 
    public void onClickNext() {
    	// repainting drawingView needed if session is continued
    	drawingView.repaint();
    	
    	// store paths in database in case of the user closing the application after
		// generating the score image
		persistPaths();
    	
		ImageButton nextButton = (ImageButton) findViewById(R.id.imbNext);
    	
    	if (nextButton.getTag().equals("done")) {
    		// calculate edge image in background task that the UI Thread is not blocked
    		new CalculateEdgeImageTask().execute(null, null, null);
    		
    		// show progress dialog while calculation is ongoing
    		showProgressDialog();
    	} else if (nextButton.getTag().equals("next")) {
    		// persist drawing and score image
    		ContourDatabase db = new ContourDatabase(getApplicationContext());
    		int drawingId = db.persistDrawing(currentEdgeImage.getImageId(), drawing);    		
    		db.persistScoreImage(drawingId, currentScore, scoreImage);
    		
    		// delete paths of drawing after successfully persisting it
    		db.deletePathsForDrawing(currentEdgeImage.getImageId());
    		
    		// if current image is not the last image in the track, get the next image
    		// and set everything up for the next round
    		if (currentEdgeImage.getPosition() < db.getImageCount()) {
    			currentEdgeImage = db.getImageAtPosition(currentEdgeImage.getPosition() + 1);
    			
    			// if there is another image, prepare DrawingView for the next image
    			setHeaderData(currentEdgeImage);
    			
	        	final FrameLayout containerTop = (FrameLayout) findViewById(R.id.containerTop);
	        	
	        	// remove retry button
	        	if (containerTop.getChildCount() == 2) {
	        		containerTop.removeViewAt(1);
	        	}
	        	
	        	scoreImage = null;
	        	
	        	setUpLayoutListenerForDrawingViewRescaling();
	        	
    			// reset DrawingView
    			drawingView.onClickReset();
    			
    			// unlock DrawingView for user interaction
    			drawingView.setLocked(false);
    			
    			// changing button drawable to done symbol
    			nextButton.setBackground(getResources().getDrawable(R.drawable.done_selector));
    			nextButton.setTag("done");
    		} else {
    			// if there is no further image, switch to session finished activity
    			db.close();
    			
    			Intent intent = new Intent(this, SessionFinishedActivity.class);
    			startActivity(intent);
    		}
    	}    	
    }
    
    /**
     * This method is called after the edge image calculation is finished.
     * It's purpose is to update the score image information and put post-game
     * options/information to the screen.
     */
    protected void updateGameActivity() {
    	// update score
    	DecimalFormat scoreFormat = new DecimalFormat("#.##");
    	scoreFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
		scoreView.setText(" " + Double.valueOf(scoreFormat.format(overallScore)));
		
		// show score for current drawing
		prepareToast("+" + Double.valueOf(scoreFormat.format(currentScore)));
		toast.show();
		
		// show score image in background
		Drawable scoreImageDrawable = new BitmapDrawable(getResources(), scoreImage);
		drawingView.setBackground(scoreImageDrawable);
		
		// reset path and lock canvas
		drawingView.onClickReset();
		drawingView.setLocked(true);
		
		// changing button drawable to next symbol
		final ImageButton nextButton = (ImageButton) findViewById(R.id.imbNext);
		nextButton.setBackground(getResources().getDrawable(R.drawable.next_selector));
		nextButton.setTag("next");
		
		// define try again button			
		final ImageButton imbTryAgain = new ImageButton(GameActivity.this);
		LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
				LayoutParams.WRAP_CONTENT);
		lp.gravity = Gravity.CENTER;
		imbTryAgain.setLayoutParams(lp);
		
		imbTryAgain.setImageDrawable(getResources().getDrawable(R.drawable.retry));
		imbTryAgain.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				ContourDatabase db = new ContourDatabase(getApplicationContext());
				db.deletePathsForDrawing(currentEdgeImage.getImageId());
				db.close();
				
				// unlock drawing view
				drawingView.setLocked(false);
				
				// set the next button to "done"
				nextButton.setBackground(getResources().getDrawable(R.drawable.next_selector));
				nextButton.setTag("done");
				
				// remove retry button again
				FrameLayout containerTop = (FrameLayout) findViewById(R.id.containerTop);
				containerTop.removeView(imbTryAgain);
				
				scoreImage = null;
				
				// wait for the layout to be finalized to get the parent's actual width/height
				setUpLayoutListenerForDrawingViewRescaling();									
				
				setHeaderData(currentEdgeImage);
			}				
		});
		
		// add try again button
		FrameLayout containerTop = (FrameLayout) findViewById(R.id.containerTop);
		containerTop.addView(imbTryAgain);
		
		// enable control buttons again
		setControlButtonsEnabled(true);
		
		// dismiss progress dialog
		progressDialog.dismiss();
    }
    
    /**
     * This method makes sure, that the DrawingView is rescaled AFTER the layout
     * is fully inflated and set up. This way the getWidth() and getHeight() methods
     * called on it's parent return values bigger than zero.
     */
    private void setUpLayoutListenerForDrawingViewRescaling() {
    	final FrameLayout containerTop = (FrameLayout) findViewById(R.id.containerTop);
    	
    	// wait for the layout to be finalized to get the parent's actual width/height
		containerTop.getViewTreeObserver().addOnGlobalLayoutListener(
				new ViewTreeObserver.OnGlobalLayoutListener() {
			@Override
			public void onGlobalLayout() {
				setUpDrawingView(containerTop.getWidth(), containerTop.getHeight());
				
        		// remove this listener again, so it's not called again
	        	containerTop.getViewTreeObserver().removeOnGlobalLayoutListener(this);	        	
			}
    	});
    }
    
    /**
     * When the back button is pressed during a drawing session prompt
     * the user if he wants to end the session (and discard it) or if
     * he wants to continue.
     */
    @Override
    public void onBackPressed() {
    	new AlertDialog.Builder(GameActivity.this)
    	.setTitle(getString(R.string.end_session_dialog_title))
    	.setMessage(getString(R.string.end_session_dialog_message))
    	.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
	        public void onClick(DialogInterface dialog, int which) {
	        	// discard drawing session
	        	ContourDatabase db = new ContourDatabase(getApplicationContext());
	        	db.discardSession();
	        	db.close();
	        	
	        	sessionFinished = true;
	        	
	        	// redirect to main screen
	        	Intent intent = new Intent(GameActivity.this, MainMenuActivity.class);
    			startActivity(intent);
	        }
    	})
    	.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
	        public void onClick(DialogInterface dialog, int which) {
	        	// proceed with drawing session
	        }
	     })
	    .show();
    }
    
    /**
     * Enables/Disabled control buttons when edge image calculation is running.
     * @param enabled True if buttons should be enabled, false to disable.
     */
    private void setControlButtonsEnabled(boolean enabled) {
    	ImageButton imbUndo = (ImageButton) findViewById(R.id.imbUndo);
    	imbUndo.setEnabled(enabled);
    	ImageButton imbRedo = (ImageButton) findViewById(R.id.imbRedo);
    	imbRedo.setEnabled(enabled);
    	ImageButton imbNext = (ImageButton) findViewById(R.id.imbNext);
    	imbNext.setEnabled(enabled);
    	ImageButton imbReset = (ImageButton) findViewById(R.id.imbReset);
    	imbReset.setEnabled(enabled);
    }
    
    @SuppressLint("ShowToast") 
    private void prepareToast(String message) {
    	if (toast != null) {
    		toast.cancel();
    	}
    	toast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT);
    }
    
    /**
     * Shows a progress dialog when edge image calculation Task is in process.
     */
    private void showProgressDialog() {
    	progressDialog = new ProgressDialog(this);
    	progressDialog.setCancelable(false);
    	progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
		progressDialog.setTitle(getString(R.string.progress_game_activity_title) + "...");
		progressDialog
				.setMessage(getString(R.string.progress_game_activity_message) + "...");
		progressDialog.setCancelable(false);
		progressDialog.setIndeterminate(true);
		progressDialog.show();
    }
    
    /**
     * Asynchronous Task that manages the edge image calculation.
     * @author Johannes Riedmann
     *
     */
    private class CalculateEdgeImageTask extends AsyncTask<Void, Void, Void> {

		@Override
		protected Void doInBackground(Void... arg0) {
			// extracting background image from DrawingView (= edge image)
	    	Drawable edgeImage = drawingView.getBackground();
	    	Bitmap edgeBitmap = ((BitmapDrawable) edgeImage).getBitmap();	    	
	    	ByteArrayOutputStream stream = new ByteArrayOutputStream();
	    	edgeBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
	    	byte[] edgeBytes = stream.toByteArray();
	    	
	    	try {
				stream.close();
			} catch (IOException e) {
				// do nothing
			}
	    	
	    	// converting edge image byte array to OpenCV Mat object
	    	Mat edgeMat = Highgui.imdecode(new MatOfByte(edgeBytes), Highgui.IMREAD_GRAYSCALE);
	    	
	    	Imgproc.threshold(edgeMat, edgeMat, 100, 255, Imgproc.THRESH_BINARY);
			
	    	// eroding edge image to make the game a little easier
			Imgproc.erode(edgeMat, edgeMat, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(9, 9)));
			
			// convert it back to bitmap
			Utils.matToBitmap(edgeMat, edgeBitmap);
	    	
			// store current user drawing
			drawing = drawingView.getBitmap().copy(Config.ARGB_8888, true);
			
			scoreImage = Bitmap.createBitmap(edgeBitmap.getWidth(), edgeBitmap.getHeight(), Config.RGB_565);
	    	
			int edgePixelCount = 0;
			int backgroundPixelHits = 0;
			int drawingHitCount = 0;
			
			for (int i = 0; i < edgeBitmap.getWidth(); i++) {
				for (int j = 0; j < edgeBitmap.getHeight(); j++) {					
					if (edgeBitmap.getPixel(i, j) == Color.WHITE && drawing.getPixel(i, j) == Color.TRANSPARENT) {
						scoreImage.setPixel(i, j, Color.BLACK);
					} else if (edgeBitmap.getPixel(i, j) == Color.BLACK && drawing.getPixel(i, j) == Color.TRANSPARENT) {
						edgePixelCount++;
						scoreImage.setPixel(i, j, Color.WHITE);
					} else if (edgeBitmap.getPixel(i, j) == Color.BLACK && drawing.getPixel(i, j) == Color.BLACK) {
						edgePixelCount++;
						drawingHitCount++;
						scoreImage.setPixel(i, j, Color.GREEN);
					} else if (edgeBitmap.getPixel(i, j) == Color.WHITE && drawing.getPixel(i, j) == Color.BLACK) {
						backgroundPixelHits++;
						scoreImage.setPixel(i, j, Color.RED);
					}
				}
			}
			
			// calculating score			
			if (edgePixelCount == 0) {
				// prevent division by 0
				edgePixelCount = 1;
			}
			
			// the hitrate is defined as the percentage of green pixels out of
			// all edge pixels (green and white pixels of score image)
			double hitrate = (double) drawingHitCount / (double) edgePixelCount * 100;
			
			int totalBackgroundPixels = (int) edgeMat.total() - edgePixelCount;
			
			if (totalBackgroundPixels == 0) {
				// prevent division by 0
				totalBackgroundPixels = 1;
			}
			
			// the backgroundHitRate is defined as the percentage of background pixels hit
			// by the user drawing
			double backgroundHitRate = (double) backgroundPixelHits / (double) totalBackgroundPixels;
			
			// the higher the backgroundHitRate, the lower the score
			currentScore = hitrate * (1 - backgroundHitRate);			
			currentScore = (double) Math.round(100 * currentScore) / 100;
			
			// calculate overall score from previously calculated 
    		overallScore = (double) Math.round((overallScore + currentScore) * 100) / 100;
			
			return null;
		}
		
		protected void onPostExecute(Void result) {
			updateGameActivity();
		}
    	
    }
    
}
