package org.cocos2d.opengl;

import static javax.microedition.khronos.opengles.GL10.GL_REPEAT;
import static javax.microedition.khronos.opengles.GL10.GL_TEXTURE_2D;
import static javax.microedition.khronos.opengles.GL10.GL_TEXTURE_WRAP_S;
import static javax.microedition.khronos.opengles.GL10.GL_TEXTURE_WRAP_T;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

import org.cocos2d.config.ccConfig;
import org.cocos2d.config.ccMacros;
import org.cocos2d.nodes.CCTextureCache;
import org.cocos2d.include.ccTypes.ccColor4B;
import org.cocos2d.types.ccQuad2;
import org.cocos2d.types.ccQuad3;
import org.cocos2d.types.ccV3F_C4B_T2F_Quad;
import org.cocos2d.utils.CCFormatter;

import com.badlogic.gdx.utils.BufferUtils;

/**
 * @addtogroup textures
 * @{
 */

/** @brief A class that implements a Texture Atlas.
 * Supported features:
 * - The atlas file can be a PVRTC, PNG or any other format supported by Texture2D
 * - Quads can be updated in runtime
 * - Quads can be added in runtime
 * - Quads can be removed in runtime
 * - Quads can be re-ordered in runtime
 * - The TextureAtlas capacity can be increased or decreased in runtime
 * - OpenGL component: V3F, C4B, T2F.
 * The quads are rendered using an OpenGL ES VBO.
 * To render the quads using an interleaved vertex array list, you should modify the ccConfig.h file 
 */
public class CCTextureAtlas {
	private static final String TAG = CCTextureAtlas.class.getSimpleName();

	private ShortBuffer			m_pIndices;

	protected int				m_uVAOname;

	protected int []			m_pBuffersVBO = new int[2]; //0: vertex  1: indices
	protected boolean			m_bDirty; //indicates whether or not the array buffer of the VBO needs to be updated

	/** quantity of quads that are going to be drawn */
	protected int m_uTotalQuads;

	public int getTotalQuads() {
		return m_uTotalQuads;
	}

	/** quantity of quads that can be stored with the current texture atlas size */
	protected int m_uCapacity;

	public int getCapacity() {
		return m_uCapacity;
	}

	/** Texture of the texture atlas */
	protected CCTexture2D m_pTexture;

	public CCTexture2D getTexture() {
		return m_pTexture;
	}

	public void setTexture(CCTexture2D var) {
		m_pTexture = var;
	}

	/** Quads that are going to be rendered */
	protected ccV3F_C4B_T2F_Quad m_pQuads;

	public ccV3F_C4B_T2F_Quad getQuads() {
		//if someone accesses the quads directly, presume that changes will be made
		m_bDirty = true;
		return m_pQuads;
	}

	public void setQuads(ccV3F_C4B_T2F_Quad var) {
		m_pQuads = var;
	}

	public CCTextureAtlas() {
		m_pIndices = null;
		m_bDirty = false;
		m_pTexture = null;
		m_pQuads = null;
	}

	public String description() {
		// TODO
		return null;
	}

	/** creates a TextureAtlas with an filename and with an initial capacity for Quads.
	 * The TextureAtlas capacity can be increased in runtime.
	 */
	public static CCTextureAtlas create(String file , int capacity) {
		CCTextureAtlas pTextureAtlas = new CCTextureAtlas();
		if((pTextureAtlas != null) && pTextureAtlas.initWithFile(file, capacity)) {
			return pTextureAtlas;
		}
		pTextureAtlas = null;
		return null;
	}

	/** initializes a TextureAtlas with a filename and with a certain capacity for Quads.
	 * The TextureAtlas capacity can be increased in runtime.
	 *
	 * WARNING: Do not reinitialize the TextureAtlas because it will leak memory (issue #706)
	 */
	public boolean initWithFile(String file, int capacity) {
		// retained in property
		CCTexture2D texture = CCTextureCache.sharedTextureCache().addImage(file);

		if(texture != null) {
			return initWithTexture(texture, capacity);
		} else {
			ccMacros.CCLOG(TAG, "cocos2d: Could not open file: " + file);
			return false;
		}
	}

