/****************************************************************************
Copyright (c) 2010-2012 cocos2d-x.org
Copyright (c) 2009-2010 Ricardo Quesada
Copyright (C) 2009      Matt Oswald
Copyright (c) 2011      Zynga Inc.

http://www.cocos2d-x.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package org.cocos2d.sprite_nodes;

import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.cocos2d.base_nodes.CCNode;
import org.cocos2d.include.CCProtocols.CCTextureProtocol;
import org.cocos2d.include.ccMacros;
import org.cocos2d.include.ccTypes.ccBlendFunc;
import org.cocos2d.include.ccTypes.ccV3F_C4B_T2F_Quad;
import org.cocos2d.kazmath.GL.Matrix;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.shaders.CCGLProgram;
import org.cocos2d.shaders.CCShaderCache;
import org.cocos2d.shaders.ccGLStateCache;
import org.cocos2d.support.CCProfiling;
import org.cocos2d.textures.CCTexture2D;
import org.cocos2d.textures.CCTextureAtlas;
import org.cocos2d.textures.CCTextureCache;
import org.cocos2d.utils.CCFormatter;

import android.opengl.GLES20;

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

/** CCSpriteBatchNode is like a batch node: if it contains children, it will draw them in 1 single OpenGL call
 * (often known as "batch draw").
 *
 * A CCSpriteBatchNode can reference one and only one texture (one image file, one texture atlas).
 * Only the CCSprites that are contained in that texture can be added to the CCSpriteBatchNode.
 * All CCSprites added to a CCSpriteBatchNode are drawn in one OpenGL ES draw call.
 * If the CCSprites are not added to a CCSpriteBatchNode then an OpenGL ES draw call will be needed for each one, which is less efficient.
 *
 *
 * Limitations:
 *  - The only object that is accepted as child (or grandchild, grand-grandchild, etc...) is CCSprite or any subclass of CCSprite. eg: particles, labels and layer can't be added to a CCSpriteBatchNode.
 *  - Either all its children are Aliased or Antialiased. It can't be a mix. This is because "alias" is a property of the texture, and all the sprites share the same texture.
 * 
 * @since v0.7.1
 */
public class CCSpriteBatchNode extends CCNode implements CCTextureProtocol {
	private static final String TAG = CCSpriteBatchNode.class.getSimpleName();

	public static final int kDefaultSpriteBatchCapacity = 29;

	// property

	// retain
	public CCTextureAtlas getTextureAtlas() {
		return m_pobTextureAtlas;
	}

	public void setTextureAtlas(CCTextureAtlas textureAtlas) { 
		if (textureAtlas != m_pobTextureAtlas) {
			m_pobTextureAtlas = textureAtlas;
		}
	}

	public List<CCSprite> getDescendants() {
		return m_pobDescendants;
	}

	/** creates a CCSpriteBatchNode with a texture2d and capacity of children.
	 * The capacity will be increased in 33% in runtime if it run out of space.
	 */
	public static CCSpriteBatchNode createWithTexture(CCTexture2D tex, int capacity) {
		CCSpriteBatchNode batchNode = new CCSpriteBatchNode();
		batchNode.initWithTexture(tex, capacity);

		return batchNode;
	}

	public static CCSpriteBatchNode createWithTexture(CCTexture2D tex) {
		return CCSpriteBatchNode.createWithTexture(tex, kDefaultSpriteBatchCapacity);
	}

	/** creates a CCSpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and capacity of children.
	 * The capacity will be increased in 33% in runtime if it run out of space.
	 * The file will be loaded using the TextureMgr.
	 */
	static CCSpriteBatchNode create(final String fileImage, int capacity) {
		CCSpriteBatchNode batchNode = new CCSpriteBatchNode();
		batchNode.initWithFile(fileImage, capacity);

		return batchNode;
	}

	static CCSpriteBatchNode create(final String fileImage) {
		return CCSpriteBatchNode.create(fileImage, kDefaultSpriteBatchCapacity);
	}

