/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "sky/compact.h"
#include "sky/disk.h"
#include "sky/grid.h"
#include "sky/logic.h"
#include "sky/compact.h"

namespace Sky {

#define	GRID_FILE_START	60000

int8 Grid::_gridConvertTable[] = {
	0,	//0
	1,	//1
	2,	//2
	3,	//3
	4,	//4
	5,	//5
	6,	//6
	7,	//7
	8,	//8
	9,	//9
	10,	//10
	11,	//11
	12,	//12
	13,	//13
	14,	//14
	15,	//15
	16,	//16
	17,	//17
	18,	//18
	19,	//19
	20,	//20
	21,	//21
	22,	//22
	23,	//23
	24,	//24
	25,	//25
	26,	//26
	27,	//27
	28,	//28
	29,	//29
	30,	//30
	31,	//31
	32,	//32
	33,	//33
	34,	//34
	-1,	//35
	35,	//36
	36,	//37
	37,	//38
	38,	//39
	39,	//40
	40,	//41
	41,	//42
	-1,	//43
	42,	//44
	43,	//45
	44,	//46
	45,	//47
	46,	//48
	-1,	//49
	-1,	//50
	-1,	//51
	-1,	//52
	-1,	//53
	-1,	//54
	-1,	//55
	-1,	//56
	-1,	//57
	-1,	//58
	-1,	//59
	-1,	//60
	-1,	//61
	-1,	//62
	-1,	//63
	-1,	//64
	47,	//65
	TOT_NO_GRIDS,	//66
	48,	//67
	49,	//68
	50,	//69
	51,	//70
	52,	//71
	53,	//72
	54,	//73
	55,	//74
	56,	//75
	57,	//76
	58,	//77
	59,	//78
	60,	//79
	-1,	//80
	61,	//81
	62,	//82
	-1,	//83
	-1,	//84
	-1,	//85
	-1,	//86
	-1,	//87
	-1,	//88
	TOT_NO_GRIDS,	//89
	63,	//90
	64,	//91
	65,	//92
	66,	//93
	67,	//94
	68,	//95
	69,	//96
};

Grid::Grid(Disk *pDisk, SkyCompact *skyCompact) {
	for (int cnt = 0; cnt < TOT_NO_GRIDS; cnt++)
		_gameGrids[cnt] = NULL;
	_skyDisk = pDisk;
	_skyCompact = skyCompact;
}

Grid::~Grid() {
	for (uint8 cnt = 0; cnt < TOT_NO_GRIDS; cnt++)
		if (_gameGrids[cnt])
			free(_gameGrids[cnt]);
}

void Grid::loadGrids() {
	// no endian conversion necessary as I'm using uint8* instead of uint32*
	for (uint8 cnt = 0; cnt < TOT_NO_GRIDS; cnt++) {
		if (_gameGrids[cnt])
			free(_gameGrids[cnt]);
		_gameGrids[cnt] = _skyDisk->loadFile(GRID_FILE_START + cnt);
	}
	if (!SkyEngine::isDemo()) { // single disk demos never get that far
		// Reloading the grids can sometimes cause problems eg when reichs door is
		// open the door grid bit gets replaced so you can't get back in (or out)
		if (Logic::_scriptVariables[REICH_DOOR_FLAG])
			removeGrid(256, 280, 1, _skyCompact->fetchCpt(CPT_REICH_DOOR_20));
			//removeGrid(256, 280, 1, &SkyCompact::reich_door_20);
	}
}

bool Grid::getGridValues(Compact *cpt, uint8 *resGrid, uint32 *resBitNum, uint32 *resWidth) {
	uint16 width = SkyCompact::getMegaSet(cpt)->gridWidth;
	return getGridValues(cpt->xcood, cpt->ycood, width, cpt, resGrid, resBitNum, resWidth);
}

bool Grid::getGridValues(uint32 x, uint32 y, uint32 width, Compact *cpt, uint8 *resGrid, uint32 *resBitNum, uint32 *resWidth) {
	uint32 bitPos;
	if (y < TOP_LEFT_Y)
		return false; // off screen
	y -= TOP_LEFT_Y;
	y >>= 3; // convert to blocks
	if (y >= GAME_SCREEN_HEIGHT >> 3)
		return false; // off screen
	bitPos = y * 40;
	width++;
	x >>= 3; // convert to blocks

	if (x < (TOP_LEFT_X >> 3)) { // at least partially off screen
		if (x + width < (TOP_LEFT_X >> 3))
			return false; // completely off screen
		else {
			width -= (TOP_LEFT_X >> 3) - x;
			x = 0;
		}
	} else
		x -= TOP_LEFT_X >> 3;

	if ((GAME_SCREEN_WIDTH >> 3) <= x)
		return false; // off screen
	if ((GAME_SCREEN_WIDTH >> 3) < x + width) // partially off screen
		width = (GAME_SCREEN_WIDTH >> 3) - x;

	bitPos += x;
	assert((_gridConvertTable[cpt->screen] >= 0) && (_gridConvertTable[cpt->screen] < TOT_NO_GRIDS));
	*resGrid = (uint8)_gridConvertTable[cpt->screen];

	uint32 tmpBits = 0x1F - (bitPos&0x1F);
	bitPos &= ~0x1F; // divide into dword address and bit number
	bitPos += tmpBits;
	*resBitNum = bitPos;
	*resWidth = width;
	return true;
}

void Grid::removeObjectFromWalk(Compact *cpt) {
	uint32 bitNum, width;
	uint8 gridIdx;
	if (getGridValues(cpt, &gridIdx, &bitNum, &width))
		removeObjectFromWalk(gridIdx, bitNum, width);
}

void Grid::removeObjectFromWalk(uint8 gridIdx, uint32 bitNum, uint32 width) {
	for (uint32 cnt = 0; cnt < width; cnt++) {
		_gameGrids[gridIdx][bitNum >> 3] &= ~(1 << (bitNum & 0x7));
		if ((bitNum & 0x1F) == 0)
			bitNum += 0x3F;
		else
			bitNum--;
	}
}

void Grid::objectToWalk(Compact *cpt) {
	uint32 bitNum, width;
	uint8 gridIdx;
	if (getGridValues(cpt, &gridIdx, &bitNum, &width))
		objectToWalk(gridIdx, bitNum, width);
}

void Grid::objectToWalk(uint8 gridIdx, uint32 bitNum, uint32 width) {
	for (uint32 cnt = 0; cnt < width; cnt++) {
		_gameGrids[gridIdx][bitNum >> 3] |= (1 << (bitNum & 0x7));
		if ((bitNum & 0x1F) == 0)
			bitNum += 0x3F;
		else
			bitNum--;
	}
}

void Grid::plotGrid(uint32 x, uint32 y, uint32 width, Compact *cpt) {
	uint32 resBitPos, resWidth;
	uint8 resGridIdx;
	if (getGridValues(x, y, width-1, cpt, &resGridIdx, &resBitPos, &resWidth))
		objectToWalk(resGridIdx, resBitPos, resWidth);
}

void Grid::removeGrid(uint32 x, uint32 y, uint32 width, Compact *cpt) {
	uint32 resBitPos, resWidth;
	uint8 resGridIdx;
	if (getGridValues(x, y, width, cpt, &resGridIdx, &resBitPos, &resWidth))
		removeObjectFromWalk(resGridIdx, resBitPos, resWidth);
}

uint8 *Grid::giveGrid(uint32 pScreen) {
	if ((_gridConvertTable[pScreen] >= 0) && (_gridConvertTable[pScreen] < TOT_NO_GRIDS)) {
		return _gameGrids[_gridConvertTable[pScreen]];
	}
	return 0;
}

} // End of namespace Sky
