package aerys.minko.scene.node
{
	import aerys.minko.ns.minko_scene;
	import aerys.minko.render.shader.part.phong.attenuation.MatrixShadowMapAttenuationShaderPart;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.IRebindableController;
	import aerys.minko.scene.controller.TransformController;
	import aerys.minko.type.Signal;
	import aerys.minko.type.clone.CloneOptions;
	import aerys.minko.type.clone.ControllerCloneAction;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;

	use namespace minko_scene;

	/**
	 * The base class to extend in order to create new scene node types.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class AbstractSceneNode implements ISceneNode
	{
		private static var _id	    	    	: uint;

		private var _name	    	    		: String;
		private var _root	        			: ISceneNode;
		private var _parent	        			: Group;

        private var _visible                    : Boolean;
        private var _computedVisibility         : Boolean;

		private var _transform			        : Matrix4x4;

		private var _controllers		        : Vector.<AbstractController>;

		private var _added				        : Signal;
		private var _removed			        : Signal;
		private var _addedToScene		        : Signal;
		private var _removedFromScene	        : Signal;
		private var _controllerAdded	        : Signal;
		private var _controllerRemoved	        : Signal;
        private var _visibilityChanged          : Signal;
        private var _computedVisibilityChanged  : Signal;
		private var _localToWorldChanged		: Signal;

        final public function get x() : Number
		{
			return _transform.translationX;
		}
        final public function set x(value : Number) : void
		{
			_transform.translationX = value;
		}

        final public function get y() : Number
		{
			return _transform.translationX;
		}
        final public function set y(value : Number) : void
		{
			_transform.translationY = value;
		}

        final public function get z() : Number
		{
			return _transform.translationX;
		}
        final public function set z(value : Number) : void
		{
			_transform.translationZ = value;
		}

        final public function get rotationX() : Number
		{
			return _transform.rotationX;
		}
        final public function set rotationX(value : Number) : void
		{
			_transform.rotationX = value;
		}

        final public function get rotationY() : Number
		{
			return _transform.rotationY;
		}
        final public function set rotationY(value : Number) : void
		{
			_transform.rotationY = value;
		}

        final public function get rotationZ() : Number
		{
			return _transform.rotationZ;
		}
        final public function set rotationZ(value : Number) : void
		{
			_transform.rotationZ = value;
		}

        final public function get scaleX() : Number
		{
			return _transform.scaleX;
		}
        final public function set scaleX(value : Number) : void
		{
			_transform.scaleX = value;
		}

        final public function get scaleY() : Number
		{
			return _transform.scaleY;
		}
        final public function set scaleY(value : Number) : void
		{
			_transform.scaleY = value;
		}

        final public function get scaleZ() : Number
		{
			return _transform.scaleZ;
		}
        final public function set scaleZ(value : Number) : void
		{
			_transform.scaleZ = value;
		}

        public function get name() : String
		{
			return _name;
		}
        public function set name(value : String) : void
		{
			_name = value;
		}

        final public function get parent() : Group
		{
			return _parent;
		}
        final public function set parent(value : Group) : void
		{
			if (value == _parent)
				return ;

			// remove child
			if (_parent)
			{
				var oldParent : Group = _parent;

				oldParent._children.splice(oldParent.getChildIndex(this), 1);

				parent._numChildren--;
				_parent = null;
				_removed.execute(this, oldParent);
				oldParent.descendantRemoved.execute(oldParent, this);
			}

			// set parent
			_parent = value;

			// add child
			if (_parent)
			{
				_parent._children[_parent.numChildren] = this;
				_parent._numChildren++;
				_added.execute(this, _parent);
				_parent.descendantAdded.execute(_parent, this);
			}
		}

        final public function get scene() : Scene
        {
            return _root as Scene;
        }

        final public function get root() : ISceneNode
		{
			return _root;
		}

        final public function get transform() : Matrix4x4
		{
			return _transform;
		}

        final public function get added() : Signal
		{
			return _added;
		}

        final public function get removed() : Signal
		{
			return _removed;
		}

        final public function get addedToScene() : Signal
		{
			return _addedToScene;
		}

        final public function get removedFromScene() : Signal
		{
			return _removedFromScene;
		}

		public function get numControllers() : uint
		{
			return _controllers.length;
		}

		public function get controllerAdded() : Signal
		{
			return _controllerAdded;
		}

		public function get controllerRemoved() : Signal
		{
			return _controllerRemoved;
		}

		public function get localToWorldTransformChanged() : Signal
		{
			return _localToWorldChanged;
		}

		public function AbstractSceneNode()
		{
			initialize();
		}

		private function initialize() : void
		{
			_name = getDefaultSceneName(this);

            _visible = true;
            _computedVisibility = true;

			_transform = new Matrix4x4();

			_controllers = new <AbstractController>[];

			_added = new Signal('AbstractSceneNode.added');
			_removed = new Signal('AbstractSceneNode.removed');
			_addedToScene = new Signal('AbstractSceneNode.addedToScene');
			_removedFromScene = new Signal('AbstractSceneNode.removedFromScene');
			_controllerAdded = new Signal('AbstractSceneNode.controllerAdded');
			_controllerRemoved = new Signal('AbstractSceneNode.controllerRemoved');
            _visibilityChanged = new Signal('AbstractSceneNode.visibilityChanged');
            _computedVisibilityChanged = new Signal('AbstractSceneNode.computedVisibilityChanged');
			_localToWorldChanged = new Signal('AbstractSceneNode.localToWorldChanged');

			updateRoot();

			_added.add(addedHandler);
			_removed.add(removedHandler);
			_addedToScene.add(addedToSceneHandler);
			_removedFromScene.add(removedFromSceneHandler);

			addController(new TransformController());
		}

		protected function addedHandler(child : ISceneNode, ancestor : Group) : void
		{
            updateRoot();
		}

		protected function removedHandler(child : ISceneNode, ancestor : Group) : void
		{
            updateRoot();
		}

        private function updateRoot() : void
        {
            var newRoot : ISceneNode    = _parent ? _parent.root : this;
            var oldRoot : ISceneNode    = _root;

            if (newRoot != oldRoot)
            {
                _root = newRoot;
                if (oldRoot is Scene)
                    _removedFromScene.execute(this, oldRoot);
                if (newRoot is Scene)
                    _addedToScene.execute(this, newRoot);
            }
        }

		protected function addedToSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			// nothing
		}

		protected function removedFromSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			// nothing
		}

		public function addController(controller : AbstractController) : ISceneNode
		{
			_controllers.push(controller);

			controller.addTarget(this);
			_controllerAdded.execute(this, controller);

			return this;
		}

		public function removeController(controller : AbstractController) : ISceneNode
		{
			var numControllers	: uint  = _controllers.length - 1;
            var controllerId    : int   = _controllers.indexOf(controller);

            if (controllerId < 0)
                throw new Error('The controller is not on the node.');

			_controllers[controllerId] = _controllers[numControllers];
			_controllers.length = numControllers;

			controller.removeTarget(this);
			_controllerRemoved.execute(this, controller);

			return this;
		}

		public function getController(index : uint) : AbstractController
		{
			return _controllers[index];
		}

		public function getControllersByType(type			: Class,
											 controllers	: Vector.<AbstractController> = null) : Vector.<AbstractController>
		{
			controllers ||= new Vector.<AbstractController>();

			var nbControllers : uint = numControllers;

			for (var i : int = 0; i < nbControllers; ++i)
			{
				var ctrl 	: AbstractController	= getController(i);

				if (ctrl is type)
					controllers.push(ctrl);
			}

			return controllers;
		}

		public static function getDefaultSceneName(scene : ISceneNode) : String
		{
			var className : String = getQualifiedClassName(scene);

			return className.substr(className.lastIndexOf(':') + 1)
				   + '_' + (++_id);
		}

		minko_scene function cloneNode() : AbstractSceneNode
		{
			throw new Error('Must be overriden');
		}

		public final function clone(cloneOptions : CloneOptions = null) : ISceneNode
		{
			cloneOptions ||= CloneOptions.defaultOptions;

			// fill up 2 dics with all nodes and controllers
			var nodeMap			: Dictionary = new Dictionary();
			var controllerMap	: Dictionary = new Dictionary();
			listItems(cloneNode(), nodeMap, controllerMap);

			// clone controllers with respect with instructions
			cloneControllers(controllerMap, cloneOptions);

			// rebind all controller dependencies.
			rebindControllerDependencies(controllerMap, nodeMap, cloneOptions);

			// add cloned/rebinded/original controllers to clones
			for (var objNode : Object in nodeMap)
			{
				var node			: AbstractSceneNode = AbstractSceneNode(objNode);
				var numControllers	: uint = node.numControllers;

				for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				{
					var controller : AbstractController = controllerMap[node.getController(controllerId)];
					if (controller != null)
						nodeMap[node].addController(controller);
				}
			}

			return nodeMap[this];
		}

		minko_scene function getLocalToWorldTransformUnsafe() : Matrix4x4
		{
			var transformController : TransformController = root.getControllersByType(
				TransformController
			)[0] as TransformController;

			return transformController.getLocalToWorldTransform(this);
		}

		public function getLocalToWorldTransform(output : Matrix4x4 = null) : Matrix4x4
		{
			output ||= new Matrix4x4();
			output.copyFrom(getLocalToWorldTransformUnsafe());

			return output;
		}

		public function getWorldToLocalTransform(output : Matrix4x4 = null) : Matrix4x4
		{
			return getLocalToWorldTransform(output).invert();
		}

		public function localToWorld(vector : Vector4, output : Vector4 = null) : Vector4
		{
			var localToWorld : Matrix4x4 = getLocalToWorldTransformUnsafe();

			return localToWorld.transformVector(vector, output);
		}

		public function deltaLocalToWorld(vector : Vector4, output : Vector4 = null) : Vector4
		{
			var localToWorld : Matrix4x4 = getLocalToWorldTransformUnsafe();

			return localToWorld.deltaTransformVector(vector, output);
		}

		private function listItems(clonedRoot	: ISceneNode,
								   nodes		: Dictionary,
								   controllers	: Dictionary) : void
		{
			var numControllers : uint = this.numControllers;
			for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				controllers[getController(controllerId)] = true;

			nodes[this] = clonedRoot;

			if (this is Group)
			{
				var group		: Group = Group(this);
				var clonedGroup	: Group = Group(clonedRoot);
				var numChildren	: uint	= group.numChildren;

				for (var childId : uint = 0; childId < numChildren; ++childId)
				{
					var child		: AbstractSceneNode = AbstractSceneNode(group.getChildAt(childId));
					var clonedChild	: AbstractSceneNode = AbstractSceneNode(clonedGroup.getChildAt(childId));

					child.listItems(clonedChild, nodes, controllers);
				}
			}
		}

		private function cloneControllers(controllerMap : Dictionary, cloneOptions : CloneOptions) : void
		{
			for (var objController : Object in controllerMap)
			{
				var controller	: AbstractController = AbstractController(objController);
				var action		: uint				 = cloneOptions.getActionForController(controller);

				if (action == ControllerCloneAction.CLONE)
					controllerMap[controller] = controller.clone();
				else if (action == ControllerCloneAction.REASSIGN)
					controllerMap[controller] = controller;
				else if (action == ControllerCloneAction.IGNORE)
					controllerMap[controller] = null;
				else
					throw new Error();
			}
		}

		private function rebindControllerDependencies(controllerMap	: Dictionary,
													  nodeMap		: Dictionary,
													  cloneOptions	: CloneOptions) : void
		{
			for (var objController : Object in controllerMap)
			{
				var controller	: AbstractController	= AbstractController(objController);
				var action		: uint					= cloneOptions.getActionForController(controller);

				if (controller is IRebindableController && action == ControllerCloneAction.CLONE)
					IRebindableController(controllerMap[controller]).rebindDependencies(nodeMap, controllerMap);
			}
		}
	}
}

package aerys.minko.scene.node
{
	import aerys.minko.ns.minko_scene;
	import aerys.minko.render.shader.part.phong.attenuation.MatrixShadowMapAttenuationShaderPart;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.IRebindableController;
	import aerys.minko.scene.controller.TransformController;
	import aerys.minko.type.Signal;
	import aerys.minko.type.clone.CloneOptions;
	import aerys.minko.type.clone.ControllerCloneAction;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	import flash.utils.setTimeout;

	use namespace minko_scene;

	/**
	 * The base class to extend in order to create new scene node types.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class AbstractSceneNode implements ISceneNode
	{
		private static var _id	    	    	: uint;

		private var _name	    	    		: String;
		private var _root	        			: ISceneNode;
		private var _parent	        			: Group;

        private var _visible                    : Boolean;
        private var _computedVisibility         : Boolean;

		private var _transform			        : Matrix4x4;

		private var _controllers		        : Vector.<AbstractController>;

		private var _added				        : Signal;
		private var _removed			        : Signal;
		private var _addedToScene		        : Signal;
		private var _removedFromScene	        : Signal;
		private var _controllerAdded	        : Signal;
		private var _controllerRemoved	        : Signal;
        private var _visibilityChanged          : Signal;
        private var _computedVisibilityChanged  : Signal;
		private var _localToWorldChanged		: Signal;

        final public function get x() : Number
		{
			return _transform.translationX;
		}
        final public function set x(value : Number) : void
		{
			_transform.translationX = value;
		}

        final public function get y() : Number
		{
			return _transform.translationX;
		}
        final public function set y(value : Number) : void
		{
			_transform.translationY = value;
		}

        final public function get z() : Number
		{
			return _transform.translationX;
		}
        final public function set z(value : Number) : void
		{
			_transform.translationZ = value;
		}

        final public function get rotationX() : Number
		{
			return _transform.rotationX;
		}
        final public function set rotationX(value : Number) : void
		{
			_transform.rotationX = value;
		}

        final public function get rotationY() : Number
		{
			return _transform.rotationY;
		}
        final public function set rotationY(value : Number) : void
		{
			_transform.rotationY = value;
		}

        final public function get rotationZ() : Number
		{
			return _transform.rotationZ;
		}
        final public function set rotationZ(value : Number) : void
		{
			_transform.rotationZ = value;
		}

        final public function get scaleX() : Number
		{
			return _transform.scaleX;
		}
        final public function set scaleX(value : Number) : void
		{
			_transform.scaleX = value;
		}

        final public function get scaleY() : Number
		{
			return _transform.scaleY;
		}
        final public function set scaleY(value : Number) : void
		{
			_transform.scaleY = value;
		}

        final public function get scaleZ() : Number
		{
			return _transform.scaleZ;
		}
        final public function set scaleZ(value : Number) : void
		{
			_transform.scaleZ = value;
		}

        public function get name() : String
		{
			return _name;
		}
        public function set name(value : String) : void
		{
			_name = value;
		}

        final public function get parent() : Group
		{
			return _parent;
		}
        final public function set parent(value : Group) : void
		{
			if (value == _parent)
				return ;

			// remove child
			if (_parent)
			{
				var oldParent : Group = _parent;

				oldParent._children.splice(oldParent.getChildIndex(this), 1);

				parent._numChildren--;
				_parent = null;
				_removed.execute(this, oldParent);
				oldParent.descendantRemoved.execute(oldParent, this);
			}

			// set parent
			_parent = value;

			// add child
			if (_parent)
			{
				_parent._children[_parent.numChildren] = this;
				_parent._numChildren++;
				_added.execute(this, _parent);
				_parent.descendantAdded.execute(_parent, this);
			}
		}

        final public function get scene() : Scene
        {
            return _root as Scene;
        }

        final public function get root() : ISceneNode
		{
			return _root;
		}

        final public function get transform() : Matrix4x4
		{
			return _transform;
		}

        final public function get added() : Signal
		{
			return _added;
		}

        final public function get removed() : Signal
		{
			return _removed;
		}

        final public function get addedToScene() : Signal
		{
			return _addedToScene;
		}

        final public function get removedFromScene() : Signal
		{
			return _removedFromScene;
		}

		public function get numControllers() : uint
		{
			return _controllers.length;
		}

		public function get controllerAdded() : Signal
		{
			return _controllerAdded;
		}

		public function get controllerRemoved() : Signal
		{
			return _controllerRemoved;
		}

		public function get localToWorldTransformChanged() : Signal
		{
			return _localToWorldChanged;
		}

		public function AbstractSceneNode()
		{
			initialize();
		}

		private function initialize() : void
		{
			_name = getDefaultSceneName(this);

            _visible = true;
            _computedVisibility = true;

			_transform = new Matrix4x4();

			_controllers = new <AbstractController>[];

			_added = new Signal('AbstractSceneNode.added');
			_removed = new Signal('AbstractSceneNode.removed');
			_addedToScene = new Signal('AbstractSceneNode.addedToScene');
			_removedFromScene = new Signal('AbstractSceneNode.removedFromScene');
			_controllerAdded = new Signal('AbstractSceneNode.controllerAdded');
			_controllerRemoved = new Signal('AbstractSceneNode.controllerRemoved');
            _visibilityChanged = new Signal('AbstractSceneNode.visibilityChanged');
            _computedVisibilityChanged = new Signal('AbstractSceneNode.computedVisibilityChanged');
			_localToWorldChanged = new Signal('AbstractSceneNode.localToWorldChanged');

			updateRoot();

			_added.add(addedHandler);
			_removed.add(removedHandler);
			_addedToScene.add(addedToSceneHandler);
			_removedFromScene.add(removedFromSceneHandler);

			addController(new TransformController());
		}

		protected function addedHandler(child : ISceneNode, ancestor : Group) : void
		{
            setTimeout(updateRoot, 0);
		}

		protected function removedHandler(child : ISceneNode, ancestor : Group) : void
		{
			setTimeout(updateRoot, 0);
		}

        private function updateRoot() : void
        {
            var newRoot : ISceneNode    = _parent ? _parent.root : this;
            var oldRoot : ISceneNode    = _root;

            if (newRoot != oldRoot)
            {
                _root = newRoot;
                if (oldRoot is Scene)
                    _removedFromScene.execute(this, oldRoot);
                if (newRoot is Scene)
                    _addedToScene.execute(this, newRoot);
            }
        }

		protected function addedToSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			// nothing
		}

		protected function removedFromSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			// nothing
		}

		public function addController(controller : AbstractController) : ISceneNode
		{
			_controllers.push(controller);

			controller.addTarget(this);
			_controllerAdded.execute(this, controller);

			return this;
		}

		public function removeController(controller : AbstractController) : ISceneNode
		{
			var numControllers	: uint  = _controllers.length - 1;
            var controllerId    : int   = _controllers.indexOf(controller);

            if (controllerId < 0)
                throw new Error('The controller is not on the node.');

			_controllers[controllerId] = _controllers[numControllers];
			_controllers.length = numControllers;

			controller.removeTarget(this);
			_controllerRemoved.execute(this, controller);

			return this;
		}

		public function getController(index : uint) : AbstractController
		{
			return _controllers[index];
		}

		public function getControllersByType(type			: Class,
											 controllers	: Vector.<AbstractController> = null) : Vector.<AbstractController>
		{
			controllers ||= new Vector.<AbstractController>();

			var nbControllers : uint = numControllers;

			for (var i : int = 0; i < nbControllers; ++i)
			{
				var ctrl 	: AbstractController	= getController(i);

				if (ctrl is type)
					controllers.push(ctrl);
			}

			return controllers;
		}

		public static function getDefaultSceneName(scene : ISceneNode) : String
		{
			var className : String = getQualifiedClassName(scene);

			return className.substr(className.lastIndexOf(':') + 1)
				   + '_' + (++_id);
		}

		minko_scene function cloneNode() : AbstractSceneNode
		{
			throw new Error('Must be overriden');
		}

		public final function clone(cloneOptions : CloneOptions = null) : ISceneNode
		{
			cloneOptions ||= CloneOptions.defaultOptions;

			// fill up 2 dics with all nodes and controllers
			var nodeMap			: Dictionary = new Dictionary();
			var controllerMap	: Dictionary = new Dictionary();
			listItems(cloneNode(), nodeMap, controllerMap);

			// clone controllers with respect with instructions
			cloneControllers(controllerMap, cloneOptions);

			// rebind all controller dependencies.
			rebindControllerDependencies(controllerMap, nodeMap, cloneOptions);

			// add cloned/rebinded/original controllers to clones
			for (var objNode : Object in nodeMap)
			{
				var node			: AbstractSceneNode = AbstractSceneNode(objNode);
				var numControllers	: uint = node.numControllers;

				for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				{
					var controller : AbstractController = controllerMap[node.getController(controllerId)];
					if (controller != null)
						nodeMap[node].addController(controller);
				}
			}

			return nodeMap[this];
		}

		minko_scene function getLocalToWorldTransformUnsafe() : Matrix4x4
		{
			var transformController : TransformController = root.getControllersByType(
				TransformController
			)[0] as TransformController;

			return transformController.getLocalToWorldTransform(this);
		}

		public function getLocalToWorldTransform(output : Matrix4x4 = null) : Matrix4x4
		{
			output ||= new Matrix4x4();
			output.copyFrom(getLocalToWorldTransformUnsafe());

			return output;
		}

		public function getWorldToLocalTransform(output : Matrix4x4 = null) : Matrix4x4
		{
			return getLocalToWorldTransform(output).invert();
		}

		public function localToWorld(vector : Vector4, output : Vector4 = null) : Vector4
		{
			var localToWorld : Matrix4x4 = getLocalToWorldTransformUnsafe();

			return localToWorld.transformVector(vector, output);
		}

		public function deltaLocalToWorld(vector : Vector4, output : Vector4 = null) : Vector4
		{
			var localToWorld : Matrix4x4 = getLocalToWorldTransformUnsafe();

			return localToWorld.deltaTransformVector(vector, output);
		}

		private function listItems(clonedRoot	: ISceneNode,
								   nodes		: Dictionary,
								   controllers	: Dictionary) : void
		{
			var numControllers : uint = this.numControllers;
			for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				controllers[getController(controllerId)] = true;

			nodes[this] = clonedRoot;

			if (this is Group)
			{
				var group		: Group = Group(this);
				var clonedGroup	: Group = Group(clonedRoot);
				var numChildren	: uint	= group.numChildren;

				for (var childId : uint = 0; childId < numChildren; ++childId)
				{
					var child		: AbstractSceneNode = AbstractSceneNode(group.getChildAt(childId));
					var clonedChild	: AbstractSceneNode = AbstractSceneNode(clonedGroup.getChildAt(childId));

					child.listItems(clonedChild, nodes, controllers);
				}
			}
		}

		private function cloneControllers(controllerMap : Dictionary, cloneOptions : CloneOptions) : void
		{
			for (var objController : Object in controllerMap)
			{
				var controller	: AbstractController = AbstractController(objController);
				var action		: uint				 = cloneOptions.getActionForController(controller);

				if (action == ControllerCloneAction.CLONE)
					controllerMap[controller] = controller.clone();
				else if (action == ControllerCloneAction.REASSIGN)
					controllerMap[controller] = controller;
				else if (action == ControllerCloneAction.IGNORE)
					controllerMap[controller] = null;
				else
					throw new Error();
			}
		}

		private function rebindControllerDependencies(controllerMap	: Dictionary,
													  nodeMap		: Dictionary,
													  cloneOptions	: CloneOptions) : void
		{
			for (var objController : Object in controllerMap)
			{
				var controller	: AbstractController	= AbstractController(objController);
				var action		: uint					= cloneOptions.getActionForController(controller);

				if (controller is IRebindableController && action == ControllerCloneAction.CLONE)
					IRebindableController(controllerMap[controller]).rebindDependencies(nodeMap, controllerMap);
			}
		}
	}
}

fix AbstractSceneNode.updateRoot() to be called "after" using setTimeout in order to make sure the addedToScene/removedFromScene signals are not called inside the added/removed signals


package battlecode.common {

    public class RobotType {
        public static const HQ:String = "HQ";
        public static const TOWER:String = "TOWER";

        public static const SUPPLYDEPOT:String = "SUPPLYDEPOT";
        public static const TECHNOLOGYINSTITUTE:String = "TECHNOLOGYINSTITUTE";
        public static const BARRACKS:String = "BARRACKS";
        public static const HELIPAD:String = "HELIPAD";
        public static const TRAININGFIELD:String = "TRAININGFIELD";
        public static const TANKFACTORY:String = "TANKFACTORY";
        public static const HANDWASHSTATION:String = "HANDWASHSTATION";
        public static const AEROSPACELAB:String = "AEROSPACELAB";

        public static const BEAVER:String = "BEAVER";
        public static const COMPUTER:String = "COMPUTER";
        public static const SOLDIER:String = "SOLDIER";
        public static const BASHER:String = "BASHER";
        public static const MINER:String = "MINER";
        public static const DRONE:String = "DRONE";
        public static const TANK:String = "TANK";
        public static const COMMANDER:String = "COMMANDER";
        public static const LAUNCHER:String = "LAUNCHER";
        public static const MISSLE:String = "MISSLE";

        public function RobotType() {
        }

        public static function values():Array {
            return [
                HQ,
                TOWER,
                SUPPLYDEPOT,
                TECHNOLOGYINSTITUTE,
                BARRACKS,
                HELIPAD,
                TRAININGFIELD,
                TANKFACTORY,
                HANDWASHSTATION,
                AEROSPACELAB,
                BEAVER,
                COMPUTER,
                SOLDIER,
                BASHER,
                MINER,
                DRONE,
                TANK,
                COMMANDER,
                LAUNCHER,
                MISSLE
            ];
        }

        public static function buildings():Array {
            return [
                HQ,
                TOWER,
                SUPPLYDEPOT,
                TECHNOLOGYINSTITUTE,
                BARRACKS,
                HELIPAD,
                TRAININGFIELD,
                TANKFACTORY,
                HANDWASHSTATION,
                AEROSPACELAB
            ];
        }

        public static function ground():Array {
            return [
                BEAVER,
                COMPUTER,
                SOLDIER,
                BASHER,
                MINER,
                TANK,
                COMMANDER,
                LAUNCHER,
                MISSLE
            ];
        }

        public static function air():Array {
            return []; // TODO air types
        }

        // TODO fix values
        public static function maxEnergon(type:String):Number {
            switch (type) {
                case HQ:
                    return int.MAX_VALUE;
                case SOLDIER:
                    return 100.0;
            }
            throw new ArgumentError("Unknown type: " + type);
        }

        public static function movementDelay(type:String):uint {
            return 1;
        }

        public static function movementDelayDiagonal(type:String):uint {
            return 1;
        }

        public static function attackDelay(type:String):uint {
            return 0;
        }
    }
}

package battlecode.common {

    public class RobotType {
        public static const HQ:String = "HQ";
        public static const TOWER:String = "TOWER";

        public static const SUPPLYDEPOT:String = "SUPPLYDEPOT";
        public static const TECHNOLOGYINSTITUTE:String = "TECHNOLOGYINSTITUTE";
        public static const BARRACKS:String = "BARRACKS";
        public static const HELIPAD:String = "HELIPAD";
        public static const TRAININGFIELD:String = "TRAININGFIELD";
        public static const TANKFACTORY:String = "TANKFACTORY";
        public static const HANDWASHSTATION:String = "HANDWASHSTATION";
        public static const AEROSPACELAB:String = "AEROSPACELAB";

        public static const BEAVER:String = "BEAVER";
        public static const COMPUTER:String = "COMPUTER";
        public static const SOLDIER:String = "SOLDIER";
        public static const BASHER:String = "BASHER";
        public static const MINER:String = "MINER";
        public static const DRONE:String = "DRONE";
        public static const TANK:String = "TANK";
        public static const COMMANDER:String = "COMMANDER";
        public static const LAUNCHER:String = "LAUNCHER";
        public static const MISSILE:String = "MISSILE";

        public function RobotType() {
        }

        public static function values():Array {
            return [
                HQ,
                TOWER,
                SUPPLYDEPOT,
                TECHNOLOGYINSTITUTE,
                BARRACKS,
                HELIPAD,
                TRAININGFIELD,
                TANKFACTORY,
                HANDWASHSTATION,
                AEROSPACELAB,
                BEAVER,
                COMPUTER,
                SOLDIER,
                BASHER,
                MINER,
                DRONE,
                TANK,
                COMMANDER,
                LAUNCHER,
                MISSILE
            ];
        }

        public static function buildings():Array {
            return [
                HQ,
                TOWER,
                SUPPLYDEPOT,
                TECHNOLOGYINSTITUTE,
                BARRACKS,
                HELIPAD,
                TRAININGFIELD,
                TANKFACTORY,
                HANDWASHSTATION,
                AEROSPACELAB
            ];
        }

        public static function ground():Array {
            return [
                BEAVER,
                COMPUTER,
                SOLDIER,
                BASHER,
                MINER,
                TANK,
                COMMANDER,
                LAUNCHER,
                MISSILE
            ];
        }

        public static function air():Array {
            return []; // TODO air types
        }

        // TODO fix values
        public static function maxEnergon(type:String):Number {
            switch (type) {
                case HQ:
                    return 2000;
                case TOWER:
                    return 1000;
                case SUPPLYDEPOT:
                case TECHNOLOGYINSTITUTE:
                case BARRACKS:
                case HELIPAD:
                case TRAININGFIELD:
                case TANKFACTORY:
                case HANDWASHSTATION:
                case AEROSPACELAB:
                    return 100;
                case BEAVER:
                    return 30;
                case MINER:
                    return 50;
                case COMPUTER:
                    return 1;
                case SOLDIER:
                    return 40;
                case BASHER:
                    return 50;
                case DRONE:
                    return 70;
                case TANK:
                    return 160;
                case COMMANDER:
                    return 120;
                case LAUNCHER:
                    return 400;
                case MISSILE:
                    return 3;
            }
            throw new ArgumentError("Unknown type: " + type);
        }

        public static function movementDelay(type:String):uint {
            return 1;
        }

        public static function movementDelayDiagonal(type:String):uint {
            return 1;
        }

        public static function attackDelay(type:String):uint {
            return 0;
        }
    }
}

add HP for robot types

add HP for robot types


//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.presents.client {

import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.getTimer; // function import

import com.threerings.util.ClassUtil;
import com.threerings.util.HashMap;
import com.threerings.util.Log;

import com.threerings.presents.dobj.CompoundEvent;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessError;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.dobj.Subscriber;

import com.threerings.presents.net.BootstrapNotification;
import com.threerings.presents.net.EventNotification;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.FailureResponse;
import com.threerings.presents.net.ForwardEventRequest;
import com.threerings.presents.net.ObjectResponse;
import com.threerings.presents.net.PongResponse;
import com.threerings.presents.net.SubscribeRequest;
import com.threerings.presents.net.UnsubscribeRequest;
import com.threerings.presents.net.UnsubscribeResponse;

/**
 * The client distributed object manager manages a set of proxy objects
 * which mirror the distributed objects maintained on the server.
 * Requests for modifications, etc. are forwarded to the server and events
 * are dispatched from the server to this client for objects to which this
 * client is subscribed.
 */
public class ClientDObjectMgr
    implements DObjectManager
{
    private static const log :Log = Log.getLog(ClientDObjectMgr);

    /**
     * Constructs a client distributed object manager.
     *
     * @param comm a communicator instance by which it can communicate
     * with the server.
     * @param client a reference to the client that is managing this whole
     * communications and event dispatch business.
     */
    public function ClientDObjectMgr (comm :Communicator, client :Client)
    {
        _comm = comm;
        _client = client;

        // register a flush interval
        _flushInterval = new Timer(FLUSH_INTERVAL);
        _flushInterval.addEventListener(TimerEvent.TIMER, flushObjects);
        _flushInterval.start();

        client.getStage().addEventListener(
            Event.ENTER_FRAME, processNextAction);
    }

    // documentation inherited from interface DObjectManager
    public function isManager (object :DObject) :Boolean
    {
        // we are never authoritative in the present implementation
        return false;
    }

    // inherit documentation from the interface DObjectManager
    public function subscribeToObject (oid :int, target :Subscriber) :void
    {
        if (oid <= 0) {
            target.requestFailed(oid, new ObjectAccessError("Invalid oid " + oid + "."));
        } else {
            queueAction(oid, target, true);
        }
    }

    // inherit documentation from the interface DObjectManager
    public function unsubscribeFromObject (oid :int, target :Subscriber) :void
    {
        queueAction(oid, target, false);
    }

    protected function queueAction (
            oid :int, target :Subscriber, subscribe :Boolean) :void
    {
        // queue up an action
        _actions.push(new ObjectAction(oid, target, subscribe));
    }

    // inherit documentation from the interface
    public function postEvent (event :DEvent) :void
    {
        // send a forward event request to the server
        _comm.postMessage(new ForwardEventRequest(event));
    }

    // inherit documentation from the interface
    public function removedLastSubscriber (
            obj :DObject, deathWish :Boolean) :void
    {
        // if this object has a registered flush delay, don't can it just
        // yet, just slip it onto the flush queue
        for each (var dclass :Class in _delays.keys()) {
            if (obj is dclass) {
                var expire :Number = getTimer() + Number(_delays.get(dclass));
                _flushes.put(obj.getOid(), new FlushRecord(obj, expire));
//                 log.info("Flushing " + obj.getOid() + " at " +
//                          new java.util.Date(expire));
                return;
            }
        }

        // if we didn't find a delay registration, flush immediately
        flushObject(obj);
    }

    /**
     * Registers an object flush delay.
     *
     * @see Client#registerFlushDelay
     */
    public function registerFlushDelay (objclass :Class, delay :Number) :void
    {
        _delays.put(objclass, delay);
    }

    /**
     * Called by the communicator when a downstream message arrives from
     * the network layer. We queue it up for processing and request some
     * processing time on the main thread.
     */
    public function processMessage (msg :DownstreamMessage) :void
    {
        // append it to our queue
        _actions.push(msg);
    }

    /**
     * Invoked on the main client thread to process any newly arrived
     * messages that we have waiting in our queue.
     */
    public function processNextAction (event :Event) :void
    {
        while (_actions.length > 0) {
            doNextAction();
        }
    }

    protected function doNextAction () :void
    {
        // process the next event on our queue
        if (_actions.length == 0) {
            return;
        }

        var obj :Object = _actions.shift();
        // do the proper thing depending on the object
        if (obj is EventNotification) {
            var evt :DEvent = (obj as EventNotification).getEvent();
//             log.info("Dispatch event: " + evt);
            dispatchEvent(evt);

        } else if (obj is BootstrapNotification) {
            _client.gotBootstrap((obj as BootstrapNotification).getData(), this);

        } else if (obj is ObjectResponse) {
            registerObjectAndNotify((obj as ObjectResponse).getObject());

        } else if (obj is UnsubscribeResponse) {
            var oid :int = (obj as UnsubscribeResponse).getOid();
            if (_dead.remove(oid) == null) {
                log.warning("Received unsub ACK from unknown object [oid=" + oid + "].");
            }

        } else if (obj is FailureResponse) {
            notifyFailure((obj as FailureResponse).getOid(), (obj as FailureResponse).getMessage());

        } else if (obj is PongResponse) {
            _client.gotPong(obj as PongResponse);

        } else if (obj is ObjectAction) {
            var act :ObjectAction = (obj as ObjectAction);
            if (act.subscribe) {
                doSubscribe(act.oid, act.target);
            } else {
                doUnsubscribe(act.oid, act.target);
            }
        }
    }

    /**
     * Called when a new event arrives from the server that should be
     * dispatched to subscribers here on the client.
     */
    protected function dispatchEvent (event :DEvent) :void
    {
        // if this is a compound event, we need to process its contained
        // events in order
        if (event is CompoundEvent) {
            var events :Array = (event as CompoundEvent).getEvents();
            var ecount :int = events.length;
            for (var ii :int = 0; ii < ecount; ii++) {
                dispatchEvent(events[ii] as DEvent);
            }
            return;
        }

        // look up the object on which we're dispatching this event
        var toid :int = event.getTargetOid();
        var target :DObject = (_ocache.get(toid) as DObject);
        if (target == null) {
            if (_dead.get(toid) == null) {
                log.warning("Unable to dispatch event on non-proxied " +
                    "object [event=" + event + "].");
            }
            return;
        }

        try {
            // apply the event to the object
            var notify :Boolean = event.applyToObject(target);

            // if this is an object destroyed event, we need to remove the
            // object from our object table
            if (event is ObjectDestroyedEvent) {
//                 log.info("Pitching destroyed object " +
//                          "[oid=" + toid + ", class=" +
//                          StringUtil.shortClassName(target) + "].");
                _ocache.remove(toid);
            }

            // have the object pass this event on to its listeners
            if (notify) {
                target.notifyListeners(event);
            }

        } catch (e :Error) {
            log.warning("Failure processing event [event=" + event +
                ", target=" + target + "].");
            log.logStackTrace(e);
        }
    }

    /**
     * Registers this object in our proxy cache and notifies the
     * subscribers that were waiting for subscription to this object.
     */
    protected function registerObjectAndNotify (obj :DObject) :void
    {
        // let the object know that we'll be managing it
        obj.setManager(this);

        var oid :int = obj.getOid();
        // stick the object into the proxy object table
        _ocache.put(oid, obj);

        // let the penders know that the object is available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Got object, but no one cares?! " +
                "[oid=" + oid + ", obj=" + obj + "].");
            return;
        }
        // log.debug("Got object: pendReq=" + req);

        for (var ii :int = 0; ii < req.targets.length; ii++) {
            var target :Subscriber = (req.targets[ii] as Subscriber);
            // log.debug("Notifying subscriber: " + target);
            // add them as a subscriber
            obj.addSubscriber(target);
            // and let them know that the object is in
            target.objectAvailable(obj);
        }
    }

    /**
     * Notifies the subscribers that had requested this object (for subscription) that it is not
     * available.
     */
    protected function notifyFailure (oid :int, message :String) :void
    {
        // let the penders know that the object is not available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Failed to get object, but no one cares?! [oid=" + oid + "].");
            return;
        }

        for (var ii :int = 0; ii < req.targets.length; ii++) {
            var target :Subscriber = req.targets[ii];
            // and let them know that the object is in
            target.requestFailed(oid, new ObjectAccessError(message));
        }
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doSubscribe (oid :int, target :Subscriber) :void
    {
        // log.info("doSubscribe: " + oid + ": " + target);

        // first see if we've already got the object in our table
        var obj :DObject = (_ocache.get(oid) as DObject);
        if (obj != null) {
            // clear the object out of the flush table if it's in there
            _flushes.remove(oid);
            // add the subscriber and call them back straight away
            obj.addSubscriber(target);
            target.objectAvailable(obj);
            return;
        }

        // see if we've already got an outstanding request for this object
        var req :PendingRequest = (_penders.get(oid) as PendingRequest);
        if (req != null) {
            // add this subscriber to the list of subscribers to be
            // notified when the request is satisfied
            req.addTarget(target);
            return;
        }

        // otherwise we need to create a new request
        req = new PendingRequest(oid);
        req.addTarget(target);
        _penders.put(oid, req);
        // log.info("Registering pending request [oid=" + oid + "].");

        // and issue a request to get things rolling
        _comm.postMessage(new SubscribeRequest(oid));
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doUnsubscribe (oid :int, target :Subscriber) :void
    {
        var dobj :DObject = (_ocache.get(oid) as DObject);
        if (dobj != null) {
            dobj.removeSubscriber(target);

        } else {
            log.info("Requested to remove subscriber from " +
                     "non-proxied object [oid=" + oid +
                     ", sub=" + target + "].");
        }
    }

    /**
     * Flushes a distributed object subscription, issuing an unsubscribe
     * request to the server.
     */
    protected function flushObject (obj :DObject) :void
    {
        // move this object into the dead pool so that we don't claim to
        // have it around anymore; once our unsubscribe message is
        // processed, it'll be 86ed
        var ooid :int = obj.getOid();
        _ocache.remove(ooid);
        _dead.put(ooid, obj);

        // ship off an unsubscribe message to the server; we'll remove the
        // object from our table when we get the unsub ack
        _comm.postMessage(new UnsubscribeRequest(ooid));
    }

    /**
     * Called periodically to flush any objects that have been lingering
     * due to a previously enacted flush delay.
     */
    protected function flushObjects (event :TimerEvent) :void
    {
        var now :Number = getTimer();
        for each (var oid :int in _flushes.keys()) {
            var rec :FlushRecord = (_flushes.get(oid) as FlushRecord);
            if (rec.expire <= now) {
                _flushes.remove(oid);
                flushObject(rec.obj);
            }
        }
    }

    /** A reference to the communicator that sends and receives messages
     * for this client. */
    protected var _comm :Communicator;

    /** A reference to our client instance. */
    protected var _client :Client;

    /** Our primary dispatch queue. */
    protected var _actions :Array = new Array();

    /** All of the distributed objects that are active on this client. */
    protected var _ocache :HashMap = new HashMap();

    /** Objects that have been marked for death. */
    protected var _dead :HashMap = new HashMap();

    /** Pending object subscriptions. */
    protected var _penders :HashMap = new HashMap();

    /** A mapping from distributed object class to flush delay. */
    protected var _delays :HashMap = new HashMap();

    /** A set of objects waiting to be flushed. */
    protected var _flushes :HashMap = new HashMap();

    /** Flushes objects every now and again. */
    protected var _flushInterval :Timer;

    /** Flush expired objects every 30 seconds. */
    protected static const FLUSH_INTERVAL :Number = 30 * 1000;
}
}

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.Subscriber;

