/* 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */


#include "common/debug.h"
#include "common/endian.h"
#include "common/textconsole.h"

#include "sky/disk.h"
#include "sky/logic.h"
#include "sky/text.h"
#include "sky/sky.h"
#include "sky/skydefs.h"
#include "sky/struc.h"
#include "sky/compact.h"

namespace Sky {

#define FIRST_TEXT_SEC	77
#define	FIRST_TEXT_BUFFER	274
#define LAST_TEXT_BUFFER	284
#define NO_OF_TEXT_SECTIONS	8	// 8 sections per language
#define	CHAR_SET_FILE	60150
#define MAX_SPEECH_SECTION	7
#define CHAR_SET_HEADER	128
#define	MAX_NO_LINES	10

Text::Text(Disk *skyDisk, SkyCompact *skyCompact) {
	_skyDisk = skyDisk;
	_skyCompact = skyCompact;

	initHuffTree();

	_mainCharacterSet.addr = _skyDisk->loadFile(CHAR_SET_FILE);
	_mainCharacterSet.charHeight = MAIN_CHAR_HEIGHT;
	_mainCharacterSet.charSpacing = 0;

	fnSetFont(0);

	if (!SkyEngine::isDemo()) {
		_controlCharacterSet.addr = _skyDisk->loadFile(60520);
		_controlCharacterSet.charHeight = 12;
		_controlCharacterSet.charSpacing = 0;

		_linkCharacterSet.addr = _skyDisk->loadFile(60521);
		_linkCharacterSet.charHeight = 12;
		_linkCharacterSet.charSpacing = 1;
	} else {
		_controlCharacterSet.addr = NULL;
		_linkCharacterSet.addr = NULL;
	}
}

Text::~Text() {
	for (int i = FIRST_TEXT_BUFFER; i <= LAST_TEXT_BUFFER; i++)
		if (SkyEngine::_itemList[i]) {
			free(SkyEngine::_itemList[i]);
			SkyEngine::_itemList[i] = NULL;
		}

	free(_mainCharacterSet.addr);
	free(_controlCharacterSet.addr);
	free(_linkCharacterSet.addr);
}

void Text::fnSetFont(uint32 fontNr) {
	charSet *newCharSet;

	switch (fontNr) {
	case 0:
		newCharSet = &_mainCharacterSet;
		break;
	case 1:
		newCharSet = &_linkCharacterSet;
		break;
	case 2:
		newCharSet = &_controlCharacterSet;
		break;
	default:
		error("Tried to set invalid font (%d)", fontNr);
	}

	_curCharSet = fontNr;
	_characterSet = newCharSet->addr;
	_charHeight = (byte)newCharSet->charHeight;
	_dtCharSpacing = newCharSet->charSpacing;
}

void Text::fnTextModule(uint32 textInfoId, uint32 textNo) {
	fnSetFont(1);
	uint16* msgData = (uint16 *)_skyCompact->fetchCpt(textInfoId);
	DisplayedText textId = lowTextManager(textNo, msgData[1], msgData[2], 209, false);
	Logic::_scriptVariables[RESULT] = textId.compactNum;
	Compact *textCompact = _skyCompact->fetchCpt(textId.compactNum);
	textCompact->xcood = msgData[3];
	textCompact->ycood = msgData[4];
	fnSetFont(0);
}

void Text::getText(uint32 textNr) { //load text #"textNr" into textBuffer
	if (patchMessage(textNr))
		return;

	uint32 sectionNo = (textNr & 0x0F000) >> 12;

	if (SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] == NULL) { //check if already loaded
		debug(5, "Loading Text item(s) for Section %d", (sectionNo >> 2));

		uint32 fileNo = sectionNo + ((SkyEngine::_systemVars.language * NO_OF_TEXT_SECTIONS) + 60600);
		SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] = (void **)_skyDisk->loadFile((uint16)fileNo);
	}
	uint8 *textDataPtr = (uint8 *)SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo];

	uint32 offset = 0;

	uint32 blockNr = textNr & 0xFE0;
	textNr &= 0x1F;

	if (blockNr) {
		uint16 *blockPtr = (uint16 *)(textDataPtr + 4);
		uint32 nr32MsgBlocks = blockNr >> 5;

		do {
			offset += READ_LE_UINT16(blockPtr);
			blockPtr++;
		} while (--nr32MsgBlocks);
	}

	if (textNr) {
		uint8 *blockPtr = textDataPtr + blockNr + READ_LE_UINT16(textDataPtr);
		do {
			uint16 skipBytes = *blockPtr++;
			if (skipBytes & 0x80) {
				skipBytes &= 0x7F;
				skipBytes <<= 3;
			}
			offset += skipBytes;
		} while (--textNr);
	}

	uint32 bitPos = offset & 3;
	offset >>= 2;
	offset += READ_LE_UINT16(textDataPtr + 2);

	textDataPtr += offset;

	//bit pointer: 0->8, 1->6, 2->4 ...
	bitPos ^= 3;
	bitPos++;
	bitPos <<= 1;

	char *dest = (char *)_textBuffer;
	char textChar;

	do {
		textChar = getTextChar(&textDataPtr, &bitPos);
		*dest++ = textChar;
	} while (textChar);
}

