/////////////////////////////////////////////////////////////////////////////////
//
// c) 2010 Panther Panache, LLC.  
// This source code is Confidential and Proprietary information of the company.
//
/////////////////////////////////////////////////////////////////////////////////
package com.panache.utilities
{
	import com.panache.debug.MediaServicesAdDebug;
	
	import flash.utils.ByteArray;
	import flash.utils.getDefinitionByName;
	import flash.utils.getTimer;
	
	/**
	 * PanacheCoreUtilities is a core class which provides utilities to other toolkit classes.
	 * It is a utility member of the PanacheToolkitCoreComponent.
	 * 
	 * <p>
	 * PanacheCoreUtilities provides a set of static functions that may be used to perform
	 * recurring types of processing.  These utilities prevent the need from implementing 
	 * the same method in a number of different places.
	 * </p>
	 */
	public class PanacheCoreUtilities
	{
		/////////////////////////////////////////////////////////////////////////////
		// CONSTANTS
		public static const IGNORE_CASE:uint				= 0x0001;
		public static const IGNORE_WHITESPACE:uint			= 0x0002;
		
		private static const DEBUG_SOURCE:String			= "PanacheCoreUtilities";
		
		/**
		 * PanacheCoreUtilities() is the constructor which creates an instance of this class.
		 * There is usually no need to instantiate this class since all of the methods are
		 * defined as static.
		 */
		public function PanacheCoreUtilities()
		{
		}

		/**
		 * coerceValueToType() converts a value into a specific data type.
		 * 
		 * @param value is the raw value that is being converted to the specified datatype.
		 * 
		 * @param type is a String which specifies the datatype of the value being returned.  The argument
		 * should match one of the AS3 datatypes.  Currently, PanacheObject supports the following datatypes (Other classes
		 * that extend PanacheObject may support additional datatypes):
		 * <ul>
		 *   <li>String</li>
		 *   <li>Number</li>
		 *   <li>Boolean</li>
		 *   <li>Object</li>
		 *   <li>~~</li>
		 * </ul>
		 * 
		 * @param defaultValue is a value that is returned if the value cannot be converted to the specified 
		 * datatype.
		 * 
		 * @default <code>null</code>
		 * 
		 * @return The coerced value if the conversion was successful; otherwise the default value is returned.
		 **/
		 public static function coerceValueToType(value:*, type:String, defaultValue:* = null) : *
		 {
		 	// set the answer to the default value; this will be changed if the value can be converted
			var answer:* = defaultValue;
			if (value != null)
			{
				try
				{
					// do case insensitive comparison
					switch (type.toLowerCase())
					{
						case "string" :
							answer = (value as String);
							break;
						case "boolean" :
							var val:String = String(value);
							var bool:Boolean = (val.toLowerCase() == "true");
							answer = bool;
							break;
						case "number" :
							var num:Number = Number(value.valueOf());
							answer = num;
							break;
						case "array" :
							answer = (value as Array);
							break;	
						case "*" :
							answer = value;
							break;
						case "hex":
							answer = convertFromHex(value, defaultValue);
							break;
						case "XML":
							answer = (value as XML);
							break;
						case "object" :
						default:
							answer = (value as Object);
							break;
					}	
				}
				catch (ex:Error)
				{
					MediaServicesAdDebug.reportErrorTrace("Cannot coerce " + value + " to " + type + "; return value=" + answer,  
							ex, DEBUG_SOURCE, "coerceValueToType");
				}
			}
			
			return answer;
		 }

		/**
		 * converts a string value that has been described as a hex value into a number.
		 * 
		 * <p>
		 * The correct format for hex value would be "0xnn" where n is some numerical
		 * digit, e.g. "0xccff23".  The function will ensure that the number starts
		 * with 0x
		 * </p>
		 * 
		 * @param value is the hex string that is being coverted into a number.
		 * Correct format is "0xn" where n is 1 or more numerical digits.
		 * 
		 * @param defaultValue is the value that is returned if the value cannot
		 * be converted from a hex string to a number.  This value should be a 
		 * valid Number object
		 */ 
		protected static function convertFromHex(value:String, defaultValue:*) : Number
		{
			var answer:Number = defaultValue;
			if ((value != null) && (value.length > 0))
			{
				var ix:int = value.indexOf("x");
				var hexString:String;
				switch (ix)
				{
					case 1 :
						// do nothing - this is the correct position
						hexString = value;
						break;
						
					case 0 :
						// add an preceding 0
						hexString = "0" + value;
						break;
						
					case -1 :
						// it is not present so add "0x"
						hexString = "0x" + value;
						break;
							
					default :
						// this means it is some where it shouldn't be
						// set it to null so that you return the default value
						hexString = null;
				}
				
				if (hexString != null)
				{
					answer = parseInt(hexString);
				}
			}
			
			return answer;
		}
		 
		/**
		 * isStringNotEmpty() determines if a String value is "empty".
		 * 
		 * <p>
		 * A String value is considered to be "empty" if it is null or if its length is zero.
		 * </p>
		 * 
		 * @param value is any String value.
		 * 
		 * @return A value of <code>true</code> means that the value is not empty.
		 * <code>false</code> means that either the value is null or the length of the value is zero.
		 **/
		 public static function isStringNotEmpty(value:String) : Boolean
		 {
		 	return ((value != null) && (value.length > 0));
		 }
		 
		/**
		 * isStringEmpty() determines if a String value is "empty".
		 * 
		 * <p>
		 * A String value is considered to be "empty" if it is null or if its length is zero.
		 * </p>
		 * 
		 * @param 	value 	is any String value.
		 * 
		 * @return 	A value of <code>true</code> means that the value is empty (null or empty).
		 * <code>false</code> means that the string contains one or more characters.
		 **/
		 public static function isStringEmpty(value:String) : Boolean
		 {
		 	return ((value == null) || (value.length < 1));
		 }
		 
		/**
		 * Just make sure that if http:// is missing - to add it  not going to 
		 * do complete boundary checking this is just a simple url accommodation.
		 * 
		 * @param	urlString	the url string to normalize
		 * 
		 * @return	the <code>String</code> that has been normalized
		 */
		public static function normalizeUrl( urlString:String ) : String
		{
			var answer:String = trim(urlString);
			if ((urlString != null) && (urlString.length > 0))
			{
				var regExp:RegExp = /\w+ :\/\/\w+ /x; 
				if ( !regExp.test(urlString) )
				{
					answer = "http://" + answer;	
				}
			}
			
			return answer;	
		}	
		
		/**
		 * Trim white spaces.
		 * 
		 * @param	str		the string to trim white spaces.
		 * 
		 * @return	<code>String</code> the string after front and back white spaces
		 * are removed.
		 */
		public static function trim( str:String ) : String
		{
			var regExp:RegExp = /\S.*\S/;
			var returnString:String = regExp.exec(str);
			return (returnString==null)?"":returnString;
		}
		
		/**
		 * Returns true if a String starts with a target string; otherwise returns false.
		 * The optional options argument allows you to specify whether the test should be
		 * case sensitive and whether leading white space should be ignored.
		 * 
		 * @param str is a String where the match will be conducted. 
		 * This value should not be <code>null</code>.
		 * 
		 * @param target is a String that will be searched for at the beginning of the test String.
		 * This value should not be <code>null</code>.
		 * 
		 * @param options is an optional Number argument which allows you to customize the test.
		 * The options are set as bits in the options number. 
		 * If the value of options is zero, then the target string must be found exactly as it is 
		 * passed in at the start of the string you are searching.
		 * 
		 * <p>
		 * Examples:  looking for foo
		 * <ul>
		 * <li>No options
		 * <ul>
		 * <li>Value = 0</li>
		 * <li>Reg Exp = /^\Qfoo\E/</li>
		 * <li>Matches: "foobar" but not "  foobar" or "FOObar"</li>
		 * </ul>
		 * </li>
		 * <li>Ignore Case
		 * <ul>
		 * <li>Value = 1</li>
		 * <li>Reg Exp = /^\Qfoo\E/i</li>
		 * <li>Matches: "foobar" "FOObar" but not "  foobar"</li>
		 * </ul>
		 * </li>
		 * <li>Ignore Whitespace
		 * <ul>
		 * <li>Value = 2</li>
		 * <li>Reg Exp = /^\s*\Qfoo\E/</li>
		 * <li>Matches: "foobar" "  foobar" but not "FOObar" </li>
		 * </ul>
		 * </li>
		 * <li>Ignore Case & Whitespace
		 * <ul>
		 * <li>Value = 3</li>
		 * <li>Reg Exp = /^\s*\Qfoo\E/i</li>
		 * <li>Matches: "foobar" "FOObar" and "  foobar"</li>
		 * </ul>
		 * </li>
		 * </ul>
		 * </p>
		 * 
		 * @return A return value of <code>true</code> indicates that the test string does
		 * start with the target string (as specified in the options).
		 * A return value of <code>false</code> indicates that the test string does NOT
		 * start with the target string, as specified.
		 */  
		public static function startsWith(str:String, target:String, options:Number = 0) : Boolean
		{
			var sRegExpFlags:String = Boolean(options & IGNORE_CASE) ? "i" : "";
			var sOptionalLeadingSpaces:String = Boolean(options & IGNORE_WHITESPACE) ? "\\s*" : "";
			var sEscapedTarget:String = escapeMetaChars(target);
			var sRegExpSource:String = "^" + sOptionalLeadingSpaces + sEscapedTarget;
			var reStartsWith:RegExp = new RegExp(sRegExpSource, sRegExpFlags);
			
			trace("reg exp = " + reStartsWith.toString());

			return reStartsWith.test(str);
		}
		
		// - 	Replaces the special END metasequence "\E" with 
		// 		The end sequence, FOLLOWED BY
		// 		A literal slash FOLLOWED BY
		// 		The Quote literal text metasequence "\Q"
		// -	Wraps the string in the "\Q" and "\E" to escape any special sequence
		// 		of characters (except \E).
		//
		// Examples:
		//		"|"			-->		"\Q|\E"
		//		"foo*"		-->		"\Qfoo*\E"
		//		"^foo[]"	-->		"\Q^foo[]\E"
		//		"S.P.A.M.\\Eand eggs"	-->		"\QS.P.A.M.\E\\E\Qand eggs\E"
		/**
		 * Wraps the string argument with "\Q" and "\E" to escape any special sequence
		 * of characters (except \E.)
		 * Examples:
		 * <ul>
		 * <li>"|"			-->		"\Q|\E"
		 * </li>
		 * <li>"foo*"		-->		"\Qfoo*\E"
		 * </li>
		 * <li>"^foo[]"	-->		"\Q^foo[]\E"
		 * </li>
		 * <li>"S.P.A.M.\\Eand eggs"	-->		"\QS.P.A.M.\E\\E\Qand eggs\E"
		 * </li>
		 * </ul>
		 * 
		 * @return Returns the escaped string
		 */ 
		public static function escapeMetaChars(str:String) : String
		{
			var answer :String = "";
			
			var reEscapeRegExpMeta:RegExp;
			if (str){
				// Replace every occurance of "\E" with "\E\\E\Q"
				answer = str.replace(/(\\E)/g, "\\E\\$1\\Q");
				
				answer = "\\Q" + answer + "\\E";
			}
			
			return answer;	
		}
		
		
		/**
		 * Create a unique name, using a "seed" if provided, using the current time as a suffix
		 */
		public static function createUniqueName(seedName:String) : String
		{
			var answer:String = "UNSPECIFIED";
			if ((seedName != null) && (seedName.length > 0))
				answer = seedName;
			
			return answer + getTimer();
		}
		
		/**
		 * Creates an instance of a Class with the specified class name. 
		 * The class must be present in the current application domain.
		 * This also assumes that the class constructor does NOT take more 
		 * than 5 parameters.
		 * 
		 * @param className is the name of the Class for which an instance is to be created.
		 * The className should be fully qualified including the package path.
		 * 
		 * @returns an instance of the named Class;
		 * if the instances cannot be created, then <code>null</code> is returned.
		 * 
		 * @example The following code creates an instance of PanacheSecretSauce.
		 * This class is defined in the com.panache.sauce package.
		 * <listing version="3.0">
		 * var mySauce:* = PanacheCoreUtilities("com.panache.sauce.PanacheSecretSauce");
		 * var myPanacheSauce = mySauce as IPanacheSecretSauce;
		 * </listing> 
		 */  
		public static function createInstanceForClassName(className:String, args:Array): *
		{
			MediaServicesAdDebug.reportTrace(className, DEBUG_SOURCE, "createInstanceForClassName");
			var instance:* = null;
			if (PanacheCoreUtilities.isStringNotEmpty(className))
			{
				try
				{
					var classRef:Class = getDefinitionByName(className) as Class;
					// here is where the magic is .. but don't look close .. this gets ugly .. BAZINGA
					// you can thank Dan here 
					// start black box here
					switch (args.length)
					{
						case 0:
							instance = new classRef();
							break;
							
						case 1:
							instance = new classRef(args[0]);
							break;
								
						case 2:
							instance = new classRef(args[0], args[1]);
							break;
								
						case 3:
							instance = new classRef(args[0], args[1], args[2]);
							break;
								
						case 4:
							instance = new classRef(args[0], args[1], args[2], args[3]);
							break;
								
						case 5:
							instance = new classRef(args[0], args[1], args[2], args[3], args[4]);
							break;
							
						default:
							MediaServicesAdDebug.reportErrorTrace("Cannot create instance of class- too many constructor parameters: " + className,
								null, DEBUG_SOURCE, "createInstanceForClassName");
					}
				}
				catch(ex:Error)
				{
					MediaServicesAdDebug.reportErrorTrace("Error creating instance for class: " + className, 
							ex, DEBUG_SOURCE, "createInstanceForClassName");	
				}
			}
			else
			{
				MediaServicesAdDebug.reportErrorTrace("", null, DEBUG_SOURCE, "createInstanceForClassName");
			}
			
			return instance;
		}
		
		/**
		 * creates a string representation of the fields and values in an object.
		 * If the value of a field is itself another type of the object,
		 * then just the object class name will be printed
		 * 
		 * @param obj is an instance of Object whose contents are to be
		 * described.
		 * 
		 * @return A string representing the contents of the object.
		 * If the parameter is null, then an empty string is returned.
		 * 
		 * @example The following code creates a string description
		 * of an object to be used in a trace statement.
		 * <listing version="3.0">
		 * trace(PanacheCoreUtilities.objectToString(myObj));
		 * </listing> 
		 */
		public static function objectToString(obj:Object) : String
		{
			var answer:String = "";
			if (obj != null)
			{
				if (obj is String)
				{
					answer = String(obj);
				}
				else if ((obj is Number) || (obj is Boolean))
				{
					answer += obj	
				}
				else
				{
					for (var key:String in obj)
					{
						if (PanacheCoreUtilities.isStringNotEmpty(key))
						{
							var fld:String = "[" + key + "=";
							fld += obj[key];
							fld += "]";
							answer += fld; 
						}
					}
				}	
			}
			
			return answer;	
		} 

		/**
		 * Returns true if the argument is a dynamic object;
		 * i.e., the type of object that can hold dynamic fields.
		 * 
		 * @param obj is the argument being tested.
		 * 
		 * @return A return value of <code>true</code>
		 * indicates that the argument is a dynamic Object.
		 * A return value of <code>false</code> indicates that
		 * the argument is NOT a dynamic Object.
		 */ 		
		public static function isObject(obj:*) : Boolean
		{
			try
			{
				var a:* = obj["test"];
			}
			catch (err:Error)
			{
				return false;
			}
			return true;
		}
		
		/**
		 * Returns a String representation of the contents of a 
		 * byte array.
		 * Each byte in the array is displayed as a two digit 
		 * hex number.
		 * 
		 * @param payload is a ByteArray containing the data
		 * to be converted.
		 * 
		 * @return A non-empty String displays the hex value
		 * for each element in the ByteArray.
		 * An empty string is returned if the ByteArray is null
		 * or empty.
		 */ 
		public static function binaryToString(payload:ByteArray) : String
		// convert a binary data payload into a string representation
		{
			var payloadString:String = "";
			if ((payload != null) && (payload.length > 0))
			{
				for (var i:int = 0; i < payload.length; i++)
				{
					var b:Number = Number(payload[i]);
					var bs:String = b.toString(16);
					if (bs.length < 2)
						bs = "0" + bs;
					payloadString += (bs + " ");		
				}
			}
			
			return payloadString;
		}

		/**
		 * Returns XML from a byte array which presumably cames from an embedded resource.
		 * 
		 * @param source is a ByteArray which contains the xml data loaded from an
		 * embedded resource.
		 * 
		 * @return A non-null value represents the XML data that was present in the
		 * byte array.
		 * A return value of <code>null</code> indicates that the byte array was
		 * either null or empty or that an error occured while reading the bytes.
		 */ 
		// 		[Embed(source='../../../assets/levels/definition.xml', mimeType="application/octet-stream")]
		// 		public static const AD_DEF:Class; 
		public static function xmlFromEmbeddedSource(source:ByteArray) : XML
		{
			var xml:XML = null;
			try
			{
				if ((source != null) && (source.length > 0))
				{
					// read the bytes from the byte array
					var str:String = source.readUTFBytes(source.length);
					// convert it to XML;
					xml = new XML(str);
				}
			}
			catch (ex:Error)
			{
				MediaServicesAdDebug.reportErrorTrace("Error retrieving embedded xml: ", ex, DEBUG_SOURCE, "xmlFromEmbeddedSource");	
			}
			
			return xml;
		}
		
		 			 
	}
}