/**
 * The object action is used to queue up a subscribe or unsubscribe
 * request.
 */
class ObjectAction
{
    public var oid :int;
    public var target :Subscriber;
    public var subscribe :Boolean;

    public function ObjectAction (
            oid :int, target :Subscriber, subscribe :Boolean)
    {
        this.oid = oid;
        this.target = target;
        this.subscribe = subscribe;
    }

    public function toString () :String
    {
        return "oid=" + oid + ", target=" + target + ", subscribe=" + subscribe;
    }
}

class PendingRequest
{
    public var oid :int;
    public var targets :Array = new Array();

    public function PendingRequest (oid :int)
    {
        this.oid = oid;
    }

    public function addTarget (target :Subscriber) :void
    {
        targets.push(target);
    }
}

/** Used to manage pending object flushes. */
class FlushRecord
{
    /** The object to be flushed. */
    public var obj :DObject;

    /** The time at which we flush it. */
    public var expire :Number;

    public function FlushRecord (obj :DObject, expire :Number)
    {
        this.obj = obj;
        this.expire = expire;
    }
}

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.presents.client {

import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.getTimer; // function import

import com.threerings.util.ClassUtil;
import com.threerings.util.HashMap;
import com.threerings.util.Log;

import com.threerings.presents.dobj.CompoundEvent;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.ObjectAccessError;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.dobj.Subscriber;

import com.threerings.presents.net.BootstrapNotification;
import com.threerings.presents.net.EventNotification;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.FailureResponse;
import com.threerings.presents.net.ForwardEventRequest;
import com.threerings.presents.net.ObjectResponse;
import com.threerings.presents.net.PongResponse;
import com.threerings.presents.net.SubscribeRequest;
import com.threerings.presents.net.UnsubscribeRequest;
import com.threerings.presents.net.UnsubscribeResponse;

/**
 * The client distributed object manager manages a set of proxy objects
 * which mirror the distributed objects maintained on the server.
 * Requests for modifications, etc. are forwarded to the server and events
 * are dispatched from the server to this client for objects to which this
 * client is subscribed.
 */
public class ClientDObjectMgr
    implements DObjectManager
{
    private static const log :Log = Log.getLog(ClientDObjectMgr);

    /**
     * Constructs a client distributed object manager.
     *
     * @param comm a communicator instance by which it can communicate
     * with the server.
     * @param client a reference to the client that is managing this whole
     * communications and event dispatch business.
     */
    public function ClientDObjectMgr (comm :Communicator, client :Client)
    {
        _comm = comm;
        _client = client;

        // register a flush interval
        _flushInterval = new Timer(FLUSH_INTERVAL);
        _flushInterval.addEventListener(TimerEvent.TIMER, flushObjects);
        _flushInterval.start();
    }

    // documentation inherited from interface DObjectManager
    public function isManager (object :DObject) :Boolean
    {
        // we are never authoritative in the present implementation
        return false;
    }

    // inherit documentation from the interface DObjectManager
    public function subscribeToObject (oid :int, target :Subscriber) :void
    {
        if (oid <= 0) {
            target.requestFailed(oid, new ObjectAccessError("Invalid oid " + oid + "."));
        } else {
            doSubscribe(oid, target);
        }
    }

    // inherit documentation from the interface DObjectManager
    public function unsubscribeFromObject (oid :int, target :Subscriber) :void
    {
        doUnsubscribe(oid, target);
    }

    // inherit documentation from the interface
    public function postEvent (event :DEvent) :void
    {
        // send a forward event request to the server
        _comm.postMessage(new ForwardEventRequest(event));
    }

    // inherit documentation from the interface
    public function removedLastSubscriber (
            obj :DObject, deathWish :Boolean) :void
    {
        // if this object has a registered flush delay, don't can it just
        // yet, just slip it onto the flush queue
        for each (var dclass :Class in _delays.keys()) {
            if (obj is dclass) {
                var expire :Number = getTimer() + Number(_delays.get(dclass));
                _flushes.put(obj.getOid(), new FlushRecord(obj, expire));
//                 log.info("Flushing " + obj.getOid() + " at " +
//                          new java.util.Date(expire));
                return;
            }
        }

        // if we didn't find a delay registration, flush immediately
        flushObject(obj);
    }

    /**
     * Registers an object flush delay.
     *
     * @see Client#registerFlushDelay
     */
    public function registerFlushDelay (objclass :Class, delay :Number) :void
    {
        _delays.put(objclass, delay);
    }

    /**
     * Called by the communicator when a downstream message arrives from
     * the network layer. We queue it up for processing and request some
     * processing time on the main thread.
     */
    public function processMessage (msg :DownstreamMessage) :void
    {
        // do the proper thing depending on the object
        if (msg is EventNotification) {
            var evt :DEvent = (msg as EventNotification).getEvent();
//             log.info("Dispatch event: " + evt);
            dispatchEvent(evt);

        } else if (msg is BootstrapNotification) {
            _client.gotBootstrap((msg as BootstrapNotification).getData(), this);

        } else if (msg is ObjectResponse) {
            registerObjectAndNotify((msg as ObjectResponse).getObject());

        } else if (msg is UnsubscribeResponse) {
            var oid :int = (msg as UnsubscribeResponse).getOid();
            if (_dead.remove(oid) == null) {
                log.warning("Received unsub ACK from unknown object [oid=" + oid + "].");
            }

        } else if (msg is FailureResponse) {
            notifyFailure((msg as FailureResponse).getOid(), (msg as FailureResponse).getMessage());

        } else if (msg is PongResponse) {
            _client.gotPong(msg as PongResponse);
        }
    }

    /**
     * Called when a new event arrives from the server that should be
     * dispatched to subscribers here on the client.
     */
    protected function dispatchEvent (event :DEvent) :void
    {
        // if this is a compound event, we need to process its contained
        // events in order
        if (event is CompoundEvent) {
            var events :Array = (event as CompoundEvent).getEvents();
            var ecount :int = events.length;
            for (var ii :int = 0; ii < ecount; ii++) {
                dispatchEvent(events[ii] as DEvent);
            }
            return;
        }

        // look up the object on which we're dispatching this event
        var toid :int = event.getTargetOid();
        var target :DObject = (_ocache.get(toid) as DObject);
        if (target == null) {
            if (_dead.get(toid) == null) {
                log.warning("Unable to dispatch event on non-proxied " +
                    "object [event=" + event + "].");
            }
            return;
        }

        try {
            // apply the event to the object
            var notify :Boolean = event.applyToObject(target);

            // if this is an object destroyed event, we need to remove the
            // object from our object table
            if (event is ObjectDestroyedEvent) {
//                 log.info("Pitching destroyed object " +
//                          "[oid=" + toid + ", class=" +
//                          StringUtil.shortClassName(target) + "].");
                _ocache.remove(toid);
            }

            // have the object pass this event on to its listeners
            if (notify) {
                target.notifyListeners(event);
            }

        } catch (e :Error) {
            log.warning("Failure processing event [event=" + event +
                ", target=" + target + "].");
            log.logStackTrace(e);
        }
    }

    /**
     * Registers this object in our proxy cache and notifies the
     * subscribers that were waiting for subscription to this object.
     */
    protected function registerObjectAndNotify (obj :DObject) :void
    {
        // let the object know that we'll be managing it
        obj.setManager(this);

        var oid :int = obj.getOid();
        // stick the object into the proxy object table
        _ocache.put(oid, obj);

        // let the penders know that the object is available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Got object, but no one cares?! " +
                "[oid=" + oid + ", obj=" + obj + "].");
            return;
        }
        // log.debug("Got object: pendReq=" + req);

        for (var ii :int = 0; ii < req.targets.length; ii++) {
            var target :Subscriber = (req.targets[ii] as Subscriber);
            // log.debug("Notifying subscriber: " + target);
            // add them as a subscriber
            obj.addSubscriber(target);
            // and let them know that the object is in
            target.objectAvailable(obj);
        }
    }

    /**
     * Notifies the subscribers that had requested this object (for subscription) that it is not
     * available.
     */
    protected function notifyFailure (oid :int, message :String) :void
    {
        // let the penders know that the object is not available
        var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
        if (req == null) {
            log.warning("Failed to get object, but no one cares?! [oid=" + oid + "].");
            return;
        }

        for (var ii :int = 0; ii < req.targets.length; ii++) {
            var target :Subscriber = req.targets[ii];
            // and let them know that the object is in
            target.requestFailed(oid, new ObjectAccessError(message));
        }
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doSubscribe (oid :int, target :Subscriber) :void
    {
        // log.info("doSubscribe: " + oid + ": " + target);

        // first see if we've already got the object in our table
        var obj :DObject = (_ocache.get(oid) as DObject);
        if (obj != null) {
            // clear the object out of the flush table if it's in there
            _flushes.remove(oid);
            // add the subscriber and call them back straight away
            obj.addSubscriber(target);
            target.objectAvailable(obj);
            return;
        }

        // see if we've already got an outstanding request for this object
        var req :PendingRequest = (_penders.get(oid) as PendingRequest);
        if (req != null) {
            // add this subscriber to the list of subscribers to be
            // notified when the request is satisfied
            req.addTarget(target);
            return;
        }

        // otherwise we need to create a new request
        req = new PendingRequest(oid);
        req.addTarget(target);
        _penders.put(oid, req);
        // log.info("Registering pending request [oid=" + oid + "].");

        // and issue a request to get things rolling
        _comm.postMessage(new SubscribeRequest(oid));
    }

    /**
     * This is guaranteed to be invoked via the invoker and can safely do
     * main thread type things like call back to the subscriber.
     */
    protected function doUnsubscribe (oid :int, target :Subscriber) :void
    {
        var dobj :DObject = (_ocache.get(oid) as DObject);
        if (dobj != null) {
            dobj.removeSubscriber(target);

        } else {
            log.info("Requested to remove subscriber from " +
                     "non-proxied object [oid=" + oid +
                     ", sub=" + target + "].");
        }
    }

    /**
     * Flushes a distributed object subscription, issuing an unsubscribe
     * request to the server.
     */
    protected function flushObject (obj :DObject) :void
    {
        // move this object into the dead pool so that we don't claim to
        // have it around anymore; once our unsubscribe message is
        // processed, it'll be 86ed
        var ooid :int = obj.getOid();
        _ocache.remove(ooid);
        _dead.put(ooid, obj);

        // ship off an unsubscribe message to the server; we'll remove the
        // object from our table when we get the unsub ack
        _comm.postMessage(new UnsubscribeRequest(ooid));
    }

    /**
     * Called periodically to flush any objects that have been lingering
     * due to a previously enacted flush delay.
     */
    protected function flushObjects (event :TimerEvent) :void
    {
        var now :Number = getTimer();
        for each (var oid :int in _flushes.keys()) {
            var rec :FlushRecord = (_flushes.get(oid) as FlushRecord);
            if (rec.expire <= now) {
                _flushes.remove(oid);
                flushObject(rec.obj);
            }
        }
    }

    /** A reference to the communicator that sends and receives messages
     * for this client. */
    protected var _comm :Communicator;

    /** A reference to our client instance. */
    protected var _client :Client;

    /** Our primary dispatch queue. */
    protected var _actions :Array = new Array();

    /** All of the distributed objects that are active on this client. */
    protected var _ocache :HashMap = new HashMap();

    /** Objects that have been marked for death. */
    protected var _dead :HashMap = new HashMap();

    /** Pending object subscriptions. */
    protected var _penders :HashMap = new HashMap();

    /** A mapping from distributed object class to flush delay. */
    protected var _delays :HashMap = new HashMap();

    /** A set of objects waiting to be flushed. */
    protected var _flushes :HashMap = new HashMap();

    /** Flushes objects every now and again. */
    protected var _flushInterval :Timer;

    /** Flush expired objects every 30 seconds. */
    protected static const FLUSH_INTERVAL :Number = 30 * 1000;
}
}

