package com.sticksports.nativeExtensions.gameCenter
{
	import com.sticksports.nativeExtensions.gameCenter.signals.GCSignal0;
	import com.sticksports.nativeExtensions.gameCenter.signals.GCSignal1;

	import flash.display.BitmapData;
	import flash.events.StatusEvent;
	import flash.external.ExtensionContext;

	public class GameCenter
	{
		public static var localPlayerAuthenticated : GCSignal0 = new GCSignal0();
		public static var localPlayerNotAuthenticated : GCSignal0 = new GCSignal0();
		public static var localPlayerFriendsLoadComplete : GCSignal1 = new GCSignal1( Array );
		public static var localPlayerFriendsLoadFailed : GCSignal0 = new GCSignal0();
		public static var leaderboardLoadComplete : GCSignal1 = new GCSignal1( GCLeaderboard );
		public static var leaderboardLoadFailed : GCSignal0 = new GCSignal0();
		public static var localPlayerScoreLoadComplete : GCSignal1 = new GCSignal1( GCLeaderboard );
		public static var localPlayerScoreLoadFailed : GCSignal0 = new GCSignal0();
		public static var localPlayerScoreReported : GCSignal0 = new GCSignal0();
		public static var localPlayerScoreReportFailed : GCSignal0 = new GCSignal0();
		public static var localPlayerAchievementReported : GCSignal0 = new GCSignal0();
		public static var localPlayerAchievementReportFailed : GCSignal0 = new GCSignal0();
		public static var achievementsLoadComplete : GCSignal1 = new GCSignal1( Vector );
		public static var achievementsLoadFailed : GCSignal0 = new GCSignal0();
		public static var gameCenterViewRemoved : GCSignal0 = new GCSignal0();

		private static var _loadPlayerPhotoCompleteSignals:Object = {};
		private static var _loadPlayerPhotoFailedSignals:Object = {};
		private static var _loadingPlayerPhotos:Object = {};

		public static var isAuthenticating : Boolean;

		private static var _isSupported : Boolean;
		private static var _isSupportedTested : Boolean;
		private static var _isAuthenticated : Boolean;
		private static var _isAuthenticatedTested : Boolean;

		private static var _localPlayer : GCLocalPlayer;
		private static var _localPlayerTested : Boolean;

		private static var extensionContext : ExtensionContext = null;
		private static var initialised : Boolean = false;

		/**
		 * Initialise the extension
		 */
		public static function init() : void
		{
			if ( !initialised )
			{
				initialised = true;

				extensionContext = ExtensionContext.createExtensionContext( "com.sticksports.nativeExtensions.GameCenter", null );

				extensionContext.addEventListener( StatusEvent.STATUS, handleStatusEvent );
			}
		}

		private static function handleStatusEvent( event : StatusEvent ) : void
		{
			//trace( "internal event", event.level );
			switch( event.level )
			{
				case InternalMessages.localPlayerAuthenticated :
					isAuthenticating = false;
					_isAuthenticated = true;
					_isAuthenticatedTested = true;
					_localPlayerTested = false;
					localPlayerAuthenticated.dispatch();
					break;
				case InternalMessages.localPlayerNotAuthenticated :
					isAuthenticating = false;
					_isAuthenticated = false;
					_isAuthenticatedTested = true;
					_localPlayerTested = false;
					localPlayerNotAuthenticated.dispatch();
					break;
				case InternalMessages.notAuthenticated :
					throw new Error( InternalMessages.notAuthenticatedError );
					break;
				case InternalMessages.scoreReported :
					localPlayerScoreReported.dispatch();
					break;
				case InternalMessages.scoreNotReported :
					localPlayerScoreReportFailed.dispatch();
					break;
				case InternalMessages.achievementReported :
					localPlayerAchievementReported.dispatch();
					break;
				case InternalMessages.achievementNotReported :
					localPlayerAchievementReportFailed.dispatch();
					break;
				case InternalMessages.loadFriendsComplete :
					var friends : Array = getReturnedPlayers( event.code );
					if( friends )
					{
						localPlayerFriendsLoadComplete.dispatch( friends );
					}
					else
					{
						localPlayerFriendsLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadFriendsFailed :
					localPlayerFriendsLoadFailed.dispatch();
					break;
				case InternalMessages.loadLocalPlayerScoreComplete :
					var score : GCLeaderboard = getReturnedLocalPlayerScore( event.code );
					if( score )
					{
						localPlayerScoreLoadComplete.dispatch( score );
					}
					else
					{
						localPlayerScoreLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadLocalPlayerScoreFailed :
					localPlayerScoreLoadFailed.dispatch();
					break;
				case InternalMessages.gameCenterViewRemoved :
					gameCenterViewRemoved.dispatch();
					break;
				case InternalMessages.loadLeaderboardComplete :
					var leaderboard : GCLeaderboard = getStoredLeaderboard( event.code );
					if( leaderboard )
					{
						leaderboardLoadComplete.dispatch( leaderboard );
					}
					else
					{
						leaderboardLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadLeaderboardFailed :
					leaderboardLoadFailed.dispatch();
					break;
				case InternalMessages.loadAchievementsComplete :
					var achievements : Vector.<GCAchievement> = getStoredAchievements( event.code );
					if( achievements )
					{
						achievementsLoadComplete.dispatch( achievements );
					}
					else
					{
						achievementsLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadAchievementsFailed :
					achievementsLoadFailed.dispatch();
					break;
				case InternalMessages.loadPlayerPhotoComplete:
					onLoadPlayerPhotoComplete(event.code);
					break;
				case InternalMessages.loadPlayerPhotoFailed:
					onLoadPlayerPhotoFailed(event.code);
					break;
			}
		}

		/**
		 * Is the extension supported
		 */
		public static function get isSupported() : Boolean
		{
			if( !_isSupportedTested )
			{
				_isSupportedTested = true;
				init();
				_isSupported = extensionContext.call( NativeMethods.isSupported ) as Boolean;
			}
			return _isSupported;
		}

		private static function assertIsSupported() : void
		{
			if( !isSupported )
			{
				throw new Error( InternalMessages.notSupportedError );
			}
		}

		/**
		 * Authenticate the local player
		 */
		public static function authenticateLocalPlayer() : void
		{
			assertIsSupported();
			isAuthenticating = true;
			extensionContext.call( NativeMethods.authenticateLocalPlayer );
		}

		/**
		 * Is the local player authenticated
		 */
		public static function get isAuthenticated() : Boolean
		{
			return _isAuthenticated;
		}

		private static function assertIsAuthenticatedTested() : void
		{
			assertIsSupported();
			if( !_isAuthenticatedTested )
			{
				throw new Error( InternalMessages.authenticationNotAttempted );
			}
		}

		private static function assertIsAuthenticated() : void
		{
			assertIsAuthenticatedTested();
			if( !_isAuthenticated )
			{
				throw new Error( InternalMessages.notAuthenticatedError );
			}
		}

		/**
		 * Authenticate the local player
		 */
		public static function get localPlayer() : GCLocalPlayer
		{
			assertIsAuthenticatedTested();
			if( _isAuthenticated && !_localPlayerTested )
			{
				_localPlayer = extensionContext.call( NativeMethods.getLocalPlayer ) as GCLocalPlayer;
			}
			return _localPlayer;
		}

		/**
		 * Report a score to Game Center
		 */
		public static function reportScore( category : String, value : int ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.reportScore, category, value );
			}
		}

		/**
		 * Report a achievement to Game Center
		 */
		public static function reportAchievement( category : String, value : Number, banner : Boolean = false ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.reportAchievement, category, value, banner );
			}
		}

		public static function showStandardLeaderboard( category : String = "", timeScope : int = -1 ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				if( category )
				{
					if( timeScope != -1 )
					{
						extensionContext.call( NativeMethods.showStandardLeaderboardWithCategoryAndTimescope, category, timeScope );
					}
					else
					{
						extensionContext.call( NativeMethods.showStandardLeaderboardWithCategory, category );
					}
				}
				else if( timeScope != -1 )
				{
					extensionContext.call( NativeMethods.showStandardLeaderboardWithTimescope, timeScope );
				}
				else
				{
					extensionContext.call( NativeMethods.showStandardLeaderboard );
				}
			}
		}

		public static function showStandardAchievements() : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.showStandardAchievements );
			}
		}

		public static function getLocalPlayerFriends() : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getLocalPlayerFriends );
			}
		}

		public static function getLocalPlayerScore( category : String, playerScope : int = 0, timeScope : int = 2 ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getLocalPlayerScore, category, playerScope, timeScope );
			}
		}

		public static function getPlayerPhoto(id:String, bmd:BitmapData, successCB:Function, errorCB:Function):void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				if(_loadingPlayerPhotos[id]) {
					return;
				}

				addLoadPlayerPhotoCompleteSignal(id, successCB);
				addLoadPlayerPhotoFailedSignal(id, errorCB);
				extensionContext.call( NativeMethods.getPlayerPhoto, id );
				_loadingPlayerPhotos[id] = bmd;
			}
		}

		public static function addLoadPlayerPhotoCompleteSignal( id:String, cb:Function ):void
		{
			var sig:GCSignal1 = _loadPlayerPhotoCompleteSignals[id];
			if(!sig) {
				sig = new GCSignal1( BitmapData );
				_loadPlayerPhotoCompleteSignals[id] = sig;
			}
			sig.addOnce(cb);
		}

		public static function addLoadPlayerPhotoFailedSignal( id:String, cb:Function ):void
		{
			var sig:GCSignal0 = _loadPlayerPhotoFailedSignals[id];
			if(!sig) {
				sig = new GCSignal0();
				_loadPlayerPhotoFailedSignals[id] = sig;
			}
			sig.addOnce(cb);
		}

		private static function onLoadPlayerPhotoComplete( id:String ):void
		{
			if(_loadingPlayerPhotos[id] && _loadPlayerPhotoCompleteSignals[id]) {
				var loadingPhoto:BitmapData = _loadingPlayerPhotos[id];
				try {
					var ret:Boolean = getStoredPlayerPhoto(id, loadingPhoto);
					if(true) { // XXX why isn't the ANE returning true?
						_loadPlayerPhotoCompleteSignals[id].dispatch(loadingPhoto);
					} else {
						_loadPlayerPhotoFailedSignals[id].dispatch();
					}
				} catch (e:Error) {
					_loadPlayerPhotoFailedSignals[id].dispatch();
				}
			}
			cleanupLoadingPhoto(id);
		}

		private static function onLoadPlayerPhotoFailed( id:String ):void
		{
			if(_loadPlayerPhotoFailedSignals[id]) {
				_loadPlayerPhotoFailedSignals[id].dispatch();
			}
			cleanupLoadingPhoto(id);
		}

		private static function cleanupLoadingPhoto( id:String ):void
		{
			if(_loadPlayerPhotoCompleteSignals[id]) {
				_loadPlayerPhotoCompleteSignals[id].removeAll();
				delete _loadPlayerPhotoCompleteSignals[id];
			}
			if(_loadPlayerPhotoFailedSignals[id]) {
				_loadPlayerPhotoFailedSignals[id].removeAll();
				delete _loadPlayerPhotoFailedSignals[id];
			}
			delete _loadingPlayerPhotos[id];
		}

		public static function getLeaderboard( category : String, playerScope : int = 0, timeScope : int = 2, rangeStart : int = 1, rangeLength : int = 25 ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getLeaderboard, category, playerScope, timeScope, rangeStart, rangeLength );
			}
		}

		public static function getAchievements() : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getAchievements );
			}
		}

		private static function getStoredPlayerPhoto(key:String, inBMD:BitmapData) : void
		{
			extensionContext.call( NativeMethods.getStoredPlayerPhoto, key, inBMD );
		}

		private static function getStoredLeaderboard( key : String ) : GCLeaderboard
		{
			return extensionContext.call( NativeMethods.getStoredLeaderboard, key ) as GCLeaderboard;
		}

		private static function getStoredAchievements( key : String ) : Vector.<GCAchievement>
		{
			return extensionContext.call( NativeMethods.getStoredAchievements, key ) as Vector.<GCAchievement>;
		}

		private static function getReturnedLocalPlayerScore( key : String ) : GCLeaderboard
		{
			return extensionContext.call( NativeMethods.getStoredLocalPlayerScore, key ) as GCLeaderboard;
		}

		private static function getReturnedPlayers( key : String ) : Array
		{
			return extensionContext.call( NativeMethods.getStoredPlayers, key ) as Array;
		}

		/**
		 * Clean up the extension - only if you no longer need it or want to free memory. All listeners will be removed.
		 */
		public static function dispose() : void
		{
			if ( extensionContext )
			{
				extensionContext.dispose();
				extensionContext = null;
			}
			localPlayerAuthenticated.removeAll();
			localPlayerNotAuthenticated.removeAll();
			localPlayerFriendsLoadComplete.removeAll();
			localPlayerFriendsLoadFailed.removeAll();
			leaderboardLoadComplete.removeAll();
			leaderboardLoadFailed.removeAll();
			achievementsLoadComplete.removeAll();
			achievementsLoadFailed.removeAll();
			localPlayerScoreLoadComplete.removeAll();
			localPlayerScoreLoadFailed.removeAll();
			localPlayerScoreReported.removeAll();
			localPlayerScoreReportFailed.removeAll();
			localPlayerAchievementReported.removeAll();
			localPlayerAchievementReportFailed.removeAll();
			gameCenterViewRemoved.removeAll();

			for (var k:String in _loadPlayerPhotoCompleteSignals) {
				_loadPlayerPhotoCompleteSignals[k].removeAll();
				delete _loadPlayerPhotoCompleteSignals[k];
			}
			for (k in _loadPlayerPhotoFailedSignals) {
				_loadPlayerPhotoFailedSignals[k].removeAll();
				delete _loadPlayerPhotoFailedSignals[k];
			}

			initialised = false;
		}
	}
}