void Text::fnPointerText(uint32 pointedId, uint16 mouseX, uint16 mouseY) {
	Compact *ptrComp = _skyCompact->fetchCpt(pointedId);
	DisplayedText text = lowTextManager(ptrComp->cursorText, TEXT_MOUSE_WIDTH, L_CURSOR, 242, false);
	Logic::_scriptVariables[CURSOR_ID] = text.compactNum;
	if (Logic::_scriptVariables[MENU]) {
		_mouseOfsY = TOP_LEFT_Y - 2;
		if (mouseX < 150)
			_mouseOfsX = TOP_LEFT_X + 24;
		else
			_mouseOfsX = TOP_LEFT_X  - 8 - text.textWidth;
	} else {
		_mouseOfsY = TOP_LEFT_Y - 10;
		if (mouseX < 150)
			_mouseOfsX = TOP_LEFT_X + 13;
		else
			_mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
	}
	Compact *textCompact = _skyCompact->fetchCpt(text.compactNum);
	logicCursor(textCompact, mouseX, mouseY);
}

void Text::logicCursor(Compact *textCompact, uint16 mouseX, uint16 mouseY) {
	textCompact->xcood = (uint16)(mouseX + _mouseOfsX);
	textCompact->ycood = (uint16)(mouseY + _mouseOfsY);
	if (textCompact->ycood < TOP_LEFT_Y)
		textCompact->ycood = TOP_LEFT_Y;
}

bool Text::getTextBit(uint8 **data, uint32 *bitPos) {
	if (*bitPos) {
		(*bitPos)--;
	} else {
		(*data)++;
		*bitPos = 7;
	}

	return (bool)(((**data) >> (*bitPos)) & 1);
}

char Text::getTextChar(uint8 **data, uint32 *bitPos) {
	int pos = 0;
	while (1) {
		if (getTextBit(data, bitPos))
			pos = _huffTree[pos].rChild;
		else
			pos = _huffTree[pos].lChild;

		if (_huffTree[pos].lChild == 0 && _huffTree[pos].rChild == 0) {
			return _huffTree[pos].value;
		}
	}
}

DisplayedText Text::displayText(uint32 textNum, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
	//Render text into buffer *dest
	getText(textNum);
	return displayText(_textBuffer, dest, center, pixelWidth, color);
}

