package org.cocos2d.nodes;

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.shaders.CCShaderCache;
import org.cocos2d.types.CGSize;
import org.cocos2d.include.ccTypes.ccBlendFunc;
import org.cocos2d.include.ccTypes.ccColor3B;

/**
 * @addtogroup base_nodes
 * @{
 */

/** @brief CCAtlasNode is a subclass of CCNode that implements the CCRGBAProtocol and CCTextureProtocol protocol
 * 
 * It knows how to render a TextureAtlas object.
 * If you are going to render a TextureAtlas consider subclassing CCAtlasNode (or a subclass of CCAtlasNode)
 * 
 * All features from CCNode are valid, plus the following features:
 * - opacity and RGB colors
 */
public class CCAtlasNode extends CCNodeRGBA implements CCTextureProtocol {
	private static final String TAG = CCAtlasNode.class.getSimpleName();

	//! chars per row
	protected int m_uItemsPerRow;
	//! chars per column
	protected int m_uItemsPerColumn;

	//! width of each char
	protected int m_uItemWidth;
	//! height of each char
	protected int m_uItemHeight;

	protected ccColor3B m_tColorUnmodified = new ccColor3B();

	protected CCTextureAtlas m_pTextureAtlas;

	public CCTextureAtlas getTextureAtlas() {
		return m_pTextureAtlas;
	}

	public void setTextureAtlas(CCTextureAtlas textureAtlas) {
		m_pTextureAtlas = textureAtlas;
	}

	// protocol variables
	protected boolean m_bIsOpacityModifyRGB;

	protected ccBlendFunc m_tBlendFunc = new ccBlendFunc();

	public ccBlendFunc getBlendFunc() {
		return m_tBlendFunc;
	}

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

	// quads to draw
	protected int m_uQuadsToDraw;

	public int getQuadsToDraw() {
		return m_uQuadsToDraw;
	}

	public void setQuadsToDraw(int uQuadsToDraw) {
		m_uQuadsToDraw = uQuadsToDraw;
	}

	// color uniform
	int m_nUniformColor;

	public CCAtlasNode() {
		m_uItemsPerRow = 0;
		m_uItemsPerColumn = 0;
		m_uItemWidth = 0;
		m_uItemHeight = 0;
		m_pTextureAtlas = null;
		m_bIsOpacityModifyRGB = false;
		m_uQuadsToDraw = 0;
		m_nUniformColor = 0;
	}

	/** creates a CCAtlasNode  with an Atlas file the width and height of each item and the quantity of items to render*/
	public static CCAtlasNode create(String tile, int tileWidth, int tileHeight, int itemsToRender) {
		CCAtlasNode pRet = new CCAtlasNode();
		if(pRet.initWithTileFile(tile, tileWidth, tileHeight, itemsToRender)) {
			return pRet;
		}
		pRet = null;
		return null;
	}

	/** initializes an CCAtlasNode  with an Atlas file the width and height of each item and the quantity of items to render*/
	public boolean initWithTileFile(String tile, int tileWidth, int tileHeight, int itemsToRender) {
		assert tile != null : "title should not be null";
		CCTexture2D texture = CCTextureCache.sharedTextureCache().addImage(tile);
		return initWithTexture(texture, tileWidth, tileHeight, itemsToRender);
	}

	/** initializes an CCAtlasNode  with a texture the width and height of each item measured in points and the quantity of items to render*/
	public boolean initWithTexture(CCTexture2D texture, int tileWidth, int tileHeight, int itemsToRender) {
		m_uItemWidth  = tileWidth;
		m_uItemHeight = tileHeight;

		m_tColorUnmodified.set(ccColor3B.ccWHITE);
		m_bIsOpacityModifyRGB = true;

		m_tBlendFunc.set(ccConfig.CC_BLEND_SRC, ccConfig.CC_BLEND_DST);

// TODO		m_pTextureAtlas = new CCTextureAtlas();
// TODO		m_pTextureAtlas.initWithTexture(texture, itemsToRender);
		m_pTextureAtlas = CCTextureAtlas.textureAtlas(texture, itemsToRender);

		if(m_pTextureAtlas == null) {
			ccMacros.CCLOG(TAG, "cocos2d: Could not initialize CCAtlasNode. Invalid Texture.");
			return false;
		}

		this.updateBlendFunc();
		this.updateOpacityModifyRGB();

		this.calculateMaxItems();

		// TODO legacy
		calculateTexCoordsSteps();

		m_uQuadsToDraw = itemsToRender;

		// shader stuff
// TODO		setShaderProgram(CCShaderCache.sharedShaderCache().programForKey(kCCShader_PositionTexture_uColor));
// TODO		m_nUniformColor = glGetUniformLocation(getShaderProgram().getProgram(), "u_color");

		return true;
	}

