public class

SpriteBatch

extends Entity
package org.anddev.andengine.entity.sprite.batch;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import org.anddev.andengine.engine.camera.Camera;
import org.anddev.andengine.entity.Entity;
import org.anddev.andengine.entity.shape.Shape;
import org.anddev.andengine.entity.sprite.BaseSprite;
import org.anddev.andengine.opengl.texture.ITexture;
import org.anddev.andengine.opengl.texture.region.BaseTextureRegion;
import org.anddev.andengine.opengl.texture.region.buffer.SpriteBatchTextureRegionBuffer;
import org.anddev.andengine.opengl.util.GLHelper;
import org.anddev.andengine.opengl.vertex.SpriteBatchVertexBuffer;

/**
 * TODO Texture could be semi-changeable, being resetting to null in end(...)
 * TODO Make use of pGL.glColorPointer(size, type, stride, pointer) which should allow individual color tinting.
 * 
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 * 
 * @author Nicolas Gramlich
 * @since 11:45:48 - 14.06.2011
 */
public class SpriteBatch extends Entity {
	// ===========================================================
	// Constants
	// ===========================================================

	// ===========================================================
	// Fields
	// ===========================================================

	protected final ITexture mTexture;
	protected final int mCapacity;

	protected int mIndex;
	private int mVertices;

	private int mSourceBlendFunction;
	private int mDestinationBlendFunction;

	private final SpriteBatchVertexBuffer mSpriteBatchVertexBuffer;
	private final SpriteBatchTextureRegionBuffer mSpriteBatchTextureRegionBuffer;

	// ===========================================================
	// Constructors
	// ===========================================================

	public SpriteBatch(final ITexture pTexture, final int pCapacity) {
		this(pTexture, pCapacity, new SpriteBatchVertexBuffer(pCapacity, GL11.GL_STATIC_DRAW, true), new SpriteBatchTextureRegionBuffer(pCapacity, GL11.GL_STATIC_DRAW, true));
	}