	/** creates a TextureAtlas with a previously initialized Texture2D object, and
	 * with an initial capacity for n Quads. 
	 * The TextureAtlas capacity can be increased in runtime.
	 */
	public static CCTextureAtlas createWithTexture(CCTexture2D texture, int capacity) {
		CCTextureAtlas pTextureAtlas = new CCTextureAtlas();
		if((pTextureAtlas != null) && pTextureAtlas.initWithTexture(texture, capacity)) {
			return pTextureAtlas;
		}
		pTextureAtlas = null;
		return null;
	}

	/** initializes a TextureAtlas with a previously initialized Texture2D object, and
	 * with an initial capacity for Quads. 
	 * The TextureAtlas capacity can be increased in runtime.
	 *
	 * WARNING: Do not reinitialize the TextureAtlas because it will leak memory (issue #706)
	 */
	public boolean initWithTexture(CCTexture2D texture, int capacity) {
/* TODO
//		assert texture != null : "texture should not be null";
		m_uCapacity = capacity;
		m_uTotalQuads = 0;

		// retained in property
		this.m_pTexture = texture;

		// Re-initialization is not allowed
		assert (m_pQuads == null) && (m_pIndices == null) : "";

		m_pQuads = (ccV3F_C4B_T2F_Quad*)malloc( m_uCapacity * sizeof(ccV3F_C4B_T2F_Quad) );
		m_pIndices = (GLushort *)malloc( m_uCapacity * 6 * sizeof(GLushort) );

		if(! ((m_pQuads != null) && (m_pIndices != null)) && (m_uCapacity > 0)) {
			//ccMacros.CCLOG(TAG, "cocos2d: CCTextureAtlas: not enough memory");
			m_pQuads = null;
			m_pIndices = null;

			// release texture, should set it to null, because the destruction will
			// release it too. see cocos2d-x issue #484
			m_pTexture = null;
			return false;
		}

		memset( m_pQuads, 0, m_uCapacity * sizeof(ccV3F_C4B_T2F_Quad) );
		memset( m_pIndices, 0, m_uCapacity * 6 * sizeof(GLushort) );

		// listen the event when app go to background
		CCNotificationCenter.sharedNotificationCenter().addObserver(this,
				callfuncO_selector(CCTextureAtlas.listenBackToForeground),
				EVNET_COME_TO_FOREGROUND,
				NULL);

		this.setupIndices();

		if(ccConfig.CC_TEXTURE_ATLAS_USE_VAO != 0) {
			setupVBOandVAO();
		} else {
			setupVBO();
		}

		m_bDirty = true;

		return true;
*/
		return false;
	}

	/** updates a Quad (texture, vertex and color) at a certain index
	 * index must be between 0 and the atlas capacity - 1
	 * @since v0.8
	 */
	public void updateQuad(ccV3F_C4B_T2F_Quad quad, int index) {
		// TODO;
	}

	/** Inserts a Quad (texture, vertex and color) at a certain index
	 * index must be between 0 and the atlas capacity - 1
	 * @since v0.8
	 */
	public void insertQuad(ccV3F_C4B_T2F_Quad quad, int index) {
		// TODO;
	}

	/** Inserts a c array of quads at a given index
	 * index must be between 0 and the atlas capacity - 1
	 * this method doesn't enlarge the array when amount + index > totalQuads
	 * @since v1.1
	 */
	public void insertQuads(ccV3F_C4B_T2F_Quad quads, int index, int amount) {
		// TODO;
	}