	/** updates the Atlas (indexed vertex array).
	 * Shall be overriden in subclasses
	 */
	public void updateAtlasValues() {
		assert false : "CCAtlasNode:Abstract updateAtlasValue not overridden";
	}

	@Override
	public void draw(GL10 gl) {

		// TODO legacy -->

		// 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_TEXTURE_COORD_ARRAY
		// Unneeded states: GL_COLOR_ARRAY
		gl.glDisableClientState(GL10.GL_COLOR_ARRAY);

		gl.glColor4f(_displayedColor.r / 255f, _displayedColor.g / 255f, _displayedColor.b / 255f, _displayedOpacity / 255f);

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

		m_pTextureAtlas.drawQuads(gl);

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


		// is this chepear than saving/restoring color state ?
		// XXX: There is no need to restore the color to (255,255,255,255). Objects should use the color
		// XXX: that they need
		//    	glColor4ub( 255, 255, 255, 255);

		// restore default GL state
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
	}

	// CC Texture protocol

	/** returns the used texture*/
	public CCTexture2D getTexture() {
		return m_pTextureAtlas.getTexture();
	}

	/** sets a new texture. it will be retained*/
	public void setTexture(CCTexture2D texture) {
		m_pTextureAtlas.setTexture(texture);
		this.updateBlendFunc();
		this.updateOpacityModifyRGB();
	}

	@Override
	public boolean isOpacityModifyRGB() {
		return m_bIsOpacityModifyRGB;
	}

	@Override
	public void setOpacityModifyRGB(boolean isOpacityModifyRGB) {
		ccColor3B oldColor = this.getColor();
		m_bIsOpacityModifyRGB = isOpacityModifyRGB;
		this.setColor(oldColor);
	}

	@Override
	public ccColor3B getColor() {
		if (m_bIsOpacityModifyRGB) {
			return m_tColorUnmodified;
		}
		return super.getColor();
	}

	@Override
	public void setColor(final ccColor3B color) {
		ccColor3B tmp = new ccColor3B(color);
		m_tColorUnmodified.set(color);

		if(m_bIsOpacityModifyRGB) {
			tmp.r = tmp.r * _displayedOpacity/255;
			tmp.g = tmp.g * _displayedOpacity/255;
			tmp.b = tmp.b * _displayedOpacity/255;
		}
		super.setColor(tmp);
	}

	public void setOpacity(int opacity) {
		super.setOpacity(opacity);

		// special opacity for premultiplied textures
		if(m_bIsOpacityModifyRGB) {
			this.setColor(m_tColorUnmodified);
		}
	}

	private void calculateMaxItems() {
		final CGSize s = m_pTextureAtlas.getTexture().getContentSize();
		m_uItemsPerColumn = (int)(s.height / m_uItemHeight);
		m_uItemsPerRow = (int)(s.width / m_uItemWidth);
	}

	public void updateBlendFunc() {
		if(! (m_pTextureAtlas.getTexture().hasPremultipliedAlpha() )) {
			m_tBlendFunc.set(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
		}
	}

	public void updateOpacityModifyRGB() {
		m_bIsOpacityModifyRGB = m_pTextureAtlas.getTexture().hasPremultipliedAlpha();
	}


	// TODO legacy -->

    /// texture coordinate x increment
    protected float texStepX;
    /// texture coordinate y increment
    protected float texStepY;

    private void calculateTexCoordsSteps() {
        CCTexture2D tex = m_pTextureAtlas.getTexture();
        texStepX = m_uItemWidth / (float) tex.pixelsWide();
        texStepY = m_uItemHeight / (float) tex.pixelsHigh();
    }
}

// end of base_node group
/// @}
