Engine.DrawableStatics = {};
Engine.DrawableStatics.SortMode = {"Index" : 0, "Z" : 1};
Engine.DrawableStatics.QueuedFrameEvents = [];
Engine.DrawableStatics.PlayMode = {Forwards : 0, Backwards : 1};

Engine.Drawable = class extends PIXI.Sprite
{
	constructor(graphicName)
	{
		super();

		this.isDrawable = true;
		this.sequence = 0;
    	this.frame = 0;
    	this.numFrames = 0;

    	this.playing = false;
		this.playMode = Engine.DrawableStatics.PlayMode.Forwards;
    	this.frameTimer = 0;
    	this.frameRate = 30;

    	this.graphicData = new Engine.GraphicDef();
		this.skeletalController = null;

		//this.filterList = null;//[];
		this.multiFilters = null;//[];
		this.greyscaleFilter == null;

		//this.isNinePlane = false;
		this.ninePlane = null;
		this.nineRectData = null;
		this.onStage = false;
		this.name = "Drawable";

		this.rotationDegrees = 0;

		this.stopAtEnd = false;
		this.animationCompleted = new Action();
		this.onGraphicLoaded = new Action();
		this.onGraphicReady = new Action();

		this.containerSortMode = Engine.DrawableStatics.SortMode.Index;

		this._graphics = null;

		this.events = new Engine.DrawableEvents(this);

		this.frameEvents = {};

		this.graphicName = "";
		if (graphicName)
		{
			if (isNumeric(graphicName))
			{
				this.loadFromGraphicID(graphicName);
			} else {
				this.setGraphicDataByName(graphicName);
			}
		}

		this.canvas = null;
		
		this.z = 0;

		this.performanceTime = null;

		this.parent = null;
		this.cacheAsBitmap = false;
    	this.bakeHandler = null;
	}

	// renderWebGL(renderer)
	// {
	// 	var before = performance.now();		
	// 	super.renderWebGL(renderer);
	// 	var after = performance.now();
	// 	var val = Math.trunc((after - before) * 10);
	// 	//if (this.children.length > 0 && this.parent != null && this.parent.name == "Game UI Layer")
	// 	if (this.children.length > 0 && this.parent != null && this.parent.name == "Hero Box")
	// 	{
	// 		if (this.performanceTime == null)
	// 		{
	// 			this.performanceTime = new Engine.DrawableText();
	// 			this.performanceTime.setOutline();
	// 		}
	// 		this.removeChild(this.performanceTime);
	// 		this.addChild(this.performanceTime);
	// 		this.performanceTime.setText(val);

	// 		Debug.Log("" + this.constructor.name + ": " + val);
	// 		//this.performanceTime.x = (this.width - this.performanceTime.width) * 0.5;
	// 		//this.performanceTime.y = (this.height - this.performanceTime.height) * 0.5;
	// 	}
	// }

	// renderCanvas(renderer)
	// {
	// 	var before = performance.now();		
	// 	super.renderCanvas(renderer);
	// 	var after = performance.now();
	// 	var val = Math.trunc((after - before) * 10);
	// 	if (this.children.length > 0)
	// 	{
	// 		this.removeChild(this.performanceTime);
	// 		this.addChild(this.performanceTime);
	// 		this.performanceTime.setText(val);

	// 		this.performanceTime.x = (this.width - this.performanceTime.width) * 0.5;
	// 		this.performanceTime.y = (this.height - this.performanceTime.height) * 0.5;
	// 	}
	// }

	// Stuff for UI baking 
    addUIBakeHandler(showLog = false) { this.bakeHandler = new Engine.BakeHandler(this, showLog); }

    allowUIBake(bakeAllowed)
    {
        if (this.bakeHandler != null) this.bakeHandler.allowBake(bakeAllowed);
    }

    incrementUnbakeableComponents()
    {
        if (this.bakeHandler != null)
        {
            this.bakeHandler.changeUnbakeableChildren(1);
        }
        else if (this.parent != null && this.parent instanceof Engine.Drawable)
        {
            this.parent.incrementUnbakeableComponents();
        }
    }

    decrementUnbakeableComponents()
    {
        if (this.bakeHandler != null)
        {
            this.bakeHandler.changeUnbakeableChildren(-1);
        }
        else if (this.parent != null && this.parent instanceof Engine.Drawable)
        {
            this.parent.decrementUnbakeableComponents();
        }

    }
    // end UI Baking Kogic
	
	sortChildren(sortFunc)
	{
		var childs = this.children.concat();
		childs.sort(sortFunc);
		for (var i = 0; i < childs.length; i++)
		{
			this.setChildIndex(childs[i], i);
		}
	}
	
	addChildAt(child, index) {
		if (index < 0) index = 0;
		if (index > this.children.length) index = this.children.length;
		
		super.addChildAt(child, index);
	}
	
	getChildIndex(child) {
        return this.children.indexOf(child);
    };
	
	// get filters()
	// {
	// 	return this.filterList;
	// }
	// set filters(f)
	// {
	// 	if (this.filterList == null) this.filterList = [];
	// 	else this.filterList.length = 0;

	// 	for (var i = 0; i < f.length; i++)
	// 	{
	// 		if (!this.filterList.contains(f[i]))
	// 		{
	// 			this.filterList.push(f[i]);
	// 		}
	// 	}
		
	// 	if (!Engine.Drawable.fastRenderMode)
	// 	{
	// 		super.filters = f;
	// 	}
	// }

	get greyscale()
	{
		return this.filters != null && this.filters.contains(this.greyscaleFilter);
	}
	set greyscale(enabled)
	{
		if (!enabled && this.greyscaleFilter == null) return;
		// || this.filterList == null || !this.filterList.contains(this.greyscaleFilter))) return;
		
		if (enabled && !this.greyscale)
		{
			if (this.greyscaleFilter == null) 
			{
				this.greyscaleFilter = new PIXI.filters.ColorMatrixFilter();
				this.greyscaleFilter.desaturate();
			}

			this.addLiveEffect(this.greyscaleFilter);
			// this.filterList.push(this.greyscaleFilter);
			// super.filters = this.filterList;
		} 
		else if (!enabled && this.greyscale)
		{
			this.removeLiveEffect(this.greyscaleFilter);
			// this.filterList.remove(this.greyscaleFilter); 
			// super.filters = this.filterList;
		}
	}

	set filterMatrix(value)
	{
		if (this.tintFilter == null) this.tintFilter = new PIXI.filters.ColorMatrixFilter();

		this.tintFilter.matrix = value;
		
		this.addLiveEffect(this.tintFilter);
	}

	get filterMatrix()
	{
		if (!this.tintFilter) return [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0];
		return this.tintFilter.matrix;
	}
	
	set filterTint(value)
	{
		if (value.getHex) value = value.getHex();
		if (this.tintFilter == null) this.tintFilter = new PIXI.filters.ColorMatrixFilter();
		
		this.tintFilter.matrix[0] = ((value & 0xFF0000) >> 16) / 0xFF;
		this.tintFilter.matrix[6] = ((value & 0xFF00) >> 8) / 0xFF;
		this.tintFilter.matrix[12] = (value & 0xFF) / 0xFF;
		
		this.addLiveEffect(this.tintFilter);
		// if (!this.filterList.contains(this.tintFilter))
		// {
		// 	this.filterList.push(this.tintFilter);
		// }
		
		// super.filters = this.filterList;
	}
	
	get filterTint()
	{
		if (!this.tintFilter) return Colors.white;
		return new Color(this.tintFilter.matrix[0], this.tintFilter.matrix[6], this.tintFilter.matrix[12]);
	}
	
	clearFilterTint()
	{
		if (this.tintFilter == null) return;
		// || !this.filterList.contains(this.tintFilter)) return;
		
		this.removeLiveEffect(this.tintFilter);
		// this.filterList.remove(this.tintFilter);
		// super.filters = this.filterList;
	}

	get numSequences()
	{
		if (this.graphicData) return this.graphicData.sequences.length;
		return 0;
	}
	
	getSequence() { return this.sequence; }
	get totalFrames() { return this.numFrames; }

	getSequenceLength(sequence)
	{
		if (this.graphicData && this.graphicData.sequences[sequence] !== undefined) 
		{
			if (this.skeletalController != null)
			{
				// this seems silly
				if (this.graphicData.sequences[sequence].length > 0)
				{
					return this.graphicData.sequences[sequence][0].sequence.length;
				}
			}
			else
			{
				return this.graphicData.sequences[sequence].length;
			}
		}
		return 0;
	}

	get numChildren()
	{
		return this.children.length;
	}

	setGameObjectName(name)
	{
		this.name = name;
	}

	get mouseEnabled()
	{
		return this.events.mouseEnabled;
	}

	set mouseEnabled(value)
	{
		if (value) this.events.enableMouseEvents();
		if (!value) this.events.disableMouseEvents();
	}

	/**
	 * Sets the current sprite to be a nine rect sprite.
	 * Defines the inner nine rect box as left, top, right, and bottom as percents.
	 */
	setNineRect(left, top, right, bottom)
	{
		/*if (this.nineRectData != null)
		{
			if (left === undefined) return;
			if (left == this.nineRectData.left &&
				top == this.nineRectData.top &&
				right == this.nineRectData.right &&
				bottom == this.nineRectData.bottom)
				return;
		}*/

		if (left === undefined)
		{
			if (this.nineRectData != null)
			{
				left = this.nineRectData.left;
				top = this.nineRectData.top;
				right = this.nineRectData.right;
				bottom = this.nineRectData.bottom;
			}
			else
			{
				left = 0.4; //0.5; //0.4;
				top = 0.4; //0.5; //0.4;
				right = 0.6; //0.501; //0.6;
				bottom = 0.6; //0.501; //0.6;
			}
		}

		//this.isNinePlane = true;
		this.nineRectData = {left: left, top: top, right: right, bottom: bottom};

		if (!this.loaded) this.texture = PIXI.Texture.WHITE;

		if (this.ninePlane == null)
		{
			//Create a nine slice plane to replace the current sprite.
			var frameWidth = this.texture.frame.width;
			var frameHeight = this.texture.frame.height;
			this.ninePlane = new PIXI.NineSlicePlane(this.texture, Math.round(frameWidth * left), Math.round(frameHeight * top), Math.round(frameWidth * (1 - right)), Math.round(frameHeight * (1 - bottom)));
			this.addChildAt(this.ninePlane, 0);

			this.texture = PIXI.Texture.EMPTY;
		} 
		else
		{
			var frameWidth = this.ninePlane.texture.frame.width;
			var frameHeight = this.ninePlane.texture.frame.height;
			this.ninePlane.leftWidth = Math.round(frameWidth * left);
			this.ninePlane.topHeight = Math.round(frameHeight * top);
			this.ninePlane.rightWidth = Math.round(frameWidth * (1 - right));
			this.ninePlane.bottomHeight = Math.round(frameHeight * (1 - bottom));
			
			this.addChildAt(this.ninePlane, 0);
		}

		// better solution
		if (this.loaded)
		{
			this.setFrame(this.frame)
		}
	}

	get width()
	{
		if (this.ninePlane != null) return this.ninePlane.width * this.scale.x;
		return this.getLocalBounds().width * Math.abs(this.scale.x);
		//return super.width;
	}

	set width(value)
	{
		//value = Math.floor(value);
		//if (this.isNinePlane) 
		//{
			if (this.ninePlane != null) { this.ninePlane.width = value; } 
		//}
		else { super.width = value; } 
	}

	get height()
	{
		if (this.ninePlane != null) return this.ninePlane.height * this.scale.y;
		return this.getLocalBounds().height * Math.abs(this.scale.y);
		//return super.height;
	}

	set height(value)
	{
		//value = Math.floor(value);
		//if (this.isNinePlane) 
		//{ 
			if (this.ninePlane != null) { this.ninePlane.height = value; } 
		//} 
		else { super.height = value; } 
	}

	get x() { return super.x; }
	set x(value) { super.x = Math.trunc(value); }

	get y() { return super.y; }
	set y(value) { super.y = Math.trunc(value); }

	get screenRect() { return this.getBounds(); }
	get screenX() { return this.localToGlobal(new Vector2()).x; }
	get screenY() { return this.localToGlobal(new Vector2()).y; }

	get rotation() { return this.rotationDegrees; }
	set rotation(value) { super.rotation = value * -(Math.PI / 180); this.rotationDegrees = value; }

	get tint()
	{
		if (this.ninePlane != null) { return this.ninePlane.tint; }
		return super.tint;
	}
	set tint(value)
	{
		if (value.getHex) value = value.getHex();
		if (this.ninePlane != null) { this.ninePlane.tint = value; return; }
		super.tint = value;
	}

	// this doesnt apply to child drawables... yet
	get additive()
	{
		return this.blendMode;
	}
	set additive(value)
	{
		if (value)
		{
			this.blendMode = PIXI.BLEND_MODES.ADD;
			if (this.ninePlane != null) { this.ninePlane.blendMode = PIXI.BLEND_MODES.ADD; }
		}
		else
		{
			this.blendMode = PIXI.BLEND_MODES.NORMAL;
			if (this.ninePlane != null) { this.ninePlane.blendMode = PIXI.BLEND_MODES.NORMAL; }
		}
	}

	localToGlobal(pos)
	{
		if (pos === undefined){
			Debug.Log("Undefined point passed into localToGlobal");
			return new Vector2(0, 0);
		}

		var globalPos = this.toGlobal(new PIXI.Point(pos.x, pos.y));
		return new Vector2(globalPos.x, globalPos.y);
	}

	localRectToGlobal(rect)
	{
		var topLeft = this.toGlobal(new PIXI.Point(rect.x, rect.y));
		var bottomRight = this.toGlobal(new PIXI.Point(rect.x + rect.width, rect.y + rect.height));
		return new PIXI.Rectangle(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
	}

	globalToLocal(pos)
	{
		//var localPos = new Vector2(pos.x - bounds.x, pos.y - bounds.y);

		if (pos === undefined){
			Debug.Log("Undefined point passed into globalToLocal");
			return new Vector2(0, 0);
		}

		var localPos = this.toLocal(new PIXI.Point(pos.x, pos.y));
		return new Vector2(localPos.x, localPos.y);
	}
		

	addChild(drawable)
	{
		if (drawable == null || drawable.parent == this){ return; }
		super.addChild(drawable);
		if (drawable.setChildrenOnStage) drawable.setChildrenOnStage(this.onStage);
	}

	removeChild(drawable)
	{
		if (drawable == null){ return; }
		super.removeChild(drawable);
		if (drawable.setChildrenOnStage) drawable.setChildrenOnStage(false);
	}

	setChildrenOnStage(onStage)
	{
		if (this.onStage != onStage)
		{
			//fire activated/deactivated
			if(this.onActivated)
			{
				this.onActivated(onStage);
				if (onStage) this.events.fireActivated();
				if (!onStage) this.events.fireDeactivated();
			}
		}
		this.onStage = onStage;

		for (var i = 0; i < this.children.length; i++)
		{
			if (this.children[i].setChildrenOnStage)
			{
				this.children[i].setChildrenOnStage(onStage, true);
			}
		}
	}

	setXYUnrounded(x, y)
	{
		super.x = x;
		super.y = y;
	}

	setSequence(seq, frame, force)
	{
		if (frame === undefined) frame = 0;
		if (force === undefined) force = false;

		if (!isFinite(seq))
		{
			Debug.LogError("invalid sequence attempted "+seq);
			seq = 0;
		}

		if (this.graphicData == null || !this.graphicData.loaded)
		{
			this.sequence = seq;
			this.frame = frame;
			return;
		}

        seq = Math.max(0, Math.min(this.numSequences - 1, seq));
		this.sequence = seq;
		this.numFrames = this.graphicData == null ? 0 : this.graphicData.getNumFramesInSeq(seq);

        var sequenceFrames = this.getSequenceLength(seq);
        if (frame < 0) frame += sequenceFrames;
        frame = Math.max(0, Math.min(sequenceFrames - 1, frame));
		this.setFrame(frame);

	}

	gotoAndPlay(frame, playMode)
	{
		if (playMode === undefined) playMode = Engine.DrawableStatics.PlayMode.Forwards;
		
		this.setFrame(frame);
		this.play();
	}

	gotoAndStop(frame)
	{
		this.playing = false;
		this.setFrame(frame);
	}
	
	stop()
	{
		this.playing = false;
	}
	
	play(playMode)
	{
		if (playMode === undefined) playMode = Engine.DrawableStatics.PlayMode.Forwards;
		
		this.playMode = playMode;
		this.playing = true;
	}
	
    looksLikeSequenceFrames()
    {
        if (this.graphicData == null || !this.graphicData.loaded) return false;
        if (this.numSequences < 2) return false;

        for (var i = 0; i < this.numSequences; i++)
        {
            if (this.getSequenceLength(i) > 1) return false;
        }
        return true;
    }

    looksLikeSingleSequence()
    {
        if (this.graphicData == null || !this.gradeData.loaded) return false;
        if (this.numSequences > 1) return false;

        if (this.getSequenceLength(0) <= 1) return false;
        return true;
    }

	setFrame(frame, isRenderFrame)
	{
		if (isRenderFrame === undefined) isRenderFrame = true;

		if (this.graphicData == null || !this.graphicData.loaded)
		{
			this.frame = frame;
			return;
		}

		this.frame = 0;
		if (this.numFrames > 0)
		{
			this.frame = (frame + this.numFrames) % this.numFrames;
		}

		if (isRenderFrame)
		{
			if (this.skeletalController != null)
			{
				this.skeletalController.setFrame(this.sequence, this.frame);
			} 
			else if (this.ninePlane != null) 
			{
				/*var newTexture = this.graphicData.getTexture(this.sequence, this.frame);
				if (newTexture.baseTexture != this.ninePlane.texture.baseTexture)
				{
					this.ninePlane.texture = newTexture;
				} 
				this.ninePlane.texture.frame = newTexture.frame;*/
				this.graphicData.matchSprite(this.ninePlane, this.sequence, this.frame);
			} 
			else 
			{
				this.graphicData.matchSprite(this, this.sequence, this.frame);
			}
		}
	}
	
	setTexturedRect(baseTexture, uvRect /*Rect*/, pixelRect /*Rect*/)
	{
		this.graphicData = null;
		this.playing = false;
		
		if (this.texture == null || this.texture == PIXI.Texture.EMPTY)
		{
			this.texture = new PIXI.Texture(baseTexture);
		}
		if (this.texture.baseTexture != baseTexture)
		{
			this.texture.baseTexture = baseTexture;
		}
		
		var pixiUVRect = uvRect;
		if (uvRect instanceof Rect)
		{
			uvRect = new PIXI.Rectangle(uvRect.xMin, uvRect.yMin, uvRect.width, uvRect.height);
		}
		
		this.texture.frame = new PIXI.Rectangle(uvRect.x * baseTexture.width,
												uvRect.y * baseTexture.height,
												uvRect.width * baseTexture.width,
												uvRect.height * baseTexture.height);
		this.texture.updateUvs();
		
		this.anchor.x = -pixelRect.xMin / pixelRect.width;
		this.anchor.y = -pixelRect.yMin / pixelRect.height;
		this.width = pixelRect.width;
		this.height = pixelRect.height;
	}

	manualAnimationUpdate()
    {
        //DirtyTransform = true;
        this.updateAnimation(0, true);
    }

	updateAnimation(timeElapsed, isRenderFrame)
	{
		if (this.playing)
		{
			if (this.numFrames > 1)
			{
				this.frameTimer += timeElapsed;
				var timePerFrame = 1.0 / this.frameRate;
				while (this.frameTimer > timePerFrame)
				{
					this.setFrame(this.playMode == Engine.DrawableStatics.PlayMode.Forwards ? this.frame + 1 : this.frame - 1);
					var breakOnFrame = false;

					if (this.frameEvents[this.sequence] !== undefined && this.frameEvents[this.sequence][this.frame])
					{
						var frameEvents = this.frameEvents[this.sequence][this.frame];
						for(var event of frameEvents) this.queueFrameEvent(event);
						breakOnFrame = true;
					}

					if (this.stopAtEnd)
					{
						if ((this.playMode == Engine.DrawableStatics.PlayMode.Forwards && this.frame >= this.numFrames - 1) ||
							(this.playMode == Engine.DrawableStatics.PlayMode.Backwards && this.frame <= 0))
						{					
							this.playing = false;
							Engine.DrawableStatics.QueuedFrameEvents.push(() => { this.animationCompleted.call(this); } );
							breakOnFrame = true;
						}
					}

					this.frameTimer -= timePerFrame;
					
					if (breakOnFrame) break;
				}
			}

			if (this.graphicData && this.graphicData.loaded && this.numFrames == 1) this.playing = false;
		}
	}

	queueFrameEvent(frameEvent)
	{
		Engine.DrawableStatics.QueuedFrameEvents.push(() => { frameEvent(this); } );
	}

    static doQueuedFrameEvents()
    {
        //Make any queued up frame events.
        for (var i = 0; i < Engine.DrawableStatics.QueuedFrameEvents.length; i++) 
        { 
        	Engine.DrawableStatics.QueuedFrameEvents[i]();
        }
        Engine.DrawableStatics.QueuedFrameEvents.length = 0;
    }

	drawableUpdate(timeElapsed, paused, isRenderFrame)
	{
		if (this.multiFilters != null)
		{
			for (var mf of this.multiFilters)
			{
				mf.update();
			}
		}
		if (!paused)
		{
			this.updateAnimation(timeElapsed, isRenderFrame);

			for (var i = 0; i < this.children.length; i++)
			{
				if (this.children[i].drawableUpdate)
				{
					this.children[i].drawableUpdate(timeElapsed);
				}
			}
		}

		if (this.containerSortMode == Engine.DrawableStatics.SortMode.Z)
		{
			this.children.sort((a, b) => {
		    	if(a.z === b.z) return 0;
		    	else return (a.z < b.z ? -1 : 1);
			});
		}
	}

    clearFrameEvents(seq)
    {
    	if (seq !== undefined)
    	{
    		delete this.frameEvents[seq];
    	} else {
    		this.frameEvents = {}; 
    	}
 	}

    setFrameEvent(seq, frame, action, add)
    {
    	if (add == undefined) add = false;

        if (this.frameEvents[seq] === undefined) this.frameEvents[seq] = {};

        if (this.frameEvents[seq][frame] === undefined)
        {
            this.frameEvents[seq][frame] = [action];
        }
        else
        {
            if (add)
            {
                this.frameEvents[seq][frame].push(action);
            }
            else
            {
                this.frameEvents[seq][frame] = [action];
            }
        }
    }

    removeFrameEvent(seq, frame, action)
    {
        if (this.frameEvents != null && this.frameEvents[seq] !== undefined && this.frameEvents[seq][frame] !== undefined)
        {
        	//This is only going to work if you have kept a reference to your action for removal...
        	if (action !== undefined)
        	{
	        	var index = this.frameEvents[seq][frame].indexOf(action);
	        	if (index != -1)
	        	{
	        		this.frameEvents[seq][frame].splice(index, 1);
	        	}
	        } 
	        else 
	        {
	        	// if no action is passed, just clear the frame...
	        	this.frameEvents[seq][frame] = [];
	        }
        }
    }


    get loaded() { return (this.graphicData != null && this.graphicData.loadStatus == GraphicDefStatics.LoadState.Loaded); }

	loadFromGraphicName(graphicName, requestedWidth, requestedHeight)
	{
		this.graphicName = graphicName;
		this.setGraphicDataByName(graphicName, requestedWidth, requestedHeight);
	}

	loadFromGraphicID(graphicId, requestedWidth, requestedHeight)
	{
		var graphicName = Engine.GraphicFactoryInstance.getGraphicNameById(graphicId);
		this.setGraphicDataByName(graphicName, requestedWidth, requestedHeight);
	}

	setGraphicDataByName(graphicName, requestedWidth, requestedHeight)
	{
		this.graphicName = graphicName;
		
		if (requestedWidth === undefined) requestedWidth = -1;
		if (requestedHeight === undefined) requestedHeight = -1;

		var graphicData = null;
        if (requestedWidth > -1 && requestedWidth > -1)
        {
            graphicData = Engine.GraphicFactoryInstance.getGraphicDataByName(graphicName, requestedWidth, requestedHeight);
        } else
        {
            graphicData = Engine.GraphicFactoryInstance.getGraphicDataByName(graphicName);
        }

		if (!graphicData)
		{
			Debug.LogError("Error loading "+graphicName+" graphic not found!");
			return;
		}

		this.setGraphicData(graphicData);
	}

	//for compatability.
	setGraphicDef(graphicDef){ this.setGraphicData(graphicDef); }

	setGraphicData(graphicData)
	{
		var t = this;
		if (this.graphicData == graphicData)
		{
			 //Graphic already set to this graphic data, nothing to do here...
			 if (this.graphicData != null && this.graphicData.loadStatus == GraphicDefStatics.LoadState.Loaded) this.onGraphicReady.call(this);
			 return;
		}
		
		this.graphicName = graphicData == null ? "" : graphicData.name;

		if (graphicData == null)
		{
			//todo clear...
			this.clearSprite();
			return;
		}

		this.clearSprite();

		this.graphicData = graphicData;
		if (graphicData.loaded)
		{
			this.graphicDataLoaded(graphicData);
		} 
		else if (graphicData.loadStatus == GraphicDefStatics.LoadState.None)
		{
			Engine.GraphicFactoryInstance.loadGraphicData(graphicData, function(){ t.graphicDataLoaded(graphicData); });
		}
	}

	graphicDataLoaded(graphicData)
	{
		// is this hacky?
		// prevents loading multiple graphics, and the first one loading after the second one.
		// happens in the hero collection screen when scrolling with scroll bar.
		if (graphicData != this.graphicData) return;

		this.graphicName = graphicData.name;
		graphicData.setTextureMode(PIXI.SCALE_MODES.LINEAR);

		if (graphicData.type == "skeletal")
		{
			if (this.skeletalController == null)
			{
				this.skeletalController = new Engine.SkeletalAnimationControllerReplaceable(graphicData);
				this.addChild(this.skeletalController.group);
			} 
			else 
			{
				//CLEAR THE CONTROLLER AND MAKE A NEW ONE!
				this.skeletalController.destroy();
				this.skeletalController = new Engine.SkeletalAnimationControllerReplaceable(graphicData);
				this.addChild(this.skeletalController.group);
			}
		}
		else if (this.skeletalController != null)
		{
			this.skeletalController.destroy();
			this.skeletalController = null;
		}

		/*if (graphicData.type == "spritesheet")
		{
			//if this was previously used as a skeletal, but now being set as a spritesheet, clear the skeletal controller!
			if (this.skeletalController != null)
			{
				this.skeletalController.destroy();
				this.skeletalController = null;
			}
		}*/

		if (graphicData.type == "scene")
		{
			var clips = graphicData.clips;
			for (var i = 0; i < clips.length; i++)
			{
				var clip = clips[i];
				var sprite = new Engine.Drawable();
				sprite.setGraphicDataByName(clip.name);
				if (sprite.graphicData.id == -1) sprite.graphicData.parentSceneData = graphicData;
				sprite.x = clip.x;
				sprite.y = clip.y;
				sprite.scale.x = clip.sx;
				sprite.scale.y = clip.sy;
				sprite.rotation = clip.rot;
				sprite.name = clip.original_name;
				sprite.gotoAndPlay(0);
				this.addChild(sprite);
			}
		}
		else
		{
			this.setSequence(this.sequence, this.frame);
			// setframe gets called in setsequence
			//this.setFrame(this.frame);
		}
		
		//reset the nine rect data, because the texture size could have changed.
		if (this.ninePlane != null) this.setNineRect(); 

		if (this.onGraphicLoaded != null) this.onGraphicLoaded.call(this);
		if (this.onGraphicReady != null) this.onGraphicReady.call(this);
	}
	
	clearSprite()
	{
		this.graphicData = null;
		this.graphicName = "";
		if (this.skeletalController != null)
		{
			this.skeletalController.destroy();
			this.skeletalController = null;
		}
		this.texture = PIXI.Texture.EMPTY;
		this.playing = false;
	}

	load(url)
	{

	}

	clear()
	{
		this.graphicData = null;
		this.removeAllChildren();
		this.clearFrameEvents();
		if (this.skeletalController != null)
		{
			this.skeletalController.destroy();
			this.skeletalController = null;
		}
		this.texture = PIXI.Texture.EMPTY;
		this.playing = false;
		
	}

	contains(drawable)
	{
		return this.children.indexOf(drawable) != -1;
	}

	getAllChildren()
	{
		return this.children;
	}

	removeAllChildren()
	{
		while(this.children.length > 0)
		{
			this.removeChild(this.children[0]);
		}
	}

	childrenByPartialName(name)
	{
		var matchingChildren = [];
		for (var i = 0; i < this.children.length; i++)
		{
			if (this.children[i].name && this.children[i].name.indexOf(name) != -1)
			{
				matchingChildren.push(this.children[i]);
			}
		}
		return matchingChildren;
	}

	childByName(name)
    {
        for (var child of this.children)
        {
            if (child.name == name) return child;
        }
        return null;
    }

	get hasParent() { return this.parent != null; }
	removeFromParent()
	{
		if (this.parent != null) this.parent.removeChild(this);
	}
	
	drawPolygon(graphics, points, color /* = 0xFFFFFF*/, alpha /* = 1 */)
	{
		if (points.length < 3) return;
		
		if (color === undefined) color = 0xFFFFFF;
		if (alpha === undefined) alpha = 1;
		
		graphics.lineStyle();
		graphics.beginFill(color, alpha);
		
		graphics.moveTo(points[0].x, points[0].y);
		for (var i = 1; i < points.length; i++)
		{
			graphics.lineTo(points[i].x, points[i].y);
		}
		
		graphics.endFill();
	}

	setAsBox(graphic, width, height, color, alpha)
	{
		//width = Math.floor(width);
		//height = Math.floor(height);

		try {
			if (color.getHex) color = color.getHex();
		    graphic.clear();
	        graphic.beginFill(color, alpha);
	        graphic.drawRect(0, 0, width, height);
	        graphic.hitArea = graphic.getLocalBounds();
    	} catch(e) {
    		Debug.Log("Set as box exception");
    	}
	}

	setAsBoxGradient(width, height, colorA, alphaA, colorB, alphaB, horizontal)
	{
		//width = Math.floor(width);
		//height = Math.floor(height);

		if (alphaA === undefined) alphaA = 1;
		if (colorB === undefined) colorB = colorA;
		if (alphaB === undefined) alphaB = alphaA;
		if (horizontal === undefined) horizontal = false;

		try {
			this.texture = this.gradientFill(width, height, colorA, alphaA, colorB, alphaB, horizontal);
    		this.texture.update();
    	} catch(e) {
    		Debug.Log("Set as box exception");
    	}
	}

	gradientFill(width, height, colorA, alphaA, colorB, alphaB, horizontal)
	{
		width = Math.floor(width);
		height = Math.floor(height);
		
		if (this.canvas == null) this.canvas = document.createElement('canvas');
		this.canvas.width  = width;
	    this.canvas.height = height;
	    var ctx = this.canvas.getContext('2d');
	    var gradient = ctx.createLinearGradient(0, 0, horizontal ? width : 0, horizontal ? 0 : height);
	    gradient.addColorStop(horizontal ? 0 : 1, colorA.getAlphaStyle(alphaA));
	    gradient.addColorStop(horizontal ? 1 : 0, colorB.getAlphaStyle(alphaB)); 
	    ctx.fillStyle = gradient;
		ctx.fillRect(0, 0, width, height);
	    return PIXI.Texture.from(this.canvas);
	}

	get graphics()
	{
		if (this._graphics == null)
		{
			this._graphics = new PIXI.Graphics();
			this.addChildAt(this._graphics, 0);
		}
		return this._graphics;
	}

	addLiveEffect(filter)
	{
		if (this.filters == null) this.filters = [];
		
		if (filter instanceof Engine.MultiFilter)
		{
			if (this.multiFilters == null) this.multiFilters = [];
			if (!this.multiFilters.contains(filter))
			{
				this.multiFilters.push(filter);
				var addedFilter = false;
				for (var f of filter.filters)
				{
					if (!this.filters.contains(f))
					{
						this.filters.push(f);
						addedFilter = true;
					}
				}
				//if (addedFilter) super.filters = this.filterList;
			}
			return;
		}

		if (filter instanceof PIXI.Filter)
		{
			if (!this.filters.contains(filter)) 
			{	
				this.filters.push(filter);
				//super.filters = this.filters;
			}
			return;
		} 
	}

	removeLiveEffect(filter)
	{
		if (filter instanceof Engine.MultiFilter && this.multiFilters != null && this.multiFilters.contains(filter))
		{
			this.multiFilters.remove(filter);
			var filterRemoved = false;
			for (var f of filter.filters)
			{
				if (this.filters.contains(f))
				{
					this.filters.remove(f);
					filterRemoved = true;
				}
			}
			//if (filterRemoved) super.filters = this.filterList;
			return;
		}

		if (filter instanceof PIXI.Filter && this.filters != null && this.filters.contains(filter))
		{
			this.filters.remove(filter);
			//super.filters = this.filters;
			return;
		}
	}
}