package net.dinglisch.android.tasker;

//Version 1.3.3

//Changelog

//Version 1.3.3
//- increased MAX_NO_ARGS to 10

//Version 1.3.2
//	- bug setting app arg
//	- pulled provider column names out of function

//For usage examples see http://tasker.dinglisch.net/invoketasks.html


import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.PatternMatcher;
import android.os.Process;
import android.util.Log;

public class TaskerIntent extends Intent {

	// 3 Tasker versions
	public final static String TASKER_PACKAGE = "net.dinglisch.android.tasker";
	public final static String TASKER_PACKAGE_MARKET = TASKER_PACKAGE + "m";
	public final static String TASKER_PACKAGE_CUPCAKE = TASKER_PACKAGE + "cupcake";
	
	// Play Store download URLs
	public final static String MARKET_DOWNLOAD_URL_PREFIX = "market://details?id=";
	private final static String TASKER_MARKET_URL =  MARKET_DOWNLOAD_URL_PREFIX + TASKER_PACKAGE_MARKET;
	private final static String TASKER_MARKET_URL_CUPCAKE = MARKET_DOWNLOAD_URL_PREFIX + TASKER_PACKAGE_CUPCAKE;
	
	// Direct-purchase version
	private final static String TASKER_DOWNLOAD_URL = "http://tasker.dinglisch.net/download.html";
	
	// Intent actions
	public final static String 	ACTION_TASK = TASKER_PACKAGE + ".ACTION_TASK";
	public final static String 	ACTION_TASK_COMPLETE = TASKER_PACKAGE + ".ACTION_TASK_COMPLETE";
	public final static String 	ACTION_TASK_SELECT = TASKER_PACKAGE + ".ACTION_TASK_SELECT";
	
	// Intent parameters
	public final static String	EXTRA_ACTION_INDEX_PREFIX = "action";
	public final static String	TASK_NAME_DATA_SCHEME = "task";
	public final static String	EXTRA_TASK_NAME  = "task_name";
	public final static String	EXTRA_TASK_PRIORITY  = "task_priority";
	public final static String	EXTRA_SUCCESS_FLAG = "success";
	public final static String	EXTRA_VAR_NAMES_LIST = "varNames";
	public final static String	EXTRA_VAR_VALUES_LIST = "varValues";
	public final static String	EXTRA_TASK_OUTPUT = "output";

	// Content provider columns
	public static final String	PROVIDER_COL_NAME_EXTERNAL_ACCESS = "ext_access";
	public static final String	PROVIDER_COL_NAME_ENABLED = "enabled";
	
	// DEPRECATED, use EXTRA_VAR_NAMES_LIST, EXTRA_VAR_VALUES_LIST
	public final static String	EXTRA_PARAM_LIST = "params";

	// Intent data
	
	public final static String	TASK_ID_SCHEME = "id";
	
	// For particular actions
	
	public final static String	DEFAULT_ENCRYPTION_KEY= "default";
	public final static String	ENCRYPTED_AFFIX = "tec";
	public final static int		MAX_NO_ARGS = 10;
	
	// Bundle keys
	// Only useful for Tasker
	public final static String	ACTION_CODE = "action";
	public final static String	APP_ARG_PREFIX = "app:";
	public final static String	ICON_ARG_PREFIX = "icn:";
	public final static String	ARG_INDEX_PREFIX = "arg:";
	public static final String	PARAM_VAR_NAME_PREFIX = "par";	
	
	// Misc
	private final static String PERMISSION_RUN_TASKS = TASKER_PACKAGE + ".PERMISSION_RUN_TASKS";

	private final static String ACTION_OPEN_PREFS = TASKER_PACKAGE + ".ACTION_OPEN_PREFS";
	public  final static String EXTRA_OPEN_PREFS_TAB_NO = "tno";
	private final static int	MISC_PREFS_TAB_NO = 3;  // 0 based

	// To query whether Tasker is enabled and external access is enabled
	private final static String TASKER_PREFS_URI = "content://" + TASKER_PACKAGE + "/prefs";
	
	private final static int	CUPCAKE_SDK_VERSION = 3;	

	// result values for TestSend

	// NotInstalled: Tasker package not found on device
	// NoPermission: calling app does not have permission PERMISSION_RUN_TASKS
	// NotEnabled: Tasker is not enabled
	// AccessBlocked: user prefs disallow external access
	// NoReceiver: Tasker has not created a listener for external access (probably a Tasker bug)
	// OK: you should be able to send a task to run. Still need to listen for result 
	//     for e.g. task not found
	
	public static enum Status { NotInstalled, NoPermission, NotEnabled, AccessBlocked, NoReceiver, OK };

	// -------------------------- PRIVATE VARS ---------------------------- //

	private final static String TAG = "TaskerIntent";

	private final static String	EXTRA_INTENT_VERSION_NUMBER = "version_number";
	private final static String	INTENT_VERSION_NUMBER = "1.1";