DisplayedText Text::displayText(char *textPtr, uint8 *dest, bool center, uint16 pixelWidth, uint8 color) {
	//Render text pointed to by *textPtr in buffer *dest
	uint32 centerTable[10];
	uint16 lineWidth = 0;

	uint32 numLines = 0;
	_numLetters = 2;

	// work around bug #778105 (line width exceeded)
	char *tmpPtr = strstr(textPtr, "MUND-BEATMUNG!");
	if (tmpPtr)
		strcpy(tmpPtr, "MUND BEATMUNG!");

	// work around bug #1151924 (line width exceeded when talking to gardener using spanish text)
	// This text apparently only is broken in the floppy versions, the CD versions contain
	// the correct string "MANIFESTACION - ARTISTICA.", which doesn't break the algorithm/game.
	tmpPtr = strstr(textPtr, "MANIFESTACION-ARTISTICA.");
	if (tmpPtr)
		strcpy(tmpPtr, "MANIFESTACION ARTISTICA.");

	char *curPos = textPtr;
	char *lastSpace = textPtr;
	uint8 textChar = (uint8)*curPos++;

	while (textChar >= 0x20) {
		if ((_curCharSet == 1) && (textChar >= 0x80))
			textChar = 0x20;

		textChar -= 0x20;
		if (textChar == 0) {
			lastSpace = curPos; //keep track of last space
			centerTable[numLines] = lineWidth;
		}

		lineWidth += _characterSet[textChar];	//add character width
		lineWidth += (uint16)_dtCharSpacing;	//include character spacing

		if (pixelWidth <= lineWidth) {
			if (*(lastSpace-1) == 10)
				error("line width exceeded");

			*(lastSpace-1) = 10;
			lineWidth = 0;
			numLines++;
			curPos = lastSpace;	//go back for new count
		}

		textChar = (uint8)*curPos++;
		_numLetters++;
	}

	uint32 dtLastWidth = lineWidth;	//save width of last line
	centerTable[numLines] = lineWidth; //and update centering table
	numLines++;

	if (numLines > MAX_NO_LINES)
		error("Maximum no. of lines exceeded");

	uint32 dtLineSize = pixelWidth * _charHeight;
	uint32 numBytes = (dtLineSize * numLines) + sizeof(DataFileHeader) + 4;

	if (!dest)
		dest = (uint8 *)malloc(numBytes);

	// clear text sprite buffer
	memset(dest + sizeof(DataFileHeader), 0, numBytes - sizeof(DataFileHeader));

	//make the header
	((DataFileHeader *)dest)->s_width = pixelWidth;
	((DataFileHeader *)dest)->s_height = (uint16)(_charHeight * numLines);
	((DataFileHeader *)dest)->s_sp_size = (uint16)(pixelWidth * _charHeight * numLines);
	((DataFileHeader *)dest)->s_offset_x = 0;
	((DataFileHeader *)dest)->s_offset_y = 0;

	//reset position
	curPos = textPtr;

	uint8 *curDest = dest +  sizeof(DataFileHeader); //point to where pixels start
	byte *prevDest = curDest;
	uint32 *centerTblPtr = centerTable;

	do {
		if (center) {
			uint32 width = (pixelWidth - *centerTblPtr) >> 1;
			centerTblPtr++;
			curDest += width;
		}

		textChar = (uint8)*curPos++;
		while (textChar >= 0x20) {
			makeGameCharacter(textChar - 0x20, _characterSet, curDest, color, pixelWidth);
			textChar = *curPos++;
		}

		prevDest = curDest = prevDest + dtLineSize;	//start of last line + start of next

	} while (textChar >= 10);

	DisplayedText ret;
	memset(&ret, 0, sizeof(ret));
	ret.textData = dest;
	ret.textWidth = dtLastWidth;
	return ret;
}

void Text::makeGameCharacter(uint8 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch) {
	bool maskBit, dataBit;
	uint8 charWidth = (uint8)((*(charSetPtr + textChar)) + 1 - _dtCharSpacing);
	uint16 data, mask;
	byte *charSpritePtr = charSetPtr + (CHAR_SET_HEADER + ((_charHeight << 2) * textChar));
	byte *startPos = dest;
	byte *curPos = startPos;

	for (int i = 0; i < _charHeight; i++) {
		byte *prevPos = curPos;

		data = READ_BE_UINT16(charSpritePtr);
		mask = READ_BE_UINT16(charSpritePtr + 2);
		charSpritePtr += 4;

		for (int j = 0; j < charWidth; j++) {

			maskBit = (mask & 0x8000) != 0; //check mask
			mask <<= 1;
			dataBit = (data & 0x8000) != 0; //check data
			data <<= 1;

			if (maskBit) {
				if (dataBit)
					*curPos = color;
				else
					*curPos = 240; //black edge
			}
			curPos++;
		}
		//advance a line
		curPos = prevPos + bufPitch;
	}
	//update position
	dest = startPos + charWidth + _dtCharSpacing * 2 - 1;
}

