package org.cocos2d.nodes;

import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.HashMap;

import javax.microedition.khronos.opengles.GL10;

import org.cocos2d.base_nodes.CCNodeRGBA;
import org.cocos2d.config.ccConfig;
import org.cocos2d.config.ccMacros;
import org.cocos2d.opengl.CCTexture2D;
import org.cocos2d.opengl.CCTextureAtlas;
import org.cocos2d.include.CCProtocols.CCTextureProtocol;
import org.cocos2d.sprite_nodes.CCSpriteBatchNode;
import org.cocos2d.types.CGAffineTransform;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;
import org.cocos2d.types.CGSize;
import org.cocos2d.include.ccTypes.ccBlendFunc;
import org.cocos2d.include.ccTypes.ccColor3B;
import org.cocos2d.include.ccTypes.ccColor4B;
import org.cocos2d.types.ccV3F_C4B_T2F_Quad;
import org.cocos2d.utils.BufferProvider;
import org.cocos2d.utils.BufferUtils;

import android.graphics.Bitmap;

/**
 * @addtogroup sprite_nodes
 * @{
 */

/** 
 * CCSprite is a 2d image ( http://en.wikipedia.org/wiki/Sprite_(computer_graphics) )
 *
 * CCSprite can be created with an image, or with a sub-rectangle of an image.
 *
 * If the parent or any of its ancestors is a CCSpriteBatchNode then the following features/limitations are valid
 *    - Features when the parent is a CCBatchNode:
 *        - MUCH faster rendering, specially if the CCSpriteBatchNode has many children. All the children will be drawn in a single batch.
 *
 *    - Limitations
 *        - Camera is not supported yet (eg: CCOrbitCamera action doesn't work)
 *        - GridBase actions are not supported (eg: CCLens, CCRipple, CCTwirl)
 *        - The Alias/Antialias property belongs to CCSpriteBatchNode, so you can't individually set the aliased property.
 *        - The Blending function property belongs to CCSpriteBatchNode, so you can't individually set the blending function property.
 *        - Parallax scroller is not supported, but can be simulated with a "proxy" sprite.
 *
 *  If the parent is an standard CCNode, then CCSprite behaves like any other CCNode:
 *    - It supports blending functions
 *    - It supports aliasing / antialiasing
 *    - But the rendering will be slower: 1 draw per children.
 *
 * The default anchorPoint in CCSprite is (0.5, 0.5).
 */
public class CCSprite extends CCNodeRGBA implements CCTextureProtocol {

	/// CCSprite invalid index on the CCSpriteBatchNode
	public static final int CCSpriteIndexNotInitialized = 0xffffffff;

	/// @{
	/// @name Creators

	/**
	 * Creates an empty sprite without texture. You can call setTexture method subsequently.
	 *
	 * @return An empty sprite object that is marked as autoreleased.
	 */
	public static CCSprite create() {
		CCSprite pSprite = new CCSprite();
		if((pSprite != null) && pSprite.init()) {
			return pSprite;
		}
		pSprite = null;
		return null;
	}

	/**
	 * Creates a sprite with an image filename.
	 *
	 * After creation, the rect of sprite will be the size of the image,
	 * and the offset will be (0,0).
	 *
	 * @param   pszFileName The string which indicates a path to image file, e.g., "scene1/monster.png".
	 * @return  A valid sprite object that is marked as autoreleased.
	 */
	public static CCSprite create(String pszFileName) {
		CCSprite pobSprite = new CCSprite();
		if((pobSprite != null) && pobSprite.initWithFile(pszFileName)) {
			return pobSprite;
		}
		pobSprite = null;
		return null;
	}

	/**
	 * Creates a sprite with an image filename and a rect.
	 *
	 * @param   pszFileName The string wich indicates a path to image file, e.g., "scene1/monster.png"
	 * @param   rect        Only the contents inside rect of pszFileName's texture will be applied for this sprite.
	 * @return  A valid sprite object that is marked as autoreleased.
	 */
	public static CCSprite create(String pszFileName, CGRect rect) {
		CCSprite pobSprite = new CCSprite();
		if((pobSprite != null) && pobSprite.initWithFile(pszFileName, rect)) {
			return pobSprite;
		}
		pobSprite = null;
		return null;
	}

	/**
	 * Creates a sprite with an exsiting texture contained in a CCTexture2D object
	 * After creation, the rect will be the size of the texture, and the offset will be (0,0).
	 *
	 * @param   pTexture    A pointer to a CCTexture2D object.
	 * @return  A valid sprite object that is marked as autoreleased.
	 */
	public static CCSprite createWithTexture(CCTexture2D pTexture) {
		CCSprite pobSprite = new CCSprite();
		if((pobSprite != null) && pobSprite.initWithTexture(pTexture)) {
			return pobSprite;
		}
		pobSprite = null;
		return null;
	}

	/**
	 * Creates a sprite with a texture and a rect.
	 *
	 * After creation, the offset will be (0,0).
	 *
	 * @param   pTexture    A pointer to an existing CCTexture2D object.
	 *                      You can use a CCTexture2D object for many sprites.
	 * @param   rect        Only the contents inside the rect of this texture will be applied for this sprite.
	 * @return  A valid sprite object that is marked as autoreleased.
	 */
	public static CCSprite createWithTexture(CCTexture2D pTexture, CGRect rect) {
		CCSprite pobSprite = new CCSprite();
		if((pobSprite != null) && pobSprite.initWithTexture(pTexture, rect)) {
			return pobSprite;
		}
		pobSprite = null;
		return null;
	}

	/**
	 * Creates a sprite with an sprite frame.
	 *
	 * @param   pSpriteFrame    A sprite frame which involves a texture and a rect
	 * @return  A valid sprite object that is marked as autoreleased.
	 */
	public static CCSprite createWithSpriteFrame(CCSpriteFrame pSpriteFrame) {
		CCSprite pobSprite = new CCSprite();
		if((pSpriteFrame != null) && (pobSprite != null) && pobSprite.initWithSpriteFrame(pSpriteFrame)) {
			return pobSprite;
		}
		pobSprite = null;
		return null;
	}

	/**
	 * Creates a sprite with an sprite frame name.
	 *
	 * A CCSpriteFrame will be fetched from the CCSpriteFrameCache by pszSpriteFrameName param.
	 * If the CCSpriteFrame doesn't exist it will raise an exception.
	 *
	 * @param   pszSpriteFrameName A null terminated string which indicates the sprite frame name.
	 * @return  A valid sprite object that is marked as autoreleased.
	 */
	public static CCSprite createWithSpriteFrameName(String pszSpriteFrameName) {
		CCSpriteFrame pFrame = CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(pszSpriteFrameName);

		if(ccConfig.COCOS2D_DEBUG > 0) {
				assert pFrame != null : "Invalid spriteFrameName: " + pszSpriteFrameName;
		}

		return createWithSpriteFrame(pFrame);
	}

	/// @}  end of creators group

	/// @{
	/// @name Initializers

	/**
	 * Default constructor
	 */
	public CCSprite() {
		m_bShouldBeHidden = false;
		m_pobTexture = null;

		// TODO legacy -->

		init();
	}

