#include "cGame.h"

cGame* cGame::instance_ = 0;

cGame* cGame::instance()
{
    if(instance_ == 0)
        instance_ = new cGame;
    return instance_;
}

cGame::cGame()
{
    // Nothing to do
}


void cGame::init()
{
    startGame();
}

void cGame::startGame()
{
    randomizeDropBlocks();
    t_ = 0;
    timeSinceDrop_ = 0;			
    timeToDrop_ = 3;
    timeToFall_ = .1;
    timeSinceMove_ = 0;
    timeToMove_ = .175;
    timeSinceExplode_ = 0;
    timeToExplode_ = .1; 
    mode_ = DROPPING_BLOCKS;

	//sound setup
	Audio.Setup();
	
	movement.Setup(L"pop.wav", Audio.getPerformance(), Audio.getLoader());
	movement.setPos(0.0f, -3.0f, 0.0f);
	
	SoundEffect.Setup(L"explo1.wav", Audio.getPerformance(), Audio.getLoader());
	
	BckgrndMsc.Setup(L"usher.wav", Audio.getPerformance(), Audio.getLoader());
	BckgrndMsc.setPos(0.0f, -5.0f, 0.0f);
	BckgrndMsc.Play();
	
    
    upKeyPressed_ = downKeyPressed_ = leftKeyPressed_ = rightKeyPressed_ = false;
}


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

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

void cGame::display()
{      
   // clear color and depth buffers
    glClear(GL_COLOR_BUFFER_BIT);
    
    update();
    
    grid_.draw();
    
    droppingBlock1_.draw();
    droppingBlock2_.draw();
    
    // flush buffer
    glutSwapBuffers();
}
    
void cGame::update()
{
    float dt = timer_.update();
    t_ += dt;
    timeSinceDrop_ += dt;
    timeSinceMove_ += dt;
    timeSinceExplode_ += dt;
    
    switch(mode_)
    {
    case DROPPING_BLOCKS: dropBlocks(); break;
    case EXPLODING: explode(); break;
    case GRID_FALL: gridFall(); break;
    }
}

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

bool cGame::neighborIsSameType(cGrid::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 < cGrid::gridXSize_ && ny >= 0 && ny < cGrid::gridYSize_) ) return false;
    
    // p is not the same type as neigbor
    if( grid_.blocks_[nx][ny].type_ != p->type_ ) return false; 
    
    return true;
}

bool cGame::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(&grid_.blocks_[nx][ny]);
        return true;
    }
    return false;
}

void cGame::explode()
{
    // no more explosions to do, make blocks fall
    if(explosionQueue_.empty())
    {
        mode_ = GRID_FALL;
		cout<< "3";
		SoundEffect.setPos(0.0f, -3.0f, 0.0f);
		SoundEffect.Play();
        return;
    }
    
    if(timeSinceExplode_ >= timeToExplode_)
    {
        timeSinceExplode_ = 0;
        cGrid::cBlock* p = explosionQueue_.front();
        explosionQueue_.pop();
        
        addNeighborToExplosionQueue(p, 1, 0);
        addNeighborToExplosionQueue(p, -1, 0);
        addNeighborToExplosionQueue(p, 0, -1);
        addNeighborToExplosionQueue(p, 0, 1);
        p->type_ = EMPTY_BLOCK;
        
    }
}

void cGame::startExplosions()
{
    for(int x = 0; x < cGrid::gridXSize_; ++x)
        for(int y = 0; y < cGrid::gridYSize_; ++y)
        {
            cGrid::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(grid_.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(&grid_.blocks_[x][y], 1, 0) ||
                neighborIsSameType(&grid_.blocks_[x][y], -1, 0) ||
                neighborIsSameType(&grid_.blocks_[x][y], 0, 1) ||
                neighborIsSameType(&grid_.blocks_[x][y], 0, -1) )
                exploded = true;
            
            if(exploded)
            {
                explosionQueue_.push(&grid_.blocks_[x][y]);
                mode_ = EXPLODING;
            }
        }
     
}

void cGame::dropBlocks()
{
    if(timeSinceMove_ >= timeToMove_)
    {
        timeSinceMove_ = 0;
        if(leftKeyPressed_) moveDroppingBlocks(-1, 0);
        if(rightKeyPressed_) moveDroppingBlocks(1, 0);
        if(downKeyPressed_ && !moveDroppingBlocks(0, -1)) collideBlocks();
    }
    if( timeSinceDrop_ >= timeToDrop_)
    {
        timeSinceDrop_ = 0;
        if(!moveDroppingBlocks(0,-1)) collideBlocks();
    } 
    }

void cGame::idle()
{
    glutPostRedisplay();
}

bool cGame::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 cGame::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 cGame::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 cGame::collideBlocks()
{
    grid_.blocks_[droppingBlock1_.x_][droppingBlock1_.y_].type_ = droppingBlock1_.type_;
    grid_.blocks_[droppingBlock2_.x_][droppingBlock2_.y_].type_ = droppingBlock2_.type_;
    mode_ = GRID_FALL;
    randomizeDropBlocks();
    
}

void cGame::keyboard(unsigned char key, int x, int y) 
{
    if(mode_ != DROPPING_BLOCKS) return;
    
    switch(key)
    {
    case 'z': 
		rotateDroppingBlocksL(); 
		movement.Play();
	break;
    case 'x': 
		rotateDroppingBlocksR(); 
		movement.Play();
		break;
	
    default: break;
    }
}

void cGame::specialKey(int key, int x, int y)
{	    
    switch (key)
    {
        case GLUT_KEY_LEFT: 	leftKeyPressed_ = true; break;
        case GLUT_KEY_RIGHT: 	rightKeyPressed_ = true; break;
        case GLUT_KEY_DOWN:  	downKeyPressed_ = true; break;
        case GLUT_KEY_UP:  	upKeyPressed_ = true; break;
    }
}

void cGame::specialKeyUp(int key, int x, int y)
{
    switch (key)
    {
        case GLUT_KEY_LEFT: 	leftKeyPressed_ = false; break;
        case GLUT_KEY_RIGHT: 	rightKeyPressed_ = false; break;
        case GLUT_KEY_DOWN:  	downKeyPressed_ = false; break;
        case GLUT_KEY_UP:  	upKeyPressed_ = false; break;
    }
}

void cGame::mouseClick(int button, int state, int x, int y) 
{
	
}

void cGame::mouseMove(int x, int y)
{
     
}
