/* 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 "glk/comprehend/comprehend.h"
#include "glk/comprehend/debugger.h"
#include "glk/comprehend/draw_surface.h"
#include "glk/comprehend/game.h"
#include "glk/comprehend/game_cc.h"
#include "glk/comprehend/game_data.h"
#include "glk/comprehend/game_oo.h"
#include "glk/comprehend/game_tm.h"
#include "glk/comprehend/game_tr1.h"
#include "glk/comprehend/game_tr2.h"
#include "glk/comprehend/pics.h"
#include "glk/quetzal.h"
#include "common/config-manager.h"
#include "common/ustr.h"
#include "engines/util.h"

namespace Glk {
namespace Comprehend {

// Even with no ScummVM scaling, internally we do a 2x scaling to
// render on a 640x480 window, to allow for better looking text
#define SCALE_FACTOR 2

Comprehend *g_comprehend;

Comprehend::Comprehend(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
	_topWindow(nullptr), _bottomWindow(nullptr), _roomDescWindow(nullptr),
	_drawSurface(nullptr), _game(nullptr), _pics(nullptr), _saveSlot(-1),
	_graphicsEnabled(true), _drawFlags(0), _disableSaves(false) {
	g_comprehend = this;
}

Comprehend::~Comprehend() {
	delete _drawSurface;
	delete _game;
	SearchMan.remove("Pics");   // This also deletes it

	g_comprehend = nullptr;
}

void Comprehend::initGraphicsMode() {
	Graphics::PixelFormat pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
	initGraphics(640, 400, &pixelFormat);
}

void Comprehend::createConfiguration() {
	GlkAPI::createConfiguration();
	switchToWhiteOnBlack();
}

void Comprehend::runGame() {
	initialize();

	// Lookup game
	createGame();

	_game->loadGame();
	_game->playGame();

	deinitialize();
}

void Comprehend::initialize() {
	_bottomWindow = (TextBufferWindow *)glk_window_open(nullptr, 0, 0, wintype_TextBuffer, 1);
	glk_set_window(_bottomWindow);

	showGraphics();
	_topWindow->fillRect(0, Rect(0, 0, _topWindow->_w, _topWindow->_h));

	// Initialize drawing surface, and the archive that abstracts
	// the room and item graphics as as individual files
	_drawSurface = new DrawSurface();
	_pics = new Pics();
	SearchMan.add("Pics", _pics, 99, true);

	// Check for savegame to load
	_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
}

void Comprehend::deinitialize() {
	glk_window_close(_topWindow);
	glk_window_close(_bottomWindow);
	glk_window_close(_roomDescWindow);
}

void Comprehend::createDebugger() {
	setDebugger(new Debugger());
}

void Comprehend::createGame() {
	if (_gameDescription._gameId == "crimsoncrown")
		_game = new CrimsonCrownGame();
	else if (_gameDescription._gameId == "ootopos")
		_game = new OOToposGame();
	else if (_gameDescription._gameId == "talisman")
		_game = new TalismanGame();
	else if (_gameDescription._gameId == "transylvania")
		_game = new TransylvaniaGame1();
	else if (_gameDescription._gameId == "transylvaniav2")
		_game = new TransylvaniaGame2();
	else
		error("Unknown game");
}

void Comprehend::print(const char *fmt, ...) {
	va_list argp;
	va_start(argp, fmt);
	Common::String msg = Common::String::vformat(fmt, argp);
	va_end(argp);

	glk_put_string_stream(glk_window_get_stream(_bottomWindow), msg.c_str());
}

void Comprehend::print_u32_internal(const Common::U32String *fmt, ...) {
	Common::U32String outputMsg;

	va_list argp;
	va_start(argp, fmt);
	Common::U32String::vformat(fmt->begin(), fmt->end(), outputMsg, argp);
	va_end(argp);

	glk_put_string_stream_uni(glk_window_get_stream(_bottomWindow), outputMsg.u32_str());
}

void Comprehend::printRoomDesc(const Common::String &desc) {
	if (_roomDescWindow) {
		glk_window_clear(_roomDescWindow);

		// Get the grid width and do a word wrap
		uint width;
		glk_window_get_size(_roomDescWindow, &width, nullptr);
		Common::String str = desc;
		str.wordWrap(width - 2);
		str += '\n';

		// Display the room description
		while (!str.empty()) {
			size_t idx = str.findFirstOf('\n');
			Common::String line = Common::String::format(" %s", Common::String(str.c_str(), str.c_str() + idx + 1).c_str());
			glk_put_string_stream(glk_window_get_stream(_roomDescWindow), line.c_str());

			str = Common::String(str.c_str() + idx + 1);
		}
	}
}

void Comprehend::readLine(char *buffer, size_t maxLen) {
	event_t ev;

	glk_request_line_event(_bottomWindow, buffer, maxLen - 1, 0);

	for (;;) {
		glk_select(&ev);
		if (ev.type == evtype_Quit) {
			glk_cancel_line_event(_bottomWindow, &ev);
			return;
		} else if (ev.type == evtype_LineInput)
			break;
	}

	buffer[ev.val1] = 0;
	debug(1, "\n> %s", buffer);
}

int Comprehend::readChar() {
	glk_request_char_event(_bottomWindow);
	setDisableSaves(true);

	event_t ev;
	while (ev.type != evtype_CharInput) {
		glk_select(&ev);

		if (ev.type == evtype_Quit) {
			glk_cancel_char_event(_bottomWindow);
			return -1;
		}
	}

	setDisableSaves(false);
	return ev.val1;
}

Common::Error Comprehend::readSaveData(Common::SeekableReadStream *rs) {
	Common::Serializer s(rs, nullptr);
	_game->synchronizeSave(s);

	_game->_updateFlags = UPDATE_ALL;

	if (isInputLineActive()) {
		// Restored game using GMM, so update grpahics and print room description
		g_comprehend->print("\n");
		_game->update();

		g_comprehend->print("> ");
	}

	return Common::kNoError;
}

Common::Error Comprehend::writeGameData(Common::WriteStream *ws) {
	Common::Serializer s(nullptr, ws);
	_game->synchronizeSave(s);

	return Common::kNoError;
}

bool Comprehend::loadLauncherSavegameIfNeeded() {
	if (_saveSlot != -1) {
		return loadGameState(_saveSlot).getCode() == Common::kNoError;
	}

	return false;
}

void Comprehend::drawPicture(uint pictureNum) {
	if (_topWindow) {
		// Clear the picture cache before each drawing in OO-Topos. Wearing the goggles
		// can producing different versions of the same scene, so we can't cache it
		if (_gameDescription._gameId == "ootopos")
			_pictures->clear();

		glk_image_draw_scaled(_topWindow, pictureNum,
			20 * SCALE_FACTOR, 0, G_RENDER_WIDTH * SCALE_FACTOR, G_RENDER_HEIGHT * SCALE_FACTOR);
	}
}

void Comprehend::drawLocationPicture(int pictureNum, bool clearBg) {
	drawPicture(pictureNum + (clearBg ? LOCATIONS_OFFSET : LOCATIONS_NO_BG_OFFSET));
}

void Comprehend::drawItemPicture(int pictureNum) {
	drawPicture(pictureNum + ITEMS_OFFSET);
}

void Comprehend::clearScreen(bool isBright) {
	drawPicture(isBright ? BRIGHT_ROOM : DARK_ROOM);
}

bool Comprehend::toggleGraphics() {
	if (_topWindow) {
		// Remove the picture window
		glk_window_close(_topWindow);
		_topWindow = nullptr;
		_graphicsEnabled = false;

		// Add the room description window
		_roomDescWindow = (TextGridWindow *)glk_window_open(_bottomWindow,
			winmethod_Above | winmethod_Fixed, 5, wintype_TextGrid, 1);
		return false;

	} else {
		glk_window_close(_roomDescWindow);
		_roomDescWindow = nullptr;

		// Create the window again
		showGraphics();
		return true;
	}
}

void Comprehend::showGraphics() {
	if (!_topWindow) {
		_topWindow = (GraphicsWindow *)glk_window_open(_bottomWindow,
			winmethod_Above | winmethod_Fixed,
			160 * SCALE_FACTOR, wintype_Graphics, 2);
		_graphicsEnabled = true;
	}
}

bool Comprehend::isInputLineActive() const {
	return _bottomWindow->_lineRequest || _bottomWindow->_lineRequestUni;
}

} // namespace Comprehend
} // namespace Glk