	// Inclusive values
	private final static int	MIN_PRIORITY = 0;
	private final static int	MAX_PRIORITY = 10;
	
	// For generating random names
	private static Random       rand = new Random();

	// Tracking state
	private int 				actionCount = 0;
	private int 				argCount;
	
	// -------------------------- PUBLIC METHODS ---------------------------- //

	public static int getMaxPriority() {
		return MAX_PRIORITY;
	}
	
	public static boolean validatePriority( int pri ) {
		return (
				( pri >= MIN_PRIORITY ) ||
				( pri <= MAX_PRIORITY )
		);
	}
	
	// Tasker has different package names for Play Store and non- versions
	// for historical reasons
	
	public static String getInstalledTaskerPackage( Context context ) {
	
		String foundPackage = null;
		
		try {
			context.getPackageManager().getPackageInfo( TASKER_PACKAGE, 0 );
			foundPackage = TASKER_PACKAGE;
		}
		catch ( PackageManager.NameNotFoundException e ) {
		}

		try {
			context.getPackageManager().getPackageInfo( TASKER_PACKAGE_MARKET, 0 );
			foundPackage = TASKER_PACKAGE_MARKET;
		}
		catch ( PackageManager.NameNotFoundException e ) {
		}
		
		return foundPackage;
	}
	
	// test we can send a TaskerIntent to Tasker
	// use *before* sending an intent
	// still need to test the *result after* sending intent
	
	public static Status testStatus( Context c ) {

		Status result;
		
		if ( ! taskerInstalled( c ) )
			result = Status.NotInstalled;
		else if ( ! havePermission( c ) )
			result = Status.NoPermission;
		else if ( ! TaskerIntent.prefSet( c, PROVIDER_COL_NAME_ENABLED ) )
			result = Status.NotEnabled;
		else if ( ! TaskerIntent.prefSet( c, PROVIDER_COL_NAME_EXTERNAL_ACCESS ) )
			result = Status.AccessBlocked;
		else if ( ! new TaskerIntent( "" ).receiverExists( c ) )
			result = Status.NoReceiver;
		else 
			result = Status.OK;

		return result;
	}

	// Check if Tasker installed 

	public static boolean taskerInstalled( Context context ) {
		return ( getInstalledTaskerPackage( context ) != null );
	}

	// Use with startActivity to retrieve Tasker from Android market
	public static Intent getTaskerInstallIntent( boolean marketFlag ) {
		
		return new Intent( 
				Intent.ACTION_VIEW, 
				Uri.parse(
						marketFlag ? 
								( ( SDKVersion() == CUPCAKE_SDK_VERSION ) ? TASKER_MARKET_URL_CUPCAKE : TASKER_MARKET_URL ) : 
								TASKER_DOWNLOAD_URL
				)
		);
	}

	public static int SDKVersion() {
		try {
			Field f = android.os.Build.VERSION.class.getField( "SDK_INT" );
			return f.getInt( null );
		}
		catch ( Exception e ) {
			return CUPCAKE_SDK_VERSION;
		}   
	}
	
	public static IntentFilter getCompletionFilter( String taskName ) {

		IntentFilter filter = new IntentFilter( TaskerIntent.ACTION_TASK_COMPLETE );

		filter.addDataScheme( TASK_NAME_DATA_SCHEME );
		filter.addDataPath( taskName, PatternMatcher.PATTERN_LITERAL );
		
		return filter;
	}

	public static Intent getTaskSelectIntent() {
		return new Intent( ACTION_TASK_SELECT ).
		setFlags( 
				Intent.FLAG_ACTIVITY_NO_USER_ACTION |
				Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | 
				Intent.FLAG_ACTIVITY_NO_HISTORY 
		);
	}
	
	// public access deprecated, use TaskerIntent.testSend() instead
	
	public static boolean havePermission( Context c ) {
		return c.checkPermission( PERMISSION_RUN_TASKS, Process.myPid(), Process.myUid() ) == 
			PackageManager.PERMISSION_GRANTED;
	}

	// Get an intent that will bring up the Tasker prefs screen with the External Access control(s)
	// Probably you want to use startActivity or startActivityForResult with it
	
	public static Intent getExternalAccessPrefsIntent() {
		return new Intent( ACTION_OPEN_PREFS ).putExtra( EXTRA_OPEN_PREFS_TAB_NO, MISC_PREFS_TAB_NO );
	}

	// ------------------------------------- INSTANCE METHODS ----------------------------- //
	
	public TaskerIntent() {
		super( ACTION_TASK );
		setRandomData();
		putMetaExtras( getRandomString() );
	}
	
	public TaskerIntent( String taskName ) {
		super( ACTION_TASK );
		setRandomData();
		putMetaExtras( taskName );
	}

	public TaskerIntent setTaskPriority( int priority ) {

		if ( validatePriority( priority ) )
			putExtra( EXTRA_TASK_PRIORITY, priority );
		else
			Log.e( TAG, "priority out of range: " + MIN_PRIORITY + ":" + MAX_PRIORITY );
		
		return this;
	}
	
