#include "cGrid.h"

#include "OGLPlatformSpecific.h"
#include <iostream>
#include <stdlib.h>
#include <math.h>
#include "cGame.h"
#include "cTextureManager.h"
#include "cParticleManager.h"
#include "cSoundManager.h"

cGrid::cGrid()
{
	reset();
}

void cGrid::reset()
{
	for(int y = 0; y < gridYSize_; ++y )
        for(int x = 0; x < gridXSize_; ++x )
        {
            blocks_[x][y].x_ = x;
            blocks_[x][y].y_ = y;
            blocks_[x][y].type_ = EMPTY_BLOCK;
            blocks_[x][y].grid_ = this;
        }

	score_ = 0;
	attackScore_ = 0;
    timeSinceDrop_ = 0;			
    timeToDrop_ = 3;
    timeToFall_ = .1;
    timeSinceMove_ = 0;
    timeToMove_ = .375;
    timeSinceExplode_ = 0;
    timeToExplode_ = .1; 
}

void cGrid::draw()
{
	for(int y = 0; y < gridYSize_; ++y )
        for(int x = 0; x < gridXSize_; ++x )
		{
			glPushMatrix();
    
			glScalef(.25,.25,.25);
			glTranslatef(((float)x - cGrid::gridXSize_/2) * 1 * 2, ((float)y- cGrid::gridYSize_/2) * 1 * 2, 0);
			glTranslatef(translategrid_,0,0);
            blocks_[x][y].drawEmpty();

			glPopMatrix();
		}

    for(int y = 0; y < gridYSize_; ++y )
        for(int x = 0; x < gridXSize_; ++x )
            blocks_[x][y].draw(translategrid_);

	droppingBlock1_.draw(translategrid_);
	droppingBlock2_.draw(translategrid_);

}

cGrid::cBlock::cBlock()
{
    size_ =   1;
}

bool cGrid::cBlock::canMove(int dx, int dy)
{
    int newX = x_ + dx;
    int newY = y_ + dy;
    
    if(newX < 0 || newX >= cGrid::gridXSize_) return false;
    if(newY < 0 || newY >= cGrid::gridYSize_) return false;
    
    if(grid_->blocks_[newX][newY].type_ != EMPTY_BLOCK) return false;
    
    return true;
}

bool cGrid::cBlock::move(int dx, int dy)
{
    if(canMove(dx, dy))
    {
        x_ += dx;
        y_ += dy;
        return true;
    }
    return false;
}

BLOCK_TYPE cGrid::cBlock::getRandomBlockType()
{
    switch(rand()%20)
    {
    case 0: case 1: case 2:  	return RED_BLOCK; 
    case 3: case 4: case 5: 	return BLUE_BLOCK;
    case 6: case 7: case 8:  	return GREEN_BLOCK;
    case 9: case 10: case 11:	return ORANGE_BLOCK;
    case 12: case 13: case 14:	return PURPLE_BLOCK;
    
    case 15: return RED_EXPLODER; 
    case 16: return BLUE_EXPLODER;
    case 17: return GREEN_EXPLODER;
    case 18: return ORANGE_EXPLODER;
    case 19: return PURPLE_EXPLODER;
    }
}

void cGrid::cBlock::draw(int translate)
{
    
    glPushMatrix();
    
    glScalef(.25,.25,.25);
    glTranslatef((x_ - cGrid::gridXSize_/2) * size_ * 2, (y_- cGrid::gridYSize_/2) * size_ * 2, 0);
	glTranslatef(translate,0,0);
    
    
    
        
    switch(type_)
    {
    case EMPTY_BLOCK: break;
    case RED_BLOCK: 
    case BLUE_BLOCK: 
    case GREEN_BLOCK:
    case ORANGE_BLOCK:
    case PURPLE_BLOCK:
        drawGem();
        break;
    case RED_EXPLODER:
    case BLUE_EXPLODER:
    case GREEN_EXPLODER:
    case ORANGE_EXPLODER:
    case PURPLE_EXPLODER:
        drawExploder();
        break;
    }
    
    glPopMatrix();
    
}

void cGrid::cBlock::drawEmpty()
{
	glDisable(GL_TEXTURE_2D);
	glColor4f(.1, .1, .1, 1);
	glEnable(GL_BLEND);						
	glBlendFunc(GL_ONE, GL_ONE);	

	// alwaays draw an empty block first
	glNormal3f(0,0,1);
	glBegin(GL_TRIANGLE_STRIP);						
	glVertex3f(size_, size_, 0); 	
	glVertex3f(-size_, size_, 0); 
	glVertex3f(size_, -size_, 0); 
	glVertex3f(-size_, -size_, 0); 	
	glEnd();
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);
}