package com.sticksports.nativeExtensions.gameCenter
{
	import com.sticksports.nativeExtensions.gameCenter.signals.GCSignal0;
	import com.sticksports.nativeExtensions.gameCenter.signals.GCSignal1;

	import flash.display.BitmapData;
	import flash.events.StatusEvent;
	import flash.external.ExtensionContext;

	public class GameCenter
	{
		public static var localPlayerAuthenticated : GCSignal0 = new GCSignal0();
		public static var localPlayerNotAuthenticated : GCSignal0 = new GCSignal0();
		public static var localPlayerFriendsLoadComplete : GCSignal1 = new GCSignal1( Array );
		public static var localPlayerFriendsLoadFailed : GCSignal0 = new GCSignal0();
		public static var leaderboardLoadComplete : GCSignal1 = new GCSignal1( GCLeaderboard );
		public static var leaderboardLoadFailed : GCSignal0 = new GCSignal0();
		public static var localPlayerScoreLoadComplete : GCSignal1 = new GCSignal1( GCLeaderboard );
		public static var localPlayerScoreLoadFailed : GCSignal0 = new GCSignal0();
		public static var localPlayerScoreReported : GCSignal0 = new GCSignal0();
		public static var localPlayerScoreReportFailed : GCSignal0 = new GCSignal0();
		public static var localPlayerAchievementReported : GCSignal0 = new GCSignal0();
		public static var localPlayerAchievementReportFailed : GCSignal0 = new GCSignal0();
		public static var achievementsLoadComplete : GCSignal1 = new GCSignal1( Vector );
		public static var achievementsLoadFailed : GCSignal0 = new GCSignal0();
		public static var gameCenterViewRemoved : GCSignal0 = new GCSignal0();

		private static var _loadPlayerPhotoCompleteSignals:Object = {};
		private static var _loadPlayerPhotoFailedSignals:Object = {};
		private static var _loadingPlayerPhotos:Object = {};

		public static var isAuthenticating : Boolean;

		private static var _isSupported : Boolean;
		private static var _isSupportedTested : Boolean;
		private static var _isAuthenticated : Boolean;
		private static var _isAuthenticatedTested : Boolean;

		private static var _localPlayer : GCLocalPlayer;
		private static var _localPlayerTested : Boolean;

		private static var extensionContext : ExtensionContext = null;
		private static var initialised : Boolean = false;

		/**
		 * Initialise the extension
		 */
		public static function init() : void
		{
			if ( !initialised )
			{
				initialised = true;

				extensionContext = ExtensionContext.createExtensionContext( "com.sticksports.nativeExtensions.GameCenter", null );

				extensionContext.addEventListener( StatusEvent.STATUS, handleStatusEvent );
			}
		}

		private static function handleStatusEvent( event : StatusEvent ) : void
		{
			//trace( "internal event", event.level );
			switch( event.level )
			{
				case InternalMessages.localPlayerAuthenticated :
					isAuthenticating = false;
					_isAuthenticated = true;
					_isAuthenticatedTested = true;
					_localPlayerTested = false;
					localPlayerAuthenticated.dispatch();
					break;
				case InternalMessages.localPlayerNotAuthenticated :
					isAuthenticating = false;
					_isAuthenticated = false;
					_isAuthenticatedTested = true;
					_localPlayerTested = false;
					localPlayerNotAuthenticated.dispatch();
					break;
				case InternalMessages.notAuthenticated :
					throw new Error( InternalMessages.notAuthenticatedError );
					break;
				case InternalMessages.scoreReported :
					localPlayerScoreReported.dispatch();
					break;
				case InternalMessages.scoreNotReported :
					localPlayerScoreReportFailed.dispatch();
					break;
				case InternalMessages.achievementReported :
					localPlayerAchievementReported.dispatch();
					break;
				case InternalMessages.achievementNotReported :
					localPlayerAchievementReportFailed.dispatch();
					break;
				case InternalMessages.loadFriendsComplete :
					var friends : Array = getReturnedPlayers( event.code );
					if( friends )
					{
						localPlayerFriendsLoadComplete.dispatch( friends );
					}
					else
					{
						localPlayerFriendsLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadFriendsFailed :
					localPlayerFriendsLoadFailed.dispatch();
					break;
				case InternalMessages.loadLocalPlayerScoreComplete :
					var score : GCLeaderboard = getReturnedLocalPlayerScore( event.code );
					if( score )
					{
						localPlayerScoreLoadComplete.dispatch( score );
					}
					else
					{
						localPlayerScoreLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadLocalPlayerScoreFailed :
					localPlayerScoreLoadFailed.dispatch();
					break;
				case InternalMessages.gameCenterViewRemoved :
					gameCenterViewRemoved.dispatch();
					break;
				case InternalMessages.loadLeaderboardComplete :
					var leaderboard : GCLeaderboard = getStoredLeaderboard( event.code );
					if( leaderboard )
					{
						leaderboardLoadComplete.dispatch( leaderboard );
					}
					else
					{
						leaderboardLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadLeaderboardFailed :
					leaderboardLoadFailed.dispatch();
					break;
				case InternalMessages.loadAchievementsComplete :
					var achievements : Vector.<GCAchievement> = getStoredAchievements( event.code );
					if( achievements )
					{
						achievementsLoadComplete.dispatch( achievements );
					}
					else
					{
						achievementsLoadFailed.dispatch();
					}
					break;
				case InternalMessages.loadAchievementsFailed :
					achievementsLoadFailed.dispatch();
					break;
				case InternalMessages.loadPlayerPhotoComplete:
					onLoadPlayerPhotoComplete(event.code);
					break;
				case InternalMessages.loadPlayerPhotoFailed:
					onLoadPlayerPhotoFailed(event.code);
					break;
			}
		}

		/**
		 * Is the extension supported
		 */
		public static function get isSupported() : Boolean
		{
			if( !_isSupportedTested )
			{
				_isSupportedTested = true;
				init();
				_isSupported = extensionContext.call( NativeMethods.isSupported ) as Boolean;
			}
			return _isSupported;
		}

		private static function assertIsSupported() : void
		{
			if( !isSupported )
			{
				throw new Error( InternalMessages.notSupportedError );
			}
		}

		/**
		 * Authenticate the local player
		 */
		public static function authenticateLocalPlayer() : void
		{
			assertIsSupported();
			isAuthenticating = true;
			extensionContext.call( NativeMethods.authenticateLocalPlayer );
		}

		/**
		 * Is the local player authenticated
		 */
		public static function get isAuthenticated() : Boolean
		{
			return _isAuthenticated;
		}

		private static function assertIsAuthenticatedTested() : void
		{
			assertIsSupported();
			if( !_isAuthenticatedTested )
			{
				throw new Error( InternalMessages.authenticationNotAttempted );
			}
		}

		private static function assertIsAuthenticated() : void
		{
			assertIsAuthenticatedTested();
			if( !_isAuthenticated )
			{
				throw new Error( InternalMessages.notAuthenticatedError );
			}
		}

		/**
		 * Authenticate the local player
		 */
		public static function get localPlayer() : GCLocalPlayer
		{
			assertIsAuthenticatedTested();
			if( _isAuthenticated && !_localPlayerTested )
			{
				_localPlayer = extensionContext.call( NativeMethods.getLocalPlayer ) as GCLocalPlayer;
			}
			return _localPlayer;
		}

		/**
		 * Report a score to Game Center
		 */
		public static function reportScore( category : String, value : int ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.reportScore, category, value );
			}
		}

		/**
		 * Report a achievement to Game Center
		 */
		public static function reportAchievement( category : String, value : Number, banner : Boolean = false ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.reportAchievement, category, value, banner );
			}
		}

		public static function showStandardLeaderboard( category : String = "", timeScope : int = -1 ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				if( category )
				{
					if( timeScope != -1 )
					{
						extensionContext.call( NativeMethods.showStandardLeaderboardWithCategoryAndTimescope, category, timeScope );
					}
					else
					{
						extensionContext.call( NativeMethods.showStandardLeaderboardWithCategory, category );
					}
				}
				else if( timeScope != -1 )
				{
					extensionContext.call( NativeMethods.showStandardLeaderboardWithTimescope, timeScope );
				}
				else
				{
					extensionContext.call( NativeMethods.showStandardLeaderboard );
				}
			}
		}

		public static function showStandardAchievements() : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.showStandardAchievements );
			}
		}

		public static function getLocalPlayerFriends() : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getLocalPlayerFriends );
			}
		}

		public static function getLocalPlayerScore( category : String, playerScope : int = 0, timeScope : int = 2 ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getLocalPlayerScore, category, playerScope, timeScope );
			}
		}

		public static function getPlayerPhoto(id:String, bmd:BitmapData, successCB:Function, errorCB:Function):void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				if(_loadingPlayerPhotos[id]) {
					return;
				}

				addLoadPlayerPhotoCompleteSignal(id, successCB);
				addLoadPlayerPhotoFailedSignal(id, errorCB);
				extensionContext.call( NativeMethods.getPlayerPhoto, id );
				_loadingPlayerPhotos[id] = bmd;
			}
		}

		public static function addLoadPlayerPhotoCompleteSignal( id:String, cb:Function ):void
		{
			var sig:GCSignal1 = _loadPlayerPhotoCompleteSignals[id];
			if(!sig) {
				sig = new GCSignal1( BitmapData );
				_loadPlayerPhotoCompleteSignals[id] = sig;
			}
			sig.addOnce(cb);
		}

		public static function addLoadPlayerPhotoFailedSignal( id:String, cb:Function ):void
		{
			var sig:GCSignal0 = _loadPlayerPhotoFailedSignals[id];
			if(!sig) {
				sig = new GCSignal0();
				_loadPlayerPhotoFailedSignals[id] = sig;
			}
			sig.addOnce(cb);
		}

		private static function onLoadPlayerPhotoComplete( id:String ):void
		{
			if(_loadingPlayerPhotos[id] && _loadPlayerPhotoCompleteSignals[id]) {
				var loadingPhoto:BitmapData = _loadingPlayerPhotos[id];
				try {
					var ret:Boolean = getStoredPlayerPhoto(id, loadingPhoto);
					if(ret) {
						_loadPlayerPhotoCompleteSignals[id].dispatch(loadingPhoto);
					} else {
						_loadPlayerPhotoFailedSignals[id].dispatch();
					}
				} catch (e:Error) {
					_loadPlayerPhotoFailedSignals[id].dispatch();
				}
			}
			cleanupLoadingPhoto(id);
		}

		private static function onLoadPlayerPhotoFailed( id:String ):void
		{
			if(_loadPlayerPhotoFailedSignals[id]) {
				_loadPlayerPhotoFailedSignals[id].dispatch();
			}
			cleanupLoadingPhoto(id);
		}

		private static function cleanupLoadingPhoto( id:String ):void
		{
			if(_loadPlayerPhotoCompleteSignals[id]) {
				_loadPlayerPhotoCompleteSignals[id].removeAll();
				delete _loadPlayerPhotoCompleteSignals[id];
			}
			if(_loadPlayerPhotoFailedSignals[id]) {
				_loadPlayerPhotoFailedSignals[id].removeAll();
				delete _loadPlayerPhotoFailedSignals[id];
			}
			delete _loadingPlayerPhotos[id];
		}

		public static function getLeaderboard( category : String, playerScope : int = 0, timeScope : int = 2, rangeStart : int = 1, rangeLength : int = 25 ) : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getLeaderboard, category, playerScope, timeScope, rangeStart, rangeLength );
			}
		}

		public static function getAchievements() : void
		{
			assertIsAuthenticated();
			if( localPlayer )
			{
				extensionContext.call( NativeMethods.getAchievements );
			}
		}

		private static function getStoredPlayerPhoto(key:String, inBMD:BitmapData) : Boolean
		{
			return extensionContext.call( NativeMethods.getStoredPlayerPhoto, key, inBMD );
		}

		private static function getStoredLeaderboard( key : String ) : GCLeaderboard
		{
			return extensionContext.call( NativeMethods.getStoredLeaderboard, key ) as GCLeaderboard;
		}

		private static function getStoredAchievements( key : String ) : Vector.<GCAchievement>
		{
			return extensionContext.call( NativeMethods.getStoredAchievements, key ) as Vector.<GCAchievement>;
		}

		private static function getReturnedLocalPlayerScore( key : String ) : GCLeaderboard
		{
			return extensionContext.call( NativeMethods.getStoredLocalPlayerScore, key ) as GCLeaderboard;
		}

		private static function getReturnedPlayers( key : String ) : Array
		{
			return extensionContext.call( NativeMethods.getStoredPlayers, key ) as Array;
		}

		/**
		 * Clean up the extension - only if you no longer need it or want to free memory. All listeners will be removed.
		 */
		public static function dispose() : void
		{
			if ( extensionContext )
			{
				extensionContext.dispose();
				extensionContext = null;
			}
			localPlayerAuthenticated.removeAll();
			localPlayerNotAuthenticated.removeAll();
			localPlayerFriendsLoadComplete.removeAll();
			localPlayerFriendsLoadFailed.removeAll();
			leaderboardLoadComplete.removeAll();
			leaderboardLoadFailed.removeAll();
			achievementsLoadComplete.removeAll();
			achievementsLoadFailed.removeAll();
			localPlayerScoreLoadComplete.removeAll();
			localPlayerScoreLoadFailed.removeAll();
			localPlayerScoreReported.removeAll();
			localPlayerScoreReportFailed.removeAll();
			localPlayerAchievementReported.removeAll();
			localPlayerAchievementReportFailed.removeAll();
			gameCenterViewRemoved.removeAll();

			for (var k:String in _loadPlayerPhotoCompleteSignals) {
				_loadPlayerPhotoCompleteSignals[k].removeAll();
				delete _loadPlayerPhotoCompleteSignals[k];
			}
			for (k in _loadPlayerPhotoFailedSignals) {
				_loadPlayerPhotoFailedSignals[k].removeAll();
				delete _loadPlayerPhotoFailedSignals[k];
			}

			initialised = false;
		}
	}
}