	/**
	 * Initializes an empty sprite with nothing init.
	 */
	public boolean init() {

// TODO		return initWithTexture(null, CGRect.zero());

// TODO legacy -->

		texCoords	= BufferProvider.createFloatBuffer(4 * 2);
		vertexes	= BufferProvider.createFloatBuffer(4 * 3);
		colors		= BufferProvider.createFloatBuffer(4 * 4);

		dirty_ = false;
		recursiveDirty_ = false;

		// zwoptex default values
		offsetPosition_ = CGPoint.zero();
		unflippedOffsetPositionFromCenter_ = new CGPoint();
		rect_ = CGRect.make(0, 0, 1, 1);

		// by default use "Self Render".
		// if the sprite is added to an SpriteSheet,
		// then it will automatically switch to "SpriteSheet Render"
		useSelfRender();

		opacityModifyRGB_			= true;
		opacity_					= 255;
		color_.set(ccColor3B.ccWHITE);
		colorUnmodified_.set(ccColor3B.ccWHITE);

		// update texture (calls updateBlendFunc)
		setTexture(null);

		flipY_ = flipX_ = false;

		// lazy alloc
		animations_ = null;

		// default transform anchor: center
		m_obAnchorPoint.set(0.5f, 0.5f);


		honorParentTransform_ = CC_HONOR_PARENT_TRANSFORM_ALL;
		hasChildren_ = false;

		// Atlas: Color
		colors.put(1.0f).put(1.0f).put(1.0f).put(1.0f);
		colors.put(1.0f).put(1.0f).put(1.0f).put(1.0f);
		colors.put(1.0f).put(1.0f).put(1.0f).put(1.0f);
		colors.put(1.0f).put(1.0f).put(1.0f).put(1.0f);
		colors.position(0);

		// Atlas: Vertex
		// updated in "useSelfRender"
		// Atlas: TexCoords
		setTextureRect(0, 0, 0, 0, rectRotated_);

		return true;

	}

	/**
	 * Initializes a sprite with a texture.
	 *
	 * After initialization, the rect used will be the size of the texture, and the offset will be (0,0).
	 *
	 * @param   pTexture    A pointer to an existing CCTexture2D object.
	 *                      You can use a CCTexture2D object for many sprites.
	 * @return  true if the sprite is initialized properly, false otherwise.
	 */
	public boolean initWithTexture(CCTexture2D pTexture) {
		assert pTexture != null : "Invalid texture for sprite";

		CGRect rect = CGRect.zero();
		rect.size = pTexture.getContentSize();

		return initWithTexture(pTexture, rect);
	}

	/**
	 * Initializes a sprite with a texture and a rect.
	 *
	 * After initialization, the offset will be (0,0).
	 *
	 * @param   pTexture    A pointer to an exisiting CCTexture2D object.
	 *                      You can use a CCTexture2D object for many sprites.
	 * @param   rect        Only the contents inside rect of this texture will be applied for this sprite.
	 * @return  true if the sprite is initialized properly, false otherwise.
	 */
	public boolean initWithTexture(CCTexture2D pTexture, CGRect rect) {
		return initWithTexture(pTexture, rect, false);
	}

