/* 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 "sherlock/tattoo/widget_talk.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_journal.h"
#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo_talk.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "sherlock/tattoo/tattoo.h"

namespace Sherlock {

namespace Tattoo {

#define STATEMENT_NUM_X 6
#define NUM_VISIBLE_TALK_LINES 6

WidgetTalk::WidgetTalk(SherlockEngine *vm) : WidgetBase(vm) {
	_talkScrollIndex = 0;
	_selector = _oldSelector = -1;
	_talkTextX = 0;
	_dialogTimer = 0;
}

void WidgetTalk::getTalkWindowSize() {
	TattooTalk &talk = *(TattooTalk *)_vm->_talk;
	int width, height;

	// See how many statements are going to be available
	int numStatements = 0;
	for (uint idx = 0; idx < talk._statements.size(); ++idx) {
		if (talk._statements[idx]._talkMap != -1)
			++numStatements;
	}

	width = SHERLOCK_SCREEN_WIDTH * 2 / 3;

	// Split up the questions into separate strings for each line
	_bounds = Common::Rect(width, 1);
	setStatementLines();

	// Make sure that the window does not get too big
	if (_statementLines.size() < 7) {
		height = (_surface.fontHeight() + 1) * _statementLines.size() + 9;
		_scroll = false;
	} else {
		// Set up the height to a constrained amount, and add extra width for the scrollbar
		width += BUTTON_SIZE + 3;
		height = (_surface.fontHeight() + 1) * 6 + 9;
		_scroll = true;
	}

	_bounds = Common::Rect(width, height);
}

void WidgetTalk::load() {
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooScene &scene = *(TattooScene *)_vm->_scene;

	// Figure out the window size
	getTalkWindowSize();

	// Place the window centered above the player
	Common::Point pt;
	int scaleVal = scene.getScaleVal(people[HOLMES]._position);
	pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2;

	if (scaleVal == SCALE_THRESHOLD) {
		pt.x += people[0].frameWidth() / 2;
		pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()
			- _bounds.height() - _surface.fontHeight();
	} else {
		pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2;
		pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal)
			- _bounds.height() - _surface.fontHeight();
	}

	_bounds.moveTo(pt);

	// Set up the surface
	_surface.create(_bounds.width(), _bounds.height());
	_surface.fill(TRANSPARENCY);

	// Form the background for the new window
	makeInfoArea();
}

void WidgetTalk::handleEvents() {
	Events &events = *_vm->_events;
	TattooJournal &journal = *(TattooJournal *)_vm->_journal;
	TattooPeople &people = *(TattooPeople *)_vm->_people;
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	Sound &sound = *_vm->_sound;
	TattooTalk &talk = *(TattooTalk *)_vm->_talk;
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
	Common::Point mousePos = events.mousePos();
	Common::KeyCode keycode = ui._keyState.keycode;
	bool hotkey = false;
	bool callParrotFile = false;

	// Handle scrollbar events
	ScrollHighlight oldHighlight = ui._scrollHighlight;
	handleScrollbarEvents(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());

	int oldScrollIndex = _talkScrollIndex;
	handleScrolling(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());

	// Only redraw the window if the the scrollbar position has changed
	if (ui._scrollHighlight != oldHighlight || oldScrollIndex != _talkScrollIndex)
		render(HL_NO_HIGHLIGHTING);

	// Flag if they started pressing outside of the window
	if (events._firstPress && !_bounds.contains(mousePos))
		_outsideMenu = true;
		
	// Check for which statement they are pointing at
	_selector = -1;
	if (ui._scrollHighlight == SH_NONE) {
		if (Common::Rect(_bounds.left, _bounds.top + 5, _bounds.right - 3, _bounds.bottom - 5).contains(mousePos)) {
			if (_scroll) {
				// Disregard the scrollbar when setting the statement number
				if (!Common::Rect(_bounds.right - BUTTON_SIZE, _bounds.top, _bounds.right, _bounds.bottom).contains(mousePos))
					_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
			} else {
				_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1);
			}

			// Now translate the line number of the displayed line into the appropriate
			// Statement number or set it to 255 to indicate no Statement selected
			if (_selector >= 0 && _selector < (int)_statementLines.size())
				_selector = _statementLines[_selector]._num;
			else
				_selector = -1;
		}
	}

	// Check for the tab keys
	if (keycode == Common::KEYCODE_TAB && ui._scrollHighlight == SH_NONE) {
		if (_selector == -1) {
			_selector = _statementLines[_scroll ? _talkScrollIndex : 0]._num;
			
			events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 10, _bounds.top + _surface.fontHeight() + 2));
		} else {
			if (ui._keyState.flags & Common::KBD_SHIFT) {
				_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
				if (_statementLines[_selector]._num == _statementLines[_talkScrollIndex]._num) {
					_selector = (_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex;
				} else {
					int idx = _selector;
					do {
						--_selector;
					} while (_selector > 0 && _statementLines[idx]._num == _statementLines[_selector]._num);
				}

				int idx = _selector;
				while ((_statementLines[idx]._num == _statementLines[_selector - 1]._num) && (_selector > _talkScrollIndex))
					--_selector;
			} else {
				_selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
				if (_statementLines[_selector]._num == _statementLines[(_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex]._num) {
					_selector = _talkScrollIndex;
				} else {
					int idx = _selector;
					do {
						++_selector;
					} while (_selector < (int)_statementLines.size() && _statementLines[idx]._num == _statementLines[_selector]._num);
				}
			}

			events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() + 2 + (_surface.fontHeight() + 1)
				* (_selector - _talkScrollIndex)));
			_selector = _statementLines[_selector]._num;
		}
	}

	// Handle selecting a talk entry if a numeric key has been pressed
	if (keycode >= Common::KEYCODE_1 && keycode <= Common::KEYCODE_9) {
		int x = 0, t = 0, y = 0;

		do {
			if (y == (keycode - Common::KEYCODE_1)) {
				_selector = _statementLines[t]._num;
				_outsideMenu = false;
				hotkey = true;
				break;
			}

			++t;
			if (_statementLines[x]._num != _statementLines[t]._num) {
				x = t;
				++y;
			}
		} while (t < (int)_statementLines.size());
	}

	// Display the selected statement highlighted and reset the last statement.
	if (_selector != _oldSelector) {
		render(HL_CHANGED_HIGHLIGHTS);
		_oldSelector = _selector;
	}

	if (events._released || events._rightReleased || keycode == Common::KEYCODE_ESCAPE || hotkey) {
		events.clearEvents();
		_dialogTimer = 0;
		ui._scrollHighlight = SH_NONE;

		// See if they want to close the menu (click outside the window or Escape pressed)
		if ((_outsideMenu && !_bounds.contains(mousePos)) || keycode == Common::KEYCODE_ESCAPE) {
			if (keycode == Common::KEYCODE_ESCAPE)
				_selector = -1;

			talk.freeTalkVars();
			talk.pullSequence();

			for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
				if (people[idx]._type == CHARACTER) {
					while (!people[idx]._pathStack.empty())
						people[idx].pullNPCPath();
				}
			}

			banishWindow();
			ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;

			if (scene._currentScene == WEARY_PUNT)
				callParrotFile = true;
		}

		_outsideMenu = false;

		// See if they have selected a statement to say
		if (_selector != -1) {
			if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal)
				journal.record(talk._converseNum, _selector);
			talk._talkHistory[talk._converseNum][_selector] = true;

			banishWindow();
			talk._speaker = _vm->readFlags(FLAG_PLAYER_IS_HOLMES) ? HOLMES : WATSON;
			_scroll = false;
			const byte *msg = (const byte *)talk._statements[_selector]._statement.c_str();
			talk.talkInterface(msg);

			if (sound._speechOn)
				sound._talkSoundFile += Common::String::format("%02dA", _selector + 1);

			int msgLen = MAX((int)talk._statements[_selector]._statement.size(), 160);
			people.setTalkSequence(talk._speaker);

			talk.waitForMore(msgLen);
			if (talk._talkToAbort)
				return;

			people.setListenSequence(talk._speaker);

			do {
				talk._scriptSelect = _selector;
				talk._speaker = talk._talkTo;
				
				// Make a copy of the reply (since talkTo can reload the statements list), and call talkTo
				Common::String reply = talk._statements[_selector]._reply;
				talk.doScript(reply);

				// Reset the misc field in case any people changed their sequences
				for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
					people[idx]._misc = 0;

				if (!talk._talkToAbort) {
					if (!talk._statements[_selector]._modified.empty()) {
						for (uint idx = 0; idx < talk._statements[_selector]._modified.size(); ++idx)
							_vm->setFlags(talk._statements[_selector]._modified[idx]);

						talk.setTalkMap();
					}

					// See if there is another talk file linked to this.
					if (!talk._statements[_selector]._linkFile.empty() && !talk._scriptMoreFlag) {
						Common::String linkFile = talk._statements[_selector]._linkFile;
						talk.freeTalkVars();
						talk.loadTalkFile(linkFile);

						_talkScrollIndex = 0;
						int select = -1;
						_selector = _oldSelector = -1;

						// Find the first statement that has all its flags set correctly
						for (uint idx = 0; idx < talk._statements.size() && select == -1; ++select) {
							if (!talk._statements[idx]._talkMap)
								select = idx;
						}

						if (select == -1) {
							talk.freeTalkVars();
							talk.nothingToSay();
							return;
						}

						// See is the new statement is in stealth mode
						talk._talkStealth = (talk._statements[select]._statement.hasPrefix("^")) ? 2 : 0;

						// See if the new file is a standard file, a reply first file, or a Stealth Mode file
						if (!talk._statements[select]._statement.hasPrefix("*") && !talk._statements[select]._statement.hasPrefix("^")) {
							load();
							summonWindow();

							setStatementLines();
							render(HL_NO_HIGHLIGHTING);
							break;
						} else {
							_selector = select;

							if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal)
								journal.record(talk._converseNum, _selector);

							talk._talkHistory[talk._converseNum][_selector] = true;
						}
					} else {
						talk.freeTalkVars();
						talk.pullSequence();

						for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
							if (people[idx]._type == CHARACTER)
								while (!people[idx]._pathStack.empty())
									people[idx].pullNPCPath();
						}

						if (ui._menuMode != PASSWORD_MODE) {
							ui.banishWindow();
							ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
							events.setCursor(ARROW);
						}
						break;
					}
				} else {
					break;
				}
			} while (!_vm->shouldQuit());

			events.clearEvents();

			// Now, if a script was pushed onto the script stack, restore them to allow the previous script to continue.
			talk.popStack();
		}
	}

	if (callParrotFile)
		talk.talkTo("POUT52A");
}

void WidgetTalk::render(Highlight highlightMode) {
	TattooTalk &talk = *(TattooTalk *)_vm->_talk;

	if (highlightMode != HL_SCROLLBAR_ONLY) {
		int yp = 5;
		int statementNum = 1;

		// Draw all the statements
		// Check whether scrolling has occurred, and if so, figure out what the starting
		// number for the first visible statement will be
		if (_talkScrollIndex) {
			for (int idx = 1; idx <= _talkScrollIndex; ++idx) {
				if (_statementLines[idx - 1]._num != _statementLines[idx]._num)
					++statementNum;
			}
		}

		// Main drawing loop
		for (uint idx = _talkScrollIndex; idx < _statementLines.size() && yp < (_bounds.height() - _surface.fontHeight()); ++idx) {
			if (highlightMode == HL_NO_HIGHLIGHTING || _statementLines[idx]._num == _selector ||
					_statementLines[idx]._num == _oldSelector) {
				// Erase the line contents
				_surface.fillRect(Common::Rect(3, yp, _surface.w() - BUTTON_SIZE - 3, yp + _surface.fontHeight()), TRANSPARENCY);

				// Different coloring based on whether the option has been previously chosen or not
				byte color = (!talk._talkHistory[talk._converseNum][_statementLines[idx]._num]) ?
					INFO_TOP : INFO_BOTTOM;

				if (_statementLines[idx]._num == _selector && highlightMode == HL_CHANGED_HIGHLIGHTS)
					color = COMMAND_HIGHLIGHTED;

				// See if it's the start of a new statement, so needs the statement number to be displayed
				if (!idx || _statementLines[idx]._num != _statementLines[idx - 1]._num) {
					Common::String numStr = Common::String::format("%d.", statementNum);
					_surface.writeString(numStr, Common::Point(STATEMENT_NUM_X, yp), color);
				}

				// Display the statement line
				_surface.writeString(_statementLines[idx]._line, Common::Point(_talkTextX, yp), color);
			}
			yp += _surface.fontHeight() + 1;

			// If the next line starts a new statement, then increment the statement number
			if (idx == (_statementLines.size() - 1) || _statementLines[idx]._num != _statementLines[idx + 1]._num)
				++statementNum;
		}
	}

	// See if the scroll bar needs to be drawn
	if (_scroll && highlightMode != HL_CHANGED_HIGHLIGHTS)
		drawScrollBar(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());
}

void WidgetTalk::setStatementLines() {
	TattooTalk &talk = *(TattooTalk *)_vm->_talk;
	const char *numStr = "19.";

	// See how many statements are going to be available
	int numStatements = 0;
	for (uint idx = 0; idx < talk._statements.size(); ++idx) {
		if (talk._statements[idx]._talkMap != -1)
			++numStatements;
	}

	// If there are more lines than can be displayed in the interface window at one time, adjust the allowed 
	// width to take into account needing a scrollbar
	int xSize = _scroll ? _bounds.width() - BUTTON_SIZE - 3 : _bounds.width();

	// Also adjust the width to allow room for the statement numbers at the left edge of the display
	int n = (numStatements < 10) ? 1 : 0;
	xSize -= _surface.stringWidth(numStr + n) + _surface.widestChar() / 2 + 9;
	_talkTextX = _surface.stringWidth(numStr + n) + _surface.widestChar() / 4 + 6;
	_statementLines.clear();

	for (uint statementNum = 0; statementNum < talk._statements.size(); ++statementNum) {
		// See if this statment meets all of its flag requirements
		if (talk._statements[statementNum]._talkMap != -1) {
			// Get the next statement text to process
			Common::String str = talk._statements[statementNum]._statement;

			Common::StringArray statementLines;
			splitLines(str, statementLines, xSize, 999);

			// Add the lines in
			for (uint idx = 0; idx < statementLines.size(); ++idx)
				_statementLines.push_back(StatementLine(statementLines[idx], statementNum));
		}
	}
}

void WidgetTalk::refresh() {
	_talkScrollIndex = 0;
	_selector = _oldSelector = -1;

	setStatementLines();
	render(HL_NO_HIGHLIGHTING);
}

} // End of namespace Tattoo

} // End of namespace Sherlock