Fix return value in getStoredPlayerPhoto


/**
 * Copyright (c) 2009 Digital Primates IT Consulting Group
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author     Alan Stearns - astearns@adobe.com
 * 			   Michael Labriola - labriola@digitalprimates.net
 * 			   David Wolever - david@wolever.net
 * @version
 *
 **/

package org.flexunit.runners
{
	import flex.lang.reflect.Field;

	import org.flexunit.constants.AnnotationConstants;
	import org.flexunit.internals.dependency.ExternalDependencyResolver;
	import org.flexunit.internals.dependency.IExternalDependencyResolver;
	import org.flexunit.internals.dependency.IExternalRunnerDependencyWatcher;
	import org.flexunit.internals.runners.ErrorReportingRunner;
	import org.flexunit.internals.runners.InitializationError;
	import org.flexunit.runner.IDescription;
	import org.flexunit.runner.IRunner;
	import org.flexunit.runner.external.IExternalDependencyRunner;
	import org.flexunit.runner.notification.IRunNotifier;
	import org.flexunit.runner.notification.StoppedByUserException;
	import org.flexunit.runners.model.FrameworkMethod;
	import org.flexunit.runners.model.IRunnerBuilder;
	import org.flexunit.token.AsyncTestToken;

	public class Parameterized extends ParentRunner implements IExternalDependencyRunner {
		private var runners:Array;
		private var klass:Class;
		private var dr:IExternalDependencyResolver;
		private var _dependencyWatcher:IExternalRunnerDependencyWatcher;
		private var dependencyDataWatchers:Array;
		private var _externalDependencyError:String;
		private var externalError:Boolean = false;

		public function set dependencyWatcher( value:IExternalRunnerDependencyWatcher ):void {
			_dependencyWatcher = value;

			if ( value && dr ) {
				value.watchDependencyResolver( dr );
			}
		}

		public function set externalDependencyError( value:String ):void {
			externalError = true;
			_externalDependencyError = value;
		}

		//Blank constructor means the old 0/1 error
		public function Parameterized(klass:Class) {
			super(klass);
			this.klass = klass;

			dr = new ExternalDependencyResolver( klass, this );
			dr.resolveDependencies();
		}

		private function buildErrorRunner( message:String ):Array {
			return [new ErrorReportingRunner( klass, new Error("There was an error retrieving the parameters for the testcase: cause " + message ) ) ];
		}

		private function buildRunners():Array {
			var runners:Array = new Array();

			try {
				var parametersList:Array = getParametersList(klass);
				if ( parametersList.length == 0 ) {
					runners.push(new TestClassRunnerForParameters(klass));
				} else {
					for (var i:int= 0; i < parametersList.length; i++) {
						runners.push(new TestClassRunnerForParameters(klass,parametersList, i));
					}
				}
			}

			catch ( error:Error ) {
				runners = buildErrorRunner( error.message );
			}

			return runners;
		}

		private function getParametersList(klass:Class):Array {
			var allParams:Array = new Array();
			var frameworkMethod:FrameworkMethod;
			var field:Field;
			var methods:Array = getParametersMethods(klass);
			var fields:Array = getParametersFields(klass);
			var data:Array;

			for ( var i:int=0; i<methods.length; i++ ) {
				frameworkMethod = methods[ i ];

				data = frameworkMethod.invokeExplosively(klass) as Array;
				allParams = allParams.concat( data );
			}

			for ( var j:int=0; j<fields.length; j++ ) {
				field = fields[ j ];

				data = field.getObj( null ) as Array;
				allParams = allParams.concat( data );
			}

			return allParams;
		}

		private function getParametersMethods(klass:Class):Array {
			var methods:Array = testClass.getMetaDataMethods( AnnotationConstants.PARAMETERS );
			return methods;
		}

		private function getParametersFields(klass:Class):Array {
			var fields:Array = testClass.getMetaDataFields( AnnotationConstants.PARAMETERS, true );
			return fields;
		}

		// begin Items copied from Suite
		override protected function get children():Array {
			if ( !runners ) {
				if ( !externalError ) {
					runners = buildRunners();
				} else {
					runners = buildErrorRunner( _externalDependencyError );
				}
			}

			return runners;
		}

		override protected function describeChild( child:* ):IDescription {
			return IRunner( child ).description;
		}

		override public function pleaseStop():void {
			super.pleaseStop();

			if ( runners ) {
				for ( var i:int=0; i<runners.length; i++ ) {
					( runners[ i ] as IRunner ).pleaseStop();
				}
			}
		}

		override protected function runChild( child:*, notifier:IRunNotifier, childRunnerToken:AsyncTestToken ):void {
			if ( stopRequested ) {
				childRunnerToken.sendResult( new StoppedByUserException() );
				return;
			}

			IRunner( child ).run( notifier, childRunnerToken );
		}
		// end Items copied from Suite
	}
}