	/** Removes the quad that is located at a certain index and inserts it at a new index
	 * This operation is faster than removing and inserting in a quad in 2 different steps
	 * @since v0.7.2
	 */
	public void insertQuad(int from, int to) {
		// TODO legacy -->
		assert (to >= 0 && to < m_uTotalQuads) : "insertQuadFromIndex:atIndex: Invalid index";
		assert (from >= 0 && from < m_uTotalQuads) : "insertQuadFromIndex:atIndex: Invalid index";

		if (from == to)
			return;

		int size = Math.abs(from - to);
		int dst = from;
		int src = from + 1;

		if (from > to) {
			dst = to + 1;
			src = to;
		}

		// tex coordinates
		float[] texCoordsBackup = getTexCoords(textureCoordinates, from);
		arraycopyTexture(textureCoordinates, src, textureCoordinates, dst, size);
		putTexCoords(textureCoordinates, texCoordsBackup, to);

		// vertexCoordinates_ coordinates
		float[] vertexQuadBackup = getVertex(vertexCoordinates, from);
		arraycopyVertex(vertexCoordinates, src, vertexCoordinates, dst, size);
		putVertex(vertexCoordinates, vertexQuadBackup, to);

		// colors_
		if (withColorArray_) {
			ccColor4B [] colorsBackup = getColor(colors, from);
			arraycopyColor(colors, src, colors, dst, size);
			putColor(colors, colorsBackup, to);
		}
	}

	/** removes a quad at a given index number.
	 * The capacity remains the same, but the total number of quads to be drawn is reduced in 1
	 * @since v0.7.2
	 */
	public void removeQuad(int index) {
		// TODO legacy -->
		assert (index >= 0 && index < m_uTotalQuads) : "removeQuadAtIndex: Invalid index";

		int remaining = (m_uTotalQuads - 1) - index;

		// last object doesn't need to be moved
		if (remaining > 0) {
			// tex coordinates
			arraycopyTexture(textureCoordinates, index + 1, textureCoordinates, index, remaining);

			// vertexCoordinates_
			arraycopyVertex(vertexCoordinates, index + 1, vertexCoordinates, index, remaining);

			// colors_
			if (withColorArray_) {
				arraycopyColor(colors, index + 1, colors, index, remaining);
			}
		}

		m_uTotalQuads--;
	}

	/** removes a amount of quads starting from index
	 * @since 1.1
	 */
	public void removeQuadsAtIndex(int index, int amount) {
		// TODO
		;
	}

	/** removes all Quads.
	 * The TextureAtlas capacity remains untouched. No memory is freed.
	 * The total number of quads to be drawn will be 0
	 * @since v0.7.2
	 */
	public void removeAllQuads() {
		// TODO legacy -->
		m_uTotalQuads = 0;
	}

	/** resize the capacity of the Texture Atlas.
	 * The new capacity can be lower or higher than the current one
	 * It returns YES if the resize was successful.
	 * If it fails to resize the capacity it will return NO with a new capacity of 0.
	 */
	public void resizeCapacity(int newCapacity) {

		// TODO legacy -->
		final int MAX_POSSIBLE_QUADS_COUNT = Short.MAX_VALUE / 6; // 6 short indices per quad 
		newCapacity = Math.min(newCapacity, MAX_POSSIBLE_QUADS_COUNT);

		if (newCapacity == m_uCapacity)
			return;

		// update capacity and getTotalQuads
		m_uTotalQuads = Math.min(m_uTotalQuads, newCapacity);

		m_uCapacity = newCapacity;

		ByteBuffer tbb = ByteBuffer.allocateDirect(ccQuad2.size * newCapacity * 4);
		tbb.order(ByteOrder.nativeOrder());
		FloatBuffer tmpTexCoords = tbb.asFloatBuffer();
		tmpTexCoords.put(textureCoordinates);
		textureCoordinates = tmpTexCoords;
		textureCoordinates.position(0);

		ByteBuffer vbb = ByteBuffer.allocateDirect(ccQuad3.size * newCapacity * 4);
		vbb.order(ByteOrder.nativeOrder());
		FloatBuffer tmpVertexCoords = vbb.asFloatBuffer();
		tmpVertexCoords.put(vertexCoordinates);
		vertexCoordinates = tmpVertexCoords;
		vertexCoordinates.position(0);

		ByteBuffer isb = ByteBuffer.allocateDirect(6 * newCapacity * 2);
		isb.order(ByteOrder.nativeOrder());
		ShortBuffer tmpIndices = isb.asShortBuffer();
		tmpIndices.put(m_pIndices);
		m_pIndices = tmpIndices;
		m_pIndices.position(0);

		initIndices();

		if (withColorArray_) {
			ByteBuffer cbb = ByteBuffer.allocateDirect(4*ccColor4B.size * newCapacity * 4);
			cbb.order(ByteOrder.nativeOrder());
			FloatBuffer tmpColors = cbb.asFloatBuffer();
			tmpColors.put(colors);
			colors = tmpColors;
			colors.position(0);
		}
	}