	// Sets subsequently %par1, %par2 etc
	public TaskerIntent addParameter( String value ) {
		
		int index = 1;
		
		if ( getExtras().containsKey( EXTRA_VAR_NAMES_LIST ) ) 
			index = getExtras().getStringArrayList( EXTRA_VAR_NAMES_LIST ).size() + 1;
			
		Log.d(TAG, "index: " + index );
		
		addLocalVariable( "%" + PARAM_VAR_NAME_PREFIX + index, value );
		
		return this;
	}

	// Arbitrary specification of (local) variable names and values
	public TaskerIntent addLocalVariable( String name, String value ) {
		
		ArrayList<String> names, values;
			
		if ( hasExtra( EXTRA_VAR_NAMES_LIST ) ) {
			names = getStringArrayListExtra( EXTRA_VAR_NAMES_LIST );
			values = getStringArrayListExtra( EXTRA_VAR_VALUES_LIST );
		}
		else {
			names = new ArrayList<String>();
			values = new ArrayList<String>();

			putStringArrayListExtra( EXTRA_VAR_NAMES_LIST, names );
			putStringArrayListExtra( EXTRA_VAR_VALUES_LIST, values );
		}

		names.add( name );
		values.add( value );

		return this;
	}

	public TaskerIntent addAction( int code ) {
		
		actionCount++;
		argCount = 1;
		
		Bundle actionBundle = new Bundle();
	
		actionBundle.putInt( ACTION_CODE, code );

		// Add action bundle to intent
		putExtra( EXTRA_ACTION_INDEX_PREFIX + Integer.toString( actionCount ), actionBundle );

		return this;
	}
	
	// string arg
	public TaskerIntent addArg( String arg ) {

		Bundle b = getActionBundle();

		if ( b != null )
			b.putString( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), arg );
		
		return this;
	}
	
	// int arg
	public TaskerIntent addArg( int arg ) {
		Bundle b = getActionBundle();

		if ( b != null ) 
			b.putInt( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), arg );
			
		return this;
	}
	
	// boolean arg
	public TaskerIntent addArg( boolean arg ) {
		Bundle b = getActionBundle();

		if ( b != null ) 
			b.putBoolean( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), arg );
		
		return this;
	}

	// Application arg
	public TaskerIntent addArg( String pkg, String cls ) {
		Bundle b = getActionBundle();

		if ( b != null ) { 
			StringBuilder builder = new StringBuilder();
			builder.append( APP_ARG_PREFIX ).
			append( pkg ). append( "," ). append( cls );
			b.putString( ARG_INDEX_PREFIX + Integer.toString( argCount++ ), b.toString() );
		}
		
		return this;
	}

	public IntentFilter getCompletionFilter() {
		return getCompletionFilter( getTaskName() );
	}

	public String getTaskName() {
		return getStringExtra( EXTRA_TASK_NAME );
	}

	public boolean receiverExists( Context context ) {
		List<ResolveInfo> recs = context.getPackageManager().queryBroadcastReceivers( this, 0 );
		return (
				( recs != null ) &&
				( recs.size() > 0 )
		);
	}

	// -------------------- PRIVATE METHODS -------------------- //
	
	private String getRandomString() {
		return Long.toString( rand.nextLong() );
	}

	// so that if multiple TaskerIntents are used in PendingIntents there's virtually no
	// clash chance
	private void setRandomData() {
		setData( Uri.parse( TASK_ID_SCHEME + ":" + getRandomString() ) );
	}
	
	private Bundle getActionBundle() {

		Bundle toReturn = null;
		
		if ( argCount > MAX_NO_ARGS )			
			Log.e( TAG, "maximum number of arguments exceeded (" + MAX_NO_ARGS + ")" );
		else {
			String key = EXTRA_ACTION_INDEX_PREFIX + Integer.toString( actionCount );

			if ( this.hasExtra( key ) ) 
				toReturn = getBundleExtra( key );
			else
				Log.e( TAG, "no actions added yet" );
		}
		
		return toReturn;
	}
	
	private void putMetaExtras( String taskName ) {
		putExtra( EXTRA_INTENT_VERSION_NUMBER, INTENT_VERSION_NUMBER );
		putExtra( EXTRA_TASK_NAME, taskName );
	}

	// for testing that Tasker is enabled and external access is allowed
	
	private static boolean prefSet( Context context, String col ) {
		
		String [] proj = new String [] { col };

		Cursor c = context.getContentResolver().query( Uri.parse( TASKER_PREFS_URI ), proj, null, null, null );

		boolean acceptingFlag = false;
		
		if ( c == null )
			Log.w( TAG, "no cursor for " + TASKER_PREFS_URI );
		else {
			c.moveToFirst();
			
			if ( Boolean.TRUE.toString().equals( c.getString( 0 ) ) )
				acceptingFlag = true;

			c.close();
		}
		
		return acceptingFlag;
	}
}