/*
VERSION:	4.2
	4.2		Added manualLoop() for manual looping  &  loopSelf() for automatic looping while useInterval = true
	4.1		Added "useInterval" flag to allow disabling setTimeout() looping for manual looping
	4.0		Removed "changeCharset" event-fire within setParams() when the charset has NOT been changed
	3.9		Updated documentation to explain image_pic as a publicly-used variable
	3.8		checks whether this movieClip has been removed before calling the next setTimeout()
	3.7		setParams now applies string values as numbers when possible
	3.6		Automaticall make loader_mc invisible while removing it
	3.5		Changed "once" looping behavior to allow stopping on the last frame, and re-animating from frame Zero
	3.4		Changed applyStopFrame() to only use 1 when there are exactly 3 frames
	3.3		Changed when isVisible() is used so invisible sprites can change charsets
	3.2		Replaced detectPoseBreak() with checkPose() because it was causing a glitch
	3.1		Added a frame-bounds check to "yoyo" animation-technique to handle 1-frame animations
	3.0		Added detectPoseBreak() to setParams() to check in case the pose setting is too high
	2.9		Added isVisible() check to avoid updates while hidden
	2.8		Shifted loader_mc and image_mc to reduce redraw-regions. setSpriteSize() and update() only run when a valid charset is loaded
	2.7		Added the "placeHolder_mc" snapshot to allow new settings to be applied immediately & prevent the resulting flicker while loading new charsets
	2.6		Added "changeCharset" event, which fires when setParams() finishes changing the charset
	2.5		Added "mask_mc" for legacy support
	2.4		Added "extra features"
	2.3		Encapsulated into a newSprite() function
	2.2		Added interface properties, methods, and events  (missing: extra features)
	2.1		Missing interface properties, methods, and events
	
NOTE:
	This is sprite system 4.
	It has been completely re-written from scratch to use BitmapData instead of masking

EVENTS
	animDone			Broadcast after a "once" animation displays its last frame
	
DESCRIPTION:
	Pros:		Performance.  Able to send BitmapData as a charset input.  (sprite system clones it)
	Cons:		Cannot load SWF files as charsets
	
NOTE:
	This sprite system places the character's feet at the origin point.	(Feet:  1/2 image width, full image height)
	
	
USAGE:
	#include "functions/sprite.as"
	newSprite();			// minimum usage		(nothing is seen, but it can be modified later)
	
	
	#include "functions/sprite.as"
	var settings = {
		charset				:	"charset/marle.png",
		direction			:	2,				//(base zero)
		directions		:	4,				//(base one)		(amount of directions)
		frame					:	1,				//(base zero)
		frames				:	3,				//(base one)		(amount of frames)
		pose					:	0,				//(base zero)
		columns				:	4,				//(base zero)
		rows					:	2,				//(base zero)
		animType			:	"yoyo",
		animDirection	:	1,
		delay					:	3,				//(base one)
		isAnimating		:	true
	}
	mySprite = newSprite( [settings], [parent], [instanceName], [depth] );			// maximum usage		(rm2k settings)
	
	mySprite.setParams({isAnimating:true});
	
	spriteState = mySprite.getParams();
	
	direction = mySprite.getParam( "direction" );
	
	// manual looping
	mySprite.useInterval = false;
	mySprite.onEnterFrame = mySprite.manualLoop;
	
	// re-enable auto-looping
	mySprite.useInterval = true;
	mySprite.loopSelf();
	

PUBLIC VARIABLES:
	image_pic		This bitmap is the currently displayed animation frame.
		(When the charset changes, this gets DESTROYED and then REPLACED.  Use the "changeCharset" event to detect when this happens)


EXTRA FEATURES:
	// Compares the PARENT movieClip's coords to the x,y passed to this function as parameters
	// This sprite must be wrapped within a movieClip. That movieClip's coords are then compared to the ones specified
	sprite.lookAtCoords( x, y );
	
	// make this sprite look at the specified movieClip.  (The specified movieClip must be a sibling of this sprite's parent)
	// This sprite must be wrapped within a movieClip. That movieClip's coords are then compared to specified movieClip
	sprite.lookAtSprite( movieClip );
	
	// DEPRECATED:		This makes the specified sprite1 look at the specified movieClip2.  (The specified movieClip must be a sibling of this sprite's parent)
	// The specified sprite1 must be wrapped within a movieClip. That movieClip's coords are then compared to specified movieClip2
	sprite.lookAt( sprite1, movieClip2 );
	
	// Report whether or not this sprite is facing in the specified direction
	// Directions can be specified either as numbers or strings:   "up" "right" "down" "left"
	_this.isFacing = function( checkDir )
	
	
	
DEFAULT SETTINGS:
	You can skip most settings when you setup a sprite.
	When you do, it defaults to these values:
		charset				:	""		(can be set later)
		direction			:	0
		directions		:	1
		frame					:	0
		frames				:	1
		pose					:	0
		columns				:	1
		rows					:	1
		animType			:	"loop"
		animDirection	:	1
		delay					:	4
		isAnimating		:	true
	
DEPENDANCIES:
	nextDepth.as

EVENTS:
	animDone			Broadcast after an animation completes or loops
	changeCharset	Broadcast after a new charset has been loaded and set

DEPTHS:
	0		loader_mc
	1		image_mc
	2		placeHolder_mc
	3		mask_mc

INTERNAL VARIABLES
	var _this, internal, spriteWidth, spriteHeight, image_mc, image_pic, loader_mc, charset_pic, delayCounter, copy, paste, placeHolder_mc, placeHolder_pic;
INTERNAL FUNCTIONS
	var loadNewCharset, setSpriteSize, update, advanceFrame, onTimer, loop, manualLoop, loopSelf, showPlaceHolder, hidePlaceHolder, isVisible;

*/
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.geom.Point;



