/**
 * Copper Swapper -- goat tile swapper game
 *
 * Copyright (C) 2005 Atani Software
 *
 * This code is licensed under the GPL v2.
 *
 * ingame.cpp -- In game handler
 */

#include "copperswapper.h"

InGame::InGame()
{
	m_scene->setTranslate(Vector(320, 240, 10));

	dbglog(DBG_WARNING, "Creating BG\n");
	m_bg = new Banner(PLX_LIST_TR_POLY, bgTitle);
	m_bg->setTranslate(Vector(0, 0, 10));
	m_bg->setSize(640.0f, 480.0f);

	dbglog(DBG_WARNING, "Creating overlay\n");
	m_bg2 = new Banner(PLX_LIST_TR_POLY, bgOverLay);
	m_bg2->setTranslate(Vector(0, 0, 11));
	m_bg2->setSize(640.0f, 480.0f);

	dbglog(DBG_WARNING, "Creating title\n");
	m_bg3 = new Banner(PLX_LIST_TR_POLY, titleText);
	m_bg3->setTranslate(Vector(0, -180, 12));
	m_bg3->setSize(256.0f, 64.0f);

	dbglog(DBG_WARNING, "Adding to scene\n");
	m_scene->subAdd(m_bg);
	m_scene->subAdd(m_bg2);
	m_scene->subAdd(m_bg3);

	loadTiles();

	for(int y = 0; y < 8; y++)
	{
		for(int x = 0; x < 8; x++)
		{
			m_tileBanners[y][x] = new Banner(PLX_LIST_TR_POLY, m_tileset[0]);
			m_tiles[y][x] = getNextTile();
			m_tileBanners[y][x]->setTranslate(Vector(-112+(x*32), -112+(y*32), 15));
			m_tileBanners[y][x]->setTexture(m_tileset[0]);
			m_tileBanners[y][x]->setSize(32.0f, 32.0f);
			m_scene->subAdd(m_tileBanners[y][x]);
			m_tileSelOffset[y][x] = Vector(-96 + (32*x), -96 + (32*y), 20);
		}
	}

	m_cursor = new Banner(PLX_LIST_TR_POLY, selectorTileTxr[m_frameCnt]);
	m_cursor->setSize(64.0f, 64.0f);
	m_cursor->setTranslate(Vector(-96, -96, 20));
	m_boardState = STATE_REFILLING;

	m_scene->subAdd(m_cursor);

	m_scoreLabel = new Label(baseFont, "Score:", 24, true, true);
	m_scoreLabel->setTranslate(Vector(-250, -112, 15));
	m_scene->subAdd(m_scoreLabel);

	m_scoreTxt = new Label(baseFont, "000000", 24, true, true);
	m_scoreTxt->setTranslate(Vector(-250, -88, 20));
	m_scene->subAdd(m_scoreTxt);

	m_levelLabel = new Label(baseFont, "Level:", 24, true, true);
	m_levelLabel->setTranslate(Vector(-250, -58, 15));
	m_scene->subAdd(m_levelLabel);

	m_levelTxt = new Label(baseFont, "1", 24, true, true);
	m_levelTxt->setTranslate(Vector(-250, -28, 20));
	m_scene->subAdd(m_levelTxt);

	m_nextLevelLabel = new Label(baseFont, "Next:", 24, true, true);
	m_nextLevelLabel->setTranslate(Vector(-250, 2, 15));
	m_scene->subAdd(m_nextLevelLabel);

	m_nextLevelTxt = new Label(baseFont, "500", 24, true, true);
	m_nextLevelTxt->setTranslate(Vector(-250, 32, 20));
	m_scene->subAdd(m_nextLevelTxt);

	m_statusTxt = new Label(baseFont, "", 24, true, true);
	m_statusTxt->setTranslate(Vector(-40, 200, 20));
	m_scene->subAdd(m_statusTxt);
}

