/*
 * cocos2d for iPhone: http://www.cocos2d-iphone.org
 *
 * Copyright (c) 2010 Lam Pham
 *
 * 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.
 *
 */

#import "CCProgressTimer.h"

#import "ccMacros.h"
#import "CCTextureCache.h"
#import "Support/CGPointExtension.h"



#define kProgressTextureCoordsCount 4
//  kProgressTextureCoords holds points {0,0} {0,1} {1,1} {1,0} we can represent it as bits
const char kProgressTextureCoords = 0x1e;

@interface CCProgressTimer (Internal)

-(void)updateProgress;
-(void)updateBar;
-(void)updateRadial;
-(void)updateColor;
-(CGPoint)boundaryTexCoord:(char)index;
@end


@implementation CCProgressTimer
@synthesize percentage = percentage_;
@synthesize sprite = sprite_;
@synthesize type = type_;

+(id)progressWithFile:(NSString*) filename
{
	return [[[self alloc]initWithFile:filename] autorelease];
}
-(id)initWithFile:(NSString*) filename
{
	return [self initWithTexture:[[CCTextureCache sharedTextureCache] addImage: filename]];
}

+(id)progressWithTexture:(CCTexture2D*) texture
{
	return [[[self alloc]initWithTexture:texture] autorelease];
}
-(id)initWithTexture:(CCTexture2D*) texture
{
	if(( self = [super init] )){
		self.sprite = [CCSprite spriteWithTexture:texture];
		percentage_ = 0.f;
		vertexData_ = NULL;
		vertexDataCount_ = 0;
		self.anchorPoint = ccp(.5f,.5f);
		self.contentSize = sprite_.contentSize;
		self.type = kCCProgressTimerTypeRadialCCW;
	}
	return self;
}
-(void)dealloc
{
	if(vertexData_)
		free(vertexData_);
	
	[sprite_ release];
	[super dealloc];
}

-(void)setPercentage:(float) percentage
{
	if(percentage_ != percentage) {
        percentage_ = clampf( percentage, 0, 100);		
		[self updateProgress];
	}
}
-(void)setSprite:(CCSprite *)newSprite
{
	if(sprite_ != newSprite){
		[sprite_ release]; 
		sprite_ = [newSprite retain];
		
		//	Everytime we set a new sprite, we free the current vertex data
		if(vertexData_){
			free(vertexData_);
			vertexData_ = NULL;
			vertexDataCount_ = 0;
		}
	}
}
-(void)setType:(CCProgressTimerType)newType
{
	if (newType != type_) {
		
		//	release all previous information
		if(vertexData_){
			free(vertexData_);
			vertexData_ = NULL;
			vertexDataCount_ = 0;
		}
		type_ = newType;
	}
}
@end

@implementation CCProgressTimer(Internal)

///
//	@returns the vertex position from the texture coordinate
///
-(ccVertex2F)vertexFromTexCoord:(CGPoint) texCoord
{
	CGPoint tmp;
	ccVertex2F ret;
	if (sprite_.texture) {
		CCTexture2D *texture = [sprite_ texture];
		CGSize texSize = [texture contentSizeInPixels];
		tmp = ccp(texSize.width * texCoord.x/texture.maxS,
				   texSize.height * (1 - (texCoord.y/texture.maxT)));
	} else
		tmp = CGPointZero;
	
	ret.x = tmp.x;
	ret.y = tmp.y;
	return ret;
}

-(void)updateColor
{
	GLubyte op = sprite_.opacity;
	ccColor3B c3b = sprite_.color;
	
	ccColor4B color = { c3b.r, c3b.g, c3b.b, op };
	if([sprite_.texture hasPremultipliedAlpha]){
		color.r *= op/255;
		color.g *= op/255;
		color.b *= op/255;
	}
	
	if(vertexData_){
		for (int i=0; i < vertexDataCount_; ++i) {
			vertexData_[i].colors = color;
		}
	}
}

-(void)updateProgress
{
	switch (type_) {
		case kCCProgressTimerTypeRadialCW:
		case kCCProgressTimerTypeRadialCCW:
			[self updateRadial];
			break;
		case kCCProgressTimerTypeHorizontalBarLR:
		case kCCProgressTimerTypeHorizontalBarRL:
		case kCCProgressTimerTypeVerticalBarBT:
		case kCCProgressTimerTypeVerticalBarTB:
			[self updateBar];
			break;
		default:
			break;
	}
}