import flex.lang.reflect.Field;
import flex.lang.reflect.Klass;
import flex.lang.reflect.Method;
import flex.lang.reflect.metadata.MetaDataArgument;

import org.flexunit.constants.AnnotationArgumentConstants;
import org.flexunit.constants.AnnotationConstants;
import org.flexunit.internals.runners.InitializationError;
import org.flexunit.runner.Description;
import org.flexunit.runner.IDescription;
import org.flexunit.runners.BlockFlexUnit4ClassRunner;
import org.flexunit.runners.model.FrameworkMethod;
import org.flexunit.runners.model.ParameterizedMethod;
import org.flexunit.token.AsyncTestToken;

class TestClassRunnerForParameters extends BlockFlexUnit4ClassRunner {
	private var klassInfo:Klass;
	private var expandedTestList:Array;
	private var parameterSetNumber:int;
	private var parameterList:Array;
	private var constructorParameterized:Boolean = false;

	private function buildExpandedTestList():Array {
		var testMethods:Array = testClass.getMetaDataMethods( AnnotationConstants.TEST );
		var finalArray:Array = new Array();

		for ( var i:int=0; i<testMethods.length; i++ ) {
			var fwMethod:FrameworkMethod = testMethods[ i ];
			var argument:MetaDataArgument = fwMethod.method.getMetaData( AnnotationConstants.TEST ).getArgument( AnnotationArgumentConstants.DATAPROVIDER );
			var classMethod:Method;
			var field:Field;
			var results:Array;
			var paramMethod:ParameterizedMethod;

			if ( argument ) {
				classMethod = klassInfo.getMethod( argument.value );

				if ( classMethod ) {
					results = classMethod.invoke( testClass ) as Array;
				} else {
					field = klassInfo.getField( argument.value );

					if ( field ) {
						var ar:Array = field.getObj(null) as Array;
						results = new Array();
						results = results.concat( ar );
					}
				}

				for ( var j:int=0; j<results.length; j++ ) {
					paramMethod = new ParameterizedMethod( fwMethod.method, results[ j ] );
					finalArray.push( paramMethod );
				}
			} else {
				finalArray.push( fwMethod );
			}
		}

		return finalArray;
	}

	override protected function computeTestMethods():Array {
		//OPTIMIZATION POINT
		if ( !expandedTestList ) {
			expandedTestList = buildExpandedTestList();
		}

		return expandedTestList;
	}

	override protected function validatePublicVoidNoArgMethods( metaDataTag:String, isStatic:Boolean, errors:Array ):void {

		//Only validate the ones that do not have a dataProvider attribute for these rules
		var methods:Array = testClass.getMetaDataMethods( metaDataTag  );
		var argument:MetaDataArgument;

		var eachTestMethod:FrameworkMethod;
		for ( var i:int=0; i<methods.length; i++ ) {
			eachTestMethod = methods[ i ] as FrameworkMethod;

			//Does it have a dataProvider?
			argument = eachTestMethod.method.getMetaData( AnnotationConstants.TEST ).getArgument( AnnotationArgumentConstants.DATAPROVIDER );

			//If there is an argument, we need to punt on verification of arguments until later when we know how many there actually are
			if ( !argument ) {
				eachTestMethod.validatePublicVoidNoArg( isStatic, errors );
			}
		}
	}

	override protected function describeChild( child:* ):IDescription {
		if ( !constructorParameterized ) {
			return super.describeChild( child );
		}

		var params:Array = computeParams();

/*		if ( !params ) {
			throw new InitializationError( "Parameterized runner has not been provided data" );
		}*/

		var paramName:String = params?params.join ( "_" ):"Missing Params";
		var method:FrameworkMethod = FrameworkMethod( child );
		return Description.createTestDescription( testClass.asClass, method.name + '_' + paramName, method.metadata );
	}

	private function computeParams():Array {
		return parameterList?parameterList[parameterSetNumber]:null;
	}

	override protected function createTest():Object {
		var args:Array = computeParams();

		if ( args && args.length > 0 ) {
			return testClass.klassInfo.constructor.newInstanceApply( args );
		} else {
			return testClass.klassInfo.constructor.newInstance();
		}
	}

	public function TestClassRunnerForParameters(klass:Class, parameterList:Array=null, i:int=0) {
		klassInfo = new Klass( klass );
		super(klass);

		this.parameterList = parameterList;
		this.parameterSetNumber = i;

		if ( parameterList && parameterList.length > 0 ) {
			constructorParameterized = true;
		}
	}
}

/**
 * Copyright (c) 2009 Digital Primates IT Consulting Group
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author     Alan Stearns - astearns@adobe.com
 * 			   Michael Labriola - labriola@digitalprimates.net
 * 			   David Wolever - david@wolever.net
 * @version
 *
 **/

package org.flexunit.runners
{
	import flex.lang.reflect.Field;

	import org.flexunit.constants.AnnotationConstants;
	import org.flexunit.internals.dependency.ExternalDependencyResolver;
	import org.flexunit.internals.dependency.IExternalDependencyResolver;
	import org.flexunit.internals.dependency.IExternalRunnerDependencyWatcher;
	import org.flexunit.internals.runners.ErrorReportingRunner;
	import org.flexunit.internals.runners.InitializationError;
	import org.flexunit.runner.IDescription;
	import org.flexunit.runner.IRunner;
	import org.flexunit.runner.external.IExternalDependencyRunner;
	import org.flexunit.runner.notification.IRunNotifier;
	import org.flexunit.runner.notification.StoppedByUserException;
	import org.flexunit.runners.model.FrameworkMethod;
	import org.flexunit.runners.model.IRunnerBuilder;
	import org.flexunit.token.AsyncTestToken;

	public class Parameterized extends ParentRunner implements IExternalDependencyRunner {
		private var runners:Array;
		private var klass:Class;
		private var dr:IExternalDependencyResolver;
		private var _dependencyWatcher:IExternalRunnerDependencyWatcher;
		private var dependencyDataWatchers:Array;
		private var _externalDependencyError:String;
		private var externalError:Boolean = false;

		public function set dependencyWatcher( value:IExternalRunnerDependencyWatcher ):void {
			_dependencyWatcher = value;

			if ( value && dr ) {
				value.watchDependencyResolver( dr );
			}
		}

		public function set externalDependencyError( value:String ):void {
			externalError = true;
			_externalDependencyError = value;
		}

		//Blank constructor means the old 0/1 error
		public function Parameterized(klass:Class) {
			super(klass);
			this.klass = klass;

			dr = new ExternalDependencyResolver( klass, this );
			dr.resolveDependencies();
		}

		private function buildErrorRunner( message:String ):Array {
			return [new ErrorReportingRunner( klass, new Error("There was an error retrieving the parameters for the testcase: cause " + message ) ) ];
		}

		private function buildRunners():Array {
			var runners:Array = new Array();

			try {
				var parametersList:Array = getParametersList(klass);
				if ( parametersList.length == 0 ) {
					runners.push(new TestClassRunnerForParameters(klass));
				} else {
					for (var i:int= 0; i < parametersList.length; i++) {
						runners.push(new TestClassRunnerForParameters(klass,parametersList, i));
					}
				}
			}

			catch ( error:Error ) {
				runners = buildErrorRunner( error.message );
			}

			return runners;
		}

		private function getParametersList(klass:Class):Array {
			var allParams:Array = new Array();
			var frameworkMethod:FrameworkMethod;
			var field:Field;
			var methods:Array = getParametersMethods(klass);
			var fields:Array = getParametersFields(klass);
			var data:Array;

			for ( var i:int=0; i<methods.length; i++ ) {
				frameworkMethod = methods[ i ];

				data = frameworkMethod.invokeExplosively(klass) as Array;
				allParams = allParams.concat( data );
			}

			for ( var j:int=0; j<fields.length; j++ ) {
				field = fields[ j ];

				data = field.getObj( null ) as Array;
				allParams = allParams.concat( data );
			}

			return allParams;
		}

		private function getParametersMethods(klass:Class):Array {
			var methods:Array = testClass.getMetaDataMethods( AnnotationConstants.PARAMETERS );
			return methods;
		}

		private function getParametersFields(klass:Class):Array {
			var fields:Array = testClass.getMetaDataFields( AnnotationConstants.PARAMETERS, true );
			return fields;
		}

		// begin Items copied from Suite
		override protected function get children():Array {
			if ( !runners ) {
				if ( !externalError ) {
					runners = buildRunners();
				} else {
					runners = buildErrorRunner( _externalDependencyError );
				}
			}

			return runners;
		}

		override protected function describeChild( child:* ):IDescription {
			return IRunner( child ).description;
		}

		override public function pleaseStop():void {
			super.pleaseStop();

			if ( runners ) {
				for ( var i:int=0; i<runners.length; i++ ) {
					( runners[ i ] as IRunner ).pleaseStop();
				}
			}
		}

		override protected function runChild( child:*, notifier:IRunNotifier, childRunnerToken:AsyncTestToken ):void {
			if ( stopRequested ) {
				childRunnerToken.sendResult( new StoppedByUserException() );
				return;
			}

			IRunner( child ).run( notifier, childRunnerToken );
		}
		// end Items copied from Suite
	}
}

