/* 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/translation.h"
#include "gui/saveload.h"
#include "sherlock/tattoo/widget_files.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"

namespace Sherlock {

namespace Tattoo {

#define FILES_LINES_COUNT 5

WidgetFiles::WidgetFiles(SherlockEngine *vm, const Common::String &target) :
		SaveManager(vm, target), WidgetBase(vm), _vm(vm) {
	_fileMode = SAVEMODE_NONE;
	_selector = _oldSelector = -1;
}

void WidgetFiles::show(SaveMode mode) {
	Events &events = *_vm->_events;
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
	Common::Point mousePos = events.mousePos();

	if (_vm->_showOriginalSavesDialog) {
		// Render and display the file dialog
		_fileMode = mode;
		ui._menuMode = FILES_MODE;
		_selector = _oldSelector = -1;
		_scroll = true;
		createSavegameList();

		// Set up the display area
		_bounds = Common::Rect(SHERLOCK_SCREEN_WIDTH * 2 / 3, (_surface.fontHeight() + 1) *
			(FILES_LINES_COUNT + 1) + 17);
		_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);

		// Create the surface and render its contents
		_surface.create(_bounds.width(), _bounds.height());
		render(RENDER_ALL);

		summonWindow();
		ui._menuMode = FILES_MODE;
	} else if (mode == SAVEMODE_LOAD) {
		showScummVMRestoreDialog();
	} else {
		showScummVMSaveDialog();
	}
}

void WidgetFiles::showScummVMSaveDialog() {
	GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);

	int slot = dialog->runModalWithCurrentTarget();
	if (slot >= 0) {
		Common::String desc = dialog->getResultString();

		if (desc.empty()) {
			// create our own description for the saved game, the user didn't enter it
			desc = dialog->createDefaultSaveDescription(slot);
		}

		_vm->saveGameState(slot, desc);
	}

	close();
	delete dialog;
}

void WidgetFiles::showScummVMRestoreDialog() {
	GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
	int slot = dialog->runModalWithCurrentTarget();
	close();
	delete dialog;

	if (slot >= 0) {
		_vm->loadGameState(slot);
	}
}

void WidgetFiles::render(FilesRenderMode mode) {
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
	ImageFile &images = *ui._interfaceImages;
	byte color;

	if (mode == RENDER_ALL) {
		_surface.clear(TRANSPARENCY);
		makeInfoArea();

		switch (_fileMode) {
		case SAVEMODE_LOAD:
			_surface.writeString(FIXED(LoadGame),
				Common::Point((_surface.width() - _surface.stringWidth(FIXED(LoadGame))) / 2, 5), INFO_TOP);
			break;

		case SAVEMODE_SAVE:
			_surface.writeString(FIXED(SaveGame),
				Common::Point((_surface.width() - _surface.stringWidth(FIXED(SaveGame))) / 2, 5), INFO_TOP);
			break;

		default:
			break;
		}

		_surface.hLine(3, _surface.fontHeight() + 7, _surface.width() - 4, INFO_TOP);
		_surface.hLine(3, _surface.fontHeight() + 8, _surface.width() - 4, INFO_MIDDLE);
		_surface.hLine(3, _surface.fontHeight() + 9, _surface.width() - 4, INFO_BOTTOM);
		_surface.SHtransBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 6));
		_surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, _surface.fontHeight() + 6));

		int xp = _surface.width() - BUTTON_SIZE - 6;
		_surface.vLine(xp, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_TOP);
		_surface.vLine(xp + 1, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_MIDDLE);
		_surface.vLine(xp + 2, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_BOTTOM);
		_surface.SHtransBlitFrom(images[6], Common::Point(xp - 1, _surface.fontHeight() + 8));
		_surface.SHtransBlitFrom(images[7], Common::Point(xp - 1, _bounds.height() - 4));
	}

	int xp = _surface.stringWidth("00.") + _surface.widestChar() + 5;
	int yp = _surface.fontHeight() + 14;

	for (int idx = _savegameIndex; idx < (_savegameIndex + FILES_LINES_COUNT); ++idx) {
		if (idx == _selector && mode != RENDER_ALL)
			color = COMMAND_HIGHLIGHTED;
		else
			color = INFO_TOP;

		if (mode == RENDER_NAMES_AND_SCROLLBAR)
			_surface.fillRect(Common::Rect(4, yp, _surface.width() - BUTTON_SIZE - 9, yp + _surface.fontHeight()), TRANSPARENCY);

		Common::String numStr = Common::String::format("%d.", idx + 1);
		_surface.writeString(numStr, Common::Point(_surface.widestChar(), yp), color);
		_surface.writeString(_savegames[idx], Common::Point(xp, yp), color);

		yp += _surface.fontHeight() + 1;
	}

	// Draw the Scrollbar if necessary
	if (mode != RENDER_NAMES)
		drawScrollBar(_savegameIndex, FILES_LINES_COUNT, _savegames.size());
}

void WidgetFiles::handleEvents() {
	Events &events = *_vm->_events;
	TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
	Common::Point mousePos = events.mousePos();
	Common::KeyState keyState = ui._keyState;

	// Handle scrollbar events
	ScrollHighlight oldHighlight = ui._scrollHighlight;
	handleScrollbarEvents(_savegameIndex, FILES_LINES_COUNT, _savegames.size());

	int oldScrollIndex = _savegameIndex;
	handleScrolling(_savegameIndex, FILES_LINES_COUNT, _savegames.size());

	// See if the mouse is pointing at any filenames in the window
	if (Common::Rect(_bounds.left, _bounds.top + _surface.fontHeight() + 14,
			_bounds.right - BUTTON_SIZE - 5, _bounds.bottom - 5).contains(mousePos)) {
		_selector = (mousePos.y - _bounds.top - _surface.fontHeight() - 14) / (_surface.fontHeight() + 1) +
			_savegameIndex;
	} else {
		_selector = -1;
	}

	// Check for the Tab key
	if (keyState.keycode == Common::KEYCODE_TAB) {
		// If the mouse is not over any of the filenames, move the mouse so that it points to the first one
		if (_selector == -1) {
			events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 20,
				_bounds.top + _surface.fontHeight() * 2 + 8));
		} else {
			// See if we're doing Tab or Shift Tab
			if (keyState.flags & Common::KBD_SHIFT) {
				// We're doing Shift Tab
				if (_selector == _savegameIndex)
					_selector = _savegameIndex + 4;
				else
					--_selector;
			} else {
				// We're doing Tab
				++_selector;
				if (_selector >= _savegameIndex + 5)
					_selector = _savegameIndex;
			}

			events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() * 2
				+ 8 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1)));
		}
	}

	// Only redraw the window if the scrollbar position has changed
	if (ui._scrollHighlight != oldHighlight || oldScrollIndex != _savegameIndex || _selector != _oldSelector)
		render(RENDER_NAMES_AND_SCROLLBAR);
	_oldSelector = _selector;

	if (events._firstPress && !_bounds.contains(mousePos))
		_outsideMenu = true;

	if (events._released || events._rightReleased || keyState.keycode == Common::KEYCODE_ESCAPE) {
		ui._scrollHighlight = SH_NONE;

		if (_outsideMenu && !_bounds.contains(mousePos)) {
			close();
		} else {
			_outsideMenu = false;

			if (_selector != -1) {
				if (_fileMode == SAVEMODE_LOAD) {
					// We're in Load Mode
					_vm->loadGameState(_selector);
				} else if (_fileMode == SAVEMODE_SAVE) {
					// We're in Save Mode
					if (getFilename())
						_vm->saveGameState(_selector, _savegames[_selector]);
					close();
				}
			}
		}
	}
}

bool WidgetFiles::getFilename() {
	Events &events = *_vm->_events;
	TattooScene &scene = *(TattooScene *)_vm->_scene;
	Screen &screen = *_vm->_screen;
	Talk &talk = *_vm->_talk;
	int index = 0;
	int done = 0;
	bool blinkFlag = false;
	int blinkCountdown = 0;
	int cursorColor = 192;
	byte color, textColor;
	bool insert = true;

	assert(_selector != -1);
	Common::Point pt(_surface.stringWidth("00.") + _surface.widestChar() + 5,
		_surface.fontHeight() + 14 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1));

	Common::String numStr = Common::String::format("%d.", _selector + 1);
	_surface.writeString(numStr, Common::Point(_surface.widestChar(), pt.y), COMMAND_HIGHLIGHTED);

	Common::String filename = _savegames[_selector];

	if (isSlotEmpty(_selector)) {
		index = 0;
		_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
		filename = "";
	} else {
		index = filename.size();
		_surface.writeString(filename, pt, COMMAND_HIGHLIGHTED);
		pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5;
	}

	do {
		scene.doBgAnim();

		if (talk._talkToAbort)
			return false;

		char currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
		Common::String charString = Common::String::format("%c", currentChar);
		int width = screen.charWidth(currentChar);

		// Wait for keypress
		while (!events.kbHit()) {
			events.pollEventsAndWait();
			events.setButtonState();

			scene.doBgAnim();

			if (talk._talkToAbort)
				return false;

			if (--blinkCountdown <= 0) {
				blinkCountdown = 3;
				blinkFlag = !blinkFlag;
				if (blinkFlag) {
					textColor = 236;
					color = cursorColor;
				} else {
					textColor = COMMAND_HIGHLIGHTED;
					color = TRANSPARENCY;
				}

				_surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), color);
				if (currentChar != ' ')
					_surface.writeString(charString, pt, textColor);
			}
			if (_vm->shouldQuit())
				return false;
		}

		while (events.kbHit()) {
			Common::KeyState keyState = events.getKey();
			if (keyState.keycode == Common::KEYCODE_BACKSPACE && index > 0) {
				pt.x -= _surface.charWidth(filename[index - 1]);
				--index;

				if (insert) {
					filename.deleteChar(index);
				} else {
					filename.setChar(' ', index);
				}

				_surface.fillRect(Common::Rect(pt.x, pt.y, _surface.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
				_surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
			} else if ((keyState.keycode == Common::KEYCODE_LEFT && index > 0)
					|| (keyState.keycode == Common::KEYCODE_RIGHT && index < (int)filename.size() && pt.x < (_bounds.right - BUTTON_SIZE - 20))
					|| (keyState.keycode == Common::KEYCODE_HOME && index > 0)
					|| (keyState.keycode == Common::KEYCODE_END)) {
				_surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), TRANSPARENCY);
				if (currentChar)
					_surface.writeString(charString, pt, COMMAND_HIGHLIGHTED);

				switch (keyState.keycode) {
				case Common::KEYCODE_LEFT:
					pt.x -= _surface.charWidth(filename[index - 1]);
					--index;
					break;

				case Common::KEYCODE_RIGHT:
					pt.x += _surface.charWidth(filename[index]);
					++index;
					break;

				case Common::KEYCODE_HOME:
					pt.x = _surface.stringWidth("00.") + _surface.widestChar() + 5;
					index = 0;
					break;

				case Common::KEYCODE_END:
					pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5;
					index = filename.size();

					while (filename[index - 1] == ' ' && index > 0) {
						pt.x -= _surface.charWidth(filename[index - 1]);
						--index;
					}
					break;

				default:
					break;
				}
			} else if (keyState.keycode == Common::KEYCODE_INSERT) {
				insert = !insert;
				if (insert)
					cursorColor = 192;
				else
					cursorColor = 200;
			} else if (keyState.keycode == Common::KEYCODE_DELETE && index < (int)filename.size()) {
				filename.deleteChar(index);

				_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
				_surface.writeString(Common::String(filename.c_str() + index), pt, COMMAND_HIGHLIGHTED);
			} else  if (keyState.keycode == Common::KEYCODE_RETURN) {
				done = 1;
			} else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
				_selector = -1;
				render(RENDER_NAMES_AND_SCROLLBAR);
				done = -1;
			}

			if ((keyState.ascii >= ' ') && (keyState.ascii <= 'z') && (index < 50)) {
				if (pt.x + _surface.charWidth(keyState.ascii) < _surface.w - BUTTON_SIZE - 20) {
					if (insert)
						filename.insertChar(keyState.ascii, index);
					else
						filename.setChar(keyState.ascii, index);

					_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9,
						pt.y + _surface.fontHeight()), TRANSPARENCY);
					_surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
					pt.x += _surface.charWidth(keyState.ascii);
					++index;
				}
			}

			currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
			charString = Common::String::format("%c", currentChar);
			width = screen.charWidth(currentChar);
		}
	} while (!done && !_vm->shouldQuit());

	scene.doBgAnim();

	if (talk._talkToAbort)
		return false;

	if (done == 1)
		_savegames[_selector] = filename;

	return done == 1;
}

Common::Rect WidgetFiles::getScrollBarBounds() const {
	Common::Rect scrollRect(BUTTON_SIZE, _bounds.height() - _surface.fontHeight() - 13);
	scrollRect.moveTo(_bounds.width() - BUTTON_SIZE - 3, _surface.fontHeight() + 10);

	return scrollRect;
}

} // End of namespace Tattoo

} // End of namespace Sherlock