void InGame::newGame()
{
	for(int i = 0; i < 7; i++)
	{
		m_tileCount[i] = 0;
	}
	m_frameCnt = 0;
	m_selectFrameCnt = 0;
	m_score = 0;
	m_tilesRemoved = 0;
	m_selX = m_selY = 0;
	m_oldselX = m_oldselY = 1;
	m_level = 1;
	m_levelUpTiles = 8;
	m_nextLevel = 500;
	for(int y = 0; y < 8; y++)
	{
		for(int x = 0; x < 8; x++)
		{
			m_tileBanners[y][x]->setTexture(m_tileset[m_tiles[y][x]]);
		}
	}
}

void InGame::visualPerFrame()
{
	// update displayed score
	sprintf((char *)scoreText, "%d", m_score);
	m_scoreTxt->setText(scoreText);
	m_frameCnt++;
	if((m_frameCnt % 4) == 0)
	{
		m_selectFrameCnt++;
	}
	m_cursor->setTexture(selectorTileTxr[m_selectFrameCnt % 4]);
	
	if((m_oldselX != m_selX) ||
	   (m_oldselY != m_selY))
	{
		m_cursor->setTranslate(m_tileSelOffset[m_selY][m_selX]);
		m_oldselX = m_selX;
		m_oldselY = m_selY;
	}
	if(checkForAlignments())
	{
		m_boardState = STATE_MARK_ALIGNED;
	}
	switch(m_boardState)
	{
		case STATE_MARK_ALIGNED:
			m_boardState = STATE_IDLE;
			if(checkForAlignments())
			{
				removeAlignments();
				refillBoard();
				m_boardState = STATE_REFILLING;
			}
			break;
		case STATE_REFILLING:
			m_boardState = STATE_MARK_ALIGNED;
			if(m_score >= m_nextLevel)
			{
				m_nextLevel *= 2;
				m_level++;

				// mark tiles to remove for level up
				for(uint32 i = 0; i < m_levelUpTiles; i++)
				{
					m_align.insertHead(new Alignment(randnum(8), randnum(8), 1, DIRECTION_HORIZ));
				}
				removeAlignments();
				refillBoard();
				// cap the extra tile removals at a reasonable level
				if(m_levelUpTiles < 24)
				{
					m_levelUpTiles++;
				}
				m_statusTxt->setTint(colorMenuDefault);
				m_statusTxt->setText("Level Up!");
				m_statusTxt->animRemoveAll();
				m_statusTxt->animAdd(new AlphaFader(0.0f, -(1.0f/120.0f)));
				m_boardState = STATE_REFILLING;
				// update displayed level
				sprintf((char *)levelText, "%d", m_level);
				m_levelTxt->setText(levelText);
				// update displayed next level
				sprintf((char *)nextLevelText, "%d", m_nextLevel);
				m_nextLevelTxt->setText(nextLevelText);
			}
			break;
		default:
			break;
	}
	GenericMenu::visualPerFrame();
}

