/* 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/config-manager.h"
#include "common/str.h"

#include "trecision/actor.h"
#include "trecision/animmanager.h"
#include "trecision/graphics.h"
#include "trecision/sound.h"
#include "trecision/trecision.h"
#include "trecision/text.h"
#include "trecision/scheduler.h"
#include "trecision/video.h"

namespace Trecision {

TextManager::TextManager(TrecisionEngine *vm) : _vm(vm) {
	_someoneSpeakTime = 0;
	_subStringAgain = false;
	_talkTime = 0;
	for (int i = 0; i < MAXSUBSTRING; ++i) {
		for (int j = 0; j < MAXLENSUBSTRING; ++j) {
			_subString[i][j] = 0;
		}
	}
	_subStringUsed = 0;
	_subStringStart = 0;
	_curSentenceId = 0;
	_curSubString = 0;
	_talkingPersonId = 0;
}

TextManager::~TextManager() {
}

Common::Point TextManager::positionString(uint16 x, uint16 y, const char *string, bool characterFl) {
	uint16 lenText = _vm->textLength(string);
	Common::Point pos;

	if (lenText > 960)
		lenText = (lenText * 2 / 5);
	else if (lenText > 320)
		lenText = (lenText * 3 / 5);

	if (x > (lenText >> 1))
		x -= (lenText >> 1);
	else
		x = 0;

	pos.x = CLIP<uint16>(x, 5, MAXX - lenText - 5);

	pos.y = characterFl ? 0 : VIDEOTOP;
	pos.y += y - 1; //15
	if (pos.y <= VIDEOTOP)
		pos.y = VIDEOTOP + 1;

	return pos;
}

void TextManager::formattingSuperString() {
	_subStringUsed = 0;
	_subStringAgain = true;
	while (_subStringAgain) {
		formattingOneString();
		++_subStringUsed;
	}
}

void TextManager::formattingOneString() {
	uint16 i;
	memset(_subString[_subStringUsed], '\0', MAXLENSUBSTRING);

	const uint16 available = (_superString.size() - _subStringStart);
	for (i = 0; i < available; ++i) {
		switch (_superString[i + _subStringStart]) {
		case '\0':
			_subStringAgain = false;
			return;

		case '@':
			_subStringAgain = true;
			_subStringStart += (i + 1);
			return;

		default:
			_subString[_subStringUsed][i] = _superString[i + _subStringStart];
			break;
		}
	}

	_subString[_subStringUsed][i] = '\0';
	_subStringAgain = false;
}

void TextManager::characterTalk(Common::String s) {
	_vm->_flagSomeoneSpeaks = true;
	_vm->_flagCharacterSpeak = true;
	_vm->_flagSkipTalk = false;

	_superString = s;
	_subStringStart = 0;
	_curSubString = 0;
	formattingSuperString();

	characterContinueTalk();

	_vm->_scheduler->initCharacterQueue();
	_vm->_actor->actorStop();
}

void TextManager::characterContinueTalk() {
	Common::Point pos;

	_vm->_flagSkipTalk = false;
	_vm->_characterSpeakTime = _vm->_curTime;

	_subStringAgain = (_curSubString < (_subStringUsed - 1));

	if (_vm->_flagShowCharacter || _vm->_animMgr->isActionActive())
		pos = positionString(_vm->_actor->_area[0], _vm->_actor->_area[2], _subString[_curSubString], true);
	else
		pos = positionString(MAXX / 2, 30, _subString[_curSubString], false);

	clearLastText();
	if (ConfMan.getBool("subtitles"))
		addText(pos, _subString[_curSubString], COLOR_OBJECT);

	if (!_vm->_flagDialogActive) {
		if (_curSubString)
			_lastFilename = Common::Path(Common::String::format("s%04d%c.wav", _curSentenceId, _curSubString + 'a'));
		else
			_lastFilename = Common::Path(Common::String::format("s%04d.wav", _curSentenceId));
	}

	_talkTime = _vm->_soundMgr->talkStart(_lastFilename);
	if (!_talkTime)
		_talkTime = Common::String(_subString[_curSubString]).size() * 5 / 2 + 50;

	++_curSubString;

	_vm->_scheduler->doEvent(MC_STRING, ME_CHARACTERSPEAKING, MP_DEFAULT, 0, 0, 0, 0);
}

void TextManager::characterMute() {
	_vm->_flagSomeoneSpeaks = false;
	_vm->_flagCharacterSpeak = false;
	_vm->_flagSkipTalk = false;
	_vm->_characterSpeakTime = 0;

	clearLastText();
	_vm->_lastObj = 0;
	_vm->_lastInv = 0;

	redrawString();
	_vm->_soundMgr->stopSoundType(kSoundTypeSpeech);

	if ((_vm->_curRoom == kRoom12CU) || (_vm->_curRoom == kRoom13CU))
		_vm->changeRoom(_vm->_oldRoom);
}

void TextManager::someoneContinueTalk() {
	_someoneSpeakTime = _vm->_curTime;
	_vm->_flagSkipTalk = false;

	_subStringAgain = (_curSubString < (_subStringUsed - 1));

	Common::Point pos;
	if (_talkingPersonId)
		pos = positionString(_vm->_obj[_talkingPersonId]._area.left, _vm->_obj[_talkingPersonId]._area.top, _subString[_curSubString], false);
	else
		pos = positionString(_vm->_actor->_area[0], _vm->_actor->_area[2], _subString[_curSubString], true);

	clearLastText();
	if (ConfMan.getBool("subtitles"))
		addText(pos, _subString[_curSubString], HYELLOW);

	if (_curSubString)
		_lastFilename = Common::Path(Common::String::format("s%04d%c.wav", _curSentenceId, _curSubString + 'a'));
	else
		_lastFilename = Common::Path(Common::String::format("s%04d.wav", _curSentenceId));

	_talkTime = _vm->_soundMgr->talkStart(_lastFilename);
	if (!_talkTime)
		_talkTime = Common::String(_subString[_curSubString]).size() * 5 / 2 + 50;

	++_curSubString;
	_vm->_scheduler->doEvent(MC_STRING, ME_SOMEONESPEAKING, MP_DEFAULT, 0, 0, 0, 0);
}

void TextManager::someoneMute() {
	_vm->_flagCharacterSpeak = false;
	_vm->_flagSkipTalk = false;
	_vm->_flagSomeoneSpeaks = false;
	_someoneSpeakTime = 0;

	clearLastText();
	_vm->_lastObj = 0;
	_vm->_lastInv = 0;

	redrawString();
	_vm->_soundMgr->stopSoundType(kSoundTypeSpeech);
}

// ******************************************************* //

void TextManager::doString() {
	switch (_vm->_curMessage->_event) {

	case ME_CHARACTERSPEAKING:
		if (_vm->_flagCharacterSpeak) {
			if (_vm->_flagSkipTalk || (_vm->_curTime > _talkTime + _vm->_characterSpeakTime)) {
				if (_subStringAgain)
					characterContinueTalk();
				else
					characterMute();
			} else
				_vm->reEvent();
		}
		break;

	case ME_SOMEONESPEAKING:
		if (_vm->_flagSomeoneSpeaks) {
			if (_vm->_flagSkipTalk || (_vm->_curTime >= (_talkTime + _someoneSpeakTime))) {
				if (_subStringAgain)
					someoneContinueTalk();
				else {
					someoneMute();
				}
			} else
				_vm->reEvent();
		}
		break;
	default:
		break;
	}
}

void TextManager::showObjName(uint16 obj, bool show) {
	static const char *dunno = "?";
	Common::String desc;

	if (_vm->_flagSomeoneSpeaks)
		return;

	if (_vm->_lastInv) {
		clearLastText();
		_vm->_lastInv = 0;
	}

	if (_vm->_flagUseWithStarted) {
		if (!show) {
			clearLastText();
			_vm->_lastObj = obj;
			return;
		}

		if ((_vm->_obj[_vm->_curObj].isFlagRoomOut() || _vm->_obj[_vm->_curObj].isFlagRoomIn()) && !_vm->_obj[_vm->_curObj].isFlagExamine())
			return;

		desc = _vm->_sysText[kMessageUse];
	
		if (_vm->_useWithInv[USED])
			desc += _vm->_objName[_vm->_inventoryObj[_vm->_useWith[USED]]._name];
		else if (_vm->_obj[_vm->_useWith[USED]].isModeHidden())
			desc += dunno;
		else
			desc += _vm->_objName[_vm->_obj[_vm->_useWith[USED]]._name];

		desc += _vm->_sysText[kMessageWith];
		if (obj && (_vm->_useWithInv[USED] || (obj != _vm->_useWith[USED]))) {
			if (_vm->_obj[obj].isModeHidden())
				desc += dunno;
			else
				desc += _vm->_objName[_vm->_obj[obj]._name];
		}

		_vm->_lastObj = (obj | 0x8000);
		const uint16 lenText = _vm->textLength(desc);
		const Common::Point pos(CLIP(320 - (lenText / 2), 2, MAXX - 2 - lenText), MAXY - CARHEI);

		if (_vm->_lastObj)
			clearLastText();
		addText(pos, desc.c_str(), COLOR_INVENTORY);
	} else {
		if (!obj || !show) {
			clearLastText();
			_vm->_lastObj = obj;
			return;
		}

		if (obj == _vm->_lastObj)
			return;
		if (!_vm->_obj[obj].isFlagExamine()) {
			if (_vm->_obj[obj].isFlagDone() || _vm->_room[_vm->_obj[obj]._goRoom].isDone()) {
				desc = _vm->_sysText[kMessageGoto];
				if (_vm->_obj[obj].isModeHidden())
					desc += dunno;
				else
					desc += _vm->_objName[_vm->_obj[obj]._name];
			} else
				desc = _vm->_sysText[kMessageGoto2];
		} else if (_vm->_obj[obj].isModeHidden())
			desc = dunno;
		else
			desc = _vm->_objName[_vm->_obj[obj]._name];

		const uint16 x = (_vm->_obj[obj]._area.left + _vm->_obj[obj]._area.right) / 2;
		const uint16 y = (obj == oWHEELS2C) ? 187 : _vm->_obj[obj]._area.top;
		Common::Point pos = positionString(x, y, desc.c_str(), false);

		if (_vm->_lastObj)
			clearLastText();
		_vm->_lastObj = obj;
		addText(pos, desc.c_str(), COLOR_OBJECT);
	}
}

void TextManager::someoneSay(uint16 sentence, uint16 person) {
	_talkingPersonId = person;
	_vm->_flagSomeoneSpeaks = true;
	_vm->_flagSkipTalk = false;

	_curSentenceId = sentence;
	_superString = _vm->_sentence[sentence];
	_subStringStart = 0;
	_curSubString = 0;

	formattingSuperString();
	someoneContinueTalk();
}

void TextManager::characterSay(uint16 i) {
	_curSentenceId = i;

	//	if he took some action
	if (_vm->_sentence[i][0] == '*' && !_vm->_animMgr->isActionActive())
		_vm->startCharacterAction(hBOH, 0, 0, 0);
	else
		characterTalk(_vm->_sentence[i]);
}

void TextManager::characterSayInAction(uint16 ss) {
	const char *s = _vm->_sentence[ss];

	if (s[0] == '*')
		return;
	_curSentenceId = ss;

	_vm->_flagSomeoneSpeaks = true;
	_vm->_flagCharacterSpeak = true;
	_vm->_flagSkipTalk = false;

	_superString = s;
	_subStringStart = 0;
	_curSubString = 0;
	formattingSuperString();

	characterContinueTalk();
}

void TextManager::addText(Common::Point pos, const char *text, uint16 textCol) {
	StackText t;
	t._x = pos.x;
	t._y = pos.y;
	t._textColor = textCol;
	t._clear = false;
	t._text = text;

	_textStack.push_back(t);
}

void TextManager::clearLastText() {
	if (!_textStack.empty()) {
		if (!_textStack.back()._clear)
			// The last entry is a string to be shown, remove it
			_textStack.pop_back();
	} else {
		StackText t;
		t._clear = true;
		_textStack.push_back(t);
	}
}

void TextManager::drawText(StackText *text) {
	_curString._rect.left = text->_x;
	_curString._rect.top = text->_y;
	_curString._rect.setWidth(_vm->textLength(text->_text));
	int16 w = _curString._rect.width();

	if (text->_y == MAXY - CARHEI && w > 600)
		w = w * 3 / 5;
	else if (text->_y != MAXY - CARHEI && w > 960)
		w = w * 2 / 5;
	else if (text->_y != MAXY - CARHEI && w > 320)
		w = w * 3 / 5;

	_curString._rect.setWidth(w);

	_curString._text = text->_text;
	uint16 height = _curString.calcHeight(_vm);
	_curString._subtitleRect = Common::Rect(_curString._rect.width(), height);
	_curString._rect.setHeight(height);
	_curString._textColor = text->_textColor;

	if (_curString._rect.top <= height)
		_curString._rect.top += height;
	else
		_curString._rect.top -= height;

	if (_curString._rect.top <= VIDEOTOP)
		_curString._rect.top = VIDEOTOP + 1;

	_vm->_textStatus |= TEXT_DRAW;
}

void TextManager::clearText() {
	if (_oldString._text.empty() && !_curString._text.empty()) {
		_oldString.set(&_curString);
		_curString._text.clear();

		_vm->_textStatus |= TEXT_DEL;
	}
}

void TextManager::drawTexts() {
	for (Common::List<StackText>::iterator it = _textStack.begin(); it != _textStack.end(); ++it) {
		if (it->_clear)
			clearText();
		else
			drawText(&*it);
	}
}

void TextManager::redrawString() {
	if (!_vm->_flagDialogActive && !_vm->_flagDialogMenuActive && !_vm->_flagSomeoneSpeaks && !_vm->_flagScriptActive && _vm->_graphicsMgr->isCursorVisible()) {
		if (_vm->isInventoryArea(_vm->_mousePos))
			_vm->showIconName();
		else {
			_vm->checkMask(_vm->_mousePos);
			showObjName(_vm->_curObj, true);
		}
	}
}

void TextManager::clearTextStack() {
	_textStack.clear();
}

void TextManager::drawCurString() {
	_curString.draw(_vm);
	_vm->_graphicsMgr->addDirtyRect(_curString._rect, false);
}

Common::Rect TextManager::getOldTextRect() const {
	return _oldString._rect;
}

void TextManager::clearOldText() {
	_oldString._text.clear();
}

} // End of namespace Trecision