void cGrid::cBlock::drawGem()
{
    switch(type_)
    {
    case RED_BLOCK:  		glColor3f(1, .1, .1); break;
    case BLUE_BLOCK:  		glColor3f(.1, .1, 1); break;
    case GREEN_BLOCK:  		glColor3f(.1, 1, .1); break;
    case ORANGE_BLOCK:		glColor3f(1, .5, 0); break;
    case PURPLE_BLOCK:		glColor3f(.5, 0, .5); break;
    }
    
    float innerScale = .7;
    float ss = innerScale * size_;
    float s = size_;
    float d = size_/4; // z-depth of gem
    
    float oneOvrRt2 = 1 / sqrt(2.0);
    
    cTextureManager::instance()->bindGemTexture();	
    
    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
    glLoadIdentity();
    glRotatef(cGame::instance()->t_ * .005 , 0, 0, 1);
    glScalef(30, 30,0);
    
    
    // center
    glNormal3f(.2,.2,.9);
    glBegin(GL_TRIANGLE_STRIP);						
    glTexCoord2f(innerScale, innerScale); glVertex3f(ss, ss, d); 	
    glTexCoord2f(-innerScale, innerScale); glVertex3f(-ss, ss, d); 
    glTexCoord2f(innerScale, -innerScale); glVertex3f(ss, -ss, d); 
    glTexCoord2f(-innerScale, -innerScale); glVertex3f(-ss, -ss, d); 	
    glEnd();
    
    // left side
    glNormal3f(-oneOvrRt2,0,oneOvrRt2);
    glBegin(GL_TRIANGLE_STRIP);						
    glTexCoord2f(-innerScale, innerScale); glVertex3f(-ss, ss, d); 	
    glTexCoord2f(-1,1); glVertex3f(-s, s, 0); 
    glTexCoord2f(-innerScale,-innerScale); glVertex3f(-ss, -ss, d); 
    glTexCoord2f(-1,-1); glVertex3f(-s, -s, 0); 	
    glEnd();
    
    //top
    glNormal3f(0,oneOvrRt2,oneOvrRt2);
    glBegin(GL_TRIANGLE_STRIP);						
    glTexCoord2f(1,1); glVertex3f(s, s, 0); 	
    glTexCoord2f(-1, 1); glVertex3f(-s, s, 0); 
    glTexCoord2f(innerScale, innerScale); glVertex3f(ss, ss, d); 
    glTexCoord2f(-innerScale, innerScale); glVertex3f(-ss, ss, d); 	
    glEnd();
    
    // right
    glNormal3f(oneOvrRt2,0,oneOvrRt2);
    glBegin(GL_TRIANGLE_STRIP);						
    glTexCoord2f(1, 1); glVertex3f(s, s, 0); 	
    glTexCoord2f(innerScale, innerScale); glVertex3f(ss, ss, d); 
    glTexCoord2f(1, -1); glVertex3f(s, -s, 0); 
    glTexCoord2f(innerScale, -innerScale); glVertex3f(ss, -ss, d); 	
    glEnd();
    
    // bottom
    glNormal3f(0,-oneOvrRt2,oneOvrRt2);
    glBegin(GL_TRIANGLE_STRIP);						
    glTexCoord2f(innerScale, -innerScale); glVertex3f(ss, -ss, d); 	
    glTexCoord2f(-innerScale, -innerScale); glVertex3f(-ss, -ss, d); 
    glTexCoord2f(1, -1); glVertex3f(s, -s, 0); 
    glTexCoord2f(-1, -1); glVertex3f(-s, -s, 0); 	
    glEnd();
    
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    
    cTextureManager::instance()->unbindTexture();
    
}