function newSprite( newSettings, newTarget, newName, newDepth )
{
	// resolve movieClip parameters
	// target
	var newTarget = (newTarget) ? newTarget : this;
	// name
	var newName = (newName) ? newName : "sprite_"+Math.floor(Math.random()*9999);
	while(newTarget[newName]){
		var newName = "sprite_"+Math.floor(Math.random()*9999);
	}// while:  this movieClip already exists
	// depth
	#include "nextDepth.as"
	var newDepth = (newDepth!=undefined) ? newDepth : nextDepth(newTarget);
	
	
	// create movieClip
	var _this = newTarget.createEmptyMovieClip( newName, newDepth );
	
	
	// internal auto-loop flag
	_this.useInterval = true;
	_this.loopInterval;
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// initialize variables
	_this.internal = {
		charset:"",
		direction:0,
		directions:1,
		frame:0,
		frames:1,
		pose:0,
		columns:1,
		rows:1,
		animType:"loop",
		animDirection:1,
		delay:4,
		isAnimating:true
	}// {internal}
	//for(var nam in newSettings)
	//	_this.internal[nam] = newSettings[nam];
	//
	_this.copy = new Rectangle(0,0, 16, 16);
	_this.paste = new Point(0,0);
	_this.delayCounter = 0;
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// define functions
	_this.loadNewCharset = function( file, callback ){
		var success = false;
		
		// references the specified bitmapData object as charset_pic
		var copyBitmap = function( new_pic ){
			_this.charset_pic.dispose();
			_this.charset_pic = new_pic;
			_this.setSpriteSize();
			//
			_this.broadcastMessage("changeCharset");
			_this.changeCharset();
			callback( _this.charset_pic );
		}// copyBitmap()
		
		// takes a snapshot of loader_mc and stores it as charset_pic
		var copyMovieClip = function(){
			//_this.charset_pic.dispose();
			_this.charset_pic = new BitmapData( _this.loader_mc._width, _this.loader_mc._height, true, 0 );
			_this.charset_pic.draw( _this.loader_mc );
			_this.loader_mc._visible = false;
			_this.loader_mc.removeMovieClip();
			_this.setSpriteSize();
			//
			callback( _this.charset_pic );
		}// copyMovieClip()
		
		
		// try direct bitmapData obejct
		if(file.generateFilterRect != undefined){
			copyBitmap( file.clone() );
			return;
		}// if:  input IS a bitmap
		
		// try internal bitmap
		var new_bitmap = BitmapData.loadBitmap( file );
		success = Boolean(new_bitmap.width > 0);
		if(success){
			copyBitmap( new_bitmap );
			return;
		}// if:  success
		
		// display a temporary snapshot while the new charset is being loaded
		_this.showPlaceHolder();
		_this.charset_pic.dispose();
		
		// try internal movieClip
		_this.loader_mc = _this.attachMovie( file, "loader_mc", 0, {_x:-_this.spriteWidth/2,_y:-_this.spriteHeight,_visible:false} );
		success = Boolean(_this.loader_mc != undefined);
		if(success){
			copyMovieClip();
			_this.hidePlaceHolder();
			return;
		}// if:  success
		
		// try external file
		_this.loader_mc = _this.createEmptyMovieClip("loader_mc", 0);
		_this.loader_mc._x = -_this.spriteWidth/2;
		_this.loader_mc._y = -_this.spriteHeight;
		var loader = new MovieClipLoader();
		loader.onLoadComplete = function(){
			_this.loader_mc._visible = false;
		}// onLoadComplete()
		loader.onLoadInit = function(){
			copyMovieClip();
			_this.hidePlaceHolder();
			return;
		}// onLoadInit()
		loader.loadClip( file, _this.loader_mc );
	}// loadNewCharset()
	
	
	_this.setSpriteSize = function(){
		var hasUpdated = false;
		var old_spriteWidth = _this.spriteWidth;
		var old_spriteHeight = _this.spriteHeight;
		_this.spriteWidth = Math.floor(_this.charset_pic.width / _this.internal.columns / _this.internal.frames);
		_this.spriteHeight = Math.floor(_this.charset_pic.height / _this.internal.rows / _this.internal.directions);
		if(_this.spriteWidth>0)
		{// if:  there is a sprite to display
			if(	old_spriteWidth != _this.spriteWidth
			||	old_spriteHeight != _this.spriteHeight){
				_this.image_pic.dispose();
				_this.image_pic = new BitmapData( _this.spriteWidth, _this.spriteHeight, true, 0 );
				_this.image_mc = _this.createEmptyMovieClip("image_mc", 1);
				// stand on top of the origin
				_this.image_mc._x = -_this.spriteWidth / 2;
				_this.image_mc._y = -_this.spriteHeight;
				//
				_this.image_mc.attachBitmap( _this.image_pic, 0 );
				//
				_this.copy.width = _this.spriteWidth;
				_this.copy.height = _this.spriteHeight;
				_this.update();		// redraw immediately to avoid flicker
				hasUpdated = true;		// sprite has been updated
				
				// legacy mask
				_this.updateMask();
			}// if:  sprite size changes
		}// if:  there is a sprite to display
		return hasUpdated;		// announce whether an update has been applied
	}// setSpriteSize()
	
	
	// copy from charset_pic -> image_pic
	_this.update = function(){
		if(_this.spriteWidth>0)
		{// if:  there is a sprite to display
			var xPoseIndex = _this.internal.pose %_this.internal.columns;
			var poseWidth = _this.spriteWidth *_this.internal.frames;
			var xPoseOffset = xPoseIndex *poseWidth;
			var frameOffset = _this.internal.frame *_this.spriteWidth;
			_this.copy.x = xPoseOffset + frameOffset;
			var yPoseIndex = Math.floor(_this.internal.pose /_this.internal.columns);
			var poseHeight = _this.spriteHeight *_this.internal.directions;
			var yPoseOffset = yPoseIndex *poseHeight;
			var directionOffset = _this.internal.direction *_this.spriteHeight;
			_this.copy.y = yPoseOffset + directionOffset;
			_this.image_pic.copyPixels( _this.charset_pic, _this.copy, _this.paste );
		}// if:  there is a sprite to display
	}// update()
	
	
	_this.advanceFrame = function(){
		if(_this.internal.frames > 1)
		{// if: there's more than 1 frame of animation
			_this.internal.frame += _this.internal.animDirection;
			
			// yoyo
			if(_this.internal.animType == 'yoyo'){
				if(_this.internal.frame >= _this.internal.frames){
					_this.internal.frame = _this.internal.frames-1;
					_this.internal.animDirection *= -1;
					if(_this.internal.frame>0)
						_this.internal.frame += _this.internal.animDirection;
					_this.broadcastMessage("animDone");
					_this.animDone();
				}else if(_this.internal.frame <= -1){
					_this.internal.frame = 0;
					_this.internal.animDirection *= -1;
					if(_this.internal.frame < _this.internal.frames-1)
						_this.internal.frame += _this.internal.animDirection;
					_this.broadcastMessage("animDone");
					_this.animDone();
				}
			}// if:  yoyo
			
			// loop
			else if(_this.internal.animType == 'loop'){
				var oldFrame = _this.internal.frame;
				
				// loop relative to how far the max frames were exceeded
				// loop beyond minimum
				while(_this.internal.frame < 0)
					_this.internal.frame += _this.internal.frames;
				// loop beyond maximum
				_this.internal.frame %= _this.internal.frames;
				
				if(oldFrame >= _this.internal.frames){
					//_this.internal.frame = 0;
					_this.broadcastMessage("animDone");
					_this.animDone();
				}else if(oldFrame <= -1){
					//_this.internal.frame = _this.internal.frames-1;
					_this.broadcastMessage("animDone");
					_this.animDone();
				}
			}// if:  loop
			
			// once
			else if(_this.internal.animType == 'once'){
				if(_this.internal.frame >= _this.internal.frames){
					// loop when animation is restarted
					_this.internal.frame = 0;
				}else if(_this.internal.frame == _this.internal.frames-1){
					// stop when last frame is hit
					_this.internal.isAnimating = false;
					_this.broadcastMessage("animDone");
					_this.animDone();
				}else if(_this.internal.frame <= -1){
					_this.internal.frame = 0;
					_this.internal.isAnimating = false;
					_this.broadcastMessage("animDone");
					_this.animDone();
				}
			}// if:  once
		}// if: there's more than 1 frame of animation
		else
		{// if:  there's only 1 frame of animation
			_this.internal.frame = 0;
		}// if:  there's only 1 frame of animation
	}// advanceFrame()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// Interface
	_this.getParams = function(){
		var output = {};
		for(var nam in _this.internal)
			output[nam] = _this.internal[nam];
		return output;
	}// getParams()
	
	
	_this.getParam = function( nam ){
		return _this.internal[nam];
	}// getParam()
	
	
	_this.setParams = function( new_settings )
	{
		var detectStopAnim = function(){
			if(	_this.internal.isAnimating == true
			&&	new_settings.isAnimating == false){
				applyStopFrame();
			}// if:  animation is being stopped
		}// detectStopAnim()
		
		var checkDirection = function(){
			var incr = _this.internal.directions/4;
			if(_this.internal.direction.toLowerCase()=="up"){
				_this.internal.direction = 0;
			}else if(_this.internal.direction.toLowerCase()=="right"){
				_this.internal.direction = Math.floor(1*incr);
			}else if(_this.internal.direction.toLowerCase()=="down"){
				_this.internal.direction = Math.floor(2*incr);
			}else if(_this.internal.direction.toLowerCase()=="left"){
				_this.internal.direction = Math.floor(3*incr);
			}
			// loop relative to how far the bounds were exceeded
			// limit minimum to looping range
			while(_this.internal.direction < 0)
				_this.internal += _this.internal.directions;
			// loop the maximum
			_this.internal.direction %= _this.internal.directions;
		}// checkDirection()
		
		var checkDelay = function(){
			if(_this.internal.delay <= 0)
				_this.internal.delay = 1;
		}// checkDelay()
		
		var checkPose = function(){
			var maxPose = _this.internal.columns *_this.internal.rows;
			if(_this.internal.pose >= maxPose)
				_this.internal.pose = 0;
		}// checkPose()
		
		var checkFrame = function(){
			// adjust frames
			if(_this.internal.frame >= _this.internal.frames){
				applyStopFrame();
			}// if:  old frame is now out-of-bounds
		}// checkFrame()
		
		var applyStopFrame = function(){
			if(_this.internal.frames == 3){
				// 3 frames
				_this.internal.frame = 1;
			}else{
				// 1, 2, or many frames
				_this.internal.frame = 0;
			}
		}// applyStopFrame()
		
		
		
		var applyOtherSettings = function(){
			detectInvalidPose();
			detectStopAnim();
			
			// copy settings
			for(var nam in new_settings){
				var newValue = new_settings[nam];
				var isNumber = !isNaN(newValue);
				if(isNumber)
					_this.internal[nam] = Number(newValue);
				else
					_this.internal[nam] = newValue;
			}// for:  each new setting
			
			checkDirection();
			checkFrame();
			checkDelay();
			checkPose();
			
			// immediately redraw
			var hasUpdated = _this.setSpriteSize();
			if(!hasUpdated)	_this.update();
		}// applyOtherSettings()
		
		
		var charsetChanged = (new_settings.charset != _this.internal.charset)
		if(new_settings.charset  &&  charsetChanged){
			_this.loadNewCharset( new_settings.charset, function(){
				_this.broadcastMessage("changeCharset");
				_this.changeCharset();
			} );// after loading charset
			applyOtherSettings();
		}
		else{
			applyOtherSettings();
			// why is this broadcastMessage here??
			//_this.broadcastMessage("changeCharset");
			//_this.changeCharset();
		}
	}// setParams()
	
	
	_this.showPlaceHolder = function(){
		_this.placeHolder_mc = _this.createEmptyMovieClip("placeHolder_mc", 2);
		// stand on top of the origin
		_this.placeHolder_mc._x = -_this.spriteWidth / 2;
		_this.placeHolder_mc._y = -_this.spriteHeight;
		_this.placeHolder_pic.dispose();
		//_this.update();
		_this.placeHolder_pic = _this.image_pic.clone();
		_this.placeHolder_mc.attachBitmap( _this.placeHolder_pic, 0 );
		_this.image_mc._visible = false;
	}// showPlaceHolder()
	
	_this.hidePlaceHolder = function(){
		_this.placeHolder_pic.dispose();
		_this.placeHolder_mc.removeMovieClip();
		_this.image_mc._visible = show;
	}// hidePlaceHolder()
	
	
	_this.isVisible = function(){
		var visibility = _this._visible && _this._parent._visible;
		var opacity = _this._alpha>0 && _this._parent._alpha>0;
		return visibility && opacity;
	}// isVisible()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// EXTRA FEATURES
	// The sprite needs to be inside of a movieClip for this to work
	// Compares the parent movieClip's coords to the x,y passed to this function as parameters
	_this.lookAtCoords = function()
	{
		var destX = arguments[0];
		var destY = arguments[1];
		// get a unit vector
		var xDiff = destX - _this._parent._x;
		var yDiff = destY - _this._parent._y;
		
		// compare x & y
		if( Math.abs(yDiff) > Math.abs(xDiff) )
		{
			// vert
			if( yDiff < 0)
			{
				// up
				_this.setParams( {direction:"up"} );
			}else{
				// down
				_this.setParams( {direction:"down"} );
			}// if:  up or down
		}else{
			// horz
			if( xDiff < 0 )
			{
				// left
				_this.setParams( {direction:"left"} );
			}else{
				// right
				_this.setParams( {direction:"right"} );
			}// if:  left or right
		}// if:  vert or horz
	}// lookAtCoords()
	
	
	// make this sprite look at the specified movieClip.  (The specified movieClip must be a sibling of this sprite's parent)
	_this.lookAtSprite = function( clip2 ){
		_this.lookAtCoords( clip2._x, clip2._y );
	}// lookAtSprite()
	
	
	/*
	// DEPRECATED:		This makes the specified sprite look at the specified movieClip.  (The specified movieClip must be a sibling of this sprite's parent)
	_this.lookAt = function( sprite1, clip2 ){
		sprite1.lookAtSprite( clip2 );
	}// lookAt()
	*/
	_this.lookAt = function( sprite1, sprite2 )
	{
		// get a unit vector
		var xDiff = sprite2._x - sprite1._x;
		var yDiff = sprite2._y - sprite1._y;
		var dist = Math.sqrt( xDiff*xDiff + yDiff*yDiff );
		var xUnit = xDiff / dist;
		var yUnit = yDiff / dist;
		
		// compare x & y
		if( Math.abs(yUnit) > Math.abs(xUnit) )
		{
			// vert
			if( yUnit < 0)
			{
				// up
				_this.setParams( {direction:"up"} );
			}else{
				// down
				_this.setParams( {direction:"down"} );
			}// if:  up or down
		}else{
			// horz
			if( xUnit < 0 )
			{
				// left
				_this.setParams( {direction:"left"} );
			}else{
				// right
				_this.setParams( {direction:"right"} );
			}// if:  left or right
		}// if:  vert or horz
	}// lookAt()
	
	
	
	// report whether or not this sprite is facing in the specified direction
	_this.isFacing = function( checkDir )
	{
		var incr = _this.internal.directions/4;
		if(checkDir.toLowerCase()=="up"){
			var checkDir = 0;
		}else if(checkDir.toLowerCase()=="right"){
			var checkDir = Math.floor(1*incr);
		}else if(checkDir.toLowerCase()=="down"){
			var checkDir = Math.floor(2*incr);
		}else if(checkDir.toLowerCase()=="left"){
			var checkDir = Math.floor(3*incr);
		}
		// limit minimum to looping range
		while(checkDir < 0)
			checkDir += _this.internal.directions;
		// loop the maximum
		checkDir %= _this.internal.directions;
		return (_this.internal.direction == checkDir);
	}// isFacing()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// Legacy features
	_this.set_charset = function( new_charset, newThis, callback ){
		_this.loadNewCharset( new_charset, callback );
	}// set_charset()
	
	
	_this.set_direction = function( new_dir ){
		_this.setParams({direction:new_dir});
	}// set_direction
	
	
	// create mask
	_this.mask_mc = _this.createEmptyMovieClip( "mask_mc", 3 );
	_this.mask_mc.beginFill(0xff0000, 0);
	_this.mask_mc.lineTo(16,0);
	_this.mask_mc.lineTo(16,16);
	_this.mask_mc.lineTo(0,16);
	_this.mask_mc.lineTo(0,0);
	_this.mask_mc.endFill();
	_this.updateMask = function(){
		// position
		_this.mask_mc._x = -_this.spriteWidth / 2;
		_this.mask_mc._y = -_this.spriteHeight;
		// size
		_this.mask_mc._width = _this.spriteWidth;
		_this.mask_mc._height = _this.spriteHeight;
	}// updateMask();
	
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// LOOP
	_this.fps = 30;
	_this.loop = function(){
		if(_this.internal.isAnimating  &&  _this.isVisible() ){
			_this.advanceFrame();
			_this.update();
		}// isAnimating()
	}// loop()
	_this.loop();
	
	
	_this.loopSelf = function()
	{
		if( _this.useInterval !== true )		return;

		var thisExists = (_this._name != "");
		if(thisExists)
		{// if:  self exists
			_this.loop();
			_this.loopInterval = setTimeout( _this.loopSelf, Math.floor(1000/_this.fps*_this.internal.delay) );
		}// if:  self exists
		else
		{// if:  self does NOT exist
			_this.loopSelf = null;
			delete _this.loopSelf;
		}// if:  self does NOT exist
	}// loopSelf()
	_this.loopInterval = setTimeout( _this.loopSelf, 0 );
	
	
	_this.manualLoop = makeManualLoop();
	function makeManualLoop(){
		var delayCounter = 0;
		return function(){
			if(delayCounter <= 0){
				_this.loop();
				delayCounter = _this.internal.delay;
			}// if:  delay has run out
			delayCounter--;
		}// return: manualLoop()
	}// makeManualLoop()
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// initial setup
	AsBroadcaster.initialize( _this );		// enable events
	//_this.loadNewCharset( _this.internal.charset );
	_this.setParams( newSettings );
	
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// clean-up
	_this.onUnload = function(){
		if(_this.loopInterval!=undefined)
			clearTimeout( _this.loopInterval );
		// clear events
		for(var i in _this._listeners)
			_this._listeners[i] = null;
		_this._listeners = [];
	}// onUnload()

	
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	return _this;
}// newSprite()