void InGame::rotateTiles(int x, int y, ROTATE_DIR dir)
{

/**
  rotate tiles using three moves...

   +-------+-------+
 A | x1/y1 | x2/y1 | C
   +-------+-------+
 B | x1/y2 | x2/y2 | D
   +-------+-------+

CLOCKWISE:
 Swap moves: 
 1) D <> B
 2) A <> D
 3) D <> C
 
COUNTER CLOCKWISE:
 Swap moves: 
 1) D <> C
 2) A <> D
 3) D <> B
 **/
	int x1 = x;
	int y1 = y;
	int x2 = x1 + 1;
	int y2 = y1 + 1;

	if(dir == ROTATE_CLOCKWISE)
	{
		swapTiles(x2, y2, x1, y2); // swap D and B
		swapTiles(x1, y1, x2, y2); // swap A and D
		swapTiles(x2, y2, x2, y1); // swap D and C

		// A to C
		m_tileBanners[y1][x2]->setTranslate(Vector(-112+(x1*32), -112+(y1*32), 15));
		m_tileBanners[y1][x2]->animRemoveAll();
		m_tileBanners[y1][x2]->animAdd(new LogXYMover(-112+(x2*32), -112+(y1*32)));

		// C to D
		m_tileBanners[y2][x2]->setTranslate(Vector(-112+(x2*32), -112+(y1*32), 15));
		m_tileBanners[y2][x2]->animRemoveAll();
		m_tileBanners[y2][x2]->animAdd(new LogXYMover(-112+(x2*32), -112+(y2*32)));

		// D to B
		m_tileBanners[y2][x1]->setTranslate(Vector(-112+(x2*32), -112+(y2*32), 15));
		m_tileBanners[y2][x1]->animRemoveAll();
		m_tileBanners[y2][x1]->animAdd(new LogXYMover(-112+(x1*32), -112+(y2*32)));

		// B to A
		m_tileBanners[y1][x1]->setTranslate(Vector(-112+(x1*32), -112+(y2*32), 15));
		m_tileBanners[y1][x1]->animRemoveAll();
		m_tileBanners[y1][x1]->animAdd(new LogXYMover(-112+(x1*32), -112+(y1*32)));
	}
	else
	{
/*
   +-------+-------+
 A | x1/y1 | x2/y1 | C
   +-------+-------+
 B | x1/y2 | x2/y2 | D
   +-------+-------+
*/
		
		swapTiles(x2, y2, x2, y1); // swap D and C
		swapTiles(x1, y1, x2, y2); // swap A and D
		swapTiles(x2, y2, x1, y2); // swap D and B

		// A to B
		m_tileBanners[y2][x1]->setTranslate(Vector(-112+(x1*32), -112+(y1*32), 15));
		m_tileBanners[y2][x1]->animRemoveAll();
		m_tileBanners[y2][x1]->animAdd(new LogXYMover(-112+(x1*32), -112+(y2*32)));

		// B to D
		m_tileBanners[y2][x2]->setTranslate(Vector(-112+(x1*32), -112+(y2*32), 15));
		m_tileBanners[y2][x2]->animRemoveAll();
		m_tileBanners[y2][x2]->animAdd(new LogXYMover(-112+(x2*32), -112+(y2*32)));

		// D to C
		m_tileBanners[y1][x2]->setTranslate(Vector(-112+(x2*32), -112+(y2*32), 15));
		m_tileBanners[y1][x2]->animRemoveAll();
		m_tileBanners[y1][x2]->animAdd(new LogXYMover(-112+(x2*32), -112+(y1*32)));

		// C to A
		m_tileBanners[y1][x1]->setTranslate(Vector(-112+(x2*32), -112+(y1*32), 15));
		m_tileBanners[y1][x1]->animRemoveAll();
		m_tileBanners[y1][x1]->animAdd(new LogXYMover(-112+(x1*32), -112+(y1*32)));
	}

	// all swapped
}

void InGame::removeAlignments()
{
	int tilesRemoved = 0;
	ListNode<Alignment> *node;
	node = m_align.getHead();
	while(node != NULL)
	{
		int xStart = (*node)->getX();
		int yStart = (*node)->getY();
		int len = (*node)->getCount();
		int alignScore = 0;
		if((*node)->getDir() == DIRECTION_HORIZ)
		{
			// remove horizontal alignment
			for(int x = xStart; x < (xStart + len); x++)
			{
				m_tileCount[m_tiles[yStart][x]]--;
				m_tilesRemoved++;
				m_tiles[yStart][x] = -1;
				tilesRemoved++;
			}
		}
		else if((*node)->getDir() == DIRECTION_VERT)
		{
			// remove vertical alignment
			for(int y = yStart; y < (yStart + len); y++)
			{
				m_tileCount[m_tiles[y][xStart]]--;
				m_tilesRemoved++;
				m_tiles[y][xStart] = -1;
				tilesRemoved++;
			}
		}

		// calculate score
		if(len == 1)
		{
			// bonus tile removal
			alignScore = 10 * (randnum(2) + 1);
		}
		else
		{
			alignScore = (10 * (tilesRemoved - 2)) * m_level;
		}
// center of tile
//m_tileBanners[y][x]->setTranslate(Vector(-112+(x*32), -112+(y*32), 15));

		m_score += alignScore;

		node = node->getNext();
	}
	m_align.delAll();
}