	/** initializes a CCSpriteBatchNode with a texture2d and capacity of children.
	 * The capacity will be increased in 33% in runtime if it run out of space.
	 */
	public boolean initWithTexture(CCTexture2D tex, int capacity) {
		m_blendFunc.src = ccMacros.CC_BLEND_SRC;
		m_blendFunc.dst = ccMacros.CC_BLEND_DST;
		m_pobTextureAtlas = new CCTextureAtlas();

		if (0 == capacity) {
			capacity = kDefaultSpriteBatchCapacity;
		}

		m_pobTextureAtlas.initWithTexture(tex, capacity);

		updateBlendFunc();

		// no lazy alloc in this node
		m_pChildren = Collections.synchronizedList(new ArrayList<CCNode>(capacity));

		m_pobDescendants = Collections.synchronizedList(new ArrayList<CCSprite>(capacity));

// TODO		setShaderProgram(CCShaderCache.sharedShaderCache().programForKey(CCGLProgram.kCCShader_PositionTextureColor));
		return true;
	}

	/** initializes a CCSpriteBatchNode with a file image (.png, .jpeg, .pvr, etc) and a capacity of children.
	 * The capacity will be increased in 33% in runtime if it run out of space.
	 * The file will be loaded using the TextureMgr.
	 */
	public boolean initWithFile(final String fileImage, int capacity) {
		CCTexture2D pTexture2D = CCTextureCache.sharedTextureCache().addImage(fileImage);
		return initWithTexture(pTexture2D, capacity);
	}

	public boolean init(){
		CCTexture2D texture = new CCTexture2D();
		return this.initWithTexture(texture, 0);
	}

	public void increaseAtlasCapacity() {
		// if we're going beyond the current TextureAtlas's capacity,
		// all the previously initialized sprites will need to redo their texture coords
		// this is likely computationally expensive
		int quantity = (m_pobTextureAtlas.getCapacity() + 1) * 4 / 3;

		ccMacros.CCLOG(TAG, CCFormatter.format("cocos2d: CCSpriteBatchNode: resizing TextureAtlas capacity from [%d] to [%d].",
				m_pobTextureAtlas.getCapacity(),
				quantity));

/* TODO
		if (! m_pobTextureAtlas.resizeCapacity(quantity)) {
			// serious problems
			CCLOGWARN("cocos2d: WARNING: Not enough memory to resize the atlas");
			ccMacros.CCAssert(false, "Not enough memory to resize the atlas");
		}
*/
		m_pobTextureAtlas.resizeCapacity(quantity);
	}


	/** removes a child given a certain index. It will also cleanup the running actions depending on the cleanup parameter.
	 * @warning Removing a child from a CCSpriteBatchNode is very slow
	 */
	public void removeChildAtIndex(int uIndex, boolean bDoCleanup) {
		removeChild((CCSprite)(m_pChildren.get(uIndex)), bDoCleanup);
	}
	public void insertChild(CCSprite pSprite, int uIndex) {
		pSprite.setBatchNode(this);
		pSprite.setAtlasIndex(uIndex);
		pSprite.setDirty(true);

		if(m_pobTextureAtlas.getTotalQuads() == m_pobTextureAtlas.getCapacity()) {
			increaseAtlasCapacity();
		}

		ccV3F_C4B_T2F_Quad quad = pSprite.getQuad();
		m_pobTextureAtlas.insertQuad(quad, uIndex);

		m_pobDescendants.add(uIndex, pSprite);

		// update indices
		int i = uIndex+1;

		CCSprite pChild = null;
		for(; i<m_pobDescendants.size(); i++){
			pChild = (CCSprite)m_pobDescendants.get(i);
			pChild.setAtlasIndex(pChild.getAtlasIndex() + 1);
		}

		// add children recursively
		for(Object pObj : pSprite.getChildren()) {
			pChild = (CCSprite) pObj;
			int idx = atlasIndexForChild(pChild, pChild.getZOrder());
			insertChild(pChild, idx);
		}
	}

	public void appendChild(CCSprite sprite) {
		m_bReorderChildDirty=true;
		sprite.setBatchNode(this);
		sprite.setDirty(true);

		if(m_pobTextureAtlas.getTotalQuads() == m_pobTextureAtlas.getCapacity()) {
			increaseAtlasCapacity();
		}

		m_pobDescendants.add(sprite);

		int index=m_pobDescendants.size()-1;

		sprite.setAtlasIndex(index);

		ccV3F_C4B_T2F_Quad quad = sprite.getQuad();
		m_pobTextureAtlas.insertQuad(quad, index);

		// add children recursively

		for(Object pObj : sprite.getChildren()) {
			CCSprite child = (CCSprite)pObj;
			appendChild(child);
		}
	}

