/*
 * PhoneGap is available under *either* the terms of the modified BSD license *or* the
 * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
 * 
 * Copyright (c) 2005-2010, Nitobi Software Inc.
 * Copyright (c) 2011, IBM Corporation
 */
package com.phonegap;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;

import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;

public class Capture extends Plugin {

	private static final String _DATA = "_data";	// The column name where the file path is stored
	private static final int CAPTURE_AUDIO = 0;		// Constant for capture audio
	private static final int CAPTURE_IMAGE = 1;		// Constant for capture image
	private static final int CAPTURE_VIDEO = 2;		// Constant for capture video
	private static final String LOG_TAG = "Capture";
	private String callbackId;						// The ID of the callback to be invoked with our result
	private long limit;								// the number of pics/vids/clips to take
	private double duration;						// optional duration parameter for video recording
	private JSONArray results;						// The array of results to be returned to the user
	private Uri imageUri;							// Uri of captured image 

	@Override
	public PluginResult execute(String action, JSONArray args, String callbackId) {
		this.callbackId = callbackId;
		this.limit = 1;
		this.duration = 0.0f;
		this.results = new JSONArray();
		
		JSONObject options = args.optJSONObject(0);
		if (options != null) {
			limit = options.optLong("limit", 1);
			duration = options.optDouble("duration", 0.0f);
		}

		if (action.equals("getFormatData")) {
			try {
				JSONObject obj = getFormatData(args.getString(0), args.getString(1));
				return new PluginResult(PluginResult.Status.OK, obj);
			} catch (JSONException e) {
				return new PluginResult(PluginResult.Status.ERROR);
			}
		}
		else if (action.equals("captureAudio")) {
			this.captureAudio();
		}
		else if (action.equals("captureImage")) {
			this.captureImage();
		}
		else if (action.equals("captureVideo")) {
			this.captureVideo(duration);	
		}
		
		PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
		r.setKeepCallback(true);
		return r;
	}

