/* 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 "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/file.h"
#include "common/func.h"
#include "common/system.h"
#include "common/timer.h"
#include "common/util.h"

#include "engines/advancedDetector.h"

#include "graphics/palette.h"
#include "graphics/surface.h"

#include "dreamweb/sound.h"
#include "dreamweb/dreamweb.h"

namespace DreamWeb {

DreamWebEngine::DreamWebEngine(OSystem *syst, const DreamWebGameDescription *gameDesc) :
	Engine(syst), _gameDescription(gameDesc), _rnd("dreamweb"),
	_exText(kNumExTexts),
	_setDesc(kNumSetTexts), _blockDesc(kNumBlockTexts),
	_roomDesc(kNumRoomTexts), _freeDesc(kNumFreeTexts),
	_personText(kNumPersonTexts) {

	DebugMan.addDebugChannel(kDebugAnimation, "Animation", "Animation Debug Flag");
	DebugMan.addDebugChannel(kDebugSaveLoad, "SaveLoad", "Track Save/Load Function");

	_vSyncInterrupt = false;

	_console = 0;
	_sound = 0;
	_speed = 1;
	_turbo = false;
	_oldMouseState = 0;

	_datafilePrefix = "DREAMWEB.";
	_speechDirName = "SPEECH";
	// ES and FR CD release use a different data file prefix
	// and speech directory naming.
	if (isCD()) {
		switch(getLanguage()) {
		case Common::ES_ESP:
			_datafilePrefix = "DREAMWSP.";
			_speechDirName = "SPANISH";
			break;
		case Common::FR_FRA:
			_datafilePrefix = "DREAMWFR.";
			_speechDirName = "FRENCH";
			break;
		default:
			// Nothing to do
			break;
		}
	}

	_openChangeSize = kInventx+(4*kItempicsize);
	_quitRequested = false;

	_speechLoaded = false;

	_backdropBlocks = 0;
	_reelList = 0;

	_oldSubject._type = 0;
	_oldSubject._index = 0;

	// misc variables
	_speechCount = 0;
	_charShift = 0;
	_brightPalette = false;
	_roomLoaded = 0;
	_didZoom = 0;
	_lineSpacing = 10;
	_textAddressX = 13;
	_textAddressY = 182;
	_textLen = 0;
	_lastXPos = 0;
	_itemFrame = 0;
	_withObject = 0;
	_withType = 0;
	_lookCounter = 0;
	_command = 0;
	_commandType = 0;
	_objectType = 0;
	_getBack = 0;
	_invOpen = 0;
	_mainMode = 0;
	_pickUp = 0;
	_lastInvPos = 0;
	_examAgain = 0;
	_newTextLine = 0;
	_openedOb = 0;
	_openedType = 0;
	_mapAdX = 0;
	_mapAdY = 0;
	_mapOffsetX = 104;
	_mapOffsetY = 38;
	_mapXStart = 0;
	_mapYStart = 0;
	_mapXSize = 0;
	_mapYSize = 0;
	_haveDoneObs = 0;
	_manIsOffScreen = 0;
	_facing = 0;
	_leaveDirection = 0;
	_turnToFace = 0;
	_turnDirection = 0;
	_mainTimer = 0;
	_introCount = 0;
	_currentKey = 0;
	_timerCount = 0;
	_mapX = 0;
	_mapY = 0;
	_ryanX = 0;
	_ryanY = 0;
	_lastFlag = 0;
	_destPos = 0;
	_realLocation = 0;
	_roomNum = 0;
	_nowInNewRoom = 0;
	_resetManXY = 0;
	_newLocation = 0xFF;
	_autoLocation = 0xFF;
	_mouseX = 0;
	_mouseY = 0;
	_mouseButton = 0;
	_oldButton = 0;
	_oldX = 0;
	_oldY = 0;
	_oldPointerX = 0;
	_oldPointerY = 0;
	_delHereX = 0;
	_delHereY = 0;
	_pointerXS = 32;
	_pointerYS = 32;
	_delXS = 0;
	_delYS = 0;
	_pointerFrame = 0;
	_pointerPower = 0;
	_pointerMode = 0;
	_pointerSpeed = 0;
	_pointerCount = 0;
	_inMapArea = 0;
	_talkMode = 0;
	_talkPos = 0;
	_character = 0;
	_watchDump = 0;
	_logoNum = 0;
	_oldLogoNum = 0;
	_pressed = 0;
	_pressPointer = 0;
	_graphicPress = 0;
	_pressCount = 0;
	_lightCount = 0;
	_folderPage = 0;
	_diaryPage = 0;
	_menuCount = 0;
	_symbolTopX = 0;
	_symbolTopNum = 0;
	_symbolTopDir = 0;
	_symbolBotX = 0;
	_symbolBotNum = 0;
	_symbolBotDir = 0;
	_walkAndExam = 0;
	_walkExamType = 0;
	_walkExamNum = 0;
	_cursLocX = 0;
	_cursLocY = 0;
	_curPos = 0;
	_monAdX = 0;
	_monAdY = 0;
	_timeCount = 0;
	_needToDumpTimed = 0;
	_loadingOrSave = 0;
	_saveLoadPage = 0;
	_currentSlot = 0;
	_cursorPos = 0;
	_colorPos = 0;
	_fadeDirection = 0;
	_numToFade = 0;
	_fadeCount = 0;
	_addToGreen = 0;
	_addToRed = 0;
	_addToBlue = 0;
	_lastSoundReel = 0;
	_lastHardKey = Common::KEYCODE_INVALID;
	_bufferIn = 0;
	_bufferOut = 0;
	_blinkFrame = 23;
	_blinkCount = 0;
	_reAssesChanges = 0;
	_pointersPath = 0;
	_mansPath = 0;
	_pointerFirstPath = 0;
	_finalDest = 0;
	_destination = 0;
	_lineStartX = 0;
	_lineStartY = 0;
	_lineEndX = 0;
	_lineEndY = 0;
	_linePointer = 0;
	_lineDirection = 0;
	_lineLength = 0;

	_subtitles = 0;
	_foreignRelease = 0;
	_wonGame = 0;
	_hasSpeech = 0;
	_roomsSample = 0;
	_copyProtection = 0;

	for (uint i = 0; i < 128; i++)
		memset(&_setDat[i], 0, sizeof(SetObject));

	for (uint i = 0; i < 80; i++)
		memset(&_freeDat[i], 0, sizeof(DynObject));

	for (uint i = 0; i < kNumExObjects; i++)
		memset(&_exData[i], 0, sizeof(DynObject));

	memset(&_vars, 0, sizeof(GameVars));

	for (uint i = 0; i < 96; i++)
		memset(&_backdropFlags[i], 0, sizeof(BackdropMapFlag));

	for (uint i = 0; i < kNumReelRoutines+1; i++)
		memset(&_reelRoutines[i], 0, sizeof(ReelRoutine));

	_personData = 0;

	for (uint i = 0; i < 16; i++)
		memset(&_openInvList[i], 0, sizeof(ObjectRef));

	for (uint i = 0; i < 30; i++)
		memset(&_ryanInvList[i], 0, sizeof(ObjectRef));

	for (uint i = 0; i < 11*10; i++)
		memset(&_mapFlags[i], 0, sizeof(MapFlag));

	for (uint i = 0; i < kNumChanges; i++)
		memset(&_listOfChanges[i], 0, sizeof(Change));

	_currentCharset = 0;

	for (uint i = 0; i < 36; i++)
		memset(&_pathData[i], 0, sizeof(RoomPaths));
}

DreamWebEngine::~DreamWebEngine() {
	DebugMan.clearAllDebugChannels();
	delete _console;
	delete _sound;
}

static void vSyncInterrupt(void *refCon) {
	DreamWebEngine *vm = (DreamWebEngine *)refCon;

	if (!vm->isPaused()) {
		vm->setVSyncInterrupt(true);
	}
}

void DreamWebEngine::setVSyncInterrupt(bool flag) {
	_vSyncInterrupt = flag;
}

void DreamWebEngine::waitForVSync() {
	processEvents();

	if (!_turbo) {
		while (!_vSyncInterrupt) {
			_system->delayMillis(10);
		}
		setVSyncInterrupt(false);
	}

	doShake();
	doFade();
	_system->updateScreen();
}

void DreamWebEngine::quit() {
	_quitRequested = true;
	_lastHardKey = Common::KEYCODE_ESCAPE;
}

void DreamWebEngine::processEvents() {
	if (_eventMan->shouldQuit()) {
		quit();
		return;
	}

	_sound->soundHandler();
	Common::Event event;
	int softKey;
	while (_eventMan->pollEvent(event)) {
		switch(event.type) {
		case Common::EVENT_RTL:
			quit();
			break;
		case Common::EVENT_KEYDOWN:
			if (event.kbd.flags & Common::KBD_CTRL) {
				switch (event.kbd.keycode) {

				case Common::KEYCODE_d:
					_console->attach();
					_console->onFrame();
					break;

				case Common::KEYCODE_f:
					setSpeed(_speed != 20? 20: 1);
					break;

				case Common::KEYCODE_g:
					_turbo = !_turbo;
					break;

				case Common::KEYCODE_c: //skip statue puzzle
					_symbolBotNum = 3;
					_symbolTopNum = 5;
					break;

				default:
					break;
				}

				return; //do not pass ctrl + key to the engine
			}

			// Some parts of the code uses the hardware key
			// code directly.

			switch (event.kbd.keycode) {
			case Common::KEYCODE_ESCAPE:
				_lastHardKey = Common::KEYCODE_ESCAPE;
				break;
			case Common::KEYCODE_SPACE:
				_lastHardKey = Common::KEYCODE_SPACE;
				break;
			default:
				_lastHardKey = Common::KEYCODE_INVALID;
				break;
			}

			// The rest of the keys are converted to ASCII. This
			// is fairly restrictive, and eventually we may want
			// to let through more keys. I think this is mostly to
			// keep weird glyphs out of savegame names.

			softKey = 0;

			debug(1, "DreamWebEngine::processEvents() KeyDown keycode:%d ascii:0x%02x", event.kbd.keycode, event.kbd.ascii);
			if ((event.kbd.ascii >= 'a' && event.kbd.ascii <= 'z') ||
				(event.kbd.ascii >= 'A' && event.kbd.ascii <= 'Z')) {
				softKey = event.kbd.ascii & ~0x20; // (& ~0x20) forces ascii codes for a-z to map to A-Z
			} else if (event.kbd.ascii == '-' ||
				event.kbd.ascii == ' ' ||
				(event.kbd.ascii >= '0' && event.kbd.ascii <= '9')) {
				softKey = event.kbd.ascii;
			} else if (event.kbd.keycode >= Common::KEYCODE_KP0 && event.kbd.keycode <= Common::KEYCODE_KP9) {
				softKey = event.kbd.keycode - Common::KEYCODE_KP0 + '0';
			} else if (event.kbd.keycode == Common::KEYCODE_KP_MINUS) {
				softKey = '-';
			} else if (event.kbd.keycode == Common::KEYCODE_BACKSPACE ||
				event.kbd.keycode == Common::KEYCODE_DELETE) {
				softKey = 8;
			} else if (event.kbd.keycode == Common::KEYCODE_RETURN
				|| event.kbd.keycode == Common::KEYCODE_KP_ENTER) {
				softKey = 13;
			}

			if (softKey)
				keyPressed(softKey);
			break;
		default:
			break;
		}
	}
}

Common::Error DreamWebEngine::run() {
	syncSoundSettings();
	_console = new DreamWebConsole(this);
	_sound = new DreamWebSound(this);

	_hasSpeech = Common::File::exists(_speechDirName + "/r01c0000.raw") && !ConfMan.getBool("speech_mute");
	_brightPalette = ConfMan.getBool("bright_palette");
	_copyProtection = ConfMan.getBool("copy_protection");

	_timer->installTimerProc(vSyncInterrupt, 1000000 / 70, this, "dreamwebVSync");
	dreamweb();
	dreamwebFinalize();
	_quitRequested = false;

	_timer->removeTimerProc(vSyncInterrupt);

	return Common::kNoError;
}

void DreamWebEngine::setSpeed(uint speed) {
	debug(0, "setting speed %u", speed);
	_speed = speed;
	_timer->removeTimerProc(vSyncInterrupt);
	_timer->installTimerProc(vSyncInterrupt, 1000000 / 70 / speed, this, "dreamwebVSync");
}

Common::String DreamWebEngine::getSavegameFilename(int slot) const {
	// TODO: Are saves from all versions of Dreamweb compatible with each other?
	// Then we can can consider keeping the filenames as DREAMWEB.Dnn.
	// Otherwise, this must be changed to be target dependent.
	//Common::String filename = _targetName + Common::String::format(".d%02d", savegameId);
	Common::String filename = Common::String::format("DREAMWEB.D%02d", slot);
	return filename;
}

void DreamWebEngine::keyPressed(uint16 ascii) {
	debug(2, "key pressed = %04x", ascii);
	uint16 in = (_bufferIn + 1) % ARRAYSIZE(_keyBuffer);
	uint16 out = _bufferOut;
	if (in == out) {
		warning("keyboard buffer is full");
		return;
	}
	_bufferIn = in;
	_keyBuffer[in] = ascii;
}

void DreamWebEngine::getPalette(uint8 *data, uint start, uint count) {
	_system->getPaletteManager()->grabPalette(data, start, count);
	while (count--)
		*data++ >>= 2;
}

void DreamWebEngine::setPalette(const uint8 *data, uint start, uint count) {
	assert(start + count <= 256);
	uint8 fixed[3*256];
	for (uint i = 0; i < count * 3; ++i) {
		fixed[i] = data[i] << 2;
	}
	_system->getPaletteManager()->setPalette(fixed, start, count);
}

void DreamWebEngine::blit(const uint8 *src, int pitch, int x, int y, int w, int h) {
	if (y + h > (int)kScreenheight)
		h = kScreenheight - y;
	if (x + w > (int)kScreenwidth)
		w = kScreenwidth - x;
	if (h <= 0 || w <= 0)
		return;
	_system->copyRectToScreen(src, pitch, x, y, w, h);
}

void DreamWebEngine::printUnderMonitor() {
	uint8 *dst = workspace() + kScreenwidth * 43 + 76;

	Graphics::Surface *s = _system->lockScreen();
	if (!s)
		error("lockScreen failed");

	for (uint y = 0; y < 104; ++y) {
		uint8 *src = (uint8 *)s->getBasePtr(76, 43 + 8 + y);
		for (uint x = 0; x < 170; ++x) {
			if (*src < 231)
				*dst++ = *src++;
			else {
				++dst; ++src;
			}
		}
		dst += kScreenwidth - 170;
	}
	_system->unlockScreen();
}

void DreamWebEngine::cls() {
	_system->fillScreen(0);
}

uint8 DreamWebEngine::modifyChar(uint8 c) const {
	if (c < 128)
		return c;

	switch(getLanguage()) {
	case Common::DE_DEU:
		switch(c) {
		case 129:
			return 'Z' + 3;
		case 132:
			return 'Z' + 1;
		case 142:
			return 'Z' + 4;
		case 154:
			return 'Z' + 6;
		case 225:
			return 'A' - 1;
		case 153:
			return 'Z' + 5;
		case 148:
			return 'Z' + 2;
		default:
			return c;
		}
	case Common::ES_ESP:
		switch(c) {
		case 160:
			return 'Z' + 1;
		case 130:
			return 'Z' + 2;
		case 161:
			return 'Z' + 3;
		case 162:
			return 'Z' + 4;
		case 163:
			return 'Z' + 5;
		case 164:
			return 'Z' + 6;
		case 165:
			return ',' - 1;
		case 168:
			return 'A' - 1;
		case 173:
			return 'A' - 4;
		case 129:
			return 'A' - 5;
		default:
			return c;
		}
	case Common::FR_FRA:
	case Common::IT_ITA:
		switch(c) {
		case 133:
			return 'Z' + 1;
		case 130:
			return 'Z' + 2;
		case 138:
			return 'Z' + 3;
		case 136:
			return 'Z' + 4;
		case 140:
			return 'Z' + 5;
		case 135:
			return 'Z' + 6;
		case 149:
			return ',' - 1;
		case 131:
			return ',' - 2;
		case 141:
			return ',' - 3;
		case 139:
			return ',' - 4;
		case 151:
			return 'A' - 1;
		case 147:
			return 'A' - 3;
		case 150:
			return 'A' - 4;
		default:
			return c;
		}
	default:
		return c;
	}
}

Common::String DreamWebEngine::modifyFileName(const char *name) {
	Common::String fileName(name);

	// Sanity check
	if (!fileName.hasPrefix("DREAMWEB."))
		return fileName;

	// Make sure we use the correct file name as it differs depending on the game variant
	fileName = _datafilePrefix;
	fileName += name + 9;
	return fileName;
}

bool DreamWebEngine::hasSpeech() {
	return isCD() && _hasSpeech;
}

} // End of namespace DreamWeb