///
//	Update does the work of mapping the texture onto the triangles
//	It now doesn't occur the cost of free/alloc data every update cycle.
//	It also only changes the percentage point but no other points if they have not
//	been modified.
//	
//	It now deals with flipped texture. If you run into this problem, just use the
//	sprite property and enable the methods flipX, flipY.
///
-(void)updateRadial
{		
	//	Texture Max is the actual max coordinates to deal with non-power of 2 textures
	CGPoint tMax = ccp(sprite_.texture.maxS,sprite_.texture.maxT);
	
	//	Grab the midpoint
	CGPoint midpoint = ccpCompMult(self.anchorPoint, tMax);
	
	float alpha = percentage_ / 100.f;
	
	//	Otherwise we can get the angle from the alpha
	float angle = 2.f*((float)M_PI) * ( type_ == kCCProgressTimerTypeRadialCW? alpha : 1.f - alpha);
	
	//	We find the vector to do a hit detection based on the percentage
	//	We know the first vector is the one @ 12 o'clock (top,mid) so we rotate 
	//	from that by the progress angle around the midpoint pivot
	CGPoint topMid = ccp(midpoint.x, 0.f);
	CGPoint percentagePt = ccpRotateByAngle(topMid, midpoint, angle);
	
	
	int index = 0;
	CGPoint hit = CGPointZero;
	
	if (alpha == 0.f) {
		//	More efficient since we don't always need to check intersection
		//	If the alpha is zero then the hit point is top mid and the index is 0.
		hit = topMid;
		index = 0;
	} else if (alpha == 1.f) {
		//	More efficient since we don't always need to check intersection
		//	If the alpha is one then the hit point is top mid and the index is 4.
		hit = topMid;
		index = 4;
	} else {
		//	We run a for loop checking the edges of the texture to find the
		//	intersection point
		//	We loop through five points since the top is split in half
		
		float min_t = FLT_MAX;
		
		for (int i = 0; i <= kProgressTextureCoordsCount; ++i) {
			int pIndex = (i + (kProgressTextureCoordsCount - 1))%kProgressTextureCoordsCount;
			
			CGPoint edgePtA = ccpCompMult([self boundaryTexCoord:i % kProgressTextureCoordsCount],tMax);
			CGPoint edgePtB = ccpCompMult([self boundaryTexCoord:pIndex],tMax);
			
			//	Remember that the top edge is split in half for the 12 o'clock position
			//	Let's deal with that here by finding the correct endpoints
			if(i == 0){
				edgePtB = ccpLerp(edgePtA,edgePtB,.5f);
			} else if(i == 4){
				edgePtA = ccpLerp(edgePtA,edgePtB,.5f);
			}
			
			//	s and t are returned by ccpLineIntersect
			float s = 0, t = 0;
			if(ccpLineIntersect(edgePtA, edgePtB, midpoint, percentagePt, &s, &t))
			{
				
				//	Since our hit test is on rays we have to deal with the top edge
				//	being in split in half so we have to test as a segment
				if ((i == 0 || i == 4)) {
					//	s represents the point between edgePtA--edgePtB
					if (!(0.f <= s && s <= 1.f)) {
						continue;
					}
				}
				//	As long as our t isn't negative we are at least finding a 
				//	correct hitpoint from midpoint to percentagePt.
				if (t >= 0.f) {
					//	Because the percentage line and all the texture edges are
					//	rays we should only account for the shortest intersection
					if (t < min_t) {
						min_t = t;
						index = i;
					}
				}
			}
		}
		
		//	Now that we have the minimum magnitude we can use that to find our intersection
		hit = ccpAdd(midpoint, ccpMult(ccpSub(percentagePt, midpoint),min_t));
		
	}
	
	
	//	The size of the vertex data is the index from the hitpoint
	//	the 3 is for the midpoint, 12 o'clock point and hitpoint position.
	
	BOOL sameIndexCount = YES;
	if(vertexDataCount_ != index + 3){
		sameIndexCount = NO;
		if(vertexData_){
			free(vertexData_);
			vertexData_ = NULL;
			vertexDataCount_ = 0;
		}
	}
	
	
	if(!vertexData_) {
		vertexDataCount_ = index + 3;
		vertexData_ = malloc(vertexDataCount_ * sizeof(ccV2F_C4B_T2F));
		NSAssert( vertexData_, @"CCProgressTimer. Not enough memory");
		
		[self updateColor];
	}
	
	if (!sameIndexCount) {
		
		//	First we populate the array with the midpoint, then all 
		//	vertices/texcoords/colors of the 12 'o clock start and edges and the hitpoint
		vertexData_[0].texCoords = (ccTex2F){midpoint.x, midpoint.y};
		vertexData_[0].vertices = [self vertexFromTexCoord:midpoint];
		
		vertexData_[1].texCoords = (ccTex2F){midpoint.x, 0.f};
		vertexData_[1].vertices = [self vertexFromTexCoord:ccp(midpoint.x, 0.f)];
		
		for(int i = 0; i < index; ++i){
			CGPoint texCoords = ccpCompMult([self boundaryTexCoord:i], tMax);
			
			vertexData_[i+2].texCoords = (ccTex2F){texCoords.x, texCoords.y};
			vertexData_[i+2].vertices = [self vertexFromTexCoord:texCoords];
		}
		
		//	Flip the texture coordinates if set
		if (sprite_.flipY || sprite_.flipX) {
			for(int i = 0; i < vertexDataCount_ - 1; ++i){
				if (sprite_.flipX) {
					vertexData_[i].texCoords.u = tMax.x - vertexData_[i].texCoords.u;
				}
				if(sprite_.flipY){
					vertexData_[i].texCoords.v = tMax.y - vertexData_[i].texCoords.v;
				}
			}
		}
	}
	
	//	hitpoint will go last
	vertexData_[vertexDataCount_ - 1].texCoords = (ccTex2F){hit.x, hit.y};
	vertexData_[vertexDataCount_ - 1].vertices = [self vertexFromTexCoord:hit];
	
	if (sprite_.flipY || sprite_.flipX) {
		if (sprite_.flipX) {
			vertexData_[vertexDataCount_ - 1].texCoords.u = tMax.x - vertexData_[vertexDataCount_ - 1].texCoords.u;
		}
		if(sprite_.flipY){
			vertexData_[vertexDataCount_ - 1].texCoords.v = tMax.y - vertexData_[vertexDataCount_ - 1].texCoords.v;
		}
	}
}