	/**
	 * Initializes a sprite with a texture and a rect in points, optionally rotated.
	 *
	 * After initialization, the offset will be (0,0).
	 * @note    This is the designated initializer.
	 *
	 * @param   pTexture    A CCTexture2D object whose texture will be applied to this sprite.
	 * @param   rect        A rectangle assigned the contents of texture.
	 * @param   rotated     Whether or not the texture rectangle is rotated.
	 * @return  true if the sprite is initialized properly, false otherwise.
	 */
	public boolean initWithTexture(CCTexture2D pTexture, CGRect rect, boolean rotated) {
/* TODO
		if(super.init()) {
			m_pobBatchNode = null;
			// shader program
			setShaderProgram(CCShaderCache.sharedShaderCache().programForKey(kCCShader_PositionTextureColor));

			m_bRecursiveDirty = false;
			setDirty(false);

			m_bOpacityModifyRGB = true;

			m_sBlendFunc = new ccBlendFunc(
					ccConfig.CC_BLEND_SRC, ccConfig.CC_BLEND_DST);

			m_bFlipX = m_bFlipY = false;

			// default transform anchor: center
			setAnchorPoint(CGPoint.ccp(0.5f, 0.5f));

			// zwoptex default values
			m_obOffsetPosition = CGPoint.zero();

			m_bHasChildren = false;

			// clean the Quad
			m_sQuad = new ccV3F_C4B_T2F_Quad();
			m_sQuad.bl.vertexes		= BufferProvider.createFloatBuffer(4 * 3);
			m_sQuad.bl.colors		= BufferProvider.createFloatBuffer(4 * 4);
			m_sQuad.bl.texCoords	= BufferProvider.createFloatBuffer(4 * 2);
			m_sQuad.br.vertexes		= BufferProvider.createFloatBuffer(4 * 3);
			m_sQuad.br.colors		= BufferProvider.createFloatBuffer(4 * 4);
			m_sQuad.br.texCoords	= BufferProvider.createFloatBuffer(4 * 2);
			m_sQuad.tl.vertexes		= BufferProvider.createFloatBuffer(4 * 3);
			m_sQuad.tl.colors		= BufferProvider.createFloatBuffer(4 * 4);
			m_sQuad.tl.texCoords	= BufferProvider.createFloatBuffer(4 * 2);
			m_sQuad.tr.vertexes		= BufferProvider.createFloatBuffer(4 * 3);
			m_sQuad.tr.colors		= BufferProvider.createFloatBuffer(4 * 4);
			m_sQuad.tr.texCoords	= BufferProvider.createFloatBuffer(4 * 2);


			// Atlas: Color
			m_sQuad.bl.colors = ccColor4B.ccc4(255, 255, 255, 255);
			m_sQuad.br.colors = ccColor4B.ccc4(255, 255, 255, 255);
			m_sQuad.tl.colors = ccColor4B.ccc4(255, 255, 255, 255);
			m_sQuad.tr.colors = ccColor4B.ccc4(255, 255, 255, 255);

			// update texture (calls updateBlendFunc)
			setTexture(pTexture);
			setTextureRect(rect, rotated, rect.size);

			// by default use "Self Render".
			// if the sprite is added to a batchnode, then it will automatically switch to "batchnode Render"
			setBatchNode(null);

			return true;
		} else {
			return false;
		}
*/
		// TODO legacy -->

		if(init()) {
			setTexture(pTexture);
			setTextureRect(rect, rotated, rect.size);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Initializes a sprite with an SpriteFrame. The texture and rect in SpriteFrame will be applied on this sprite
	 *
	 * @param   pSpriteFrame  A CCSpriteFrame object. It should includes a valid texture and a rect
	 * @return  true if the sprite is initialized properly, false otherwise.
	 */
	public boolean initWithSpriteFrame(CCSpriteFrame pSpriteFrame) {
		// TODO
		return false;
	}

	/**
	 * Initializes a sprite with an sprite frame name.
	 *
	 * A CCSpriteFrame will be fetched from the CCSpriteFrameCache by name.
	 * If the CCSpriteFrame doesn't exist it will raise an exception.
	 *
	 * @param   pszSpriteFrameName  A key string that can fected a volid CCSpriteFrame from CCSpriteFrameCache
	 * @return  true if the sprite is initialized properly, false otherwise.
	 */
	public boolean initWithSpriteFrameName(String pszSpriteFrameName) {
		assert pszSpriteFrameName != null : "";

		CCSpriteFrame pFrame = CCSpriteFrameCache.sharedSpriteFrameCache().spriteFrameByName(pszSpriteFrameName);
		return initWithSpriteFrame(pFrame);
	}

	/**
	 * Initializes a sprite with an image filename.
	 *
	 * This method will find pszFilename from local file system, load its content to CCTexture2D,
	 * then use CCTexture2D to create a sprite.
	 * After initialization, the rect used will be the size of the image. The offset will be (0,0).
	 *
	 * @param   pszFilename The path to an image file in local file system
	 * @return  true if the sprite is initialized properly, false otherwise.
	 */
	public boolean initWithFile(String pszFilename) {
		assert pszFilename != null : "Invalid filename for sprite";

		CCTexture2D pTexture = CCTextureCache.sharedTextureCache().addImage(pszFilename);
		if(pTexture != null) {
			CGRect rect = CGRect.zero();
			rect.size = pTexture.getContentSize();
			return initWithTexture(pTexture, rect);
		}

		return false;
	}

	/**
	 * Initializes a sprite with an image filename, and a rect.
	 *
	 * This method will find pszFilename from local file system, load its content to CCTexture2D,
	 * then use CCTexture2D to create a sprite.
	 * After initialization, the offset will be (0,0).
	 *
	 * @param   pszFilename The path to an image file in local file system.
	 * @param   rect        The rectangle assigned the content area from texture.
	 * @return  true if the sprite is initialized properly, false otherwise.
	 */
	public boolean initWithFile(String pszFilename, CGRect rect) {
		assert pszFilename != null : "";

		CCTexture2D pTexture = CCTextureCache.sharedTextureCache().addImage(pszFilename);
		if(pTexture != null) {
			return initWithTexture(pTexture, rect);
		}

		return false;
	}

	/// @} end of initializers

	/// @{
	/// @name Functions inherited from CCTextureProtocol

	public void setTexture(CCTexture2D texture) {

		// TODO legacy -->

		assert ! usesSpriteSheet_: "CCSprite: setTexture doesn't work when the sprite is rendered using a CCSpriteSheet";

		// accept texture==nil as argument
		assert (texture==null || texture instanceof CCTexture2D) 
			: "setTexture expects a CCTexture2D. Invalid argument";
		texture_ = texture;
		updateBlendFunc();
	}

	public CCTexture2D getTexture() {

		// TODO legacy -->

		return texture_;
	}

	public void setBlendFunc(ccBlendFunc blendFunc) {

		// TODO legacy -->

		blendFunc_ = blendFunc;
	}

	public ccBlendFunc getBlendFunc() {

		// TODO legacy -->

		return blendFunc_;
	}

	/// @}

	/// @{
	/// @name Functions inherited from CCNode

	@Override
	public void setScaleX(float sx) {

		// TODO legacy -->

		super.setScaleX(sx);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setScaleY(float sy) {

		// TODO legacy -->

		super.setScaleY(sy);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setPosition(CGPoint pos) {

		// TODO legacy -->

		super.setPosition(pos);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setRotation(float rot) {

		// TODO legacy -->

		super.setRotation(rot);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setRotationX(float fRotationX) {

		// TODO legacy -->

		super.setRotationX(fRotationX);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setRotationY(float fRotationY) {

		// TODO legacy -->

		super.setRotationY(fRotationY);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setSkewX(float sx) {
		// TODO;
	}

	@Override
	public void setSkewY(float sy) {
		// TODO;
	}

	@Override
	public void removeChild(CCNode node, boolean doCleanup) {

		// TODO legacy -->

		if( usesSpriteSheet_ ) {
			CCSprite sprite = (CCSprite) node;
			spriteSheet_.removeSpriteFromAtlas(sprite);
		}

		super.removeChild(node, doCleanup);

		hasChildren_ = (m_pChildren.size() > 0);
	}

	@Override
	public void removeAllChildrenWithCleanup(boolean doCleanup) {

		// TODO legacy -->

		if( usesSpriteSheet_ ) {
			for( CCNode child : m_pChildren ) {
				CCSprite sprite = (CCSprite)child;
				spriteSheet_.removeSpriteFromAtlas(sprite);
			}
		}

		super.removeAllChildrenWithCleanup(doCleanup);
		hasChildren_ = false;
	}

	@Override
	public void reorderChild(CCNode child, int z) {

		// TODO legacy -->

		// assert child != null: "Child must be non-nil";
		// assert children_.has(child): "Child doesn't belong to Sprite";

		if( z == child.getZOrder() )
			return;

		if( usesSpriteSheet_ ) {
			// XXX: Instead of removing/adding, it is more efficient to reorder manually
			removeChild(child, false);
			addChild(child, z);
		} else {
			super.reorderChild(child, z);
		}
	}

	@Override
	public void addChild(CCNode child) {
		// TODO
	}

	@Override
	public void addChild(CCNode child, int z) {
		// TODO
	}

	@Override
	public void addChild(CCNode child, int z, int aTag) {

		// TODO legacy -->

		super.addChild(child, z, aTag);

		if(child instanceof CCSprite && usesSpriteSheet_) {
			CCSprite sprite = (CCSprite)child;
			int index = spriteSheet_.atlasIndex(sprite, z);
			spriteSheet_.insertChild(sprite, index);
		}

		hasChildren_ = true;
	}

	@Override
	public void sortAllChildren() {
		// TODO
		;
	}

	@Override
	public void setScale(float s) {

		// TODO legacy -->

		super.setScale(s);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setVertexZ(float z) {

		// TODO legacy -->

		super.setVertexZ(z);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void setAnchorPoint(CGPoint anchor) {

		// TODO legacy -->

		super.setAnchorPoint(anchor);
		SET_DIRTY_RECURSIVELY();
	}

	@Override
	public void ignoreAnchorPointForPosition(boolean value) {
		// TODO;
	}

	@Override
	public void setVisible(boolean v) {

		// TODO legacy -->

		if( v != m_bVisible ) {
			super.setVisible(v);
			if( usesSpriteSheet_ && ! recursiveDirty_ ) {
				dirty_ = recursiveDirty_ = true;
				if(m_pChildren != null)
					for (CCNode child:m_pChildren) {
						child.setVisible(v);
					}
			}
		}
	}

	@Override
	public void draw(GL10 gl) {	

		// TODO legacy -->

		assert !usesSpriteSheet_:"If CCSprite is being rendered by CCSpriteSheet, CCSprite#draw SHOULD NOT be called";

		// Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
		// Needed states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
		// Unneeded states: -

		boolean newBlend = false;
		if( blendFunc_.src != ccConfig.CC_BLEND_SRC || blendFunc_.dst != ccConfig.CC_BLEND_DST ) {
			newBlend = true;
			gl.glBlendFunc( blendFunc_.src, blendFunc_.dst );
		}

//		((EGL10) gl).eglWaitNative(EGL10.EGL_NATIVE_RENDERABLE, null);
//		// bug fix in case texture name = 0
//		texture_.checkName();
		// #define kQuadSize sizeof(quad_.bl)
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texture_.name());

		// int offset = (int)&quad_;

		// vertex
		// int diff = offsetof( ccV3F_C4B_T2F, vertices);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0 , vertexes);

		// color
		// diff = offsetof( ccV3F_C4B_T2F, colors);
		gl.glColorPointer(4, GL10.GL_FLOAT, 0, colors);

		// tex coords
		// diff = offsetof( ccV3F_C4B_T2F, texCoords);
		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoords);

		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);

		if( newBlend )
			gl.glBlendFunc(ccConfig.CC_BLEND_SRC, ccConfig.CC_BLEND_DST);

		/*
        if (ccConfig.CC_SPRITE_DEBUG_DRAW) {
            CGSize s = this.contentSize();
            CGPoint vertices[]= new CGPoint [] {
                CGPoint.ccp(0,0),   CGPoint.ccp(s.width,0),
                CGPoint.ccp(s.width,s.height),  CGPoint.ccp(0,s.height)
            };
            ccDrawingPrimitives.ccDrawPoly(vertices, 4, true);
        } // CC_TEXTURENODE_DEBUG_DRAW
		 */
	}

	/// @}

	/// @{
	/// @name Functions inherited from CCNodeRGBA

	@Override
	public void setColor(final ccColor3B color3) {

		// TODO legacy -->

		color_.set(color3);
		colorUnmodified_.set(color3);

		if( opacityModifyRGB_ ){
			color_.r = color3.r * opacity_/255;
			color_.g = color3.g * opacity_/255;
			color_.b = color3.b * opacity_/255;
		}

		updateColor();
	}

	@Override
	public void updateDisplayedColor(final ccColor3B color) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setOpacity(int anOpacity) {

		// TODO legacy -->

		opacity_			= anOpacity;

		// special opacity for premultiplied textures
		if( opacityModifyRGB_ )
			setColor(colorUnmodified_);
		updateColor();
	}

	@Override
	public void setOpacityModifyRGB(boolean modify) {

		// TODO legacy -->

		if (opacityModifyRGB_ != modify) {
			ccColor3B oldColor	= this.color_;
			opacityModifyRGB_	= modify;
			setColor(oldColor);
		}
	}

	@Override
	public boolean isOpacityModifyRGB() {

		// TODO legacy -->

		return opacityModifyRGB_;
	}

	@Override
	public void updateDisplayedOpacity(int opacity) {
		// TODO Auto-generated method stub
		
	}

	/// @}

	/// @{
	/// @name BatchNode methods

	/**
	 * Updates the quad according the rotation, position, scale values. 
	 */
	public void updateTransform() {

		// TODO legacy -->

		tmpMatrix.setToIdentity();

		// Optimization: if it is not visible, then do nothing
		if( ! m_bVisible ) {
			Arrays.fill(tmpV, 0);
			textureAtlas_.putVertex(textureAtlas_.getVertexBuffer(), tmpV, atlasIndex);
			dirty_ = recursiveDirty_ = false;
			return;
		}

		// Optimization: If parent is spritesheet, or parent is nil
		// build Affine transform manually
		if( m_pParent==null || m_pParent == spriteSheet_ ) {
			float radians = -ccMacros.CC_DEGREES_TO_RADIANS(m_fRotationX);
			float c = (float)Math.cos(radians);
			float s = (float)Math.sin(radians);

			tmpMatrix.set(c * m_fScaleX,  s * m_fScaleX,
					-s * m_fScaleY, c * m_fScaleY,
					m_obPosition.x, m_obPosition.y);

			tmpMatrix.translate(-m_obAnchorPointInPoints.x, -m_obAnchorPointInPoints.y);
		} 

		// else do affine transformation according to the HonorParentTransform
		else if( m_pParent != spriteSheet_ ) {

			int prevHonor = CC_HONOR_PARENT_TRANSFORM_ALL;

			for (CCNode p = this; p != null && p != spriteSheet_; p = p.getParent()) {
				CCSprite sprP = (CCSprite)p;

				tmpNewMatrix.setToIdentity();
				// 2nd: Translate, Rotate, Scale
				if( (prevHonor & CC_HONOR_PARENT_TRANSFORM_TRANSLATE) !=0 )
					tmpNewMatrix.translate(sprP.m_obPosition.x, sprP.m_obPosition.y);
				if( (prevHonor & CC_HONOR_PARENT_TRANSFORM_ROTATE) != 0 )
					tmpNewMatrix.rotate(-ccMacros.CC_DEGREES_TO_RADIANS(sprP.m_fRotationX));
				if( (prevHonor & CC_HONOR_PARENT_TRANSFORM_SCALE) != 0 ) {
					tmpNewMatrix.scale(sprP.m_fScaleX, sprP.m_fScaleY);
				}

				// 3rd: Translate anchor point
				tmpNewMatrix.translate(-sprP.m_obAnchorPointInPoints.x, -sprP.m_obAnchorPointInPoints.y);
				// 4th: Matrix multiplication
				tmpMatrix.multiply(tmpNewMatrix);
				prevHonor = sprP.honorParentTransform_;
			}
		}

		//
		// calculate the Quad based on the Affine Matrix
		//

		CGSize size = rect_.size;

		float x1 = offsetPosition_.x;
		float y1 = offsetPosition_.y;

		float x2 = x1 + size.width;
		float y2 = y1 + size.height;
		float x = (float) tmpMatrix.m02;
		float y = (float) tmpMatrix.m12;

		float cr = (float) tmpMatrix.m00;
		float sr = (float) tmpMatrix.m10;
		float cr2 = (float) tmpMatrix.m11;
		float sr2 = (float) -tmpMatrix.m01;

		float ax = x1 * cr - y1 * sr2 + x;
		float ay = x1 * sr + y1 * cr2 + y;

		float bx = x2 * cr - y1 * sr2 + x;
		float by = x2 * sr + y1 * cr2 + y;

		float cx = x2 * cr - y2 * sr2 + x;
		float cy = x2 * sr + y2 * cr2 + y;

		float dx = x1 * cr - y2 * sr2 + x;
		float dy = x1 * sr + y2 * cr2 + y;

		tmpV[0] = dx; tmpV[1] = dy; tmpV[2] = m_fVertexZ;
		tmpV[3] = ax; tmpV[4] = ay; tmpV[5] = m_fVertexZ;
		tmpV[6] = cx; tmpV[7] = cy; tmpV[8] = m_fVertexZ;
		tmpV[9] = bx; tmpV[10] = by; tmpV[11] = m_fVertexZ;

		textureAtlas_.putVertex(textureAtlas_.getVertexBuffer(), tmpV, atlasIndex);
		dirty_ = recursiveDirty_ = false;
	}

	/**
	 * Returns the batch node object if this sprite is rendered by CCSpriteBatchNode
	 *
	 * @return The CCSpriteBatchNode object if this sprite is rendered by CCSpriteBatchNode,
	 *         NULL if the sprite isn't used batch node.
	 */
	public CCSpriteBatchNode getBatchNode() {
		// TODO
		return null;
	}

	/**
	 * Sets the batch node to sprite
	 * @warning This method is not recommended for game developers. Sample code for using batch node
	 * @code
	 * CCSpriteBatchNode *batch = CCSpriteBatchNode::create("Images/grossini_dance_atlas.png", 15);
	 * CCSprite *sprite = CCSprite::createWithTexture(batch->getTexture(), CCRectMake(0, 0, 57, 57));
	 * batch->addChild(sprite);
	 * layer->addChild(batch);
	 * @endcode
	 */
	public void setBatchNode(CCSpriteBatchNode pobSpriteBatchNode) {
		// TODO
		;
	}

	/// @} end of BatchNode methods

	/// @{
	/// @name Texture methods

	/**
	 * Updates the texture rect of the CCSprite in points.
	 * It will call setTextureRect:rotated:untrimmedSize with rotated = NO, and utrimmedSize = rect.size.
	 */
	public void setTextureRect(CGRect rect) {
		setTextureRect(rect, false, rect.size);

/* TODO legacy --> 
		setTextureRect(rect, rectRotated_);
*/
	}

	/**
	 * Sets the texture rect, rectRotated and untrimmed size of the CCSprite in points.
	 * It will update the texture coordinates and the vertex rectangle.
	 */
	public void setTextureRect(CGRect rect, boolean rotated, CGSize untrimmedSize) {
/* TODO
		m_bRectRotated = rotated;

		setContentSize(untrimmedSize);
		setVertexRect(rect);
		setTextureCoords(rect);

		CGPoint relativeOffset = m_obUnflippedOffsetPositionFromCenter;

		// issue #732
		if(m_bFlipX) {
			relativeOffset.x = -relativeOffset.x;
		}
		if (m_bFlipY) {
			relativeOffset.y = -relativeOffset.y;
		}

		m_obOffsetPosition.x = relativeOffset.x + (m_obContentSize.width - m_obRect.size.width) / 2;
		m_obOffsetPosition.y = relativeOffset.y + (m_obContentSize.height - m_obRect.size.height) / 2;

		// rendering using batch node
		if(m_pobBatchNode != null) {
			// update dirty_, don't update recursiveDirty_
			setDirty(true);
		} else {
			// self rendering

			// Atlas: Vertex
			float x1 = 0 + m_obOffsetPosition.x;
			float y1 = 0 + m_obOffsetPosition.y;
			float x2 = x1 + m_obRect.size.width;
			float y2 = y1 + m_obRect.size.height;

			// Don't update Z.
			m_sQuad.bl.vertices = vertex3(x1, y1, 0);
			m_sQuad.br.vertices = vertex3(x2, y1, 0);
			m_sQuad.tl.vertices = vertex3(x1, y2, 0);
			m_sQuad.tr.vertices = vertex3(x2, y2, 0);
		}
*/
		// TODO legacy -->
		setTextureRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, untrimmedSize.width, untrimmedSize.height, rotated);
	}

	/**
	 * Sets the vertex rect.
	 * It will be called internally by setTextureRect.
	 * Useful if you want to create 2x images from SD images in Retina Display.
	 * Do not call it manually. Use setTextureRect instead.
	 */
	public void setVertexRect(CGRect rect) {
		m_obRect.set(rect);
	}

	/// @} end of texture methods

	/// @{
	/// @name Frames methods

	/**
	 * Sets a new display frame to the CCSprite.
	 */
	public void setDisplayFrame(CCSpriteFrame frame) {

		// TODO legacy -->

		// BRIGOSX 11AUG2012 - Just make sure you have a valid frame -
		if(frame != null) {
			unflippedOffsetPositionFromCenter_.set(frame.offset_);

			CCTexture2D newTexture = frame.getTexture();
			// update texture before updating texture rect
			if ( texture_ == null || newTexture.name() != texture_.name())
				setTexture(newTexture);

			// update rect
			setTextureRect(frame.rect_, frame.originalSize_, frame.rotated_);
		}
	}

	/**
	 * Returns whether or not a CCSpriteFrame is being displayed
	 */
	public boolean isFrameDisplayed(CCSpriteFrame frame) {

		// TODO legacy -->

		// BRIGOSX 10AUG2012 - Make sure you have a valid frame
		if(frame == null) return false;

		CGRect r = frame.rect_;
		CGPoint p = frame.offset_;
		return (CGRect.equalToRect(r, rect_) &&
				frame.getTexture().name() == this.getTexture().name() &&
				CGPoint.equalToPoint(p, offsetPosition_));
	}

	/**
	 * Returns the current displayed frame.
	 */
	public CCSpriteFrame displayedFrame() {

		// TODO legacy -->

		return CCSpriteFrame.frame(getTexture(), rect_, CGPoint.zero());
	}

	/// @} End of frames methods

	/// @{
	/// @name Animation methods

	/**
	 * Changes the display frame with animation name and index.
	 * The animation name will be get from the CCAnimationCache
	 */
	public void setDisplayFrameWithAnimationName(String animationName, int frameIndex) {
		// TODO
		;
	}

	/// @}

	/// @{
	/// @name Sprite Properties' setter/getters

	/** 
	 * Whether or not the Sprite needs to be updated in the Atlas.
	 *
	 * @return true if the sprite needs to be updated in the Atlas, false otherwise.
	 */
	public boolean isDirty() {
//TODO		return m_bDirty;
		return false;
	}

	/** 
	 * Makes the Sprite to be updated in the Atlas.
	 */
	public void setDirty(boolean bDirty) {
//TODO		m_bDirty = bDirty;
	}

	/**
	 * Returns the quad (tex coords, vertex coords and color) information. 
	 */
	public ccV3F_C4B_T2F_Quad getQuad() {
// TODO		return m_sQuad;
		return null;
	}

	/** 
	 * Returns whether or not the texture rectangle is rotated.
	 */
	public boolean isTextureRectRotated() {
// TODO		return m_bRectRotated;
		return false;
	}

	/** 
	 * Returns the index used on the TextureAtlas. 
	 */
	public int getAtlasIndex() {
// TODO		return m_uAtlasIndex;
		return 0;
		}

	/** 
	 * Sets the index used on the TextureAtlas.
	 * @warning Don't modify this value unless you know what you are doing
	 */
	public void setAtlasIndex(int uAtlasIndex) {
// TODO		m_uAtlasIndex = uAtlasIndex;
		}

	/** 
	 * Returns the rect of the CCSprite in points 
	 */
	public CGRect getTextureRect() {

		// TODO legacy -->

		return rect_;
	}

	/**
	 * Gets the weak reference of the CCTextureAtlas when the sprite is rendered using via CCSpriteBatchNode
	 */
	public CCTextureAtlas getTextureAtlas() {
// TODO		return m_pobTextureAtlas;
		return null;
	}

	/**
	 * Sets the weak reference of the CCTextureAtlas when the sprite is rendered using via CCSpriteBatchNode
	 */
	public void setTextureAtlas(CCTextureAtlas pobTextureAtlas) {
// TODO		m_pobTextureAtlas = pobTextureAtlas;
	}

	/** 
	 * Gets the offset position of the sprite. Calculated automatically by editors like Zwoptex.
	 */
	public CGPoint getOffsetPosition() {
// TODO		return m_obOffsetPosition;
		return null;
	}

	/** 
	 * Returns the flag which indicates whether the sprite is flipped horizontally or not.
	 *
	 * It only flips the texture of the sprite, and not the texture of the sprite's children.
	 * Also, flipping the texture doesn't alter the anchorPoint.
	 * If you want to flip the anchorPoint too, and/or to flip the children too use:
	 * sprite->setScaleX(sprite->getScaleX() * -1);
	 *
	 * @return true if the sprite is flipped horizaontally, false otherwise.
	 */
	public boolean isFlipX() {
		//TODO
		return false;
	}

	/**
	 * Sets whether the sprite should be flipped horizontally or not.
	 *
	 * @param bFlipX true if the sprite should be flipped horizaontally, false otherwise.
	 */
	public void setFlipX(boolean b) {

		// TODO legacy -->

		if( flipX_ != b ) {
			flipX_ = b;
			setTextureRect(rect_);
		}
	}

	/** 
	 * Return the flag which indicates whether the sprite is flipped vertically or not.
	 * 
	 * It only flips the texture of the sprite, and not the texture of the sprite's children.
	 * Also, flipping the texture doesn't alter the anchorPoint.
	 * If you want to flip the anchorPoint too, and/or to flip the children too use:
	 * sprite->setScaleY(sprite->getScaleY() * -1);
	 * 
	 * @return true if the sprite is flipped vertically, flase otherwise.
	 */
	public boolean isFlipY() {
		//TODO
		return false;
	}

	/**
	 * Sets whether the sprite should be flipped vertically or not.
	 *
	 * @param bFlipY true if the sprite should be flipped vertically, flase otherwise.
	 */
	public void setFlipY(boolean b) {

		// TODO legacy -->

		if( flipY_ != b ) {
			flipY_ = b;	
			setTextureRect(rect_);
		}
	}

	/// @} End of Sprite properties getter/setters

	protected void updateColor() {

		// TODO legacy -->

		float tmpR = color_.r/255.f;
		float tmpG = color_.g/255.f;
		float tmpB = color_.b/255.f;
		float tmpA = opacity_/255.f;

		colors.put(tmpR).put(tmpG).put(tmpB).put(tmpA)
			.put(tmpR).put(tmpG).put(tmpB).put(tmpA)
			.put(tmpR).put(tmpG).put(tmpB).put(tmpA)
			.put(tmpR).put(tmpG).put(tmpB).put(tmpA);
		colors.position(0);

		// renders using Sprite Manager
		if( usesSpriteSheet_ ) {
			if( atlasIndex != CCSpriteIndexNotInitialized) {
				tmpColor4B.r = color_.r; tmpColor4B.g = color_.g; tmpColor4B.b = color_.b; tmpColor4B.a = opacity_;
				textureAtlas_.updateColor(tmpColors, atlasIndex);
			} else {
				// no need to set it recursively
				// update dirty_, don't update recursiveDirty_
				dirty_ = true;
			}
		}
		// self render
		// do nothing
	}

	protected void setTextureCoords(CGRect rect) {
		//TODO
		;
	}

	private void updateBlendFunc() {

		// TODO legacy -->

		assert ! usesSpriteSheet_ :
			"CCSprite: updateBlendFunc doesn't work when the sprite is rendered using a CCSpriteSheet";

		// it's possible to have an untextured sprite
		if( texture_==null || !texture_.hasPremultipliedAlpha()) {
			blendFunc_.src = GL10.GL_SRC_ALPHA;
			blendFunc_.dst = GL10.GL_ONE_MINUS_SRC_ALPHA;
			setOpacityModifyRGB(false);
		} else {
			blendFunc_.src = ccConfig.CC_BLEND_SRC;
			blendFunc_.dst = ccConfig.CC_BLEND_DST;
			setOpacityModifyRGB(true);
		}
	}

	public void setReorderChildDirtyRecursively() {
		// TODO
		;
	}

	public void setDirtyRecursively(boolean b) {

		// TODO legacy -->

		dirty_ = recursiveDirty_ = b;
		// recursively set dirty
		if( hasChildren_ ) {
			for (CCNode child: m_pChildren) {
				CCSprite sprite = (CCSprite)child;
				sprite.setDirtyRecursively(true);
			}
		}
	}

	//
	// Data used when the sprite is rendered using a CCSpriteSheet
	//
	protected CCTextureAtlas	m_pobTextureAtlas;			/// CCSpriteBatchNode texture atlas (weak reference)
	protected int				m_uAtlasIndex;				/// Absolute (real) Index on the SpriteSheet
	protected CCSpriteBatchNode	m_pobBatchNode;				/// Used batch node (weak reference)

	protected boolean			m_bDirty;					/// Whether the sprite needs to be updated
	protected boolean			m_bRecursiveDirty;			/// Whether all of the sprite's children needs to be updated
	protected boolean			m_bHasChildren;				/// Whether the sprite contains children
	protected boolean			m_bShouldBeHidden;			/// should not be drawn because one of the ancestors is not visible
	protected CGAffineTransform	m_transformToBatch;

	//
	// Data used when the sprite is self-rendered
	//
	protected ccBlendFunc		m_sBlendFunc;				/// It's required for CCTextureProtocol inheritance
	protected CCTexture2D		m_pobTexture;				/// CCTexture2D object that is used to render the sprite

	//
	// Shared data
	//

	// texture
	protected CGRect			m_obRect = new CGRect();	/// Retangle of CCTexture2D
	protected boolean			m_bRectRotated;				/// Whether the texture is rotated

	// Offset Position (used by Zwoptex)
	protected CGPoint			m_obOffsetPosition = new CGPoint();
	protected CGPoint			m_obUnflippedOffsetPositionFromCenter = new CGPoint();

	// vertex coords, texture coords and color info
	protected ccV3F_C4B_T2F_Quad m_sQuad = new ccV3F_C4B_T2F_Quad();

	// opacity and RGB protocol
	protected boolean			m_bOpacityModifyRGB;

	// image is flipped
	protected boolean			m_bFlipX;					/// Whether the sprite is flipped horizaontally or not.
	protected boolean			m_bFlipY;					/// Whether the sprite is flipped vertically or not.



	// TODO legacy -->

	// XXX: Optmization
	class TransformValues {
		CGPoint pos = new CGPoint();		// position x and y
		CGPoint	scale = new CGPoint();		// scale x and y
		float	rotation;
		CGPoint ap = new CGPoint();			// anchor point in pixels
	}

	/** 
	 * Whether or not an CCSprite will rotate, scale or translate with it's parent.
	 * Useful in health bars, when you want that the health bar translates with it's parent
	 * but you don't want it to rotate with its parent.
	 * @since v0.99.0
	 */
	//! Translate with it's parent
	public static final int CC_HONOR_PARENT_TRANSFORM_TRANSLATE =  1 << 0;
	//! Rotate with it's parent
	public static final int CC_HONOR_PARENT_TRANSFORM_ROTATE	=  1 << 1;
	//! Scale with it's parent
	public static final int CC_HONOR_PARENT_TRANSFORM_SCALE		=  1 << 2;

	//! All possible transformation enabled. Default value.
	public static final int CC_HONOR_PARENT_TRANSFORM_ALL		=  CC_HONOR_PARENT_TRANSFORM_TRANSLATE
			| CC_HONOR_PARENT_TRANSFORM_ROTATE | CC_HONOR_PARENT_TRANSFORM_SCALE;

	// Animations that belong to the sprite
	private HashMap<String, CCAnimation> animations_;

	// image is flipped
	/** whether or not the sprite is flipped vertically.
	 * It only flips the texture of the sprite, and not the texture of the sprite's children.
	 * Also, flipping the texture doesn't alter the anchorPoint.
	 * If you want to flip the anchorPoint too, and/or to flip the children too use: 
	 * sprite.scaleY *= -1;
	 */
	public boolean flipY_;

	/** whether or not the sprite is flipped horizontally. 
	 * It only flips the texture of the sprite, and not the texture of the sprite's children.
	 * Also, flipping the texture doesn't alter the anchorPoint.
	 * If you want to flip the anchorPoint too, and/or to flip the children too use:
	 * sprite.scaleX *= -1;
	 */
	public boolean flipX_;

	// opacity and RGB protocol
	/** opacity: conforms to CCRGBAProtocol protocol */
	int		opacity_;

	public int getOpacity() {
		return opacity_;
	}

	/** RGB colors: conforms to CCRGBAProtocol protocol */
	ccColor3B	color_ = new ccColor3B();
	ccColor3B	colorUnmodified_ = new ccColor3B();
	boolean		opacityModifyRGB_;

	public ccColor3B getColor() {
		if(opacityModifyRGB_){
			return new ccColor3B(colorUnmodified_);
		}
		return new ccColor3B(color_);
	}

	//
	// Data used when the sprite is self-rendered
	//
	protected CCTexture2D			texture_;				// Texture used to render the sprite

	/** conforms to CCTextureProtocol protocol */
	protected ccBlendFunc blendFunc_ = new ccBlendFunc(ccConfig.CC_BLEND_SRC, ccConfig.CC_BLEND_DST);

	// texture pixels
	CGRect rect_ = CGRect.zero();
	Boolean rectRotated_ = false;

	/** offset position of the sprite. Calculated automatically by editors like Zwoptex.
	 @since v0.99.0
	 */
	CGPoint	offsetPosition_;	// absolute
	CGPoint unflippedOffsetPositionFromCenter_;

	//
	// Data used when the sprite is rendered using a CCSpriteSheet
	//
	/** weak reference of the CCTextureAtlas used when the sprite is rendered using a CCSpriteSheet */
	CCTextureAtlas			textureAtlas_;

	/** The index used on the TextureATlas.
	 * Don't modify this value unless you know what you are doing */
	public		int			atlasIndex;	// Absolute (real) Index on the SpriteSheet

	/** weak reference to the CCSpriteSheet that renders the CCSprite */
	CCSpriteSheet			spriteSheet_;

	// whether or not to transform according to its parent transformations
	/** whether or not to transform according to its parent transfomrations.
	 * Useful for health bars. eg: Don't rotate the health bar, even if the parent rotates.
	 * IMPORTANT: Only valid if it is rendered using an CCSpriteSheet.
	 * @since v0.99.0
	 */
	int						honorParentTransform_;

	/** whether or not the Sprite needs to be updated in the Atlas */
	boolean					dirty_;					// Sprite needs to be updated
	boolean					recursiveDirty_;		// Subchildren needs to be updated
	boolean					hasChildren_;			// optimization to check if it contain children

	// vertex coords, texture coords and color info
	/** buffers that are going to be rendered */
	/** the quad (tex coords, vertex coords and color) information */
	private FloatBuffer texCoords;
	public float[] getTexCoordsArray() {
		float ret[] = new float[texCoords.limit()];
		texCoords.get(ret, 0, texCoords.limit());
		return ret;
	}

	private FloatBuffer vertexes;
	public float[] getVertexArray() {
		float ret[] = new float[vertexes.limit()];
		vertexes.get(ret, 0, vertexes.limit());
		return ret;
	}

	public FloatBuffer getTexCoords() {
		texCoords.position(0);
		return texCoords;
	}

	public FloatBuffer getVertices() {
		vertexes.position(0);
		return vertexes;
	}

	private FloatBuffer colors;

	// whether or not it's parent is a CCSpriteSheet
	/** whether or not the Sprite is rendered using a CCSpriteSheet */
	boolean				usesSpriteSheet_;

	public Boolean getTextureRectRotated()
	{
		return rectRotated_;
	}

	/** Creates an sprite with a texture.
	 * The rect used will be the size of the texture.
	 * The offset will be (0,0).
	 */
	public static CCSprite sprite(CCTexture2D texture) {
		return new CCSprite(texture);
	}

	/** Creates an sprite with a texture and a rect.
	 * The offset will be (0,0).
	 */
	public static CCSprite sprite(CCTexture2D texture, CGRect rect) {
		return new CCSprite(texture, rect);
	}

	/** Creates an sprite with an sprite frame.
	 */
	public static CCSprite sprite(CCSpriteFrame spriteFrame) {
		return new CCSprite(spriteFrame);
	}

	/** Creates an sprite with an sprite frame name.
	 * An CCSpriteFrame will be fetched from the CCSpriteFrameCache by name.
	 * If the CCSpriteFrame doesn't exist it will raise an exception.
	 * @since v0.9
	 */
	public static CCSprite sprite(String spriteFrameName, boolean isFrame) {
		return new CCSprite(spriteFrameName, isFrame);
	}

	/** Creates an sprite with an image filepath.
	 * The rect used will be the size of the image.
	 * The offset will be (0,0).
	 */
	public static CCSprite sprite(String filepath) {
		return new CCSprite(filepath);
	}

	/** Creates an sprite with an image filepath and a rect.
	 * The offset will be (0,0).
	 */
	public static CCSprite sprite(String filepath, CGRect rect) {
		return new CCSprite(filepath, rect);
	}

	/** Creates an sprite with a CGImageRef.
	 * BE AWARE OF the fact that copy of image is stored in memory,
	 * use assets method if you can.
	 * 
	 * @deprecated Use spriteWithCGImage:key: instead. Will be removed in v1.0 final
	 */
	public static CCSprite sprite(Bitmap image) {
		return new CCSprite(image);
	}

	/** Creates an sprite with a CGImageRef and a key.
	 * The key is used by the CCTextureCache to know if a texture was already created with this CGImage.
	 * For example, a valid key is: @"sprite_frame_01".
	 * If key is nil, then a new texture will be created each time by the CCTextureCache. 
	 * 
	 * BE AWARE OF the fact that copy of image is stored in memory,
	 * use assets method if you can.
	 * 
	 * @since v0.99.0
	 */
	public static CCSprite sprite(Bitmap image, String key) {
		return new CCSprite(image, key);
	}

	/** Creates an sprite with an CCSpriteSheet and a rect
	 */
	public static CCSprite sprite(CCSpriteSheet spritesheet, CGRect rect) {
		return new CCSprite(spritesheet, rect);
	}

	/** Initializes an sprite with a texture.
	 * 	The rect used will be the size of the texture.
	 * 	The offset will be (0,0).
	 */
	public CCSprite(CCTexture2D texture) {
		CGSize size = texture.getContentSize();
		CGRect rect = CGRect.make(0, 0, size.width, size.height);
		init(texture, rect);
	}

	public CCSprite(CCTexture2D texture, CGRect rect) {
		init(texture, rect);
	}

	/** Initializes an sprite with a texture and a rect.
	 * The offset will be (0,0).
	 */
	protected void init(CCTexture2D texture, CGRect rect) {
		assert texture!=null:"Invalid texture for sprite";
		// IMPORTANT: [self init] and not [super init];
		init();
		setTexture(texture);
		setTextureRect(rect);
	}

	/** Initializes an sprite with an sprite frame.
	 */
	public CCSprite(CCSpriteFrame spriteFrame) {
		init(spriteFrame);
	}

	protected void init(CCSpriteFrame spriteFrame) {
		assert spriteFrame!=null:"Invalid spriteFrame for sprite";

		rectRotated_ = spriteFrame.rotated_;
		init(spriteFrame.getTexture(), spriteFrame.getRect());
		setDisplayFrame(spriteFrame);    	
	}

	/** Initializes an sprite with an sprite frame name.
	 * An CCSpriteFrame will be fetched from the CCSpriteFrameCache by name.
	 * If the CCSpriteFrame doesn't exist it will raise an exception.
	 * @since v0.9
	 */
	public CCSprite(String spriteFrameName, boolean isFrame) {
		assert spriteFrameName!=null:"Invalid spriteFrameName for sprite";
		CCSpriteFrame frame = CCSpriteFrameCache.sharedSpriteFrameCache()
				.getSpriteFrame(spriteFrameName);
		init(frame);
	}

	/** Initializes an sprite with an image filepath.
	 * The rect used will be the size of the image.
	 * The offset will be (0,0).
	 */
	public CCSprite(String filepath) {
		assert filepath!=null:"Invalid filename for sprite";

		CCTexture2D texture = CCTextureCache.sharedTextureCache().addImage(filepath);
		if( texture != null) {
			CGRect rect = CGRect.make(0, 0, 0, 0);
			rect.size = texture.getContentSize();
			init(texture, rect);
		} else {
		ccMacros.CCLOGERROR("CCSprite", "Unable to load texture from file: " + filepath);
		}
	}

	/** Initializes an sprite with an image filepath, and a rect.
	 * The offset will be (0,0).
	 */
	public CCSprite(String filepath, CGRect rect) {
		assert filepath!=null:"Invalid filename for sprite";

		CCTexture2D texture = CCTextureCache.sharedTextureCache().addImage(filepath);
		if( texture != null) {
			init(texture, rect);
		}
	}

	/** Initializes an sprite with a CGImageRef
	 * @deprecated Use spriteWithCGImage:key: instead. Will be removed in v1.0 final
	 */
	public CCSprite(Bitmap image) {
		assert image!=null:"Invalid CGImageRef for sprite";

		// XXX: possible bug. See issue #349. New API should be added
		String key = image.toString();
		CCTexture2D texture = CCTextureCache.sharedTextureCache().addImage(image, key);

		CGSize size = texture.getContentSize();
		CGRect rect = CGRect.make(0, 0, size.width, size.height );

		init(texture, rect);
	}

	/** Initializes an sprite with a CGImageRef and a key
	 * The key is used by the CCTextureCache to know if a texture was already created with this CGImage.
	 * For example, a valid key is: @"sprite_frame_01".
	 * If key is nil, then a new texture will be created each time by the CCTextureCache. 
	 * @since v0.99.0
	 */
	public CCSprite(Bitmap image, String key) {
		assert image!=null:"Invalid CGImageRef for sprite";

		// XXX: possible bug. See issue #349. New API should be added
		CCTexture2D texture = CCTextureCache.sharedTextureCache().addImage(image, key);

		CGSize size = texture.getContentSize();
		CGRect rect = CGRect.make(0, 0, size.width, size.height );

		init(texture, rect);
	}

	/** Initializes an sprite with an CCSpriteSheet and a rect
	 */
	public CCSprite(CCSpriteSheet spritesheet, CGRect rect) {
		init(spritesheet.getTexture(), rect);
		useSpriteSheetRender(spritesheet);
	}

	/** updates the texture rect of the CCSprite.
	 */

	public void setTextureRect(float x, float y, float w, float h, Boolean rotated) {
		setTextureRect(x, y, w, h, w, h, rotated);
	}

	public void setTextureRect(CGRect rect, Boolean rotated) {
		setTextureRect(rect, rect.size, rotated);
	}

	/** tell the sprite to use self-render.
	 * @since v0.99.0
	 */
	public void useSelfRender() {
		atlasIndex = CCSpriteIndexNotInitialized;
		usesSpriteSheet_ = false;
		textureAtlas_ = null;
		spriteSheet_ = null;
		dirty_ = recursiveDirty_ = false;

		float x1 = 0 + offsetPosition_.x;
		float y1 = 0 + offsetPosition_.y;
		float x2 = x1 + rect_.size.width;
		float y2 = y1 + rect_.size.height;

		vertexes.position(0);
		tmpV[0] = x1;
		tmpV[1] = y2;
		tmpV[2] = 0;
		tmpV[3] = x1;
		tmpV[4] = y1;
		tmpV[5] = 0;
		tmpV[6] = x2;
		tmpV[7] = y2;
		tmpV[8] = 0;
		tmpV[9] = x2;
		tmpV[10] = y1;
		tmpV[11] = 0;
		BufferUtils.copyFloats(tmpV, 0, vertexes, 12);
//		vertexes.put(x1);
//		vertexes.put(y2);
//		vertexes.put(0);
//		vertexes.put(x1);
//		vertexes.put(y1);
//		vertexes.put(0);
//		vertexes.put(x2);
//		vertexes.put(y2);
//		vertexes.put(0);
//		vertexes.put(x2);
//		vertexes.put(y1);
//		vertexes.put(0);
		vertexes.position(0);
	}

	/** tell the sprite to use sprite sheet render.
	 * @since v0.99.0
	 */
	public void useSpriteSheetRender(CCSpriteSheet spriteSheet) {
		usesSpriteSheet_ = true;
		textureAtlas_ = spriteSheet.getTextureAtlas(); // weak ref
		spriteSheet_ = spriteSheet; // weak ref
	}

	/** changes the display frame based on an animation and an index. */
	public void setDisplayFrame(String animationName, int frameIndex) {
		if (animations_ == null)
			initAnimationDictionary();

		CCAnimation anim = animations_.get(animationName);
		CCSpriteFrame frame = (CCSpriteFrame) anim.frames().get(frameIndex);
		setDisplayFrame(frame);
	}

	/** adds an Animation to the Sprite. */
	public void addAnimation(CCAnimation anim) {
		// lazy alloc
		if (animations_ == null)
			initAnimationDictionary();

		animations_.put(anim.name(), anim);
	}

	/** returns an Animation given it's name. */
	public CCAnimation animationByName(String animationName) {
		assert animationName != null : "animationName parameter must be non null";
		return animations_.get(animationName);
	}

	private static final ccColor4B tmpColor4B = ccColor4B.ccc4(0, 0, 0, 0);
	private static final ccColor4B[] tmpColors = new ccColor4B[] { tmpColor4B, tmpColor4B, tmpColor4B, tmpColor4B };

	public boolean getFlipX() {
		return flipX_;
	}

	public boolean getFlipY() {
		return flipY_;
	}

	// XXX HACK: optimization
	private void SET_DIRTY_RECURSIVELY() {
		if( usesSpriteSheet_ && ! recursiveDirty_ ) {
			dirty_ = recursiveDirty_ = true;
			if( hasChildren_)
				setDirtyRecursively(true);
		}
	}

	private void initAnimationDictionary() {
		animations_ = new HashMap<String, CCAnimation>();
	}

	private void setTextureRect(CGRect rect, CGSize size, Boolean rotated) {
		setTextureRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, size.width, size.height, rotated);
	}

	private void setTextureRect(float x, float y, float w, float h, float sw, float sh, boolean rotated) {
		rect_.set(x, y, w, h);
		rectRotated_ = rotated;

		setContentSize(CGSize.make(sw, sh));
		updateTextureCoords(rect_);

		float relativeOffsetX = unflippedOffsetPositionFromCenter_.x;
		float relativeOffsetY = unflippedOffsetPositionFromCenter_.y;

		// issue #732
		if( flipX_ )
			relativeOffsetX = - relativeOffsetX;
		if( flipY_ )
			relativeOffsetY = - relativeOffsetY;

		offsetPosition_.x = relativeOffsetX + (m_obContentSize.width - rect_.size.width) / 2;
		offsetPosition_.y = relativeOffsetY + (m_obContentSize.height - rect_.size.height) / 2;

		// rendering using SpriteSheet
		if( usesSpriteSheet_ ) {
			// update dirty_, don't update recursiveDirty_
			dirty_ = true;
		} else { // self rendering
			// Atlas: Vertex
			float x1 = 0 + offsetPosition_.x;
			float y1 = 0 + offsetPosition_.y;
			float x2 = x1 + w;
			float y2 = y1 + h;

			// Don't update Z.
			vertexes.position(0);
			tmpV[0] = x1;
			tmpV[1] = y2;
			tmpV[2] = 0;
			tmpV[3] = x1;
			tmpV[4] = y1;
			tmpV[5] = 0;
			tmpV[6] = x2;
			tmpV[7] = y2;
			tmpV[8] = 0;
			tmpV[9] = x2;
			tmpV[10] = y1;
			tmpV[11] = 0;
			BufferUtils.copyFloats(tmpV, 0, vertexes, 12);
//			vertexes.put(x1);
//			vertexes.put(y2);
//			vertexes.put(0);
//			vertexes.put(x1);
//			vertexes.put(y1);
//			vertexes.put(0);
//			vertexes.put(x2);
//			vertexes.put(y2);
//			vertexes.put(0);
//			vertexes.put(x2);
//			vertexes.put(y1);
//			vertexes.put(0);
			vertexes.position(0);
		}
	}

	// XXX: Optimization: instead of calling 5 times the parent sprite to obtain: position, scale.x, scale.y, anchorpoint and rotation,
	// this fuction return the 5 values in 1 single call
	protected TransformValues getTransformValues() {
		TransformValues tv = new TransformValues();
		tv.pos.set(m_obPosition);
		tv.scale.set(m_fScaleX, m_fScaleY);
		tv.rotation = m_fRotationX;
		tv.ap.set(m_obAnchorPointInPoints);

		return tv;
	}

	@Override
	public void setPosition(float x, float y) {
		super.setPosition(x, y);
		SET_DIRTY_RECURSIVELY();
    }

	public void setRelativeAnchorPoint(boolean relative) {
		assert !usesSpriteSheet_:"relativeTransformAnchor is invalid in CCSprite";
		super.setRelativeAnchorPoint(relative);
	}

	private void updateTextureCoords(CGRect rect) {
		float atlasWidth = 1;
		float atlasHeight = 1;

		if (texture_ != null) {
			atlasWidth = texture_.pixelsWide();
			atlasHeight = texture_.pixelsHigh();
		}

		if (rectRotated_) {
			float left	= (2*rect.origin.x+1)/(2*atlasWidth);
			float right	= left+(rect.size.height*2-2)/(2*atlasWidth);
			float top	= (2*rect.origin.y+1)/(2*atlasHeight);
			float bottom= top+(rect.size.width*2-2)/(2*atlasHeight);

			if( flipX_) {
				float tmp = top;
				top = bottom;
				bottom = tmp;
			}

			if( flipY_) {
				float tmp = left;
				left = right;
				right = tmp;
			}

			tmpV[0] = right;
			tmpV[1] = top; // tl v
			tmpV[2] = left; // bl u
			tmpV[3] = top; // bl v
			tmpV[4] = right; // tr u
			tmpV[5] = bottom; // tr v
			tmpV[6] = left; // br u
			tmpV[7] = bottom; // br v

			BufferUtils.copyFloats(tmpV, 0, texCoords, 8);

//			texCoords.put(0, right); // tl u
//			texCoords.put(1, top); // tl v
//			texCoords.put(2, left); // bl u
//			texCoords.put(3, top); // bl v   
//			texCoords.put(4, right); // tr u
//			texCoords.put(5, bottom); // tr v
//			texCoords.put(6, left); // br u
//			texCoords.put(7, bottom); // br v
		} else {
			float left	= (2*rect.origin.x+1)/(2*atlasWidth);
			float right	= left + (rect.size.width*2-2)/(2*atlasWidth);
			float top	= (2*rect.origin.y+1)/(2*atlasHeight);
			float bottom= top + (rect.size.height*2-2)/(2*atlasHeight);

			if( flipX_) {
				float tmp = left;
				left = right;
				right = tmp;
			}

			if( flipY_) {
				float tmp = top;
				top = bottom;
				bottom = tmp;
			}

			tmpV[0] = left; // tl u
			tmpV[1] = top; // tl v
			tmpV[2] = left; // bl u
			tmpV[3] = bottom; // bl v
			tmpV[4] = right; // tr u
			tmpV[5] = top; // tr v
			tmpV[6] = right; // br u
			tmpV[7] = bottom; // br v
			BufferUtils.copyFloats(tmpV, 0, texCoords, 8);

//			texCoords.put(0, left); // tl u
//			texCoords.put(1, top); // tl v
//			texCoords.put(2, left); // bl u
//			texCoords.put(3, bottom); // bl v
//			texCoords.put(4, right); // tr u
//			texCoords.put(5, top); // tr v
//			texCoords.put(6, right); // br u
//			texCoords.put(7, bottom); // br v
		}

		texCoords.position(0);

		if(usesSpriteSheet_)
			textureAtlas_.putTexCoords( texCoords, atlasIndex);
	}

	private final static CGAffineTransform tmpMatrix = CGAffineTransform.identity();
	private final static CGAffineTransform tmpNewMatrix = CGAffineTransform.identity();
	private final static float tmpV[] = new float[] { 
		0, 0, 0 , 	0, 0, 0,
		0, 0, 0,  	0, 0, 0
	};  
}

// end of sprite_nodes group
/// @}