void cGrid::cBlock::drawExploder()
{
    glDisable(GL_TEXTURE_2D);
    glColor3f(	.05, .05, .05 );
    glutSolidSphere(size_*.9,15,15);
    glEnable(GL_TEXTURE_2D);
    
    switch(type_)
    {
    case RED_EXPLODER:		glColor4f(1, 0, 0, .9); break;
    case BLUE_EXPLODER:		glColor4f(0, 0, 1, .9); break;
    case GREEN_EXPLODER:	glColor4f(0, 1, 0, .9); break;
    case ORANGE_EXPLODER:	glColor4f(.7, .5, 0, .9); break;
    case PURPLE_EXPLODER:	glColor4f(.5, 0, .5, .9); break;
    }
    
    
    cTextureManager::instance()->bindExploderTexture();	
    glEnable(GL_BLEND);							// Enable Blending
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);	
    
    int numParticles = 3;
    
    float t = .1 * cGame::instance()->t_;
    
    for(int i = 0; i < numParticles; ++i)
    {
        float fi = i;
        float particleSize = size_ * .9;
        
        glPushMatrix();
        
        glMatrixMode(GL_TEXTURE);
        glPushMatrix();
        glLoadIdentity();
        
        glTranslatef(.5, .5, 0);
        glRotatef(cGame::instance()->t_ * 5 * fi + fi , 0, 0, 1);
        glTranslatef(-.5, -.5, 0);
        
        glBegin(GL_TRIANGLE_STRIP);						// Build Quad From A Triangle Strip
        glTexCoord2d(1,1); glVertex3f(particleSize, particleSize, 0); // Top Right
        glTexCoord2d(0,1); glVertex3f(-particleSize, particleSize, 0); // Top Left
        glTexCoord2d(1,0); glVertex3f(particleSize, -particleSize, 0); // Bottom Right
        glTexCoord2d(0,0); glVertex3f(-particleSize, -particleSize, 0); // Bottom Left
        glEnd();
        
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);
        
        glPopMatrix();
    }
    
    glDisable(GL_BLEND);
    cTextureManager::instance()->unbindTexture();
}



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void cGrid::randomizeDropBlocks()
{
    // create a 2 random new dropping blocks
    droppingBlock1_.y_ = gridYSize_ - 1;
    droppingBlock1_.x_ = gridXSize_/2;
    droppingBlock1_.type_ = cBlock::getRandomBlockType();
    droppingBlock1_.grid_ = &*this;

    droppingBlock2_.y_ = gridYSize_ - 1;
    droppingBlock2_.x_ = droppingBlock1_.x_ + 1;
    droppingBlock2_.type_ = cBlock::getRandomBlockType();
    droppingBlock2_.grid_ = &*this;
}

void cGrid::gridFall()
{            
    if( timeSinceDrop_ >= timeToFall_) timeSinceDrop_ = 0;
    else return;
    
    bool anythingFell = false;
    for(int x = 0; x < gridXSize_; ++x)
        for(int y = 0; y < gridYSize_; ++y)
        {
            if(blocks_[x][y].type_ != EMPTY_BLOCK && blocks_[x][y].canMove(0, -1))
            {
                blocks_[x][y-1].type_ = blocks_[x][y].type_;
                blocks_[x][y].type_ = EMPTY_BLOCK;
                anythingFell = true;
            }
        }
        
    if( !anythingFell )
    { 
        mode_ = DROPPING_BLOCKS; 
        startExplosions();
    }
}

bool cGrid::addNeighborToExplosionQueue(cGrid::cBlock* p, int dx, int dy)
{
    int nx = p->x_ + dx;
    int ny = p->y_ + dy;
    
    if(neighborIsSameType(p, dx, dy))
    {
        explosionQueue_.push(&blocks_[nx][ny]);
        return true;
    }
    return false;
}

void cGrid::explode()
{
    // no more explosions to do, make blocks fall
    if(explosionQueue_.empty())
    {
        mode_ = GRID_FALL;
		cSoundManager::instance()->playExplode();
                return;
    }
    
    if(timeSinceExplode_ >= timeToExplode_)
    {
        timeSinceExplode_ = 0;
        cBlock* p = explosionQueue_.front();
        explosionQueue_.pop();
		
		
		for(int i = 0; i < 20; ++i)
		{
			float r, g, b;
			switch(p->type_)
			{
			case RED_EXPLODER: case RED_BLOCK: r = 1; g = 0; b = 0; break;
			case GREEN_EXPLODER: case GREEN_BLOCK: r = 0; g = 1; b = 0; break;
			case BLUE_EXPLODER: case BLUE_BLOCK: r = 0; g = 0; b = 1; break;
			case ORANGE_BLOCK:	case ORANGE_EXPLODER: r = 1; g = .5; b = 0; break;
			case PURPLE_BLOCK:	case PURPLE_EXPLODER: r = .5; g = 0; b = .5; break; 
			}

			cParticleManager::instance()->addParticle(p->x_ + translategrid_/1.98,p->y_,
								(float)(rand()%100-50)/200.0f,
								(float)(rand()%100-50)/200.0f, r, g, b);
		}
		
        
        addNeighborToExplosionQueue(p, 1, 0);
        addNeighborToExplosionQueue(p, -1, 0);
        addNeighborToExplosionQueue(p, 0, -1);
        addNeighborToExplosionQueue(p, 0, 1);
        p->type_ = EMPTY_BLOCK;

		
		++score_;
		++attackScore_;
        
    }
}