	public SpriteBatch(final ITexture pTexture, final int pCapacity, final SpriteBatchVertexBuffer pSpriteBatchVertexBuffer, final SpriteBatchTextureRegionBuffer pSpriteBatchTextureRegionBuffer) {
		this.mTexture = pTexture;
		this.mCapacity = pCapacity;
		this.mSpriteBatchVertexBuffer = pSpriteBatchVertexBuffer;
		this.mSpriteBatchTextureRegionBuffer = pSpriteBatchTextureRegionBuffer;

		this.initBlendFunction();
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	public void setBlendFunction(final int pSourceBlendFunction, final int pDestinationBlendFunction) {
		this.mSourceBlendFunction = pSourceBlendFunction;
		this.mDestinationBlendFunction = pDestinationBlendFunction;
	}

	public int getIndex() {
		return this.mIndex;
	}

	public void setIndex(final int pIndex) {
		this.assertCapacity(pIndex);

		this.mIndex = pIndex;

		final int vertexIndex = pIndex * 2 * SpriteBatchVertexBuffer.VERTICES_PER_RECTANGLE;

		this.mSpriteBatchVertexBuffer.setIndex(vertexIndex);
		this.mSpriteBatchTextureRegionBuffer.setIndex(vertexIndex);
	}

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================
	
	@Override
	protected void doDraw(final GL10 pGL, final Camera pCamera) {
		this.onInitDraw(pGL);

		this.begin(pGL);

		this.onApplyVertices(pGL);
		this.onApplyTextureRegion(pGL);
		this.drawVertices(pGL, pCamera);

		this.end(pGL);
	}

	@Override
	public void reset() {
		super.reset();

		this.initBlendFunction();
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();

		if(this.mSpriteBatchVertexBuffer.isManaged()) {
			this.mSpriteBatchVertexBuffer.unloadFromActiveBufferObjectManager();
		}
		if(this.mSpriteBatchTextureRegionBuffer.isManaged()) {
			this.mSpriteBatchTextureRegionBuffer.unloadFromActiveBufferObjectManager();
		}
	}

	// ===========================================================
	// Methods
	// ===========================================================

	protected void begin(@SuppressWarnings("unused") final GL10 pGL) {
//		GLHelper.disableDepthMask(pGL);
	}

	protected void end(@SuppressWarnings("unused") final GL10 pGL) {
//		GLHelper.enableDepthMask(pGL);
	}

	/**
	 * @see {@link SpriteBatchVertexBuffer#add(float, float, float, float)}.
	 */
	public void draw(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight) {
		this.assertCapacity();
		this.assertTexture(pTextureRegion);

		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	public void drawWithoutChecks(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight) {
		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	/**
	 * @see {@link SpriteBatchVertexBuffer#add(float, float, float, float, float)}.
	 */
	public void draw(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight, final float pRotation) {
		this.assertCapacity();
		this.assertTexture(pTextureRegion);

		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight, pRotation);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	public void drawWithoutChecks(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight, final float pRotation) {
		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight, pRotation);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	/**
	 * @see {@link SpriteBatchVertexBuffer#add(float, float, float, float, float, float)}.
	 */
	public void draw(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight, final float pScaleX, final float pScaleY) {
		this.assertCapacity();
		this.assertTexture(pTextureRegion);

		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight, pScaleX, pScaleY);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	public void drawWithoutChecks(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight, final float pScaleX, final float pScaleY) {
		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight, pScaleX, pScaleY);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	/**
	 * @see {@link SpriteBatchVertexBuffer#add(float, float, float, float, float, float, float)}.
	 */
	public void draw(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight, final float pRotation, final float pScaleX, final float pScaleY) {
		this.assertCapacity();
		this.assertTexture(pTextureRegion);

		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight, pRotation, pScaleX, pScaleY);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	public void drawWithoutChecks(final BaseTextureRegion pTextureRegion, final float pX, final float pY, final float pWidth, final float pHeight, final float pRotation, final float pScaleX, final float pScaleY) {
		this.mSpriteBatchVertexBuffer.add(pX, pY, pWidth, pHeight, pRotation, pScaleX, pScaleY);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	/**
	 * {@link SpriteBatchVertexBuffer#add(float, float, float, float, float, float, float, float)}.
	 */
	public void draw(final BaseTextureRegion pTextureRegion, final float pX1, final float pY1, final float pX2, final float pY2, final float pX3, final float pY3, final float pX4, final float pY4) {
		this.assertCapacity();
		this.assertTexture(pTextureRegion);

		this.mSpriteBatchVertexBuffer.addInner(pX1, pY1, pX2, pY2, pX3, pY3, pX4, pY4);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	public void drawWithoutChecks(final BaseTextureRegion pTextureRegion, final float pX1, final float pY1, final float pX2, final float pY2, final float pX3, final float pY3, final float pX4, final float pY4) {
		this.mSpriteBatchVertexBuffer.addInner(pX1, pY1, pX2, pY2, pX3, pY3, pX4, pY4);
		this.mSpriteBatchTextureRegionBuffer.add(pTextureRegion);

		this.mIndex++;
	}

	/**
	 * {@link SpriteBatchVertexBuffer#add(BaseSprite)}.
	 */
	public void draw(final BaseSprite pBaseSprite) {
		if(pBaseSprite.isVisible()) {
			this.assertCapacity();

			final BaseTextureRegion textureRegion = pBaseSprite.getTextureRegion();
			this.assertTexture(textureRegion);

			if(pBaseSprite.getRotation() == 0 && !pBaseSprite.isScaled()) {
				this.mSpriteBatchVertexBuffer.add(pBaseSprite.getX(), pBaseSprite.getY(), pBaseSprite.getWidth(), pBaseSprite.getHeight());
			} else {
				this.mSpriteBatchVertexBuffer.add(pBaseSprite.getWidth(), pBaseSprite.getHeight(), pBaseSprite.getLocalToParentTransformation());
			}

			this.mSpriteBatchTextureRegionBuffer.add(textureRegion);

			this.mIndex++;
		}
	}

	public void drawWithoutChecks(final BaseSprite pBaseSprite) {
		if(pBaseSprite.isVisible()) {
			final BaseTextureRegion textureRegion = pBaseSprite.getTextureRegion();

			if(pBaseSprite.getRotation() == 0 && !pBaseSprite.isScaled()) {
				this.mSpriteBatchVertexBuffer.add(pBaseSprite.getX(), pBaseSprite.getY(), pBaseSprite.getWidth(), pBaseSprite.getHeight());
			} else {
				this.mSpriteBatchVertexBuffer.add(pBaseSprite.getWidth(), pBaseSprite.getHeight(), pBaseSprite.getLocalToParentTransformation());
			}

			this.mSpriteBatchTextureRegionBuffer.add(textureRegion);

			this.mIndex++;
		}
	}

	public void submit() {
		this.onSubmit();
	}

	private void onSubmit() {
		this.mVertices = this.mIndex * SpriteBatchVertexBuffer.VERTICES_PER_RECTANGLE;

		this.mSpriteBatchVertexBuffer.submit();
		this.mSpriteBatchTextureRegionBuffer.submit();

		this.mIndex = 0;
		this.mSpriteBatchVertexBuffer.setIndex(0);
		this.mSpriteBatchTextureRegionBuffer.setIndex(0);
	}

	private void initBlendFunction() {
		if(this.mTexture.getTextureOptions().mPreMultipyAlpha) {
			this.setBlendFunction(Shape.BLENDFUNCTION_SOURCE_PREMULTIPLYALPHA_DEFAULT, Shape.BLENDFUNCTION_DESTINATION_PREMULTIPLYALPHA_DEFAULT);
		}
	}

	protected void onInitDraw(final GL10 pGL) {
		GLHelper.setColor(pGL, this.mRed, this.mGreen, this.mBlue, this.mAlpha);

		GLHelper.enableVertexArray(pGL);
		GLHelper.blendFunction(pGL, this.mSourceBlendFunction, this.mDestinationBlendFunction);

		GLHelper.enableTextures(pGL);
		GLHelper.enableTexCoordArray(pGL);
	}

	protected void onApplyVertices(final GL10 pGL) {
		if(GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS) {
			final GL11 gl11 = (GL11)pGL;

			this.mSpriteBatchVertexBuffer.selectOnHardware(gl11);
			GLHelper.vertexZeroPointer(gl11);
		} else {
			GLHelper.vertexPointer(pGL, this.mSpriteBatchVertexBuffer.getFloatBuffer());
		}
	}

	private void onApplyTextureRegion(final GL10 pGL) {
		if(GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS) {
			final GL11 gl11 = (GL11)pGL;

			this.mSpriteBatchTextureRegionBuffer.selectOnHardware(gl11);

			this.mTexture.bind(pGL);
			GLHelper.texCoordZeroPointer(gl11);
		} else {
			this.mTexture.bind(pGL);
			GLHelper.texCoordPointer(pGL, this.mSpriteBatchTextureRegionBuffer.getFloatBuffer());
		}
	}

	private void drawVertices(final GL10 pGL, @SuppressWarnings("unused") final Camera pCamera) {
		pGL.glDrawArrays(GL10.GL_TRIANGLES, 0, this.mVertices);
	}

	private void assertCapacity(final int pIndex) {
		if(pIndex >= this.mCapacity) {
			throw new IllegalStateException("This supplied pIndex: '" + pIndex + "' is exceeding the capacity: '" + this.mCapacity + "' of this SpriteBatch!");
		}
	}

	private void assertCapacity() {
		if(this.mIndex == this.mCapacity) {
			throw new IllegalStateException("This SpriteBatch has already reached its capacity (" + this.mCapacity + ") !");
		}
	}

	protected void assertTexture(final BaseTextureRegion pTextureRegion) {
		if(pTextureRegion.getTexture() != this.mTexture) {
			throw new IllegalArgumentException("The supplied Texture does match the Texture of this SpriteBatch!");
		}
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}