	/**
	 * Used internally by CCParticleBatchNode
	 * don't use this unless you know what you're doing
	 * @since 1.1
	 */
	public void increaseTotalQuadsWith(int amount) {
		// TODO
		;
	}

	/** Moves an amount of quads from oldIndex at newIndex
	 * @since v1.1
	 */
	public void moveQuadsFromIndex(int oldIndex, int amount, int newIndex) {
		// TODO
		;
	}

	/**
	 * Moves quads from index till totalQuads to the newIndex
	 * Used internally by CCParticleBatchNode
	 * This method doesn't enlarge the array if newIndex + quads to be moved > capacity
	 *@since 1.1
	 */
	public void moveQuadsFromIndex(int index, int newIndex) {
		// TODO
		;
	}

	/**
	 * Ensures that after a realloc quads are still empty
	 * Used internally by CCParticleBatchNode
	 * @since 1.1
	 */
	public void fillWithEmptyQuadsFromIndex(int index, int amount) {
		// TODO
		;
	}

	/** draws n quads
	 * n can't be greater than the capacity of the Atlas
	 */
	public void drawNumberOfQuads(int n) {
		// TODO
		;
	}

	/** draws n quads from an index (offset).
	 * n + start can't be greater than the capacity of the atlas
	 * 
	 * @since v1.0
	 */
	public void drawNumberOfQuads(int n, int start) {
		// TODO
		;
	}

	/** draws all the Atlas's Quads
	 */
	public void drawQuads() {
		// TODO
		;
	}

	/** listen the event that coming to foreground on Android
	 */
	public void listenBackToForeground(Object obj) {
		// TODO
		;
	}

	private void setupIndices() {
		// TODO
		;
	}

	private void mapBuffers() {
		// TODO
		;
	}

	private void setupVBOandVAO() {
		// TODO
		if(ccConfig.CC_TEXTURE_ATLAS_USE_VAO != 0) {
			;
		}
	}

	private void setupVBO() {
		// TODO
		if(ccConfig.CC_TEXTURE_ATLAS_USE_VAO == 0) {
			;
		}
	}


	// TODO legacy -->

    /** buffers that are going to be rendered */
    private FloatBuffer textureCoordinates;
    public FloatBuffer getTexCoordsBuffer() {
    	return textureCoordinates;
    }
    
    private FloatBuffer vertexCoordinates;
    public FloatBuffer getVertexBuffer() {
    	return vertexCoordinates;
    }
    
    private FloatBuffer colors;

    private boolean withColorArray_;

    public boolean withColorArray() {
        return withColorArray_;
    }
    /** creates a TextureAtlas with an filename and with an initial capacity for Quads.
     * The TextureAtlas capacity can be increased in runtime.
     */
    public static CCTextureAtlas textureAtlas(String file, int capacity) {
    	return new CCTextureAtlas(file, capacity);
    }

    /** initializes a TextureAtlas with a filename and with a certain capacity for Quads.
     * The TextureAtlas capacity can be increased in runtime.
     *
     * WARNING: Do not reinitialize the TextureAtlas because it will leak memory (issue #706)
     */
    public CCTextureAtlas(String file, int n) {
        this(CCTextureCache.sharedTextureCache().addImage(file), n);
    }