import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.Subscriber;

class PendingRequest
{
    public var oid :int;
    public var targets :Array = new Array();

    public function PendingRequest (oid :int)
    {
        this.oid = oid;
    }

    public function addTarget (target :Subscriber) :void
    {
        targets.push(target);
    }
}

/** Used to manage pending object flushes. */
class FlushRecord
{
    /** The object to be flushed. */
    public var obj :DObject;

    /** The time at which we flush it. */
    public var expire :Number;

    public function FlushRecord (obj :DObject, expire :Number)
    {
        this.obj = obj;
        this.expire = expire;
    }
}

Stop waiting for ENTER_FRAME to process incoming messages.
This was residual from the initial conversion from Java, but I don't think
it's very necessary on the flash side, and Zell is working to decouple
the presents layer from flash-player specific classes and events like
ENTER_FRAME.
So now we just process everything immediately.
I did some tests, it seems that the flash player dispatches ENTER_FRAME,
then it dispatches any network events, then the frame is rendered.
So our old way of doing things was pointlessly

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@5015 542714f4-19e9-0310-aa3c-eee0fc999fb1


//
// $Id: ObjectRemovedEvent.java 3099 2004-08-27 02:21:06Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.presents.dobj {

import com.threerings.util.StringBuilder;

import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;

/**
 * An object removed event is dispatched when an object is removed from an
 * OidList attribute of a distributed object. It can also be
 * constructed to request the removal of an oid from an
 * OidList attribute of an object and posted to the dobjmgr.
 *
 * @see DObjectManager#postEvent
 */