	public void removeSpriteFromAtlas(CCSprite pobSprite) {
		// remove from TextureAtlas
// TODO		m_pobTextureAtlas.removeQuadAtIndex(pobSprite.getAtlasIndex());
		m_pobTextureAtlas.removeQuad(pobSprite.getAtlasIndex());

		// Cleanup sprite. It might be reused (issue #569)
		pobSprite.setBatchNode(null);

		int uIndex = m_pobDescendants.indexOf(pobSprite);
		if (uIndex != Integer.MAX_VALUE) {
			m_pobDescendants.remove(uIndex);

			// update all sprites beyond this one
			int count = m_pobDescendants.size();

			for(; uIndex < count; ++uIndex) {
				CCSprite s = (CCSprite)(m_pobDescendants.get(uIndex));
				s.setAtlasIndex( s.getAtlasIndex() - 1 );
			}
		}

		// remove children recursively
		List<CCNode> pChildren = pobSprite.getChildren();
		if (pChildren != null && pChildren.size() > 0) {
			for(Object pObject : pChildren) {
				CCSprite pChild = (CCSprite) pObject;
				if (pChild != null) {
					removeSpriteFromAtlas(pChild);
				}
			}
		}
	}

	public int rebuildIndexInOrder(CCSprite pobParent, int uIndex) {
		List<CCNode> pChildren = pobParent.getChildren();

		if (pChildren != null && pChildren.size() > 0) {
			for(Object pObject : pChildren) {
				CCSprite pChild = (CCSprite) pObject;
				if (pChild != null && (pChild.getZOrder() < 0)) {
					uIndex = rebuildIndexInOrder(pChild, uIndex);
				}
			}
		}

		// ignore self (batch node)
		if (! pobParent.equals(this)) {
			pobParent.setAtlasIndex(uIndex);
			uIndex++;
		}

		if (pChildren != null && pChildren.size() > 0) {
			for(Object pObject : pChildren) {
				CCSprite pChild = (CCSprite) pObject;
				if (pChild != null && (pChild.getZOrder() >= 0)) {
					uIndex = rebuildIndexInOrder(pChild, uIndex);
				}
			}
		}

		return uIndex;
	}

	public int highestAtlasIndexInChild(CCSprite pSprite) {
		List<CCNode> pChildren = pSprite.getChildren();

		if(pChildren == null || pChildren.size() == 0) {
			return pSprite.getAtlasIndex();
		} else {
			return highestAtlasIndexInChild((CCSprite)(pChildren.get(pChildren.size())));
		}
	}

	public int lowestAtlasIndexInChild(CCSprite pSprite) {
		List<CCNode> pChildren = pSprite.getChildren();

		if (pChildren == null || pChildren.size() == 0) {
			return pSprite.getAtlasIndex();
		} else {
			return lowestAtlasIndexInChild((CCSprite)(pChildren.get(0)));
		}
	}

	public int atlasIndexForChild(CCSprite pobSprite, int nZ) {
		List<CCNode> pBrothers = pobSprite.getParent().getChildren();
		int uChildIndex = pBrothers.indexOf(pobSprite);

		// ignore parent Z if parent is spriteSheet
		boolean bIgnoreParent = (CCSpriteBatchNode)(pobSprite.getParent()) == this;
		CCSprite pPrevious = null;
		if (uChildIndex > 0 && uChildIndex < Integer.MAX_VALUE) {
			pPrevious = (CCSprite)(pBrothers.get(uChildIndex - 1));
		}

		// first child of the sprite sheet
		if (bIgnoreParent) {
			if (uChildIndex == 0) {
				return 0;
			}

			return highestAtlasIndexInChild(pPrevious) + 1;
		}

		// parent is a CCSprite, so, it must be taken into account

		// first child of an CCSprite ?
		if (uChildIndex == 0) {
			CCSprite p = (CCSprite)(pobSprite.getParent());

			// less than parent and brothers
			if (nZ < 0) {
				return p.getAtlasIndex();
			} else {
				return p.getAtlasIndex() + 1;
			}
		} else {
			// previous & sprite belong to the same branch
			if ((pPrevious.getZOrder() < 0 && nZ < 0) || (pPrevious.getZOrder() >= 0 && nZ >= 0)) {
				return highestAtlasIndexInChild(pPrevious) + 1;
			}

			// else (previous < 0 and sprite >= 0 )
			CCSprite p = (CCSprite)(pobSprite.getParent());
			return p.getAtlasIndex() + 1;
		}

		// Should not happen. Error calculating Z on SpriteSheet
//		ccMacros.CCAssert(false, "should not run here");
//		return 0;
	}