    /** creates a TextureAtlas with a previously initialized Texture2D object, and
     * with an initial capacity for n Quads. 
     * The TextureAtlas capacity can be increased in runtime.
     */
    public static CCTextureAtlas textureAtlas(CCTexture2D tex, int capacity) {
    	return new CCTextureAtlas(tex, capacity);
    }

    /** initializes a TextureAtlas with a previously initialized Texture2D object, and
     * with an initial capacity for Quads. 
     * The TextureAtlas capacity can be increased in runtime.
     *
     * WARNING: Do not reinitialize the TextureAtlas because it will leak memory (issue #706)
     */
    public CCTextureAtlas(CCTexture2D tex, int n) {
        m_uCapacity = n;
        m_pTexture = tex;
        m_uTotalQuads = 0;

        withColorArray_ = false;

        ByteBuffer tbb = ByteBuffer.allocateDirect(ccQuad2.size * m_uCapacity * 4);
        tbb.order(ByteOrder.nativeOrder());
        textureCoordinates = tbb.asFloatBuffer();

        ByteBuffer vbb = ByteBuffer.allocateDirect(ccQuad3.size * m_uCapacity * 4);
        vbb.order(ByteOrder.nativeOrder());
        vertexCoordinates = vbb.asFloatBuffer();

        ByteBuffer isb = ByteBuffer.allocateDirect(6 * m_uCapacity * 2);
        isb.order(ByteOrder.nativeOrder());
        m_pIndices = isb.asShortBuffer();

        initIndices();
    }

    public String toString() {
        return new CCFormatter().format("<%s = %08X | getTotalQuads = %i>", CCTextureAtlas.class, this, m_uTotalQuads);
    }

    public void initColorArray() {
        if (!withColorArray_) {
            // default color: 255,255,255,255
        	// modify by zt: A Texture in TextureAtlas need four colors for four vertices
            ByteBuffer cbb = ByteBuffer.allocateDirect(4 * m_uCapacity *ccColor4B.size * 4);
            cbb.order(ByteOrder.nativeOrder());
            colors = cbb.asFloatBuffer();
            for (int i = 0; i < 4 * ccColor4B.size * m_uCapacity * 1; i++) {
                colors.put(i, 1.0f);
            }
            colors.position(0);

            withColorArray_ = true;
        }
    }

    public void initIndices() {
        for (int i = 0; i < m_uCapacity; i++) {
        	if (ccConfig.CC_TEXTURE_ATLAS_USE_TRIANGLE_STRIP) {
        		m_pIndices.put((short) (i * 6 + 0), (short) (i * 4 + 0));
        		m_pIndices.put((short) (i * 6 + 1), (short) (i * 4 + 0));
        		m_pIndices.put((short) (i * 6 + 2), (short) (i * 4 + 2));
        		m_pIndices.put((short) (i * 6 + 3), (short) (i * 4 + 1));
        		m_pIndices.put((short) (i * 6 + 4), (short) (i * 4 + 3));
        		m_pIndices.put((short) (i * 6 + 5), (short) (i * 4 + 3));
        	} else {
        		m_pIndices.put((short) (i * 6 + 0), (short) (i * 4 + 0));
        		m_pIndices.put((short) (i * 6 + 1), (short) (i * 4 + 1));
        		m_pIndices.put((short) (i * 6 + 2), (short) (i * 4 + 2));

        		// inverted index.
        		m_pIndices.put((short) (i * 6 + 5), (short) (i * 4 + 1));
        		m_pIndices.put((short) (i * 6 + 4), (short) (i * 4 + 2));
        		m_pIndices.put((short) (i * 6 + 3), (short) (i * 4 + 3));
        	}
        }
        m_pIndices.position(0);
    }