public class ObjectRemovedEvent extends NamedEvent
{
    /**
     * Constructs a new object removed event on the specified target
     * object with the supplied oid list attribute name and object id to
     * remove.
     *
     * @param targetOid the object id of the object from whose oid list we
     * will remove an oid.
     * @param name the name of the attribute (data member) from which to
     * remove the specified oid.
     * @param oid the oid to remove from the oid list attribute.
     */
    public function ObjectRemovedEvent (targetOid :int, name :String, oid :int)
    {
        super(targetOid, name);
        _oid = oid;
    }

    /**
     * Returns the oid that has been removed.
     */
    public function getOid () :int
    {
        return _oid;
    }

    /**
     * Applies this event to the object.
     */
    public override function applyToObject (target :DObject) :Boolean
        //throws ObjectAccessException
    {
        var list :OidList = (target[_name] as OidList);
        list.remove(_oid);
        return true;
    }

    // documentation inherited
    protected override function notifyListener (listener :Object) :void
    {
        if (listener is OidListListener) {
            listener.objectRemoved(this);
        }
    }

    public override function writeObject (out :ObjectOutputStream) :void
    {
        super.writeObject(out);
        out.writeInt(_oid);
    }

    public override function readObject (ins :ObjectInputStream) :void
    {
        super.readObject(ins);
        _oid = ins.readInt();
    }