import flex.lang.reflect.Field;
import flex.lang.reflect.Klass;
import flex.lang.reflect.Method;
import flex.lang.reflect.metadata.MetaDataAnnotation;
import flex.lang.reflect.metadata.MetaDataArgument;

import org.flexunit.constants.AnnotationArgumentConstants;
import org.flexunit.constants.AnnotationConstants;
import org.flexunit.internals.runners.InitializationError;
import org.flexunit.runner.Description;
import org.flexunit.runner.IDescription;
import org.flexunit.runners.BlockFlexUnit4ClassRunner;
import org.flexunit.runners.model.FrameworkMethod;
import org.flexunit.runners.model.ParameterizedMethod;
import org.flexunit.token.AsyncTestToken;

class TestClassRunnerForParameters extends BlockFlexUnit4ClassRunner {
	private var klassInfo:Klass;
	private var expandedTestList:Array;
	private var parameterSetNumber:int;
	private var parameterList:Array;
	private var constructorParameterized:Boolean = false;

	private function buildExpandedTestList():Array {
		var testMethods:Array = testClass.getMetaDataMethods( AnnotationConstants.TEST );
		var finalArray:Array = new Array();

		for ( var i:int=0; i<testMethods.length; i++ ) {
			var fwMethod:FrameworkMethod = testMethods[ i ];
			var argument:MetaDataArgument = fwMethod.method.getMetaData( AnnotationConstants.TEST ).getArgument( AnnotationArgumentConstants.DATAPROVIDER );
			var classMethod:Method;
			var field:Field;
			var results:Array;
			var paramMethod:ParameterizedMethod;

			if ( argument ) {
				classMethod = klassInfo.getMethod( argument.value );

				if ( classMethod ) {
					results = classMethod.invoke( testClass ) as Array;
				} else {
					field = klassInfo.getField( argument.value );

					if ( field ) {
						var ar:Array = field.getObj(null) as Array;
						results = new Array();
						results = results.concat( ar );
					}
				}

				for ( var j:int=0; j<results.length; j++ ) {
					paramMethod = new ParameterizedMethod( fwMethod.method, results[ j ] );
					finalArray.push( paramMethod );
				}
			} else {
				finalArray.push( fwMethod );
			}
		}

		return finalArray;
	}

	override protected function computeTestMethods():Array {
		//OPTIMIZATION POINT
		if ( !expandedTestList ) {
			expandedTestList = buildExpandedTestList();
		}

		return expandedTestList;
	}

	override protected function validatePublicVoidNoArgMethods( metaDataTag:String, isStatic:Boolean, errors:Array ):void {

		//Only validate the ones that do not have a dataProvider attribute for these rules
		var methods:Array = testClass.getMetaDataMethods( metaDataTag  );
		var annotation:MetaDataAnnotation;
		var argument:MetaDataArgument;

		var eachTestMethod:FrameworkMethod;
		for ( var i:int=0; i<methods.length; i++ ) {
			eachTestMethod = methods[ i ] as FrameworkMethod;

			annotation = eachTestMethod.method.getMetaData( AnnotationConstants.TEST );

			if ( annotation ) {
				//Does it have a dataProvider?
				argument = annotation.getArgument( AnnotationArgumentConstants.DATAPROVIDER );
			}

			//If there is an argument, we need to punt on verification of arguments until later when we know how many there actually are
			if ( !argument ) {
				eachTestMethod.validatePublicVoidNoArg( isStatic, errors );
			}
		}
	}

	override protected function describeChild( child:* ):IDescription {
		if ( !constructorParameterized ) {
			return super.describeChild( child );
		}

		var params:Array = computeParams();

/*		if ( !params ) {
			throw new InitializationError( "Parameterized runner has not been provided data" );
		}*/

		var paramName:String = params?params.join ( "_" ):"Missing Params";
		var method:FrameworkMethod = FrameworkMethod( child );
		return Description.createTestDescription( testClass.asClass, method.name + '_' + paramName, method.metadata );
	}

	private function computeParams():Array {
		return parameterList?parameterList[parameterSetNumber]:null;
	}

	override protected function createTest():Object {
		var args:Array = computeParams();

		if ( args && args.length > 0 ) {
			return testClass.klassInfo.constructor.newInstanceApply( args );
		} else {
			return testClass.klassInfo.constructor.newInstance();
		}
	}

	public function TestClassRunnerForParameters(klass:Class, parameterList:Array=null, i:int=0) {
		klassInfo = new Klass( klass );
		super(klass);

		this.parameterList = parameterList;
		this.parameterSetNumber = i;

		if ( parameterList && parameterList.length > 0 ) {
			constructorParameterized = true;
		}
	}
}