    /** updates a Quad (texture, vertex and color) at a certain index
     * index must be between 0 and the atlas capacity - 1
     @since v0.8
     */
    public void updateQuad(FloatBuffer texCordBuffer, FloatBuffer vertexBuffer, int index) {
        assert (index >= 0 && index < m_uCapacity) : "update quad with texture_: Invalid index";

        m_uTotalQuads = Math.max(index + 1, m_uTotalQuads);

        putTexCoords(texCordBuffer, index);
        putVertex(vertexBuffer, index);
    }
    
    public void updateQuad(FloatBuffer texCordBuffer, float[] vertexData, int index) {
        assert (index >= 0 && index < m_uCapacity) : "update quad with texture_: Invalid index";

        m_uTotalQuads = Math.max(index + 1, m_uTotalQuads);

        putTexCoords(texCordBuffer, index);
        putVertex(vertexCoordinates, vertexData, index);
    }

    public void updateQuad(ccQuad2 texQuad, ccQuad3 vertexQuad, int index) {
        assert (index >= 0 && index < m_uCapacity) : "update quad with texture_: Invalid index";

        m_uTotalQuads = Math.max(index + 1, m_uTotalQuads);

        putTexCoords(textureCoordinates, texQuad, index);
        putVertex(vertexCoordinates, vertexQuad, index);
    }

    public void updateColor(ccColor4B[] color, int index) {
        assert (index >= 0 && index < m_uCapacity) : "update color with quad color: Invalid index";

        m_uTotalQuads = Math.max(index + 1, m_uTotalQuads);

        if (!withColorArray_)
            initColorArray();

        if (withColorArray_)
            putColor(colors, color, index);
    }


    /** Inserts a Quad (texture, vertex and color) at a certain index
    index must be between 0 and the atlas capacity - 1
    @since v0.8
    */
    public void insertQuad(FloatBuffer texCordBuffer, FloatBuffer vertexBuffer, int index) {
        assert (index >= 0 && index < m_uCapacity) : "insert quad with texture_: Invalid index";

        m_uTotalQuads++;

        int remaining = (m_uTotalQuads - 1) - index;

        // last object doesn't need to be moved
        if (remaining > 0) {
            // tex coordinates
            arraycopyTexture(textureCoordinates, index, textureCoordinates, index + 1, remaining);

            // vertexCoordinates_
            arraycopyVertex(vertexCoordinates, index, vertexCoordinates, index + 1, remaining);

            // colors_
            if (withColorArray_) {
                arraycopyColor(colors, index, colors, index + 1, remaining);
            }
        }
        putTexCoords(texCordBuffer, index);
        putVertex(vertexBuffer, index);
    }
 



    /** draws all the Atlas's Quads
     */
    public void drawQuads(GL10 gl) {
        draw(gl, m_uTotalQuads);
    }