    // documentation inherited
    protected override function toStringBuf (buf :StringBuilder) :void
    {
        buf.append("OBJREM:");
        super.toStringBuf(buf);
        buf.append(", oid=", _oid);
    }

    protected var _oid :int;
}
}

//
// $Id: ObjectRemovedEvent.java 3099 2004-08-27 02:21:06Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.presents.dobj {

import com.threerings.util.StringBuilder;

import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;

/**
 * An object removed event is dispatched when an object is removed from an
 * OidList attribute of a distributed object. It can also be
 * constructed to request the removal of an oid from an
 * OidList attribute of an object and posted to the dobjmgr.
 *
 * @see DObjectManager#postEvent
 */
public class ObjectRemovedEvent extends NamedEvent
{
    /**
     * Constructs a new object removed event on the specified target
     * object with the supplied oid list attribute name and object id to
     * remove.
     *
     * @param targetOid the object id of the object from whose oid list we
     * will remove an oid.
     * @param name the name of the attribute (data member) from which to
     * remove the specified oid.
     * @param oid the oid to remove from the oid list attribute.
     */
    public function ObjectRemovedEvent (
            targetOid :int = 0, name :String = null, oid :int = 0)
    {
        super(targetOid, name);
        _oid = oid;
    }

    /**
     * Returns the oid that has been removed.
     */
    public function getOid () :int
    {
        return _oid;
    }