void InGame::swapTiles(int x1, int y1, int x2, int y2)
{
	int t1 = m_tiles[y1][x1];
	int t2 = m_tiles[y2][x2];
	m_tiles[y1][x1] = t2;
	m_tiles[y2][x2] = t1;
	m_tileBanners[y1][x1]->setTexture(m_tileset[t2]);
	m_tileBanners[y2][x2]->setTexture(m_tileset[t1]);
}

int InGame::checkAlignment(int x, int y)
{
	// default, no alignment.
	int result = 0;
	int i = 0;
	uint8 tileToMask[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

	// First check on X axis
	for(i = x - 2; i <= x; i++)
	{
		if(i >= 0 && (i + 2) < 8)
		{
			if(tileToMask[m_tiles[y][i]] &
			   tileToMask[m_tiles[y][i + 1]] &
			   tileToMask[m_tiles[y][i + 2]])
			{
			  result |= 1;
			  break;
			}

		}
	}

	for(i = y - 2; i <= y; i++)
	{
		if(i >= 0 && (i + 2) < 8)
		{
			if(tileToMask[m_tiles[i][x]] &
			   tileToMask[m_tiles[i + 1][x]] &
 			   tileToMask[m_tiles[i + 2][x]])
			{
			  result |= 2;
			  break;
			}

		}
	}
	return result;
}

int InGame::checkForAlignments()
{
	// default, no alignment.
	int result = 0;
	int count = 0;
	int start_x = 0, start_y = 0;

	// remove existing alignments from list
	if(m_align.getHead() != NULL)
	{
		m_align.delAll();
	}

	// vertical alignment
	for(int x = 0; x < 8; x++)
	{
		for(int y = 0; y < 8; y++)
		{
			if((checkAlignment(x, y) & 2) == 2)
			{
				if(count == 0)
				{
					start_x = x;
					start_y = y;
				}
				count++;
			}
			else
			{
				if(count > 2)
				{
					m_align.insertHead(new Alignment(start_x, start_y, count, DIRECTION_VERT));
					result++;
				}
				count = 0;
			}
		}
		// reached end of row
		if(count > 2)
		{
			m_align.insertHead(new Alignment(start_x, start_y, count, DIRECTION_VERT));
			result++;
		}
		count = 0;
	}

	count = 0;

	// horizontal alignment
	for(int y = 0; y < 8; y++)
	{
		for(int x = 0; x < 8; x++)
		{
			if((checkAlignment(x, y) & 1 ) == 1)
			{
				if(count == 0)
				{
					start_x = x;
					start_y = y;
				}
				count++;
			}
			else
			{
				if(count > 2)
				{
					m_align.insertHead(new Alignment(start_x, start_y, count, DIRECTION_HORIZ));
					result++;
				}
				count = 0;
			}
		}
		// reached end of row
		if(count > 2)
		{
			m_align.insertHead(new Alignment(start_x, start_y, count, DIRECTION_HORIZ));
			result++;
		}
		count = 0;
	}
	return result;
}

void InGame::inputEvent(const Event &evt)
{
	if(evt.key == Event::KeyMiscY)
	{
		Prompt *prompt = new Prompt("Are you sure you want to end this game?");
		prompt->doMenu();
		if(prompt->getResult())
		{
			quitNow();
		}
	}
	else if(evt.key == Event::KeySelect || evt.key == Event::KeyPgdn)
	{
		if(selectSound != NULL)
		{
			selectSound->play();
		}
		rotateTiles(m_selX, m_selY, ROTATE_CLOCKWISE);
	}
	else if(evt.key == Event::KeyMiscX || evt.key == Event::KeyPgup)
	{
		if(selectSound != NULL)
		{
			selectSound->play();
		}
		rotateTiles(m_selX, m_selY, ROTATE_COUNTERCLOCK);
	}
	else if(evt.key == Event::KeyLeft)
	{
		if(m_selX > 0)
		{
			m_selX--;
		}
	}
	else if(evt.key == Event::KeyRight)
	{
		if(m_selX < 6)
		{
			m_selX++;
		}
	}
	else if(evt.key == Event::KeyUp)
	{
		if(m_selY > 0)
		{
			m_selY--;
		}
	}
	else if(evt.key == Event::KeyDown)
	{
		if(m_selY < 6)
		{
			m_selY++;
		}
	}
	else
	{
		GenericMenu::inputEvent(evt);
	}
}

int compare(const void *a, const void *b)
{
	score_entry_t *ra = (score_entry_t *)a;
	score_entry_t *rb = (score_entry_t *)b;

	return rb->score - ra->score;
}

void InGame::checkScore()
{
	if(m_score > 0)
	{
		score_entry_t entries[11];
		entries[10].score = m_score;
		entries[10].level_reached = m_level;
		sprintf(entries[10].name, "SCORED");
		memcpy(&entries, &scoreTable->entries, sizeof(score_entry_t) * 10);
		qsort(&entries, 11, sizeof(score_entry_t), &compare);
		memcpy(&scoreTable->entries, &entries, sizeof(score_entry_t) * 10);
		m_score = 0;
	}
}

void InGame::startExit()
{
	checkScore();
	GenericMenu::startExit();
}

void InGame::quitNow()
{
	checkScore();
	GenericMenu::quitNow();
}

void InGame::setMode(int mode)
{
	m_mode = mode;
}

void InGame::loadTiles()
{
	printf("loading tiles....\n");
	m_tileset[0] = new Texture("/rd/tile00.kmg.bz2", true);
	m_tileset[1] = new Texture("/rd/tile01.kmg.bz2", true);
	m_tileset[2] = new Texture("/rd/tile02.kmg.bz2", true);
	m_tileset[3] = new Texture("/rd/tile03.kmg.bz2", true);
	m_tileset[4] = new Texture("/rd/tile04.kmg.bz2", true);
	m_tileset[5] = new Texture("/rd/tile05.kmg.bz2", true);
	m_tileset[6] = new Texture("/rd/tile06.kmg.bz2", true);
	printf("loading tiles....Done\n");
}

int InGame::getNextTile()
{
	uint32 min = m_tileCount[0], max = m_tileCount[0];
	uint32 min_idx = 0, max_idx = 0;
	uint32 prev_min_idx = 0;

	for(int i = 0; i < 7; i++)
	{
		if(m_tileCount[i] < min)
		{
			min = m_tileCount[i];
			min_idx = i;
			prev_min_idx = min_idx;
		}
		if(m_tileCount[i] > max)
		{
			max = m_tileCount[i];
			max_idx = i;
		}
	}

	int32 tile;
	switch(randnum(2))
	{
		case 0:
			tile = (randnum(max_idx-min_idx)) + min_idx;
			break;
		default:
			tile = max_idx + randnum(7);
	}
	
	tile %= 7;
	m_tileCount[tile]++;
	return tile;
}

void InGame::refillBoard()
{
	for(int x = 0; x < 8; x++)
	{
		for(int y = 0; y < 8; y++)
		{
			if(m_tiles[y][x] == -1)
			{
				for(int ty = y; ty > 0; ty--)
				{
					m_tiles[ty][x] = m_tiles[ty-1][x];
					m_tileBanners[ty][x]->setTexture(m_tileset[m_tiles[ty - 1][x]]);
					m_tileBanners[ty][x]->setTranslate(Vector(-112+(x*32), -112+((ty-1)*32), 15));
					m_tileBanners[ty][x]->animRemoveAll();
					m_tileBanners[ty][x]->animAdd(new LogXYMover(-112+(x*32), -112+(ty*32)));

				}
				m_tiles[0][x] = getNextTile();
				m_tileBanners[0][x]->setTexture(m_tileset[m_tiles[0][x]]);
			}
		}
	}
}