	/* Sprites use this to start sortChildren, don't call this manually */
	public void reorderBatch(boolean reorder) {
		m_bReorderChildDirty=reorder;
	}

	// CCTextureProtocol
	public CCTexture2D getTexture() {
		return m_pobTextureAtlas.getTexture();
	}

	public void setTexture(CCTexture2D texture) {
		m_pobTextureAtlas.setTexture(texture);
		updateBlendFunc();
	}

	public void setBlendFunc(ccBlendFunc blendFunc) {
		m_blendFunc.set(blendFunc);
	}

	public ccBlendFunc getBlendFunc() {
		return m_blendFunc;
	}

	public void visit() {
		ccMacros.CC_PROFILER_START_CATEGORY(CCProfiling.kCCProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit");

		// CAREFUL:
		// This visit is almost identical to CocosNode#visit
		// with the exception that it doesn't call visit on it's children
		//
		// The alternative is to have a void CCSprite#visit, but
		// although this is less maintainable, is faster
		//
		if (! m_bVisible) {
			return;
		}

		Matrix.kmGLPushMatrix();

		if (m_pGrid != null && m_pGrid.isActive()) {
			m_pGrid.beforeDraw();
			transformAncestors();
		}

		sortAllChildren();
		transform();

		draw();

		if (m_pGrid != null && m_pGrid.isActive()) {
			m_pGrid.afterDraw(this);
		}

		Matrix.kmGLPopMatrix();
		setOrderOfArrival(0);

		ccMacros.CC_PROFILER_STOP_CATEGORY(CCProfiling.kCCProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit");
	}

	public void addChild(CCNode child) {
		super.addChild(child);
	}

	public void addChild(CCNode child, int zOrder) {
		super.addChild(child, zOrder);
	}

	public void addChild(CCNode child, int zOrder, int tag) {
		ccMacros.CCAssert(child != null, "child should not be null");
		ccMacros.CCAssert((CCSprite)child != null, "CCSpriteBatchNode only supports CCSprites as children");
		CCSprite pSprite = (CCSprite)child;
		// check CCSprite is using the same texture id
		ccMacros.CCAssert(pSprite.getTexture().getName() == m_pobTextureAtlas.getTexture().getName(), "CCSprite is not using the same texture id");

		super.addChild(child, zOrder, tag);

		appendChild(pSprite);
	}

	@Override
	public void reorderChild(CCNode child, int zOrder) {
		ccMacros.CCAssert(child != null, "the child should not be null");
		ccMacros.CCAssert(m_pChildren.contains(child), "Child doesn't belong to Sprite");

		if(zOrder == child.getZOrder()) {
			return;
		}

		//set the z-order and sort later
		super.reorderChild(child, zOrder);
	}

	@Override
	public void removeChild(CCNode child, boolean cleanup) {
		CCSprite pSprite = (CCSprite)(child);

		// explicit null handling
		if (pSprite == null) {
			return;
		}

		ccMacros.CCAssert(m_pChildren.contains(pSprite), "sprite batch node should contain the child");

		// cleanup before removing
		removeSpriteFromAtlas(pSprite);

		super.removeChild(pSprite, cleanup);
	}

	public void removeAllChildrenWithCleanup(boolean bCleanup) {
		// Invalidate atlas index. issue #569
		// useSelfRender should be performed on all descendants. issue #1216
		if((m_pobDescendants != null) && m_pobDescendants.size() > 0) {
			for(Object child : m_pobDescendants) {
				CCSprite pNode = (CCSprite) child;
				if(pNode != null) {
					pNode.setBatchNode(null);
				}
			}
		}

		super.removeAllChildrenWithCleanup(bCleanup);

		m_pobDescendants.clear();
		m_pobTextureAtlas.removeAllQuads();
	}

	public void sortAllChildren() {
		if (m_bReorderChildDirty) {
			Collections.sort(m_pChildren,
					new Comparator<CCNode>() {
						@Override
						public int compare(CCNode o1, CCNode o2) {
							int ret = o1.getZOrder() - o2.getZOrder();
							if(ret == 0) {
								ret = (o1.getOrderOfArrival() < o2.getOrderOfArrival()) ? -1 : 0;
							}
							return ret;
						}
					}
				);

			//sorted now check all children
			if (m_pChildren.size() > 0) {
				//first sort all children recursively based on zOrder
				for(Object child : m_pChildren) {
					CCSprite pNode = (CCSprite)child;
					if(pNode != null) {
						pNode.sortAllChildren();
					}
				}

				IntBuffer index = IntBuffer.allocate(0);

				//fast dispatch, give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact)
				// and at the same time reorder descendants and the quads to the right index
				for(Object pObj : m_pChildren) {
					CCSprite pChild = (CCSprite)pObj;
					updateAtlasIndex(pChild, index);
				}
			}

			m_bReorderChildDirty = false;
		}
	}

	public void draw() {
		ccMacros.CC_PROFILER_START("CCSpriteBatchNode - draw");

		// Optimization: Fast Dispatch
		if(m_pobTextureAtlas.getTotalQuads() == 0) {
			return;
		}

		ccMacros.CC_NODE_DRAW_SETUP();

		if(m_pChildren != null && m_pChildren.size() > 0) {
			for(Object pObj : m_pChildren) {
				CCSprite pNode = (CCSprite) pObj;
				if(pNode != null) {
					pNode.updateTransform();
				}
			}
		}

		ccGLStateCache.ccGLBlendFunc(m_blendFunc.src, m_blendFunc.dst);

		m_pobTextureAtlas.drawQuads();

		ccMacros.CC_PROFILER_STOP("CCSpriteBatchNode - draw");
	}


	/** Inserts a quad at a certain index into the texture atlas. The CCSprite won't be added into the children array.
	 * This method should be called only when you are dealing with very big AtlasSrite and when most of the CCSprite won't be updated.
	 * For example: a tile map (CCTMXMap) or a label with lots of characters (CCLabelBMFont)
	 */
	protected void insertQuadFromSprite(CCSprite sprite, int index) {
		ccMacros.CCAssert(sprite != null, "Argument must be non-NULL");
		ccMacros.CCAssert((CCSprite)sprite != null, "CCSpriteBatchNode only supports CCSprites as children");

		// make needed room
		while(index >= m_pobTextureAtlas.getCapacity() || m_pobTextureAtlas.getCapacity() == m_pobTextureAtlas.getTotalQuads()) {
			this.increaseAtlasCapacity();
		}
		//
		// update the quad directly. Don't add the sprite to the scene graph
		//
		sprite.setBatchNode(this);
		sprite.setAtlasIndex(index);

		ccV3F_C4B_T2F_Quad quad = sprite.getQuad();
		m_pobTextureAtlas.insertQuad(quad, index);

		// XXX: updateTransform will update the textureAtlas too, using updateQuad.
		// XXX: so, it should be AFTER the insertQuad
		sprite.setDirty(true);
		sprite.updateTransform();
	}

	/** Updates a quad at a certain index into the texture atlas. The CCSprite won't be added into the children array.
	 * This method should be called only when you are dealing with very big AtlasSrite and when most of the CCSprite won't be updated.
	 * For example: a tile map (CCTMXMap) or a label with lots of characters (CCLabelBMFont)
	 */
	protected void updateQuadFromSprite(CCSprite sprite, int index) {
		ccMacros.CCAssert(sprite != null, "Argument must be non-nil");
		ccMacros.CCAssert((CCSprite)sprite != null, "CCSpriteBatchNode only supports CCSprites as children");

		// make needed room
		while (index >= m_pobTextureAtlas.getCapacity() || m_pobTextureAtlas.getCapacity() == m_pobTextureAtlas.getTotalQuads()) {
			this.increaseAtlasCapacity();
		}

		//
		// update the quad directly. Don't add the sprite to the scene graph
		//
		sprite.setBatchNode(this);
		sprite.setAtlasIndex(index);

		sprite.setDirty(true);

		// UpdateTransform updates the textureAtlas quad
		sprite.updateTransform();
	}

	/* This is the opposite of "addQuadFromSprite.
	 * It add the sprite to the children and descendants array, but it doesn't update add it to the texture atlas
	 */
	protected CCSpriteBatchNode addSpriteWithoutQuad(CCSprite child, int z, int aTag) {
		ccMacros.CCAssert(child != null, "Argument must be non-NULL");
		ccMacros.CCAssert((CCSprite)child != null, "CCSpriteBatchNode only supports CCSprites as children");

		// quad index is Z
		child.setAtlasIndex(z);

		// XXX: optimize with a binary search
		int i=0;

		for(Object pObject : m_pobDescendants) {
			CCSprite pChild = (CCSprite) pObject;
			if (pChild != null && (pChild.getAtlasIndex() >= z)) {
				++i;
			}
		}

		m_pobDescendants.add(i, child);

		// IMPORTANT: Call super, and not self. Avoid adding it to the texture atlas array
		super.addChild(child, z, aTag);
		//#issue 1262 don't use lazy sorting, tiles are added as quads not as sprites, so sprites need to be added in order
		reorderBatch(false);

		return this;
	}


	private void updateAtlasIndex(CCSprite sprite, IntBuffer index) {
		int curIndex = index.get(0);
		int count = 0;
		List<CCNode> pArray = sprite.getChildren();
		if (pArray != null) {
			count = pArray.size();
		}

		int oldIndex = 0;

		if( count == 0 ) {
			oldIndex = sprite.getAtlasIndex();
			sprite.setAtlasIndex(curIndex);
			sprite.setOrderOfArrival(0);
			if (oldIndex != curIndex){
				swap(oldIndex, curIndex);
			}
			index.put(0, curIndex++);
		} else {
			boolean needNewIndex=true;

			if(((CCSprite)(pArray.get(0))).getZOrder() >= 0) {
				//all children are in front of the parent
				oldIndex = sprite.getAtlasIndex();
				sprite.setAtlasIndex(curIndex);
				sprite.setOrderOfArrival(0);
				if (oldIndex != curIndex) {
					swap(oldIndex, curIndex);
				}
				index.put(0, curIndex++);

				needNewIndex = false;
			}

			for(Object pObj : pArray) {
				CCSprite child = (CCSprite) pObj;
				if(needNewIndex && child.getZOrder() >= 0) {
					oldIndex = sprite.getAtlasIndex();
					sprite.setAtlasIndex(curIndex);
					sprite.setOrderOfArrival(0);
					if (oldIndex != curIndex) {
						this.swap(oldIndex, curIndex);
					}
					index.put(0, curIndex++);
					needNewIndex = false;

				}

				updateAtlasIndex(child, index);
				curIndex = index.get(0);
			}

			if (needNewIndex) {
				//all children have a zOrder < 0)
				oldIndex=sprite.getAtlasIndex();
				sprite.setAtlasIndex(curIndex);
				sprite.setOrderOfArrival(0);
				if (oldIndex!=curIndex) {
					swap(oldIndex, curIndex);
				}
				index.put(0, curIndex++);
			}
		}
	}

	private void swap(int oldIndex, int newIndex) {
		List<CCSprite> x = m_pobDescendants;
		ccV3F_C4B_T2F_Quad [] quads = m_pobTextureAtlas.getQuads();

		CCSprite tempItem = x.get(oldIndex);
		ccV3F_C4B_T2F_Quad tempItemQuad=quads[oldIndex];

		//update the index of other swapped item
		((CCSprite) x.get(newIndex)).setAtlasIndex(oldIndex);

		x.set(oldIndex, x.get(newIndex));
		quads[oldIndex]=quads[newIndex];
		x.set(newIndex, tempItem);
		quads[newIndex]=tempItemQuad;
	}

	private void updateBlendFunc() {
		if (! m_pobTextureAtlas.getTexture().hasPremultipliedAlpha()) {
			m_blendFunc.src = GLES20.GL_SRC_ALPHA;
			m_blendFunc.dst = GLES20.GL_ONE_MINUS_SRC_ALPHA;
		}
	}


	protected CCTextureAtlas m_pobTextureAtlas;
	protected ccBlendFunc m_blendFunc = new ccBlendFunc();

	// all descendants: children, gran children, etc...
	protected List<CCSprite> m_pobDescendants;
}

// end of sprite_nodes group
/// @}