Fix for bug where a [Before] or [After] in a TestNG style Parameterized test caused a crash


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.core
{
    import flash.display.DisplayObject;

    import mx.states.AddItems;
    import mx.states.SetEventHandler;
    import mx.states.SetProperty;
    import mx.states.State;

    import org.apache.flex.core.IParent;
    import org.apache.flex.core.IStatesObject;
    import org.apache.flex.events.Event;
    import org.apache.flex.events.EventDispatcher;
    import org.apache.flex.events.IEventDispatcher;
    import org.apache.flex.events.ValueChangeEvent;
    import org.apache.flex.utils.MXMLDataInterpreter;

    /**
     *  The SimpleStatesImpl class implements a minimal set of
     *  view state functionality that is sufficient for most applications.
     *  It only supports AddItems and SetProperty changes at this time.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class SimpleStatesImpl extends EventDispatcher implements IStatesImpl, IBead
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function SimpleStatesImpl()
		{
			super();
		}

        private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function set strand(value:IStrand):void
        {
            _strand = value;
            IEventDispatcher(_strand).addEventListener("currentStateChange", stateChangeHandler);
            IEventDispatcher(_strand).addEventListener("initComplete", initialStateHandler);
        }

        private function initialStateHandler(event:org.apache.flex.events.Event):void
        {
            stateChangeHandler(new ValueChangeEvent("currentStateChange", false, false, null,
                IStatesObject(_strand).currentState));
        }

        private function stateChangeHandler(event:ValueChangeEvent):void
        {
            var doc:IStatesObject = _strand as IStatesObject;
            var arr:Array = doc.states;
            for each (var s:State in arr)
            {
                if (s.name == event.oldValue)
                {
                    revert(s);
                    break;
                }
            }
            for each (s in arr)
            {
                if (s.name == event.newValue)
                {
                    apply(s);
                    break;
                }
            }
            doc.dispatchEvent(new Event("stateChangeComplete"));
        }

        private function revert(s:State):void
        {
            var arr:Array = s.overrides;
            for each (var o:Object in arr)
            {
                if (o is AddItems)
                {
                    var ai:AddItems = AddItems(o);
                    for each (var item:DisplayObject in ai.items)
                    {
                        var parent:IParent = ai.document as IParent;
                        if (ai.destination != null)
                            parent = parent[ai.destination] as IParent;
                        parent.removeElement(item);
                    }
                    if (parent is IContainer)
                        IContainer(parent).childrenAdded();
                }
                else if (o is SetProperty)
                {
                    var sp:SetProperty = SetProperty(o);
                    if (sp.target != null)
                        sp.document[sp.target][sp.name] = sp.previousValue;
                    else
                        sp.document[sp.name] = sp.previousValue;
                }
                else if (o is SetEventHandler)
                {
                    var seh:SetEventHandler = SetEventHandler(o);
                    if (seh.target != null)
                    {
                        seh.document[seh.target].removeEventListener(seh.name, seh.handlerFunction);
                    }
                    else
                    {
                        seh.document.removeEventListener(seh.name, seh.handlerFunction);
                    }
                }
            }
        }

        private function apply(s:State):void
        {
            var arr:Array = s.overrides;
            for each (var o:Object in arr)
            {
                if (o is AddItems)
                {
                    var ai:AddItems = AddItems(o);
                    if (ai.items == null)
                    {
                        ai.items = ai.itemsDescriptor.items as Array;
                        if (ai.items == null)
                        {
                            ai.items =
                                MXMLDataInterpreter.generateMXMLArray(ai.document,
                                    null, ai.itemsDescriptor.descriptor);
                            ai.itemsDescriptor.items = ai.items;
                        }
                    }
                    for each (var item:DisplayObject in ai.items)
                    {
                        var parent:IParent = ai.document as IParent;
                        if (ai.destination != null)
                            parent = parent[ai.destination] as IParent;
                        if (ai.relativeTo != null)
                        {
                            var index:int = parent.numElements;
                            if (ai.relativeTo != null)
                            {
                                var child:Object = ai.document[ai.relativeTo];
                                index = parent.getElementIndex(child);
                                if (ai.position == "after")
                                    index++;
                            }
                            parent.addElementAt(item, index);
                        }
                        else
                        {
                            parent.addElement(item);
                        }
                    }
                    if (parent is IContainer)
                        IContainer(parent).childrenAdded();
                }
                else if (o is SetProperty)
                {
                    var sp:SetProperty = SetProperty(o);
                    if (sp.target != null)
                    {
                        sp.previousValue = sp.document[sp.target][sp.name];
                        sp.document[sp.target][sp.name] = sp.value;
                    }
                    else
                    {
                        sp.previousValue = sp.document[sp.name];
                        sp.document[sp.name] = sp.value;
                    }
                }
                else if (o is SetEventHandler)
                {
                    var seh:SetEventHandler = SetEventHandler(o);
                    if (seh.target != null)
                    {
                        seh.document[seh.target].addEventListener(seh.name, seh.handlerFunction);
                    }
                    else
                    {
                        seh.document.addEventListener(seh.name, seh.handlerFunction);
                    }
                }
            }
        }
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.core
{
    import flash.display.DisplayObject;

    import mx.states.AddItems;
    import mx.states.SetEventHandler;
    import mx.states.SetProperty;
    import mx.states.State;

    import org.apache.flex.core.IParent;
    import org.apache.flex.core.IStatesObject;
    import org.apache.flex.events.Event;
    import org.apache.flex.events.EventDispatcher;
    import org.apache.flex.events.IEventDispatcher;
    import org.apache.flex.events.ValueChangeEvent;
    import org.apache.flex.utils.MXMLDataInterpreter;

    /**
     *  The SimpleStatesImpl class implements a minimal set of
     *  view state functionality that is sufficient for most applications.
     *  It only supports AddItems and SetProperty changes at this time.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class SimpleStatesImpl extends EventDispatcher implements IStatesImpl, IBead
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function SimpleStatesImpl()
		{
			super();
		}

        private var _strand:IStrand;

        private var sawInitComplete:Boolean;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function set strand(value:IStrand):void
        {
            _strand = value;
            IEventDispatcher(_strand).addEventListener("currentStateChange", stateChangeHandler);
            IEventDispatcher(_strand).addEventListener("initComplete", initialStateHandler);
        }

        private function initialStateHandler(event:org.apache.flex.events.Event):void
        {
            sawInitComplete = true;
            stateChangeHandler(new ValueChangeEvent("currentStateChange", false, false, null,
                IStatesObject(_strand).currentState));
        }

        private function stateChangeHandler(event:ValueChangeEvent):void
        {
            if (!sawInitComplete)
                return;

            var doc:IStatesObject = _strand as IStatesObject;
            var arr:Array = doc.states;
            for each (var s:State in arr)
            {
                if (s.name == event.oldValue)
                {
                    revert(s);
                    break;
                }
            }
            for each (s in arr)
            {
                if (s.name == event.newValue)
                {
                    apply(s);
                    break;
                }
            }
            doc.dispatchEvent(new Event("stateChangeComplete"));
        }

        private function revert(s:State):void
        {
            var arr:Array = s.overrides;
            for each (var o:Object in arr)
            {
                if (o is AddItems)
                {
                    var ai:AddItems = AddItems(o);
                    for each (var item:DisplayObject in ai.items)
                    {
                        var parent:IParent = item.parent as IParent;
                        parent.removeElement(item);
                    }
                    if (parent is IContainer)
                        IContainer(parent).childrenAdded();
                }
                else if (o is SetProperty)
                {
                    var sp:SetProperty = SetProperty(o);
                    if (sp.target != null)
                        sp.document[sp.target][sp.name] = sp.previousValue;
                    else
                        sp.document[sp.name] = sp.previousValue;
                }
                else if (o is SetEventHandler)
                {
                    var seh:SetEventHandler = SetEventHandler(o);
                    if (seh.target != null)
                    {
                        seh.document[seh.target].removeEventListener(seh.name, seh.handlerFunction);
                    }
                    else
                    {
                        seh.document.removeEventListener(seh.name, seh.handlerFunction);
                    }
                }
            }
        }

        private function apply(s:State):void
        {
            var arr:Array = s.overrides;
            for each (var o:Object in arr)
            {
                if (o is AddItems)
                {
                    var ai:AddItems = AddItems(o);
                    if (ai.items == null)
                    {
                        ai.items = ai.itemsDescriptor.items as Array;
                        if (ai.items == null)
                        {
                            ai.items =
                                MXMLDataInterpreter.generateMXMLArray(ai.document,
                                    null, ai.itemsDescriptor.descriptor);
                            ai.itemsDescriptor.items = ai.items;
                        }
                    }
                    for each (var item:DisplayObject in ai.items)
                    {
                        var parent:IParent = ai.document as IParent;
                        if (ai.destination != null)
                            parent = parent[ai.destination] as IParent;
                        if (ai.relativeTo != null)
                        {
                            var child:Object = ai.document[ai.relativeTo];
                            parent = child.parent as IParent;
                            var index:int = parent.getElementIndex(child);
                            if (ai.position == "after")
                                index++;
                            parent.addElementAt(item, index);
                        }
                        else
                        {
                            parent.addElement(item);
                        }
                    }
                    if (parent is IContainer)
                        IContainer(parent).childrenAdded();
                }
                else if (o is SetProperty)
                {
                    var sp:SetProperty = SetProperty(o);
                    if (sp.target != null)
                    {
                        sp.previousValue = sp.document[sp.target][sp.name];
                        sp.document[sp.target][sp.name] = sp.value;
                    }
                    else
                    {
                        sp.previousValue = sp.document[sp.name];
                        sp.document[sp.name] = sp.value;
                    }
                }
                else if (o is SetEventHandler)
                {
                    var seh:SetEventHandler = SetEventHandler(o);
                    if (seh.target != null)
                    {
                        seh.document[seh.target].addEventListener(seh.name, seh.handlerFunction);
                    }
                    else
                    {
                        seh.document.addEventListener(seh.name, seh.handlerFunction);
                    }
                }
            }
        }
	}
}

fix which parent is used for adding/removing items


package {

import avmplus.*;
import flash.utils.ByteArray;
import flash.utils.Dictionary;

/**
 * Partial AMF3 decoder, based on
 *      http://download.macromedia.com/pub/labs/amf/amf3_spec_121207.pdf
 */
public class AMF3Decoder
{
    public static function decode (bytes :ByteArray) :*
    {
        _ctx = new Context(bytes);
        return decodeValue();
    }

    private static function decodeValue () :*
    {
        var marker :int = _ctx.bytes.readByte();
        switch(marker) {
        default:
            throw new Error("Unexpected marker in AMF3: " + marker);

        case AMF3.MARK_XML:
        case AMF3.MARK_XML_DOC:
            throw new Error("XML deserialization not supported");

        case AMF3.MARK_DATE:
            // TODO: this is just lazy, it's super trivial
            throw new Error("Date deserialization not supported");

        case AMF3.MARK_UNDEFINED:
            return undefined;

        case AMF3.MARK_NULL:
            return null;

        case AMF3.MARK_FALSE:
            return false;

        case AMF3.MARK_TRUE:
            return true;

        case AMF3.MARK_INTEGER:
            return decodeInteger();

        case AMF3.MARK_DOUBLE:
            return _ctx.bytes.readDouble();

        case AMF3.MARK_STRING:
            return decodeString();

        case AMF3.MARK_ARRAY:
            return decodeArray();

        case AMF3.MARK_OBJECT:
            return decodeObject();

        case AMF3.MARK_BYTE_ARRAY:
            return decodeByteArray();

        }
    }

    private static function decodeInteger () :int
    {
        var b :uint = _ctx.bytes.readByte();
        var n :uint = (b & 0x7f);
        if ((b & 0x80) != 0) {
            b = _ctx.bytes.readByte();
            n = (n << 7) | (b & 0x7f);

            if ((b & 0x80) != 0) {
                b = _ctx.bytes.readByte();
                n = (n << 7) | (b & 0x7f);

                if ((b & 0x80) != 0) {
                    b = _ctx.bytes.readByte();
                    n = (n << 8) | b;

                    if (n >= (1 << 28)) {
                        // adjust for the fact that the 29th bit was the sign bit
                        return (1 << 29) - n;
                    }
                    return n;
                }
            }
        }
        return n;
    }

    private static function decodeString () :String
    {
        var code :uint = decodeInteger();

        // is it a reference?
        if ((code & 0x01) == 0) {
            return _ctx.sRef[code >> 1];
        }

        // if not, read N bytes
        var str :String = _ctx.bytes.readUTFBytes(code >> 1);
        _ctx.sRef.table[str] = _ctx.sRef.ix ++;
        return str;
    }

    private static function decodeArray () :Array
    {
        var code :uint = decodeInteger();

        // is it a reference?
        if ((code & 0x01) == 0) {
            return _ctx.oRef[code >> 1];
        }

        // if not, create our array & note its reference
        var result :Array = [ ];
        _ctx.oRef.table[result] = _ctx.oRef.ix ++;

        // then first read associative pairs
        var key :String;
        while ((key = decodeString()).length > 0) {
            result[key] = decodeValue();
        }

        // finally read N dense values
        var N :uint = code >> 1;
        for (var ii :int = 0; ii < N; ii ++) {
            result[ii] = decodeValue();
        }
        return result;
    }

    private static function decodeObject ():Object
    {
        var code :uint = decodeInteger();

        // is it an instance reference?
        if ((code & 0x01) == 0) {
            return _ctx.oRef[code >> 1];
        }

        var traits :Traits;
        // is it a trait reference?
        if ((code & 0x02) == 0) {
            traits = _ctx.tRef[code >> 2];

        } else if ((code & 0x04) == 0) {
            // it's a new trait, read class name & N sealed variable names
            var alias :String = decodeString();
            var className = (alias.length > 0) ? AMF3.getClassNameByAlias(alias) : "";
            var N :uint = code >> 4;
            var vars :Array = new Array(N);
            for (var ii :int = 0; ii < N; ii ++) {
                vars[ii] = decodeString();
            }
            traits = new Traits(className, (code & 0x08) != 0, vars);
            _ctx.tRef.table[traits] = _ctx.tRef.ix ++;

        } else {
            throw new Error("IExternalizable serialization not supported");
        }

        // instantiate the object
        var obj :Object;
        if (traits.className != null) {
            obj = Domain.currentDomain.getClass(traits.className);
        } else {
            obj = { };
        }

        // register the reference
        _ctx.oRef.table[obj] = _ctx.oRef.ix ++;

        // read the sealed values
        for (var ii :int = 0; ii < traits.vars.length; ii ++) {
            obj[vars[ii]] = decodeValue();
        }

        // if this object was marked dynamic, read key/value pairs until ""
        if (traits.isDynamic) {
            var key :String;
            while ((key = decodeString()).length > 0) {
                obj[key] = decodeValue();
            }
        }
        return obj;
    }

    private static function decodeByteArray () :ByteArray
    {
        var code :uint = decodeInteger();
        // is it an instance reference?
        if ((code & 0x01) == 0) {
            return _ctx.oRef[code >> 1];
        }

        // if not, instantiate
        var bytes :ByteArray = new ByteArray();

        // register
        _ctx.oRef.table[bytes] = _ctx.oRef.ix ++;

        // and read the bytes
        _ctx.bytes.readBytes(bytes, 0, code >> 1);
        return bytes;
    }

    private static var _ctx :Context;
}
}

import flash.utils.ByteArray;
import flash.utils.Dictionary;

class Context
{
    public var bytes :ByteArray;

    public var oRef :References = new References();
    public var sRef :References = new References();
    public var tRef :References = new References();

    public function Context (bytes :ByteArray)
    {
        this.bytes = bytes;
    }
}

class References
{
    public var table :Dictionary = new Dictionary();
    public var ix :uint = 0;
}

class Traits
{
    public var className :String;
    public var isDynamic :Boolean;
    public var vars :Array;