    /**
     * Applies this event to the object.
     */
    public override function applyToObject (target :DObject) :Boolean
        //throws ObjectAccessException
    {
        var list :OidList = (target[_name] as OidList);
        list.remove(_oid);
        return true;
    }

    // documentation inherited
    protected override function notifyListener (listener :Object) :void
    {
        if (listener is OidListListener) {
            listener.objectRemoved(this);
        }
    }

    public override function writeObject (out :ObjectOutputStream) :void
    {
        super.writeObject(out);
        out.writeInt(_oid);
    }

    public override function readObject (ins :ObjectInputStream) :void
    {
        super.readObject(ins);
        _oid = ins.readInt();
    }

    // documentation inherited
    protected override function toStringBuf (buf :StringBuilder) :void
    {
        buf.append("OBJREM:");
        super.toStringBuf(buf);
        buf.append(", oid=", _oid);
    }

    protected var _oid :int;
}
}

Allow zero-arg construction (for unserialization).

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@4154 542714f4-19e9-0310-aa3c-eee0fc999fb1


/*
* actionscript-cafe ClassUtils 18 avr. 2011 adesmarais
*
* Created by Servebox
* Copyright 2010 Servebox (c) All rights reserved. You may not use, distribute
* or modify this code under its source or binary form
* without the express authorization of ServeBox. Contact : info@servebox.com
*/
package org.servebox.cafe.core.util
{
	/**
	 * DateUtils is a helper class with a lot of static methods to handle dates.
	 */
	public class DateUtils
	{
		/**
		 * Returns the last day for a given month and year.
		 * @param fullYear The year.
		 * @param month The month. (0-11)
		 * @return The last day in month.
		 */
		public static function getLastDayInMonth( fullYear : int , month : int) : int
		{
			var date : Date = new Date( fullYear , month , 28 );
			var toReturn : int;
			do
			{
				toReturn = date.getDate();
				date.setDate( date.getDate() + 1 );
			}while( date.getMonth() == month );
			return toReturn;
		}

		/**
		 * Adds given values to a date, using a mask. The mask parameter can take the following values :

		 * - d : Days.

		 * - m : Months.

		 * - y : Years.

		 * @param date The date to add value to.
		 * @param mask The mask to apply on addition.
		 * @param addValue The value to add. Can be negative.
		 * @return The new date.
		 */
		public static function dateAdd( date : Date , mask : String , addValue : int ) : Date
		{
			var newDate : Date = dateCopy( date );
			switch( mask )
			{
				case "d" : newDate.setDate( newDate.getDate() + addValue );
					break;
				case "m" : newDate.setMonth( newDate.getMonth() + addValue );
					break;
				case "y" : newDate.setFullYear( newDate.getFullYear() + addValue );
					break;
			}

			return newDate;
		}

		/**
		 * Creates a fresh new clone of the passed date.
		 * @return A new Date, clone of an existing one.
		 * @param date The date to copy.
		 */
		public static function dateCopy( date : Date ) : Date
		{
			var newDate : Date = new Date( date.getFullYear() , date.getMonth() , date.getDate() , date.getHours() , date.getMinutes() , date.getSeconds() , date.getMilliseconds() );
			return newDate;
		}

		/**
		 * Checks if a date is an open date (excluding saturday/sunday).
		 * @param date The date to check.
		 * @return A boolean indicating if the given date is an open one.
		 */
		public static function isOpenDate( date : Date ) : Boolean
		{
			if( date.getDay() == 0 || date.getDay() == 6 ) return false;
			return true;
		}

		/**
		 * Adds days to a given date, including only open dates.
		 * @param date The date.
		 * @param daysToAdd The number of days to add. Can be negative.
		 * @return The new date.
		 */
		public static function dateAddOpenDatesOnly( date : Date , daysToAdd : int ) : Date
		{
			var newDate : Date = dateCopy( date );
			var direction : int = ( daysToAdd < 0 ? - 1 : 1 );
			while( daysToAdd != 0 )
			{
				newDate = dateAdd( newDate , "d" , direction );
				if( isOpenDate( newDate ) )
				{
					daysToAdd -= direction;
				}
			}

			return newDate;
		}

		/**
		 * Returns the current date, with hh:mm:ss-ms set to 00:00:00-000.
		 * @return The current date.
		 */
		public static function getCurrentDate() : Date
		{
			return getMidnightDateCopy( new Date() );
		}

		/**
		 * Returns the copy of a date, with the time set to 00:00:00-000.
		 * @param date The date to copy.
		 * @return The new date.
		 */
		public static function getMidnightDateCopy( date : Date ) : Date
		{
			return new Date( date.getFullYear() , date.getMonth() , date.getDate() , 0 , 0 , 0 , 0 );
		}

		/**
		 * Returns the difference between to dates, with the parameter mask as the unit. The mask parameter can take the following values :

		 * - d : Days.

		 * - m : Months.

		 * - y : Years.

		 * The method do not consider hours, minutes, seconds and milliseconds.
		 * @return The difference ( date1 - date2).
		 * @param date1 The first operand.
		 * @param date2 The second operand.
		 * @param mask The unit to use.
		 */
		public static function dateDiff( date1 : Date , date2 : Date , mask : String ) : Number
		{
			var first : Date = getMidnightDateCopy( date1 );
			var second : Date = getMidnightDateCopy( date2 );
			// difference expressed in ms
			var diffMs : Number = first.getTime() - second.getTime();
			var conversionRate : int;
			switch( mask )
			{
				case "d" : conversionRate = 86400000 ;
					break;
				case "m" : conversionRate = 86400000 * 30 ;
					break;
				case "y" : conversionRate = 86400000 * 365 ;
					break;
			}

			return  Math.floor( diffMs / conversionRate );
		}

		/**
		 * Dates comparator function.
		 * @return a negative value if obj1 < obj2,  zero is obj1 == obj2, a positive value is obj1 > obj2.
		 */
		public static function dateComparator( obj1 : Date , obj2 : Date ) : Number
		{
			if ( obj1 == null )
			{
				if ( obj2 == null ) return 0;
				else return - 1;
			}else
			{
				if ( obj2 == null ) return 1;
				else
				{
					var date1InMs : Number = obj1.getTime();
					var date2InMs : Number = obj2.getTime();
					if ( date1InMs > date2InMs ) return 1;
					else if ( date1InMs < date2InMs ) return - 1;
					else return 0;
				}
			}
		}

		/**
		 * Used to return a date typed in without any separators
		 */
		public static function getSeparatorsOff ( value : String ) : String
		{
			value = StringUtils.stripChar( value, " ");
			value = StringUtils.stripChar( value, "/");
			value = StringUtils.stripChar( value, "\"");
			value = StringUtils.stripChar( value, "-");
			value = StringUtils.stripChar( value, ".");
			return value;
		}

		/**
		 * Use this fucntion to validate a property using two dates
		 */

		public static function validateInterval( beginDate : Date, endDate : Date ) : Boolean
		{
			if ( beginDate == null || endDate == null)
				return false;
			else if( DateUtils.dateComparator(endDate, beginDate) >= 0 )
				return true
			else return false;
		}

		/**
		 * This function is used to validate an interval in a specified date range
		 */

		public static function validateIntervalInRange ( beginDate : Date, endDate : Date, range : Array ) : Boolean
		{
			if ( beginDate == null || endDate == null || range == null)
				return false;
			else if( validateInterval(beginDate, endDate) == true && DateUtils.dateComparator(range[0], beginDate) <=0 && DateUtils.dateComparator(range[1], endDate) >= 0)
				return true
			else return false;
		}

		/**
		 * Sets the selectable date range for a date component
		 */
		public static function setRange ( beginDate : Date, endDate : Date ) : Object
		{
			var range : Object = new Object();
			range.rangeStart = beginDate;
			range.rangeEnd = endDate;
			return range;
		}

	}
}