///
//	Update does the work of mapping the texture onto the triangles for the bar
//	It now doesn't occur the cost of free/alloc data every update cycle.
//	It also only changes the percentage point but no other points if they have not
//	been modified.
//	
//	It now deals with flipped texture. If you run into this problem, just use the
//	sprite property and enable the methods flipX, flipY.
///
-(void)updateBar
{	
	
	float alpha = percentage_ / 100.f;
	
	CGPoint tMax = ccp(sprite_.texture.maxS,sprite_.texture.maxT);
	
	unsigned char vIndexes[2] = {0,0};
	unsigned char index = 0;
	
	//	We know vertex data is always equal to the 4 corners
	//	If we don't have vertex data then we create it here and populate
	//	the side of the bar vertices that won't ever change.
	if (!vertexData_) {
		vertexDataCount_ = kProgressTextureCoordsCount;
		vertexData_ = malloc(vertexDataCount_ * sizeof(ccV2F_C4B_T2F));
		NSAssert( vertexData_, @"CCProgressTimer. Not enough memory");
		
		if(type_ == kCCProgressTimerTypeHorizontalBarLR){
			vertexData_[vIndexes[0] = 0].texCoords = (ccTex2F){0,0};
			vertexData_[vIndexes[1] = 1].texCoords = (ccTex2F){0, tMax.y};
		}else if (type_ == kCCProgressTimerTypeHorizontalBarRL) {
			vertexData_[vIndexes[0] = 2].texCoords = (ccTex2F){tMax.x, tMax.y};
			vertexData_[vIndexes[1] = 3].texCoords = (ccTex2F){tMax.x, 0.f};
		}else if (type_ == kCCProgressTimerTypeVerticalBarBT) {
			vertexData_[vIndexes[0] = 1].texCoords = (ccTex2F){0, tMax.y};
			vertexData_[vIndexes[1] = 3].texCoords = (ccTex2F){tMax.x, tMax.y};
		}else if (type_ == kCCProgressTimerTypeVerticalBarTB) {
			vertexData_[vIndexes[0] = 0].texCoords = (ccTex2F){0, 0};
			vertexData_[vIndexes[1] = 2].texCoords = (ccTex2F){tMax.x, 0};
		}
		
		index = vIndexes[0];
		vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
		
		index = vIndexes[1];
		vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
		
		if (sprite_.flipY || sprite_.flipX) {
			if (sprite_.flipX) {
				index = vIndexes[0];
				vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
				index = vIndexes[1];
				vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
			}
			if(sprite_.flipY){
				index = vIndexes[0];
				vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
				index = vIndexes[1];
				vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
			}
		}
		
		[self updateColor];
	}
	
	if(type_ == kCCProgressTimerTypeHorizontalBarLR){
		vertexData_[vIndexes[0] = 3].texCoords = (ccTex2F){tMax.x*alpha, tMax.y};
		vertexData_[vIndexes[1] = 2].texCoords = (ccTex2F){tMax.x*alpha, 0};
	}else if (type_ == kCCProgressTimerTypeHorizontalBarRL) {
		vertexData_[vIndexes[0] = 1].texCoords = (ccTex2F){tMax.x*(1.f - alpha), 0};
		vertexData_[vIndexes[1] = 0].texCoords = (ccTex2F){tMax.x*(1.f - alpha), tMax.y};
	}else if (type_ == kCCProgressTimerTypeVerticalBarBT) {
		vertexData_[vIndexes[0] = 0].texCoords = (ccTex2F){0, tMax.y*(1.f - alpha)};
		vertexData_[vIndexes[1] = 2].texCoords = (ccTex2F){tMax.x, tMax.y*(1.f - alpha)};
	}else if (type_ == kCCProgressTimerTypeVerticalBarTB) {
		vertexData_[vIndexes[0] = 1].texCoords = (ccTex2F){0, tMax.y*alpha};
		vertexData_[vIndexes[1] = 3].texCoords = (ccTex2F){tMax.x, tMax.y*alpha};
	}
	
	index = vIndexes[0];
	vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
	index = vIndexes[1];
	vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
	
	if (sprite_.flipY || sprite_.flipX) {
		if (sprite_.flipX) {
			index = vIndexes[0];
			vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
			index = vIndexes[1];
			vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
		}
		if(sprite_.flipY){
			index = vIndexes[0];
			vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
			index = vIndexes[1];
			vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
		}
	}
	
}