void cGrid::startExplosions()
{
    for(int x = 0; x < gridXSize_; ++x)
        for(int y = 0; y < gridYSize_; ++y)
        {
            cBlock b;	// imaginary block in the same color as the exploder
            b.x_ = x; 
            b.y_ = y;
            b.type_ = NULL_TYPE;
            
            bool exploder = false;
            switch(blocks_[x][y].type_)
            {
            case RED_EXPLODER: b.type_ = RED_BLOCK;  exploder = true; break;
            case BLUE_EXPLODER: b.type_ = BLUE_BLOCK;  exploder = true; break;
            case GREEN_EXPLODER: b.type_ = GREEN_BLOCK; exploder = true; break;
            case ORANGE_EXPLODER: b.type_ = ORANGE_BLOCK; exploder = true; break;
            case PURPLE_EXPLODER: b.type_ = PURPLE_BLOCK; exploder = true; break;
            default: break;
            }
            
            if(!exploder) continue;
            
            bool exploded = false;
            if(addNeighborToExplosionQueue(&b, 1, 0)) exploded = true;
            if(addNeighborToExplosionQueue(&b, -1, 0)) exploded = true;
            if(addNeighborToExplosionQueue(&b, 0, 1)) exploded = true;
            if(addNeighborToExplosionQueue(&b, 0, -1)) exploded = true;
            
            // explode if neighbor is another exploder
            if( neighborIsSameType(&blocks_[x][y], 1, 0) ||
                neighborIsSameType(&blocks_[x][y], -1, 0) ||
                neighborIsSameType(&blocks_[x][y], 0, 1) ||
                neighborIsSameType(&blocks_[x][y], 0, -1) )
                exploded = true;
            
            if(exploded)
            {
                explosionQueue_.push(&blocks_[x][y]);
                mode_ = EXPLODING;
            }
        }
     
}


bool cGrid::moveDroppingBlocks(int dx, int dy)
{   
    if(!droppingBlock1_.move(dx, dy)) return false;
    if(!droppingBlock2_.move(dx, dy))
    {
        // move block 1 back, since it was already moved
        droppingBlock1_.x_ -= dx;
        droppingBlock1_.y_ -= dy;
        return false;
    }
    
    return true;
}


bool cGrid::rotateDroppingBlocksR()
{
    int dx = droppingBlock2_.x_ - droppingBlock1_.x_;
    int dy = droppingBlock2_.y_ - droppingBlock1_.y_;
    
    if(dx == 1) return droppingBlock2_.move(-1, -1);
    if(dx == -1) return droppingBlock2_.move( 1, 1);
    
    if(dy == 1) return droppingBlock2_.move(1, -1); 
    if(dy == -1) return droppingBlock2_.move( -1, 1); 
}

bool cGrid::rotateDroppingBlocksL()
{	
	
    int dx = droppingBlock2_.x_ - droppingBlock1_.x_;
    int dy = droppingBlock2_.y_ - droppingBlock1_.y_;
    
    if(dx == 1) return droppingBlock2_.move( -1, 1);
    if(dx == -1) return droppingBlock2_.move( 1, -1);
    
    if(dy == 1) return droppingBlock2_.move(-1, -1); 
    if(dy == -1) return droppingBlock2_.move(1, 1); 
}


void cGrid::collideBlocks()
{
    blocks_[droppingBlock1_.x_][droppingBlock1_.y_].type_ = droppingBlock1_.type_;
    blocks_[droppingBlock2_.x_][droppingBlock2_.y_].type_ = droppingBlock2_.type_;
    mode_ = GRID_FALL;
	randomizeDropBlocks();
	
	for(int x = 0; x < gridXSize_; ++x)
	{
		if(blocks_[x][gridYSize_-1].type_ != EMPTY_BLOCK)
			mode_ = GAME_OVER;
	}
}

bool cGrid::neighborIsSameType(cBlock* p, int dx, int dy)
{
    if(p->type_ == EMPTY_BLOCK) return false;
    
    int nx = p->x_ + dx;
    int ny = p->y_ + dy;
    
    // neighbor is out of grid
    if( !( nx >= 0 && nx < gridXSize_ && ny >= 0 && ny < gridYSize_) ) return false;
    
    // p is not the same type as neigbor
    if( blocks_[nx][ny].type_ != p->type_ ) return false; 
    
    return true;
}

void cGrid::setTranslation(int translate)
{
	translategrid_ = translate;
}

void cGrid::makeAttackRows(int n)
{
	int attackRows = n/8;
	if(attackRows == 0) return;

	mode_ = GRID_FALL;
	for(int y = gridYSize_ - 1; y >= gridYSize_ - attackRows ; --y )
        for(int x = 0; x < gridXSize_; ++x )
		{
			switch(rand()%5)
			{
			case 0: blocks_[x][y].type_ = RED_BLOCK; break;
			case 1: blocks_[x][y].type_ = GREEN_BLOCK; break;
			case 2: blocks_[x][y].type_ = BLUE_BLOCK; break;
			case 3: blocks_[x][y].type_ = PURPLE_BLOCK; break;
			case 4: blocks_[x][y].type_ = ORANGE_BLOCK; break;
			}
            
		}
}