	/**
	 * Provides the media data file data depending on it's mime type
	 * 
	 * @param filePath path to the file
	 * @param mimeType of the file
	 * @return a MediaFileData object
	 */
	private JSONObject getFormatData(String filePath, String mimeType) {
		JSONObject obj = new JSONObject();
		try {
			// setup defaults
			obj.put("height", 0);
			obj.put("width", 0);
			obj.put("bitrate", 0);
			obj.put("duration", 0);
			obj.put("codecs", "");

			// If the mimeType isn't set the rest will fail
			// so let's see if we can determine it.
			if (mimeType == null || mimeType.equals("")) {
			    mimeType = FileUtils.getMimeType(filePath);
			}
			
			if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) {
				obj = getImageData(filePath, obj);
			}
			else if (filePath.endsWith("audio/3gpp")) {
				obj = getAudioVideoData(filePath, obj, false);
			}
			else if (mimeType.equals("video/3gpp")) {
				obj = getAudioVideoData(filePath, obj, true);
			}
		}
		catch (JSONException e) {
			Log.d(LOG_TAG, "Error: setting media file data object");
		}
		return obj;
	}

	/**
	 * Get the Image specific attributes
	 * 
	 * @param filePath path to the file
	 * @param obj represents the Media File Data
	 * @return a JSONObject that represents the Media File Data
	 * @throws JSONException
	 */
	private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException {
		Bitmap bitmap = BitmapFactory.decodeFile(filePath);
		obj.put("height", bitmap.getHeight());
		obj.put("width", bitmap.getWidth());
		return obj;
	}

	/**
	 * Get the Image specific attributes
	 * 
	 * @param filePath path to the file
	 * @param obj represents the Media File Data
	 * @param video if true get video attributes as well
	 * @return a JSONObject that represents the Media File Data
	 * @throws JSONException
	 */
	private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException {
		MediaPlayer player = new MediaPlayer();
		try {
			player.setDataSource(filePath);
			player.prepare();
			obj.put("duration", player.getDuration());
			if (video) {
				obj.put("height", player.getVideoHeight());
				obj.put("width", player.getVideoWidth());
			}
		}
		catch (IOException e) {
			Log.d(LOG_TAG, "Error: loading video file");
		} 
		return obj;
	}

	/**
	 * Sets up an intent to capture audio.  Result handled by onActivityResult()
	 */
	private void captureAudio() {
        Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION);

        this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_AUDIO);
    }

	/**
	 * Sets up an intent to capture images.  Result handled by onActivityResult()
	 */
	private void captureImage() {
        Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

        // Specify file so that large image is captured and returned
        File photo = new File(DirectoryManager.getTempDirectoryPath(ctx),  "Capture.jpg");
        intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
        this.imageUri = Uri.fromFile(photo);

        this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_IMAGE);
    }

	/**
	 * Sets up an intent to capture video.  Result handled by onActivityResult()
	 */
	private void captureVideo(double duration) {
        Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
        // Introduced in API 8
        //intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration);
        
        this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_VIDEO);
    }
	
    /**
     * Called when the video view exits. 
     * 
     * @param requestCode		The request code originally supplied to startActivityForResult(), 
     * 							allowing you to identify who this result came from.
     * @param resultCode		The integer result code returned by the child activity through its setResult().
     * @param intent			An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
     * @throws JSONException 
     */
	public void onActivityResult(int requestCode, int resultCode, Intent intent) {

		// Result received okay
		if (resultCode == Activity.RESULT_OK) {
			// An audio clip was requested
			if (requestCode == CAPTURE_AUDIO) {
				// Get the uri of the audio clip
				Uri data = intent.getData();
				// create a file object from the uri
				results.put(createMediaFile(data));

				if (results.length() >= limit) {
					// Send Uri back to JavaScript for listening to audio
					this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
				} else {
					// still need to capture more audio clips
					captureAudio();
				}
			} else if (requestCode == CAPTURE_IMAGE) {
				// For some reason if I try to do:
				// Uri data = intent.getData();
				// It crashes in the emulator and on my phone with a null pointer exception
				// To work around it I had to grab the code from CameraLauncher.java
				try {
					// Read in bitmap of captured image
					Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri);

					// Create entry in media store for image
					// (Don't use insertImage() because it uses default compression setting of 50 - no way to change it)
					ContentValues values = new ContentValues();
					values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
					Uri uri = null;
					try {
						uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
					} catch (UnsupportedOperationException e) {
						System.out.println("Can't write to external media storage.");
						try {
							uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
						} catch (UnsupportedOperationException ex) {
							System.out.println("Can't write to internal media storage.");							
							this.fail("Error capturing image - no media storage found.");
							return;
						}
					}

					// Add compressed version of captured image to returned media store Uri
					OutputStream os  = this.ctx.getContentResolver().openOutputStream(uri);
					bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
					os.close();

					bitmap.recycle();
					bitmap = null;
					System.gc();
					
					// Add image to results
					results.put(createMediaFile(uri));
					
					if (results.length() >= limit) {
						// Send Uri back to JavaScript for viewing image
						this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
					} else {
						// still need to capture more images
						captureImage();
					}
				} catch (IOException e) {
					e.printStackTrace();
					this.fail("Error capturing image.");
				}
			} else if (requestCode == CAPTURE_VIDEO) {
				// Get the uri of the video clip
				Uri data = intent.getData();
				// create a file object from the uri
				results.put(createMediaFile(data));

				if (results.length() >= limit) {
					// Send Uri back to JavaScript for viewing video
					this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);
				} else {
					// still need to capture more video clips
					captureVideo(duration);
				}
			}
		}
		// If canceled
		else if (resultCode == Activity.RESULT_CANCELED) {
			// If we have partial results send them back to the user
			if (results.length() > 0) {
				this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);				
			}
			// user canceled the action
			else {
				this.fail("Canceled.");
			}
		}
		// If something else
		else {
			// If we have partial results send them back to the user
			if (results.length() > 0) {
				this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId);				
			}
			// something bad happened
			else {
				this.fail("Did not complete!");
			}
		}
	}

	/**
	 * Creates a JSONObject that represents a File from the Uri
	 *  
	 * @param data the Uri of the audio/image/video
	 * @return a JSONObject that represents a File
	 */
	private JSONObject createMediaFile(Uri data) {
		File fp = new File(getRealPathFromURI(data));
		
		JSONObject obj = new JSONObject();

		try {		
			// File properties
			obj.put("name", fp.getName());
			obj.put("fullPath", fp.getAbsolutePath());
			obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath()));
			obj.put("lastModifiedDate", fp.lastModified());
			obj.put("size", fp.length());
		} catch (JSONException e) {
			// this will never happen
			e.printStackTrace();
		}
		
		return obj;
	}
	
	/**
	 * Queries the media store to find out what the file path is for the Uri we supply
	 * 
	 * @param contentUri the Uri of the audio/image/video
	 * @return the full path to the file
	 */
	private String getRealPathFromURI(Uri contentUri) {
        String[] proj = { _DATA };
        Cursor cursor = this.ctx.managedQuery(contentUri, proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(_DATA);
        cursor.moveToFirst();
        return cursor.getString(column_index);
    }

	/**
	 * Send error message to JavaScript.
	 * 
	 * @param err
	 */
	public void fail(String err) {
		this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId);
	}
}