/*
* actionscript-cafe ClassUtils 18 avr. 2011 adesmarais
*
* Created by Servebox
* Copyright 2010 Servebox (c) All rights reserved. You may not use, distribute
* or modify this code under its source or binary form
* without the express authorization of ServeBox. Contact : info@servebox.com
*/
package org.servebox.cafe.core.util
{
	/**
	 * DateUtils is a helper class with a lot of static methods to handle dates.
	 */
	public class DateUtils
	{
		/**
		 * Returns the last day for a given month and year.
		 * @param fullYear The year.
		 * @param month The month. (0-11)
		 * @return The last day in month.
		 */
		public static function getLastDayInMonth( fullYear : int , month : int) : int
		{
			var date : Date = new Date( fullYear , month , 28 );
			var toReturn : int;
			do
			{
				toReturn = date.getDate();
				date.setDate( date.getDate() + 1 );
			}while( date.getMonth() == month );
			return toReturn;
		}

		/**
		 * Adds given values to a date, using a mask. The mask parameter can take the following values :

		 * - d : Days.

		 * - m : Months.

		 * - y : Years.

		 * @param date The date to add value to.
		 * @param mask The mask to apply on addition.
		 * @param addValue The value to add. Can be negative.
		 * @return The new date.
		 */
		public static function dateAdd( date : Date , mask : String , addValue : int ) : Date
		{
			var newDate : Date = dateCopy( date );
			switch( mask )
			{
				case "d" : newDate.setDate( newDate.getDate() + addValue );
					break;
				case "m" : newDate.setMonth( newDate.getMonth() + addValue );
					break;
				case "y" : newDate.setFullYear( newDate.getFullYear() + addValue );
					break;
			}

			return newDate;
		}

		/**
		 * Creates a fresh new clone of the passed date.
		 * @return A new Date, clone of an existing one.
		 * @param date The date to copy.
		 */
		public static function dateCopy( date : Date ) : Date
		{
			var newDate : Date = new Date( date.getFullYear() , date.getMonth() , date.getDate() , date.getHours() , date.getMinutes() , date.getSeconds() , date.getMilliseconds() );
			return newDate;
		}

		/**
		 * Checks if a date is an open date (excluding saturday/sunday).
		 * @param date The date to check.
		 * @return A boolean indicating if the given date is an open one.
		 */
		public static function isOpenDate( date : Date ) : Boolean
		{
			if( date.getDay() == 0 || date.getDay() == 6 ) return false;
			return true;
		}

		/**
		 * Adds days to a given date, including only open dates.
		 * @param date The date.
		 * @param daysToAdd The number of days to add. Can be negative.
		 * @return The new date.
		 */
		public static function dateAddOpenDatesOnly( date : Date , daysToAdd : int ) : Date
		{
			var newDate : Date = dateCopy( date );
			var direction : int = ( daysToAdd < 0 ? - 1 : 1 );
			while( daysToAdd != 0 )
			{
				newDate = dateAdd( newDate , "d" , direction );
				if( isOpenDate( newDate ) )
				{
					daysToAdd -= direction;
				}
			}

			return newDate;
		}

		/**
		 * Returns the current date, with hh:mm:ss-ms set to 00:00:00-000.
		 * @return The current date.
		 */
		public static function getCurrentDate() : Date
		{
			return getMidnightDateCopy( new Date() );
		}

		/**
		 * Returns the copy of a date, with the time set to 00:00:00-000.
		 * @param date The date to copy.
		 * @return The new date.
		 */
		public static function getMidnightDateCopy( date : Date ) : Date
		{
			return new Date( date.getFullYear() , date.getMonth() , date.getDate() , 0 , 0 , 0 , 0 );
		}

		/**
		 * Returns the difference between to dates, with the parameter mask as the unit. The mask parameter can take the following values :

		 * - d : Days.

		 * - m : Months.

		 * - y : Years.

		 * The method do not consider hours, minutes, seconds and milliseconds.
		 * @return The difference ( date1 - date2).
		 * @param date1 The first operand.
		 * @param date2 The second operand.
		 * @param mask The unit to use.
		 */
		public static function dateDiff( date1 : Date , date2 : Date , mask : String ) : Number
		{
			var first : Date = getMidnightDateCopy( date1 );
			var second : Date = getMidnightDateCopy( date2 );
			// difference expressed in ms
			var diffMs : Number = first.getTime() - second.getTime();
			var conversionRate : int;
			switch( mask )
			{
				case "d" : conversionRate = 86400000 ;
					break;
				case "m" : conversionRate = 86400000 * 30 ;
					break;
				case "y" : conversionRate = 86400000 * 365 ;
					break;
			}

			return  Math.floor( diffMs / conversionRate );
		}

		/**
		 * Dates comparator function.
		 * @return a negative value if obj1 < obj2,  zero is obj1 == obj2, a positive value is obj1 > obj2.
		 */
		public static function dateComparator( obj1 : Date , obj2 : Date ) : Number
		{
			if ( obj1 == null )
			{
				if ( obj2 == null ) return 0;
				else return - 1;
			}else
			{
				if ( obj2 == null ) return 1;
				else
				{
					var date1InMs : Number = obj1.getTime();
					var date2InMs : Number = obj2.getTime();
					if ( date1InMs > date2InMs ) return 1;
					else if ( date1InMs < date2InMs ) return - 1;
					else return 0;
				}
			}
		}

		/**
		 * Used to return a date typed in without any separators
		 */
		public static function getSeparatorsOff ( value : String ) : String
		{
			value = StringUtils.stripChar( value, " ");
			value = StringUtils.stripChar( value, "/");
			value = StringUtils.stripChar( value, "\"");
			value = StringUtils.stripChar( value, "-");
			value = StringUtils.stripChar( value, ".");
			return value;
		}

		/**
		 * Use this fucntion to validate a property using two dates
		 */

		public static function validateInterval( beginDate : Date, endDate : Date ) : Boolean
		{
			if ( beginDate == null || endDate == null)
				return false;
			else if( DateUtils.dateComparator(endDate, beginDate) >= 0 )
				return true
			else return false;
		}

		/**
		 * This function is used to validate an interval in a specified date range
		 */

		public static function validateIntervalInRange ( beginDate : Date, endDate : Date, range : Array ) : Boolean
		{
			if ( beginDate == null || endDate == null || range == null)
				return false;
			else if( validateInterval(beginDate, endDate) == true && DateUtils.dateComparator(range[0], beginDate) <=0 && DateUtils.dateComparator(range[1], endDate) >= 0)
				return true
			else return false;
		}

		/**
		 * Sets the selectable date range for a date component
		 */
		public static function setRange ( beginDate : Date, endDate : Date ) : Object
		{
			var range : Object = new Object();
			range.rangeStart = beginDate;
			range.rangeEnd = endDate;
			return range;
		}

		public static function getDayNumber( date : Date ) : int
		{
			if ( date.day == 0 )
			{
				return 7;
			}
			else
			{
				return date.day;
			}
		}

	}
}

Add method in date utils.

Add method in date utils.