//	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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import com.j0h1.contour.R;
import com.j0h1.contour.database.ContourDatabase;
import com.j0h1.contour.pojo.Image;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;
import android.provider.MediaStore;

/**
 * Fragment that is responsible of defining a custom drawing session.
 * @author Johannes Riedmann
 *
 */
public class CustomModeFragment extends Fragment {

	private static final int RESULT_LOAD_IMAGE = 1;

	private ImageView ivChooseImage;
	private EditText tbxCustomSessionImageName;
	private SeekBar sbImageUpload;
	
	private Bitmap currentSelectedImage;
	private Bitmap edgeImage;
	
	private Toast toast;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		return inflater.inflate(R.layout.fragment_custom_mode, container,
				false);
	}
	
	@Override
	public void onStart() {
		super.onStart();

		tbxCustomSessionImageName = (EditText) getView().findViewById(R.id.tbxCustomSessionImageName);
		
		// when the user clicks on the ImageView, an Intent starts that lets the
		// user choose an image from his gallery
		ivChooseImage = (ImageView) getView().findViewById(R.id.ivChooseImage);
		ivChooseImage.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent(
						Intent.ACTION_PICK,
						android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
				startActivityForResult(intent, RESULT_LOAD_IMAGE);
			}			
		});
        
		// the SeekBar defines the difficulty by setting 
		// the value for the lower threshold of the edge image calculation
		// the edge image calculation should render the image in real-time
		// when the user interacts with the SeekBar
		sbImageUpload = (SeekBar) getView().findViewById(R.id.sbCustomSessionDifficulty);
		sbImageUpload.setProgress(60);
		sbImageUpload.setMax(120);
		sbImageUpload.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
			public void onProgressChanged(SeekBar seekBar, int progress,
					boolean fromUser) {
				// if no image is selected, abort and provide feedback
				if (edgeImage == null) {
					prepareToast(getString(R.string.choose_image_first));
					toast.show();
					return;
				}
				
				// create edge image from selected image and show in ImageView
				createEdgeImage(progress);

				// attach the calculated edge image to the ImageView
				if (edgeImage != null) {
					ivChooseImage.setImageBitmap(edgeImage);
				}
			}

			public void onStartTrackingTouch(SeekBar seekBar) {
			}

			public void onStopTrackingTouch(SeekBar seekBar) {
			}
		});

		ImageButton btnStartCustomSession = (ImageButton) getView().findViewById(R.id.imbStartCustomSession);
		btnStartCustomSession.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// if there is no edge image calculated, give feedback
				if (edgeImage == null) {
					prepareToast(getString(R.string.choose_image_first));
					toast.show();
					return;
				}
				
				// if no image name is specified, give feedback
				if (tbxCustomSessionImageName.getText().toString().isEmpty()) {
					prepareToast(getString(R.string.specify_image_title));
					toast.show();
					return;
				}
				
				// convert original image to byte array
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				currentSelectedImage.compress(Bitmap.CompressFormat.PNG, 100, baos);
				byte[] originalBytes = baos.toByteArray();
				
				try {
					baos.close();
				} catch (IOException e) {
					prepareToast(getString(R.string.unable_to_read_image));
					toast.show();
					return;
				}
				
				// convert edge image to byte array
				baos = new ByteArrayOutputStream();
				edgeImage.compress(Bitmap.CompressFormat.PNG, 100, baos);
				byte[] edgeBytes = baos.toByteArray();
				
				try {
					baos.close();
				} catch (IOException e) {
					prepareToast(getString(R.string.unable_to_read_image));
					toast.show();
					return;
				}
				
				// define custom image (Integer.MAX_VALUE as imageId marks a custom image)
				Image customImage = new Image();
				customImage.setImageId(Integer.MAX_VALUE);
				customImage.setImagename(tbxCustomSessionImageName.getText().toString().trim());
				customImage.setPosition(1);
				customImage.setData(edgeBytes);
				
				// persist a new session and the image data
				ContourDatabase db = new ContourDatabase(getView().getContext());
				db.startCustomSession(0, customImage, originalBytes);
				db.close();

				// pass data via intent to GameActivity and start it
				Intent intent = new Intent(CustomModeFragment.this
						.getActivity(), GameActivity.class);
				intent.putExtra("NewOrExistingSession", "newSession");
				intent.putExtra("GameMode", "custom");

				startActivity(intent);
			}
		});
	}
	
	@SuppressLint("ShowToast") 
	private void prepareToast(String message) {
		if (toast != null) {
			toast.cancel();
		}
		toast = Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT);
	}

	/**
	 * Generates an edge image from the currently selected image. The low
	 * threshold is defined by the progress value of the SeekBar.
	 * @param progress
	 */
	private void createEdgeImage(int progress) {
		// if there is no image selected, give feedback to the user and abort
		if (currentSelectedImage == null) {
			prepareToast(getString(R.string.select_different_image));
			toast.show();
			return;
		}
		
		// low threshold (progress) should at least be 1
		if (progress == 0) {
			progress = 1;
		}
		
		// cast input image into Mat object (OpenCV) to work with
		Mat mat = new Mat();
		Bitmap tempBmp = currentSelectedImage.copy(Bitmap.Config.ARGB_8888, false);
		Utils.bitmapToMat(tempBmp, mat);

		// convert input image to grayscale image
		Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY);

		// gaussian blur with auto-computed 3x3 kernel
		// sigmaX = 0 ==> sigmaY is automatically set to 0, which results in
		// auto-computation of the kernel
		Imgproc.GaussianBlur(mat, mat, new Size(3, 3), 0);

		// computing edge image with canny edge detection with low threshold
		// defined by the user and high threshold 3 times the low threshold 
		// (= 1:3 ratio as recommended)
		Imgproc.Canny(mat, mat, progress, 3 * progress);

		// invert image (black contours, white background)
		Core.bitwise_not(mat, mat);

		// convert the Mat object to a Bitmap and set it as edgeImage
		edgeImage = Bitmap.createBitmap(currentSelectedImage.getWidth(),
				currentSelectedImage.getHeight(),
				currentSelectedImage.getConfig());

		Utils.matToBitmap(mat, edgeImage);
	}

	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		
		String filePath = "";

		if (requestCode == RESULT_LOAD_IMAGE
				&& resultCode == Activity.RESULT_OK && data != null) {
			Uri selectedImage = data.getData();

			// extract path to the image
			String[] filePathColumn = { MediaStore.Images.Media.DATA };
			Cursor cursor = getActivity().getContentResolver().query(
					selectedImage, filePathColumn, null, null, null);
			cursor.moveToFirst();
			int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
			filePath = cursor.getString(columnIndex);
			cursor.close();
			
			// convert file located at filePath to Bitmap object
			currentSelectedImage = BitmapFactory.decodeFile(filePath);
		}
		
		// in case this image is from Picasa
		if (requestCode == RESULT_LOAD_IMAGE && currentSelectedImage == null
				&& data != null) {
		    BitmapFactory.Options options = new BitmapFactory.Options();
		    
			try {
				InputStream is = getActivity().getContentResolver().openInputStream(data.getData());				
				currentSelectedImage = BitmapFactory.decodeStream(is, null, options);
			    is.close();
			} catch (FileNotFoundException e) {
				// do nothing
			} catch (IOException e) {
				// do nothing
			}		    
		  }
		
		// the image is not decoded correctly at this point, give feedback
		// to the user and abort
		if (currentSelectedImage == null) {
			prepareToast(getString(R.string.failed_to_import_image));
			toast.show();
			return;
		}
		
		// if resolution of current image is too big, rescale towards device
		// resolution
		Display display = getActivity().getWindowManager().getDefaultDisplay();
		Point displaySize = new Point();
		display.getSize(displaySize);

		float ratio = 1f;
    	
    	float dx = (float) currentSelectedImage.getWidth() / (float) displaySize.x;
    	float dy = (float) currentSelectedImage.getHeight() / (float) displaySize.y;
    	
    	// 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) {
    		ratio = (float) displaySize.x / (float) currentSelectedImage.getWidth();
    	} else {
    		ratio = (float) displaySize.y / (float) currentSelectedImage.getHeight();
    	}
    	
    	// apply scaling to currently selected image
    	currentSelectedImage = Bitmap.createScaledBitmap(currentSelectedImage, 
    			(int) ((float) currentSelectedImage.getWidth() * ratio), 
    			(int) ((float) currentSelectedImage.getHeight() * ratio), 
    			false);
		
		// generate edge image with current progress
		createEdgeImage(sbImageUpload.getProgress());
		
		// show edge image in ImageView
		ivChooseImage.setImageBitmap(edgeImage);
	}

}