    /** draws n quads
     * n can't be greater than the capacity of the Atlas
     */
    public void draw(GL10 gl, int n) {
        m_pTexture.loadTexture(gl);

//        // bug fix in case texture name = 0
//        texture_.checkName();
        gl.glBindTexture(GL10.GL_TEXTURE_2D, m_pTexture.name());

        gl.glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        gl.glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexCoordinates);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureCoordinates);

        if (withColorArray_)
            gl.glColorPointer(4, GL10.GL_FLOAT, 0, colors);

        if (ccConfig.CC_TEXTURE_ATLAS_USE_TRIANGLE_STRIP) {
        	gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, n * 6, GL10.GL_UNSIGNED_SHORT, m_pIndices);
        } else {
        	gl.glDrawElements(GL10.GL_TRIANGLES, n * 6, GL10.GL_UNSIGNED_SHORT, m_pIndices);
        }

    }
    
    private float[] getTexCoords(FloatBuffer src, int index) {
        float[] quadT = new float[ccQuad2.size];
        final int base = index * ccQuad2.size;
        for (int i = 0; i < ccQuad2.size; i++) {
            quadT[i] = src.get(base + i);
        }
        return quadT;
    }

    protected void putTexCoords(FloatBuffer dst, ccQuad2 quadT, int index) {
    	final int base = index * ccQuad2.size;
    	dst.position(base);
    	
    	tmpFloatArray[0] = quadT.tl_x; 
    	tmpFloatArray[1] = quadT.tl_y;
    	tmpFloatArray[2] = quadT.tr_x;
    	tmpFloatArray[3] = quadT.tr_y;
    	tmpFloatArray[4] = quadT.bl_x;
    	tmpFloatArray[5] = quadT.bl_y;
    	tmpFloatArray[6] = quadT.br_x;
    	tmpFloatArray[7] = quadT.br_y;
    	org.cocos2d.utils.BufferUtils.copyFloats(tmpFloatArray, 0, dst, 8);
//    	dst.put(quadT.tl_x);
//    	dst.put(quadT.tl_y);
//    	dst.put(quadT.tr_x);
//    	dst.put(quadT.tr_y);
//    	dst.put(quadT.bl_x);
//    	dst.put(quadT.bl_y);
//    	dst.put(quadT.br_x);
//    	dst.put(quadT.br_y);
    	
        dst.position(0);
    }
    
    protected void putTexCoords(FloatBuffer dst, float[] quadT, int index) {
    	final int base = index * ccQuad2.size;
    	dst.position(base);
    	org.cocos2d.utils.BufferUtils.copyFloats(quadT, 0, dst, quadT.length);
//    	dst.put(quadT);
        dst.position(0);
    }
    
    public void putTexCoords(FloatBuffer src, int index) {
    	final int base = index * ccQuad2.size;
    	textureCoordinates.position(base);
    	
    	BufferUtils.copy(src, textureCoordinates, src.capacity());
    	// if textureCoordinates.put(src) then allocation is performed
    	// this solution not efficient may be, need to find best way for copy Buffers
//    	int num = src.capacity();
//    	for(int i = 0; i < num; i++)
//    		textureCoordinates.put(src.get());
    	
    	src.position(0);
    	textureCoordinates.position(0);
    }

    protected void putVertex(FloatBuffer src, int index) {
    	final int base = index * ccQuad3.size;
    	vertexCoordinates.position(base);
    	vertexCoordinates.put(src);
    	
    	src.position(0);
        vertexCoordinates.position(0);
    }
    
    private float[] getVertex(FloatBuffer src, int index) {
        float[] quadV = new float[ccQuad3.size];
        final int base = index * ccQuad3.size;
        for (int i = 0; i < ccQuad3.size; i++) {
            quadV[i] = src.get(base + i);
        }
        return quadV;
    }
    
    private static float[] tmpFloatArray = new float[12];
    
    public void putVertex(FloatBuffer dst, ccQuad3 quadV, int index) {
    	
    	tmpFloatArray[0] = quadV.bl_x; 
    	tmpFloatArray[1] = quadV.bl_y;
    	tmpFloatArray[2] = quadV.bl_z;
    	tmpFloatArray[3] = quadV.br_x;
    	tmpFloatArray[4] = quadV.br_y;
    	tmpFloatArray[5] = quadV.br_z;
    	tmpFloatArray[6] = quadV.tl_x;
    	tmpFloatArray[7] = quadV.tl_y;
    	tmpFloatArray[8] = quadV.tl_z;
    	tmpFloatArray[9] = quadV.tr_x;
    	tmpFloatArray[10] = quadV.tr_y;
    	tmpFloatArray[11] = quadV.tr_z;
    			
    	final int base = index * ccQuad3.size;
    	dst.position(base);
    	org.cocos2d.utils.BufferUtils.copyFloats(tmpFloatArray, 0, dst, 12);
        dst.position(0);
    }
    
    public void putVertex(FloatBuffer dst, float[] quadV, int index) {
    	final int base = index * ccQuad3.size;
    	
    	dst.position(base);
    	org.cocos2d.utils.BufferUtils.copyFloats(quadV, 0, dst, quadV.length);
//    	dst.put(quadV);
        dst.position(0);
    }

    private ccColor4B [] getColor(FloatBuffer src, int index) {
    	ccColor4B [] color = new ccColor4B[4];
        
        for(int j=0; j<4; ++j) {
            color[j].r = (int)(255 * src.get(index * ccColor4B.size *4 + 4*j + 0));
            color[j].g = (int)(255 * src.get(index * ccColor4B.size *4 + 4*j + 1));
            color[j].b = (int)(255 * src.get(index * ccColor4B.size *4 + 4*j + 2));
            color[j].a = (int)(255 * src.get(index * ccColor4B.size *4 + 4*j + 3));
        }

        return color;
    }

    private void putColor(FloatBuffer dst, ccColor4B color[], int index) {
    	for(int j=0; j<4; ++j) {
    		dst.put(index * ccColor4B.size * 4 + 4*j + 0, color[j].r/255.f);
    		dst.put(index * ccColor4B.size * 4 + 4*j + 1, color[j].g/255.f);
    		dst.put(index * ccColor4B.size * 4 + 4*j + 2, color[j].b/255.f);
    		dst.put(index * ccColor4B.size * 4 + 4*j + 3, color[j].a/255.f);
        }
    	dst.position(0);
    }

    private void arraycopyTexture(FloatBuffer src, int srcPos, FloatBuffer dst, int dstPos, int length) {
        if (src == dst) {
            memmoveFloat(src, srcPos * ccQuad2.size, dst, dstPos * ccQuad2.size, length * ccQuad2.size);
        } else {
            memcopyFloat(src, srcPos * ccQuad2.size, dst, dstPos * ccQuad2.size, length * ccQuad2.size);
        }
    }

    private void arraycopyVertex(FloatBuffer src, int srcPos, FloatBuffer dst, int dstPos, int length) {
        if (src == dst) {
            memmoveFloat(src, srcPos * ccQuad3.size, dst, dstPos * ccQuad3.size, length * ccQuad3.size);
        } else {
            memcopyFloat(src, srcPos * ccQuad3.size, dst, dstPos * ccQuad3.size, length * ccQuad3.size);
        }
    }

    private void arraycopyColor(FloatBuffer src, int srcPos, FloatBuffer dst, int dstPos, int length) {
        if (src == dst) {
            memmoveFloat(src, srcPos * ccColor4B.size * 4, dst, dstPos * ccColor4B.size*4, length * ccColor4B.size * 4);
        } else {
            memcopyFloat(src, srcPos * ccColor4B.size * 4, dst, dstPos * ccColor4B.size*4, length * ccColor4B.size * 4);
        }
    }

    private void memmoveFloat(FloatBuffer src, int from, FloatBuffer dst, int to, int size) {
        if ( to < from ) {
			for (int i = 0; i < size; i++) {
				dst.put(i + to, src.get(i + from));
			}
//            memcopyFloat(src, from, dst, to, size);
        } else {
            for (int i = size - 1; i >= 0; i--) {
                dst.put(i + to, src.get(i + from));
            }
        }
    }

    private void memcopyFloat(FloatBuffer src, int from, FloatBuffer dst, int to, int size) {
    	src.position(from);
    	dst.position(to);
    	BufferUtils.copy(src, dst, size);
//        for (int i = 0; i < size; i++) {
//            dst.put(i + to, src.get(i + from));
//        }
    }

    public static void memmoveByte(ByteBuffer src, int from, ByteBuffer dst, int to, int size) {
        if ( to < from ) {
        	for (int i = 0; i < size; i++) {
				dst.put(i + to, src.get(i + from));
			}
//            memcopyByte(src, from, dst, to, size);
        } else {
            for (int i = size - 1; i >= 0; i--) {
                dst.put(i + to, src.get(i + from));
            }
        }
    }

    public static void memcopyByte(ByteBuffer src, int from, ByteBuffer dst, int to, int size) {
    	src.position(from);
    	dst.position(to);
    	BufferUtils.copy(src, dst, size);
//        for (int i = 0; i < size; i++) {
//            dst.put(i + to, src.get(i + from));
//        }
    }

}

//end of textures group
/// @}