-(CGPoint)boundaryTexCoord:(char)index
{
	if (index < kProgressTextureCoordsCount) {
		switch (type_) {
			case kCCProgressTimerTypeRadialCW:
				return ccp((kProgressTextureCoords>>((index<<1)+1))&1,(kProgressTextureCoords>>(index<<1))&1);
			case kCCProgressTimerTypeRadialCCW:
				return ccp((kProgressTextureCoords>>(7-(index<<1)))&1,(kProgressTextureCoords>>(7-((index<<1)+1)))&1);
			default:
				break;
		}
	}
	return CGPointZero;
}

-(void)draw
{
	[super draw];

	if(!vertexData_)return;
	if(!sprite_)return;
	ccBlendFunc blendFunc = sprite_.blendFunc;
	BOOL newBlend = blendFunc.src != CC_BLEND_SRC || blendFunc.dst != CC_BLEND_DST;
	if( newBlend )
		glBlendFunc( blendFunc.src, blendFunc.dst );
	
	///	========================================================================
	//	Replaced [texture_ drawAtPoint:CGPointZero] with my own vertexData
	//	Everything above me and below me is copied from CCTextureNode's draw
	glBindTexture(GL_TEXTURE_2D, sprite_.texture.name);
	glVertexPointer(2, GL_FLOAT, sizeof(ccV2F_C4B_T2F), &vertexData_[0].vertices);
	glTexCoordPointer(2, GL_FLOAT, sizeof(ccV2F_C4B_T2F), &vertexData_[0].texCoords);
	glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ccV2F_C4B_T2F), &vertexData_[0].colors);
	if(type_ == kCCProgressTimerTypeRadialCCW || type_ == kCCProgressTimerTypeRadialCW){
		glDrawArrays(GL_TRIANGLE_FAN, 0, vertexDataCount_);
	} else if (type_ == kCCProgressTimerTypeHorizontalBarLR ||
			   type_ == kCCProgressTimerTypeHorizontalBarRL ||
			   type_ == kCCProgressTimerTypeVerticalBarBT ||
			   type_ == kCCProgressTimerTypeVerticalBarTB) {
		glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexDataCount_);
	}
	//glDrawElements(GL_TRIANGLES, indicesCount_, GL_UNSIGNED_BYTE, indices_);
	///	========================================================================
	
	if( newBlend )
		glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST);
}

@end