    public function Traits (className :String, isDynamic :Boolean, vars :Array)
    {
        this.className = className;
        this.isDynamic = isDynamic;
        this.vars = vars;
    }
}

package {

import avmplus.*;
import flash.utils.ByteArray;
import flash.utils.Dictionary;

/**
 * Partial AMF3 decoder, based on
 *      http://download.macromedia.com/pub/labs/amf/amf3_spec_121207.pdf
 */
public class AMF3Decoder
{
    public static function decode (bytes :ByteArray) :*
    {
        _ctx = new Context(bytes);
        return decodeValue();
    }

    private static function decodeValue () :*
    {
        var marker :int = _ctx.bytes.readByte();
        switch(marker) {
        default:
            throw new Error("Unexpected marker in AMF3: " + marker);

        case AMF3.MARK_XML:
        case AMF3.MARK_XML_DOC:
            throw new Error("XML deserialization not supported");

        case AMF3.MARK_DATE:
            // TODO: this is just lazy, it's super trivial
            throw new Error("Date deserialization not supported");

        case AMF3.MARK_UNDEFINED:
            return undefined;

        case AMF3.MARK_NULL:
            return null;

        case AMF3.MARK_FALSE:
            return false;

        case AMF3.MARK_TRUE:
            return true;

        case AMF3.MARK_INTEGER:
            return decodeInteger();

        case AMF3.MARK_DOUBLE:
            return _ctx.bytes.readDouble();

        case AMF3.MARK_STRING:
            return decodeString();

        case AMF3.MARK_ARRAY:
            return decodeArray();

        case AMF3.MARK_OBJECT:
            return decodeObject();

        case AMF3.MARK_BYTE_ARRAY:
            return decodeByteArray();

        }
    }

    private static function decodeInteger () :int
    {
        var b :uint = _ctx.bytes.readUnsignedByte();
        var n :uint = (b & 0x7f);
        if ((b & 0x80) != 0) {
            b = _ctx.bytes.readUnsignedByte();
            n = (n << 7) | (b & 0x7f);

            if ((b & 0x80) != 0) {
                b = _ctx.bytes.readUnsignedByte();
                n = (n << 7) | (b & 0x7f);

                if ((b & 0x80) != 0) {
                    b = _ctx.bytes.readUnsignedByte();
                    n = (n << 8) | b;

                    if (n >= (1 << 28)) {
                        // adjust for the fact that the 29th bit was the sign bit
                        return n - (1 << 29);
                    }
                }
            }
        }
        return n;
    }

    private static function decodeString () :String
    {
        var code :uint = decodeInteger();

        // is it a reference?
        if ((code & 0x01) == 0) {
            return _ctx.sRef[code >> 1];
        }

        // if not, read N bytes
        var str :String = _ctx.bytes.readUTFBytes(code >> 1);
        _ctx.sRef.table[str] = _ctx.sRef.ix ++;
        return str;
    }

    private static function decodeArray () :Array
    {
        var code :uint = decodeInteger();

        // is it a reference?
        if ((code & 0x01) == 0) {
            return _ctx.oRef[code >> 1];
        }

        // if not, create our array & note its reference
        var result :Array = [ ];
        _ctx.oRef.table[result] = _ctx.oRef.ix ++;

        // then first read associative pairs
        var key :String;
        while ((key = decodeString()).length > 0) {
            result[key] = decodeValue();
        }

        // finally read N dense values
        var N :uint = code >> 1;
        for (var ii :int = 0; ii < N; ii ++) {
            result[ii] = decodeValue();
        }
        return result;
    }

    private static function decodeObject ():Object
    {
        var code :uint = decodeInteger();

        // is it an instance reference?
        if ((code & 0x01) == 0) {
            return _ctx.oRef[code >> 1];
        }

        var traits :Traits;
        // is it a trait reference?
        if ((code & 0x02) == 0) {
            traits = _ctx.tRef[code >> 2];

        } else if ((code & 0x04) == 0) {
            // it's a new trait, read class name & N sealed variable names
            var alias :String = decodeString();
            var className = (alias.length > 0) ? AMF3.getClassNameByAlias(alias) : "";
            var N :uint = code >> 4;
            var vars :Array = new Array(N);
            for (var ii :int = 0; ii < N; ii ++) {
                vars[ii] = decodeString();
            }
            traits = new Traits(className, (code & 0x08) != 0, vars);
            _ctx.tRef.table[traits] = _ctx.tRef.ix ++;

        } else {
            throw new Error("IExternalizable serialization not supported");
        }

        // instantiate the object
        var obj :Object;
        if (traits.className != null) {
            obj = Domain.currentDomain.getClass(traits.className);
        } else {
            obj = { };
        }

        // register the reference
        _ctx.oRef.table[obj] = _ctx.oRef.ix ++;

        // read the sealed values
        for (var ii :int = 0; ii < traits.vars.length; ii ++) {
            obj[vars[ii]] = decodeValue();
        }

        // if this object was marked dynamic, read key/value pairs until ""
        if (traits.isDynamic) {
            var key :String;
            while ((key = decodeString()).length > 0) {
                obj[key] = decodeValue();
            }
        }
        return obj;
    }

    private static function decodeByteArray () :ByteArray
    {
        var code :uint = decodeInteger();
        // is it an instance reference?
        if ((code & 0x01) == 0) {
            return _ctx.oRef[code >> 1];
        }

        // if not, instantiate
        var bytes :ByteArray = new ByteArray();

        // register
        _ctx.oRef.table[bytes] = _ctx.oRef.ix ++;

        // and read the bytes
        _ctx.bytes.readBytes(bytes, 0, code >> 1);
        return bytes;
    }

    private static var _ctx :Context;
}
}

import flash.utils.ByteArray;
import flash.utils.Dictionary;

class Context
{
    public var bytes :ByteArray;

    public var oRef :References = new References();
    public var sRef :References = new References();
    public var tRef :References = new References();

    public function Context (bytes :ByteArray)
    {
        this.bytes = bytes;
    }
}

class References
{
    public var table :Dictionary = new Dictionary();
    public var ix :uint = 0;
}

class Traits
{
    public var className :String;
    public var isDynamic :Boolean;
    public var vars :Array;

    public function Traits (className :String, isDynamic :Boolean, vars :Array)
    {
        this.className = className;
        this.isDynamic = isDynamic;
        this.vars = vars;
    }
}

Read unsigned bytes, duh.

Read unsigned bytes, duh.


