/* 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 "common/textconsole.h"
#include "common/str.h"
#include "common/unicode-bidi.h"

#include "sword1/logic.h"
#include "sword1/text.h"
#include "sword1/resman.h"
#include "sword1/objectman.h"
#include "sword1/swordres.h"
#include "sword1/sworddefs.h"
#include "sword1/screen.h"
#include "sword1/sword1.h"

namespace Sword1 {

#define OVERLAP       3
#define DEMO_OVERLAP  1
#define DEBUG_OVERLAP 2
#define SPACE         ' '
#define MAX_LINES     30


Text::Text(SwordEngine *vm, Logic *pLogic, ObjectMan *pObjMan, ResMan *pResMan, Screen *pScreen, bool czechVersion) {
	_vm = vm;
	_logic = pLogic;
	_objMan = pObjMan;
	_resMan = pResMan;
	_screen = pScreen;
	_textCount = 0;
	_fontId = (czechVersion) ? CZECH_GAME_FONT : GAME_FONT;
	_font = (uint8 *)_resMan->openFetchRes(_fontId);

	_joinWidth = charWidth(SPACE);

	if (!SwordEngine::_systemVars.isDemo) {
		_joinWidth -= 2 * OVERLAP;
	} else {
		_joinWidth -= 2 * DEMO_OVERLAP;
	}

	_charHeight = _resMan->getUint16(_resMan->fetchFrame(_font, 0)->height); // all chars have the same height

	if (SwordEngine::isPsx())
		_charHeight /= 2;

	for (int i = 0; i < MAX_TEXT_OBS; i++)
		_textBlocks[i] = NULL;
}

Text::~Text() {
	for (int i = 0; i < MAX_TEXT_OBS; i++)
		free(_textBlocks[i]);
	//_resMan->resClose(_fontId); => wiped automatically by _resMan->flush();
}

uint32 Text::lowTextManager(uint8 *ascii, int32 width, uint8 pen) {
	_textCount++;
	if (_textCount > MAX_TEXT_OBS)
		error("Text::lowTextManager: MAX_TEXT_OBS exceeded");
	uint32 textObjId = (TEXT_sect * ITM_PER_SEC) - 1;
	do {
		textObjId++;
	} while (_objMan->fetchObject(textObjId)->o_status);
	// okay, found a free text object

	_objMan->fetchObject(textObjId)->o_status = STAT_FORE;
	makeTextSprite((uint8)textObjId, ascii, (uint16)width, pen);

	return textObjId;
}

void Text::makeTextSprite(uint8 slot, const uint8 *text, uint16 maxWidth, uint8 pen) {
	LineInfo lines[MAX_LINES];
	uint16 numLines = analyzeSentence(text, maxWidth, lines);

	uint16 sprWidth = 0;
	uint16 lineCnt;
	for (lineCnt = 0; lineCnt < numLines; lineCnt++)
		if (lines[lineCnt].width > sprWidth)
			sprWidth = lines[lineCnt].width;

	uint16 sprHeight = _charHeight * numLines;
	if (SwordEngine::isPsx()) {
		sprHeight = 2 * _charHeight * numLines - 4 * (numLines - 1);
		sprWidth = (sprWidth + 1) & 0xFFFE;
	}

	uint32 sprSize = sprWidth * sprHeight;
	assert(!_textBlocks[slot]); // if this triggers, the speechDriver failed to call Text::releaseText.
	_textBlocks[slot] = (FrameHeader *)malloc(sprSize + sizeof(FrameHeader));

	memcpy(_textBlocks[slot]->runTimeComp, "Nu  ", 4);
	_textBlocks[slot]->compSize = 0;
	_textBlocks[slot]->width    = _resMan->toUint16(sprWidth);
	_textBlocks[slot]->height   = _resMan->toUint16(sprHeight);
	_textBlocks[slot]->offsetX  = 0;
	_textBlocks[slot]->offsetY  = 0;

	uint8 *linePtr = ((uint8 *)_textBlocks[slot]) + sizeof(FrameHeader);
	memset(linePtr, NO_COL, sprSize);
	for (lineCnt = 0; lineCnt < numLines; lineCnt++) {
		uint8 *sprPtr = linePtr + (sprWidth - lines[lineCnt].width) / 2; // center the text
		Common::String textString;
		const uint8 *curTextLine = text;
		if (SwordEngine::_systemVars.isLangRtl) {
			Common::String textLogical = Common::String((const char *)text, (uint32)lines[lineCnt].length);
			textString = Common::convertBiDiString(textLogical, Common::kWindows1255);
			curTextLine = (const uint8 *)textString.c_str();
		}
		for (uint16 pos = 0; pos < lines[lineCnt].length; pos++) {
			if (isKoreanChar(*curTextLine, *(curTextLine + 1))) {
				sprPtr += copyWChar(*curTextLine, *(curTextLine + 1), sprPtr, sprWidth, pen) - OVERLAP;
				curTextLine += 2;
				pos++;
			} else {
				sprPtr += copyChar(*curTextLine++, sprPtr, sprWidth, pen);

				if (!SwordEngine::_systemVars.isDemo) {
					sprPtr -= OVERLAP;
				} else {
					sprPtr -= DEMO_OVERLAP;
				}
			}
		}

		curTextLine++; // skip space at the end of the line
		text += lines[lineCnt].length + 1;

		if (SwordEngine::isPsx())
			linePtr += (_charHeight - 4) * sprWidth;
		else
			linePtr += _charHeight * sprWidth;
	}
}

uint16 Text::charWidth(uint8 ch) {
	if (ch < SPACE)
		ch = 64;
	return _resMan->getUint16(_resMan->fetchFrame(_font, ch - SPACE)->width);
}

uint16 Text::analyzeSentence(const uint8 *text, uint16 maxWidth, LineInfo *line) {
	uint16 lineNo = 0;
	bool firstWord = true;

	if (SwordEngine::isPsx())
		maxWidth = 254;

	while (*text) {
		uint16 wordWidth = 0;
		uint16 wordLength = 0;

		while ((*text != SPACE) && *text) {
			if (isKoreanChar(*text, *(text + 1))) {
				wordWidth += wCharWidth(*text, *(text + 1)) - OVERLAP;
				wordLength += 2;
				text += 2;
			} else {
				wordWidth += charWidth(*text);

				if (!SwordEngine::_systemVars.isDemo) {
					wordWidth -= OVERLAP;
				} else {
					wordWidth -= DEMO_OVERLAP;
				}

				wordLength++;
				text++;
			}
		}

		if (*text == SPACE)
			text++;

		// no overlap on final letter of word!
		if (!SwordEngine::_systemVars.isDemo) {
			wordWidth += OVERLAP;
		} else {
			wordWidth += DEMO_OVERLAP;
		}

		if (firstWord)  { // first word on first line, so no separating SPACE needed
			line[0].width = wordWidth;
			line[0].length = wordLength;
			firstWord = false;
		} else {
			// see how much extra space this word will need to fit on current line
			// (with a separating space character - also overlapped)
			uint16 spaceNeeded = _joinWidth + wordWidth;

			if (line[lineNo].width + spaceNeeded <= maxWidth) {
				line[lineNo].width += spaceNeeded;
				line[lineNo].length += 1 + wordLength; // NB. space+word characters
			} else {    // put word (without separating SPACE) at start of next line
				lineNo++;
				assert(lineNo < MAX_LINES);
				line[lineNo].width = wordWidth;
				line[lineNo].length = wordLength;
			}
		}
	}
	return lineNo + 1;  // return no of lines
}

uint16 Text::copyChar(uint8 ch, uint8 *sprPtr, uint16 sprWidth, uint8 pen) {
	if (ch < SPACE)
		ch = 64;
	FrameHeader *chFrame = _resMan->fetchFrame(_font, ch - SPACE);
	uint8 *chData = ((uint8 *)chFrame) + sizeof(FrameHeader);
	uint8 *dest = sprPtr;
	uint8 *decBuf = NULL;
	uint8 *decChr;
	uint16 frameHeight = 0;

	if (SwordEngine::isPsx()) {
		frameHeight = _resMan->getUint16(chFrame->height) / 2;
		if (_fontId == CZECH_GAME_FONT) { //Czech game fonts are compressed
			decBuf = (uint8 *)malloc((_resMan->getUint16(chFrame->width)) * (_resMan->getUint16(chFrame->height) / 2));
			Screen::decompressHIF(chData, decBuf);
			decChr = decBuf;
		} else //Normal game fonts are not compressed
			decChr = chData;
	} else {
		frameHeight =  _resMan->getUint16(chFrame->height);
		decChr = chData;
	}

	for (uint16 cnty = 0; cnty < frameHeight; cnty++) {
		for (uint16 cntx = 0; cntx < _resMan->getUint16(chFrame->width); cntx++) {
			if (*decChr == LETTER_COL)
				dest[cntx] = pen;
			else if (((*decChr == BORDER_COL) || (*decChr == BORDER_COL_PSX)) && (!dest[cntx])) // don't do a border if there's already a color underneath (chars can overlap)
				dest[cntx] = BORDER_COL;
			decChr++;
		}
		dest += sprWidth;
	}
	free(decBuf);
	return _resMan->getUint16(chFrame->width);
}

FrameHeader *Text::giveSpriteData(uint32 textTarget) {
	// textTarget is the resource ID of the Compact linking the textdata.
	// that's 0x950000 for slot 0 and 0x950001 for slot 1. easy, huh? :)
	textTarget &= ITM_ID;
	assert(textTarget < MAX_TEXT_OBS);

	return _textBlocks[textTarget];
}

void Text::releaseText(uint32 id, bool updateCount) {
	id &= ITM_ID;
	assert(id < MAX_TEXT_OBS);
	if (_textBlocks[id]) {
		free(_textBlocks[id]);
		_textBlocks[id] = NULL;
		if (updateCount)
			_textCount--;
	}
}

void Text::printDebugLine(uint8 *ascii, uint8 first, int x, int y) {
	FrameHeader *head;
	int chr;

	do {
		chr = (int)*(ascii);
		chr -= first;

		head = (FrameHeader *)_resMan->fetchFrame(_font, chr);

		uint8 *sprData = (uint8 *)head + sizeof(FrameHeader);

		// The original drawSprite routine also clipped the sprite, so
		// let's do that as well in order to produce an accurate result...
		uint16 newCoordsX = x;
		uint16 newCoordsY = y;
		uint16 newWidth = _resMan->getUint16(head->width);
		uint16 newHeight = SwordEngine::isPsx() ?
			_resMan->getUint16(head->height) / 2 : _resMan->getUint16(head->height);
		uint16 incr;
		_screen->spriteClipAndSet(&newCoordsX, &newCoordsY, &newWidth, &newHeight, &incr);
		_screen->drawSprite(sprData + incr, newCoordsX, newCoordsY, newWidth, newHeight, newWidth);

		x += _resMan->getUint16(head->width);

		if (SwordEngine::isPsx()) {
			x -= OVERLAP;
		} else {
			// The very first executable version didn't use any overlap (verified on UK disasm)
			if (SwordEngine::_systemVars.realLanguage != Common::EN_ANY)
				x -= DEBUG_OVERLAP;
		}

		ascii++;
	} while (*ascii);
}

uint16 Text::wCharWidth(uint8 hi, uint8 lo) {
	if (isKoreanChar(hi, lo)) {
		return 20; // fixed width : 20
	}
	return charWidth(hi) + charWidth(lo);
}

uint16 Text::copyWChar(uint8 hi, uint8 lo, uint8 *sprPtr, uint16 sprWidth, uint8 pen) {
	if (!isKoreanChar(hi, lo)) {
		return copyChar(hi, sprPtr, sprWidth, pen) + copyChar(lo, sprPtr, sprWidth, pen);
	}

	uint16 frameWidth = 20;
	uint16 frameHeight = 26;
	FrameHeader *chFrame = _resMan->fetchFrame(_font, 0xFF - SPACE);
	uint8 *dest = sprPtr;
	uint8 *decChr = ((uint8 *)chFrame) + sizeof(FrameHeader) + chFrame->width * chFrame->height + ((hi - 0xB0) * 94 + (lo - 0xA1)) * frameWidth * frameHeight;

	for (uint16 cnty = 0; cnty < frameHeight; cnty++) {
		for (uint16 cntx = 0; cntx < frameWidth; cntx++) {
			if (*decChr == LETTER_COL)
				dest[cntx] = pen;
			else if (((*decChr == BORDER_COL) || (*decChr == BORDER_COL_PSX)) && (!dest[cntx])) // don't do a border if there's already a color underneath (chars can overlap)
				dest[cntx] = BORDER_COL;
			decChr++;
		}
		dest += sprWidth;
	}
	return frameWidth;
}

bool Text::isKoreanChar(uint8 hi, uint8 lo) {
	if (SwordEngine::_systemVars.realLanguage != Common::KO_KOR)
		return false;
	if (hi >= 0xB0 && hi <= 0xC8 && lo >= 0xA1 && lo <= 0xFE)
		return true;
	return false;
}

} // End of namespace Sword1