DisplayedText Text::lowTextManager(uint32 textNum, uint16 width, uint16 logicNum, uint8 color, bool center) {
	getText(textNum);
	DisplayedText textInfo = displayText(_textBuffer, NULL, center, width, color);

	uint32 compactNum = FIRST_TEXT_COMPACT;
	Compact *cpt = _skyCompact->fetchCpt(compactNum);
	while (cpt->status != 0) {
		compactNum++;
		cpt = _skyCompact->fetchCpt(compactNum);
	}

	cpt->flag = (uint16)(compactNum - FIRST_TEXT_COMPACT) + FIRST_TEXT_BUFFER;

	if (SkyEngine::_itemList[cpt->flag])
		free(SkyEngine::_itemList[cpt->flag]);

	SkyEngine::_itemList[cpt->flag] = textInfo.textData;

	cpt->logic = logicNum;
	cpt->status = ST_LOGIC | ST_FOREGROUND | ST_RECREATE;
	cpt->screen = (uint16) Logic::_scriptVariables[SCREEN];

	textInfo.compactNum = (uint16)compactNum;
	return textInfo;
}

void Text::changeTextSpriteColor(uint8 *sprData, uint8 newCol) {
	DataFileHeader *header = (DataFileHeader *)sprData;
	sprData += sizeof(DataFileHeader);
	for (uint16 cnt = 0; cnt < header->s_sp_size; cnt++)
		if (sprData[cnt] >= 241)
			sprData[cnt] = newCol;
}

uint32 Text::giveCurrentCharSet() {
	return _curCharSet;
}

void Text::initHuffTree() {
	switch (SkyEngine::_systemVars.gameVersion) {
	case 109:
		_huffTree = _huffTree_00109;
		break;
	case 272: // FIXME: Extract data
	case 267:
		_huffTree = _huffTree_00267;
		break;
	case 288:
		_huffTree = _huffTree_00288;
		break;
	case 303:
		_huffTree = _huffTree_00303;
		break;
	case 331:
		_huffTree = _huffTree_00331;
		break;
	case 348:
		_huffTree = _huffTree_00348;
		break;
	case 365:
		_huffTree = _huffTree_00365;
		break;
	case 368:
		_huffTree = _huffTree_00368;
		break;
	case 372:
		_huffTree = _huffTree_00372;
		break;
	default:
		error("Unknown game version %d", SkyEngine::_systemVars.gameVersion);
	}
}

bool Text::patchMessage(uint32 textNum) {
	uint16 patchIdx = _patchLangIdx[SkyEngine::_systemVars.language];
	uint16 patchNum = _patchLangNum[SkyEngine::_systemVars.language];
	for (uint16 cnt = 0; cnt < patchNum; cnt++) {
		if (_patchedMessages[cnt + patchIdx].textNr == textNum) {
			strcpy(_textBuffer, _patchedMessages[cnt + patchIdx].text);
			return true;
		}
	}
	return false;
}

const PatchMessage Text::_patchedMessages[NUM_PATCH_MSG] = {
	{ 28724, "Testo e Parlato" }, // - italian
	{ 28707, "Solo Testo" },
	{ 28693, "Solo Parlato" },
	{ 28724, "Text och tal" }, // - swedish
	{ 28707, "Endast text" },
	{ 28693, "Endast tal" },
	{ 28686, "Musikvolym" },
	{ 4336, "Wir befinden uns EINHUNDERTZWANZIG METER #ber dem ERBODEN!" }, // - german
	{ 28686, "Volume de musique" }, // - french
};

const uint16 Text::_patchLangIdx[8] = {
	0xFFFF, // SKY_ENGLISH
	7,		// SKY_GERMAN
	8,		// SKY_FRENCH
	0xFFFF, // SKY_USA
	3,		// SKY_SWEDISH
	0,		// SKY_ITALIAN
	0xFFFF, // SKY_PORTUGUESE
	0xFFFF  // SKY_SPANISH
};

const uint16 Text::_patchLangNum[8] = {
	0, // SKY_ENGLISH
	1, // SKY_GERMAN
	1, // SKY_FRENCH
	0, // SKY_USA
	4, // SKY_SWEDISH
	3, // SKY_ITALIAN
	0, // SKY_PORTUGUESE
	0  // SKY_SPANISH
};

} // End of namespace Sky