package goplayer
{
  public class ConfigurationParser
  {
    public static const DEFAULT_API_URL : String
      = "http://staging.streamio.se/api"
    public static const DEFAULT_SKIN_URL : String = "goplayer-skin.swf"
    public static const DEFAULT_TRACKER_ID : String = "global"

    private const result : Configuration = new Configuration

    private var parameters : Object

    public function ConfigurationParser(parameters : Object)
    { this.parameters = parameters }

    public function execute() : void
    {
      result.apiURL = getString("api", DEFAULT_API_URL)
      result.skinURL = getString("skin", DEFAULT_SKIN_URL)
      result.movieID = getString("movie", null)
      result.trackerID = getString("tracker", DEFAULT_TRACKER_ID)
      result.bitratePolicy = getBitratePolicy("bitrate", BitratePolicy.BEST)
      result.enableRTMP = getBoolean("enableRtmp", true)
      result.enableAutoplay = getBoolean("enableAutoplay", false)
      result.enableLooping = getBoolean("enableLooping", false)
      result.enableChrome = getBoolean("enableChrome", true)
      result.enableUpperPanel = getBoolean("enableUpperPanel", true)
    }

    public static function parse(parameters : Object) : Configuration
    {
      const parser : ConfigurationParser
        = new ConfigurationParser(parameters)

      parser.execute()

      return parser.result
    }

    // -----------------------------------------------------

    private function getString(name : String, fallback : String) : String
    { return name in parameters ? parameters[name] : fallback }

    private function getBoolean
      (name : String, fallback : Boolean) : Boolean
    {
      if (name in parameters)
        return $getBoolean(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBoolean
      (name : String, value : String, fallback : Boolean) : Boolean
    {
      try
        { return $$getBoolean(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, ["true", "false"])
          return fallback
        }

      throw new Error
    }

    private function $$getBoolean(value : String) : Boolean
    {
      if (value == "true")
        return true
      else if (value == "false")
        return false
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function getBitratePolicy
      (name : String, fallback : BitratePolicy) : BitratePolicy
    {
      if (name in parameters)
        return $getBitratePolicy(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBitratePolicy
      (name : String, value : String,
       fallback : BitratePolicy) : BitratePolicy
    {
      try
        { return $$getBitratePolicy(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, BITRATE_POLICY_VALUES)
          return fallback
        }

      throw new Error
    }

    private const BITRATE_POLICY_VALUES : Array =
      ["<number>kbps", "min", "max", "best"]

    private function $$getBitratePolicy(value : String) : BitratePolicy
    {
      if (value == "max")
        return BitratePolicy.MAX
      else if (value == "min")
        return BitratePolicy.MIN
      else if (value == "best")
        return BitratePolicy.BEST
      else if (Bitrate.parse(value))
        return BitratePolicy.specific(Bitrate.parse(value))
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function reportInvalidParameter
      (name : String, value : String, validValues : Array) : void
    {
      debug("Error: Invalid parameter: “" + name + "=" + value + "”; " +
            getInvalidParameterHint(validValues) + ".")
    }

    private function getInvalidParameterHint(values : Array) : String
    { return $getInvalidParameterHint(getQuotedValues(values)) }

    private function $getInvalidParameterHint(values : Array) : String
    {
      return "please use " +
        "either " + values.slice(0, -1).join(", ") + " " +
        "or " + values[values.length - 1]
    }

    private function getQuotedValues(values : Array) : Array
    {
      var result : Array = []

      for each (var value : String in values)
        result.push("“" + value + "”")

      return result
    }
  }
}

package goplayer
{
  public class ConfigurationParser
  {
    public static const DEFAULT_API_URL : String
      = "http://staging.streamio.se/api"
    public static const DEFAULT_SKIN_URL : String = "goplayer-skin.swf"
    public static const DEFAULT_TRACKER_ID : String = "global"

    public static const VALID_PARAMETERS : Array = [
      "api", "tracker", "skin", "movie", "bitrate",
      "enablertmp", "enableautoplay", "enablelooping",
      "enablechrome", "enableupperpanel" ]

    private const result : Configuration = new Configuration

    private var parameters : Object
    private var originalParameterNames : Object

    public function ConfigurationParser
      (parameters : Object, originalParameterNames : Object)
    {
      this.parameters = parameters
      this.originalParameterNames = originalParameterNames
    }

    public function execute() : void
    {
      result.apiURL = getString("api", DEFAULT_API_URL)
      result.trackerID = getString("tracker", DEFAULT_TRACKER_ID)
      result.skinURL = getString("skin", DEFAULT_SKIN_URL)
      result.movieID = getString("movie", null)
      result.bitratePolicy = getBitratePolicy("bitrate", BitratePolicy.BEST)
      result.enableRTMP = getBoolean("enablertmp", true)
      result.enableAutoplay = getBoolean("enableautoplay", false)
      result.enableLooping = getBoolean("enablelooping", false)
      result.enableChrome = getBoolean("enablechrome", true)
      result.enableUpperPanel = getBoolean("enableupperpanel", true)
    }

    public static function parse(parameters : Object) : Configuration
    {
      const normalizedParameters : Object = {}
      const originalParameterNames : Object = {}

      for (var name : String in parameters)
        if (VALID_PARAMETERS.indexOf(normalize(name)) == -1)
          reportUnknownParameter(name)
        else
          normalizedParameters[normalize(name)] = parameters[name],
            originalParameterNames[normalize(name)] = name

      const parser : ConfigurationParser = new ConfigurationParser
        (normalizedParameters, originalParameterNames)

      parser.execute()

      return parser.result
    }

    private static function normalize(name : String) : String
    { return name.toLowerCase().replace(/[^a-z]/g, "") }

    private static function reportUnknownParameter(name : String) : void
    { debug("Error: Unknown parameter: " + name) }

    // -----------------------------------------------------

    private function getString(name : String, fallback : String) : String
    { return name in parameters ? parameters[name] : fallback }

    private function getBoolean
      (name : String, fallback : Boolean) : Boolean
    {
      if (name in parameters)
        return $getBoolean(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBoolean
      (name : String, value : String, fallback : Boolean) : Boolean
    {
      try
        { return $$getBoolean(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, ["true", "false"])

          return fallback
        }

      throw new Error
    }

    private function $$getBoolean(value : String) : Boolean
    {
      if (value == "true")
        return true
      else if (value == "false")
        return false
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function getBitratePolicy
      (name : String, fallback : BitratePolicy) : BitratePolicy
    {
      if (name in parameters)
        return $getBitratePolicy(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBitratePolicy
      (name : String, value : String,
       fallback : BitratePolicy) : BitratePolicy
    {
      try
        { return $$getBitratePolicy(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, BITRATE_POLICY_VALUES)

          return fallback
        }

      throw new Error
    }

    private const BITRATE_POLICY_VALUES : Array =
      ["<number>kbps", "min", "max", "best"]

    private function $$getBitratePolicy(value : String) : BitratePolicy
    {
      if (value == "max")
        return BitratePolicy.MAX
      else if (value == "min")
        return BitratePolicy.MIN
      else if (value == "best")
        return BitratePolicy.BEST
      else if (Bitrate.parse(value))
        return BitratePolicy.specific(Bitrate.parse(value))
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function reportInvalidParameter
      (name : String, value : String, validValues : Array) : void
    {
      debug("Error: Invalid parameter: " +
            "“" + originalParameterNames[name] + "=" + value + "”; " +
            getInvalidParameterHint(validValues) + ".")
    }

    private function getInvalidParameterHint(values : Array) : String
    { return $getInvalidParameterHint(getQuotedValues(values)) }

    private function $getInvalidParameterHint(values : Array) : String
    {
      return "please use " +
        "either " + values.slice(0, -1).join(", ") + " " +
        "or " + values[values.length - 1]
    }

    private function getQuotedValues(values : Array) : Array
    {
      var result : Array = []

      for each (var value : String in values)
        result.push("“" + value + "”")

      return result
    }
  }
}

Normalize parameter names to allow hyphen/underscore-style.


package goplayer
{
  public class ConfigurationParser
  {
    private const result : Configuration = new Configuration

    private var parameters : Object

    public function ConfigurationParser(parameters : Object)
    { this.parameters = parameters }

    public function execute() : void
    {
      // XXX: Clean up the host stuff.
      result.host = parameters.host || "staging.streamio.se"
      result.skinURL = parameters.skinUrl
        || "http://" + result.host + "/swfs/goplayer-black-skin.swf"
      result.movieID = parameters.movieId
      result.trackerID = parameters.trackerId || "global"
      result.bitratePolicy = getBitratePolicy("bitrate", BitratePolicy.BEST)
      result.enableRTMP = getBoolean("rtmp", true)
      result.enableAutoplay = getBoolean("autoplay", false)
      result.enableLooping = getBoolean("loop", false)
      result.enableChrome = getBoolean("chrome", true)
      result.enableUpperPanel = getBoolean("upperPanel", true)
    }

    public static function parse(parameters : Object) : Configuration
    {
      const parser : ConfigurationParser
        = new ConfigurationParser(parameters)

      parser.execute()

      return parser.result
    }

    // -----------------------------------------------------

    private function getBitratePolicy
      (name : String, fallback : BitratePolicy) : BitratePolicy
    {
      if (name in parameters)
        return $getBitratePolicy(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBitratePolicy
      (name : String, value : String,
       fallback : BitratePolicy) : BitratePolicy
    {
      try
        { return $$getBitratePolicy(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, BITRATE_POLICY_VALUES)
          return fallback
        }

      throw new Error
    }

    private const BITRATE_POLICY_VALUES : Array =
      ["<number>kbps", "min", "max", "best"]

    private function $$getBitratePolicy(value : String) : BitratePolicy
    {
      if (value == "max")
        return BitratePolicy.MAX
      else if (value == "min")
        return BitratePolicy.MIN
      else if (value == "best")
        return BitratePolicy.BEST
      else if (Bitrate.parse(value))
        return BitratePolicy.specific(Bitrate.parse(value))
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function getBoolean
      (name : String, fallback : Boolean) : Boolean
    {
      if (name in parameters)
        return $getBoolean(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBoolean
      (name : String, value : String, fallback : Boolean) : Boolean
    {
      try
        { return $$getBoolean(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, ["true", "false"])
          return fallback
        }

      throw new Error
    }

    private function $$getBoolean(value : String) : Boolean
    {
      if (value == "true")
        return true
      else if (value == "false")
        return false
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function reportInvalidParameter
      (name : String, value : String, validValues : Array) : void
    {
      debug("Error: Invalid parameter: “" + name + "=" + value + "”; " +
            getInvalidParameterHint(validValues) + ".")
    }

    private function getInvalidParameterHint(values : Array) : String
    { return $getInvalidParameterHint(getQuotedValues(values)) }

    private function $getInvalidParameterHint(values : Array) : String
    {
      return "please use " +
        "either " + values.slice(0, -1).join(", ") + " " +
        "or " + values[values.length - 1]
    }

    private function getQuotedValues(values : Array) : Array
    {
      var result : Array = []

      for each (var value : String in values)
        result.push("“" + value + "”")

      return result
    }
  }
}

package goplayer
{
  public class ConfigurationParser
  {
    private const result : Configuration = new Configuration

    private var parameters : Object

    public function ConfigurationParser(parameters : Object)
    { this.parameters = parameters }

    public function execute() : void
    {
      // XXX: Clean up the host stuff.
      result.host = parameters.host || "staging.streamio.se"
      result.skinURL = parameters.skinUrl
        || "http://" + result.host + "/swfs/goplayer-black-skin.swf"
      result.movieID = parameters.movieId
      result.trackerID = parameters.trackerId || "global"
      result.bitratePolicy = getBitratePolicy("bitrate", BitratePolicy.BEST)
      result.enableRTMP = getBoolean("enableRtmp", true)
      result.enableAutoplay = getBoolean("enableAutoplay", false)
      result.enableLooping = getBoolean("enableLooping", false)
      result.enableChrome = getBoolean("enableChrome", true)
      result.enableUpperPanel = getBoolean("enableUpperPanel", true)
    }

    public static function parse(parameters : Object) : Configuration
    {
      const parser : ConfigurationParser
        = new ConfigurationParser(parameters)

      parser.execute()

      return parser.result
    }

    // -----------------------------------------------------

    private function getBitratePolicy
      (name : String, fallback : BitratePolicy) : BitratePolicy
    {
      if (name in parameters)
        return $getBitratePolicy(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBitratePolicy
      (name : String, value : String,
       fallback : BitratePolicy) : BitratePolicy
    {
      try
        { return $$getBitratePolicy(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, BITRATE_POLICY_VALUES)
          return fallback
        }

      throw new Error
    }

    private const BITRATE_POLICY_VALUES : Array =
      ["<number>kbps", "min", "max", "best"]

    private function $$getBitratePolicy(value : String) : BitratePolicy
    {
      if (value == "max")
        return BitratePolicy.MAX
      else if (value == "min")
        return BitratePolicy.MIN
      else if (value == "best")
        return BitratePolicy.BEST
      else if (Bitrate.parse(value))
        return BitratePolicy.specific(Bitrate.parse(value))
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function getBoolean
      (name : String, fallback : Boolean) : Boolean
    {
      if (name in parameters)
        return $getBoolean(name, parameters[name], fallback)
      else
        return fallback
    }

    private function $getBoolean
      (name : String, value : String, fallback : Boolean) : Boolean
    {
      try
        { return $$getBoolean(value) }
      catch (error : Error)
        {
          reportInvalidParameter(name, value, ["true", "false"])
          return fallback
        }

      throw new Error
    }

    private function $$getBoolean(value : String) : Boolean
    {
      if (value == "true")
        return true
      else if (value == "false")
        return false
      else
        throw new Error
    }

    // -----------------------------------------------------

    private function reportInvalidParameter
      (name : String, value : String, validValues : Array) : void
    {
      debug("Error: Invalid parameter: “" + name + "=" + value + "”; " +
            getInvalidParameterHint(validValues) + ".")
    }

    private function getInvalidParameterHint(values : Array) : String
    { return $getInvalidParameterHint(getQuotedValues(values)) }

    private function $getInvalidParameterHint(values : Array) : String
    {
      return "please use " +
        "either " + values.slice(0, -1).join(", ") + " " +
        "or " + values[values.length - 1]
    }

    private function getQuotedValues(values : Array) : Array
    {
      var result : Array = []

      for each (var value : String in values)
        result.push("“" + value + "”")

      return result
    }
  }
}

Rename each boolean parameter to enableFoo.