/* 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/debug-channels.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/textconsole.h"

#include "engines/util.h"

#include "agos/debugger.h"
#include "agos/intern.h"
#include "agos/agos.h"
#include "agos/midi.h"
#include "agos/sound.h"

#include "backends/audiocd/audiocd.h"

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

#include "audio/mididrv.h"

namespace AGOS {

static const GameSpecificSettings simon1_settings = {
	"",                                     // base_filename
	"",                                     // restore_filename
	"",                                     // tbl_filename
	"EFFECTS",                              // effects_filename
	"SIMON",                                // speech_filename
};

static const GameSpecificSettings simon2_settings = {
	"",                                     // base_filename
	"",                                     // restore_filename
	"",                                     // tbl_filename
	"",                                     // effects_filename
	"SIMON2",                               // speech_filename
};

static const GameSpecificSettings dimp_settings = {
	"Gdimp",                                // base_filename
	"",                                     // restore_filename
	"",                                     // tbl_filename
	"",                                     // effects_filename
	"MUSIC",                                // speech_filename
};

static const GameSpecificSettings jumble_settings = {
	"Gjumble",                              // base_filename
	"",                                     // restore_filename
	"",                                     // tbl_filename
	"",                                     // effects_filename
	"MUSIC",                                // speech_filename
};

static const GameSpecificSettings puzzle_settings = {
	"Gpuzzle",                              // base_filename
	"",                                     // restore_filename
	"",                                     // tbl_filename
	"",                                     // effects_filename
	"MUSIC",                                // speech_filename
};

static const GameSpecificSettings swampy_settings = {
	"Gswampy",                              // base_filename
	"",                                     // restore_filename
	"",                                     // tbl_filename
	"",                                     // effects_filename
	"MUSIC",                                // speech_filename
};

#ifdef ENABLE_AGOS2
AGOSEngine_DIMP::AGOSEngine_DIMP(OSystem *system, const AGOSGameDescription *gd)
	: AGOSEngine_PuzzlePack(system, gd) {

	_iconToggleCount = 0;
	_voiceCount = 0;

	_lastTickCount = 0;
	_startSecondCount = 0;
	_tSecondCount = 0;
}

AGOSEngine_PuzzlePack::AGOSEngine_PuzzlePack(OSystem *system, const AGOSGameDescription *gd)
	: AGOSEngine_Feeble(system, gd) {

	_oopsValid = false;
	_gameTime = 0;
}
#endif

AGOSEngine_Simon2::AGOSEngine_Simon2(OSystem *system, const AGOSGameDescription *gd)
	: AGOSEngine_Simon1(system, gd) {
}

AGOSEngine_Simon1::AGOSEngine_Simon1(OSystem *system, const AGOSGameDescription *gd)
	: AGOSEngine_Waxworks(system, gd) {
}

AGOSEngine_Waxworks::AGOSEngine_Waxworks(OSystem *system, const AGOSGameDescription *gd)
	: AGOSEngine_Elvira2(system, gd) {

	_boxCR = false;
	_boxLineCount = 0;
	memset(_boxBuffer, 0, sizeof(_boxBuffer));
	_boxBufferPtr = _boxBuffer;

	_linePtrs[0] = nullptr;
	_linePtrs[1] = nullptr;
	_linePtrs[2] = nullptr;
	_linePtrs[3] = nullptr;
	_linePtrs[4] = nullptr;
	_linePtrs[5] = nullptr;
	memset(_lineCounts, 0, sizeof(_lineCounts));
}

AGOSEngine_Elvira2::AGOSEngine_Elvira2(OSystem *system, const AGOSGameDescription *gd)
	: AGOSEngine_Elvira1(system, gd) {
}

AGOSEngine_Elvira1::AGOSEngine_Elvira1(OSystem *system, const AGOSGameDescription *gd)
	: AGOSEngine(system, gd), _sjisCurChar(0), _sjisFont(nullptr) {
}

AGOSEngine_Elvira1::~AGOSEngine_Elvira1() {
	delete _sjisFont;
}

Common::Error AGOSEngine_Elvira1::init() {
	Common::Error ret = AGOSEngine::init();
	if (ret.getCode() == Common::kNoError && getPlatform() == Common::kPlatformPC98) {
		_sjisFont = Graphics::FontSJIS::createFont(Common::kPlatformPC98);
		if (_sjisFont)
			_sjisFont->toggleFatPrint(true);
		else
			error("AGOSEngine_Elvira1::init(): Failed to load SJIS font.");
	}
	return ret;
}

AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
	: Engine(system), _rnd("agos"), _gameDescription(gd) {

	//Image dumping command disabled as it doesn't work well
#if 0
	DebugMan.addDebugChannel(kDebugImageDump, "image_dump", "Enable dumping of images to files");
#endif
	_vcPtr = nullptr;
	_vcGetOutOfCode = 0;
	_gameOffsetsPtr = nullptr;

	_gameFile = nullptr;
	_opcode = 0;

	_itemMemSize = 0;
	_tableMemSize = 0;
	_vgaMemSize = 0;

	_musicIndexBase = 0;
	_soundIndexBase = 0;
	_tableIndexBase = 0;
	_textIndexBase = 0;

	_numMusic = 0;
	_numSFX = 0;
	_numSpeech = 0;
	_numZone = 0;

	_numBitArray1 = 0;
	_numBitArray2 = 0;
	_numBitArray3 = 0;
	_numItemStore = 0;
	_numTextBoxes = 0;
	_numVars = 0;
	_numVideoOpcodes = 0;
	_vgaBaseDelay = 0;
	_vgaPeriod = 0;

	_strippedTxtMem = nullptr;
	_textMem = nullptr;
	_textSize = 0;
	_stringTabNum = 0;
	_stringTabPos = 0;
	_stringTabSize = 0;
	_stringTabPtr = nullptr;

	_itemArrayPtr = nullptr;
	_itemArraySize = 0;
	_itemArrayInited = 0;

	_iconFilePtr = nullptr;

	_codePtr = nullptr;

	_localStringtable = nullptr;
	_stringIdLocalMin = 0;
	_stringIdLocalMax = 0;

	_roomStates = nullptr;
	_numRoomStates = 0;

	_menuBase = nullptr;
	_roomsList = nullptr;
	_roomsListPtr = nullptr;

	_xtblList = nullptr;
	_xtablesHeapPtrOrg = nullptr;
	_xtablesHeapCurPosOrg = 0;
	_xsubroutineListOrg = nullptr;

	_tblList = nullptr;
	_tablesHeapPtr = nullptr;
	_tablesHeapPtrOrg = nullptr;
	_tablesheapPtrNew = nullptr;
	_tablesHeapSize = 0;
	_tablesHeapCurPos = 0;
	_tablesHeapCurPosOrg = 0;
	_tablesHeapCurPosNew = 0;
	_subroutineListOrg = nullptr;

	_subroutineList = nullptr;

	_recursionDepth = 0;

	_lastVgaTick = 0;

	_marks = 0;
	_scanFlag = false;

	_scriptVar2 = 0;
	_runScriptReturn1 = false;
	_skipVgaWait = false;
	_noParentNotify = false;
	_beardLoaded = false;
	_litBoxFlag = false;
	_mortalFlag = false;
	_displayFlag = 0;
	_syncFlag2 = false;
	_inCallBack = false;
	_cepeFlag = false;
	_fastMode = false;

	_backFlag = false;

	_copyProtection = false;
	_pause = false;
	_speech = false;
	_subtitles = false;

	_animatePointer = 0;
	_maxCursorWidth = 0;
	_maxCursorHeight = 0;
	_mouseAnim = 0;
	_mouseAnimMax = 0;
	_mouseCursor = 0;
	_mouseData = nullptr;
	_oldMouseCursor = 0;
	_currentMouseCursor = 0;
	_currentMouseAnim = 0;
	_oldMouseAnimMax = 0;

	_vgaVar9 = 0;
	_chanceModifier = 0;
	_restoreWindow6 = 0;
	_scrollX = 0;
	_scrollY = 0;
	_scrollXMax = 0;
	_scrollYMax = 0;
	_scrollCount = 0;
	_scrollFlag = 0;
	_scrollHeight = 0;
	_scrollWidth = 0;
	_scrollImage = nullptr;
	_boxStarHeight = 0;

	_scriptVerb = 0;
	_scriptNoun1 = 0;
	_scriptNoun2 = 0;
	_scriptAdj1 = 0;
	_scriptAdj2 = 0;

	_curWindow = 0;
	_textWindow = nullptr;

	_subjectItem = nullptr;
	_objectItem = nullptr;
	_currentPlayer = nullptr;

	_iOverflow = false;
	_nameLocked = false;
	_hitAreaObjectItem = nullptr;
	_lastHitArea = nullptr;
	_lastNameOn = nullptr;
	_lastHitArea3 = nullptr;
	_hitAreaSubjectItem = nullptr;
	_currentBox = nullptr;
	_currentBoxNum = 0;
	_currentVerbBox = nullptr;
	_lastVerbOn = nullptr;
	_needHitAreaRecalc = 0;
	_verbHitArea = 0;
	_defaultVerb = 0;
	_mouseHideCount = 0;

	_dragAccept = false;
	_dragEnd = false;
	_dragFlag = false;
	_dragMode = false;
	_dragCount = 0;
	_lastClickRem = nullptr;

	_windowNum = 0;

	_printCharCurPos = 0;
	_printCharMaxPos = 0;
	_printCharPixelCount = 0;
	_numLettersToPrint = 0;

	_clockStopped = 0;
	_gameStoppedClock = 0;
	_lastTime = 0;
	_lastMinute = 0;

	_firstTimeStruct = nullptr;
	_pendingDeleteTimeEvent = nullptr;

	_initMouse = 0;
	_leftButtonDown = false;
	_rightButtonDown = false;
	_clickOnly = false;
	_oneClick = 0;
	_leftClick = 0;
	_rightClick = 0;
	_noRightClick = false;

	_leftButton = 0;
	_leftButtonCount = 0;
	_leftButtonOld = 0;

	_dummyItem1 = new Item();
	_dummyItem2 = new Item();
	_dummyItem3 = new Item();

	_videoLockOut = 0;
	_scrollUpHitArea = 0;
	_scrollDownHitArea = 0;

	_noOverWrite = 0;
	_rejectBlock = false;

	_fastFadeCount = 0;
	_fastFadeInFlag = 0;
	_fastFadeOutFlag = 0;
	_exitCutscene = 0;
	_paletteFlag = 0;
	_bottomPalette = false;
	_picture8600 = 0;

	_soundFileId = 0;
	_lastMusicPlayed = 0;
	_nextMusicToPlay = 0;
	_sampleEnd = 0;
	_sampleWait = 0;

	_showPreposition = 0;
	_showMessageFlag = 0;

	_newDirtyClip = false;
	_wiped = false;
	_copyScnFlag = 0;
	_vgaSpriteChanged = 0;

	_block = nullptr;
	_blockEnd = nullptr;
	_vgaMemPtr = nullptr;
	_vgaMemEnd = nullptr;
	_vgaMemBase = nullptr;
	_vgaFrozenBase = nullptr;
	_vgaRealBase = nullptr;
	_zoneBuffers = nullptr;

	_curVgaFile1 = nullptr;
	_curVgaFile2 = nullptr;
	_curSfxFile = nullptr;
	_curSfxFileSize = 0;

	_syncCount = 0;

	_frameCount = 0;

	_zoneNumber = 0;

	_vgaWaitFor = 0;
	_lastVgaWaitFor = 0;

	_vgaCurZoneNum = 0;
	_vgaCurSpriteId = 0;

	_baseY = 0;
	_scale = 0;

	_feebleRect.left = 0;
	_feebleRect.right = 0;
	_feebleRect.top = 0;
	_feebleRect.bottom = 0;

	_scaleX = 0;
	_scaleY = 0;
	_scaleWidth = 0;
	_scaleHeight = 0;

	_nextVgaTimerToProcess = nullptr;

	_opcode177Var1 = 1;
	_opcode177Var2 = 0;
	_opcode178Var1 = 1;
	_opcode178Var2 = 0;

	_classLine = nullptr;
	_classMask = 0;
	_classMode1 = 0;
	_classMode2 = 0;
	_currentLine = nullptr;
	_currentTable = nullptr;
	_findNextPtr = nullptr;

	_agosMenu = 0;
	_currentRoom = 0;
	_superRoomNumber = 0;
	_wallOn = 0;

	memset(_objectArray, 0, sizeof(_objectArray));
	memset(_itemStore, 0, sizeof(_itemStore));
	memset(_textMenu, 0, sizeof(_textMenu));

	memset(_shortText, 0, sizeof(_shortText));
	memset(_shortTextX, 0, sizeof(_shortText));
	memset(_shortTextY, 0, sizeof(_shortText));
	memset(_longText, 0, sizeof(_longText));
	memset(_longSound, 0, sizeof(_longSound));

	memset(_bitArray, 0, sizeof(_bitArray));
	memset(_bitArrayTwo, 0, sizeof(_bitArrayTwo));
	memset(_bitArrayThree, 0, sizeof(_bitArrayThree));

	_variableArray = nullptr;
	_variableArray2 = nullptr;
	_variableArrayPtr = nullptr;

	memset(_windowArray, 0, sizeof(_windowArray));

	memset(_fcsData1, 0, sizeof(_fcsData1));
	memset(_fcsData2, 0, sizeof(_fcsData2));

	_awaitTwoByteToken = 0;
	_byteTokens = nullptr;
	_byteTokenStrings = nullptr;
	_twoByteTokens = nullptr;
	_twoByteTokenStrings = nullptr;
	_secondTwoByteTokenStrings = nullptr;
	_thirdTwoByteTokenStrings = nullptr;
	memset(_textBuffer, 0, sizeof(_textBuffer));
	_textCount = 0;

	_freeStringSlot = 0;

	memset(_stringReturnBuffer, 0, sizeof(_stringReturnBuffer));

	memset(_pathFindArray, 0, sizeof(_pathFindArray));

	memset(_pathValues, 0, sizeof(_pathValues));
	_PVCount = 0;
	_GPVCount = 0;

	memset(_pathValues1, 0, sizeof(_pathValues1));
	_PVCount1 = 0;
	_GPVCount1 = 0;

	memset(_currentPalette, 0, sizeof(_currentPalette));
	memset(_displayPalette, 0, sizeof(_displayPalette));

	memset(_videoBuf1, 0, sizeof(_videoBuf1));
	memset(_videoWindows, 0, sizeof(_videoWindows));

	_dummyWindow = new WindowBlock;
	_windowList = new WindowBlock[16];

	memset(_lettersToPrintBuf, 0, sizeof(_lettersToPrintBuf));

	_planarBuf = nullptr;
	_pak98Buf = nullptr;
	_paletteModNext = 16;

	_midiEnabled = false;

	_vgaTickCounter = 0;

	_sound = nullptr;

	_effectsMuted = false;
	_ambientMuted = false;
	_musicMuted = false;
	// Initialize at default ScummVM volumes; these will be overwritten by
	// syncSoundSettings.
	_musicVolume = 192;
	_effectsVolume = 192;
	_useDigitalSfx = true;

	_saveLoadType = 0;
	_saveLoadSlot = 0;
	memset(_saveLoadName, 0, sizeof(_saveLoadName));
	memset(_saveBuf, 0, sizeof(_saveBuf));

	_saveGameNameLen = 0;
	_saveLoadRowCurPos = 0;
	_numSaveGameRows = 0;
	_saveDialogFlag = false;
	_saveOrLoad = false;
	_saveLoadEdit = false;

	_hyperLink = 0;
	_interactY = 0;
	_oracleMaxScrollY = 0;
	_noOracleScroll = 0;

	_backGroundBuf = nullptr;
	_backBuf = nullptr;
	_scaleBuf = nullptr;
	_window4BackScn = nullptr;
	_window6BackScn = nullptr;

	_window3Flag = 0;
	_window4Flag = 0;
	_window6Flag = 0;

	_moveXMin = 0;
	_moveYMin = 0;
	_moveXMax = 0;
	_moveYMax = 0;

	_forceAscii = false;

	_vc10BasePtrOld = nullptr;
	memcpy (_hebrewCharWidths,
		"\x5\x5\x4\x6\x5\x3\x4\x5\x6\x3\x5\x5\x4\x6\x5\x3\x4\x6\x5\x6\x6\x6\x5\x5\x5\x6\x5\x6\x6\x6\x6\x6", 32);

	const Common::FSNode gameDataDir(ConfMan.get("path"));

	// Add default file directories for Acorn version of
	// Simon the Sorcerer 1
	SearchMan.addSubDirectoryMatching(gameDataDir, "execute");

	// Add default file directories for Amiga/Macintosh
	// versions of Simon the Sorcerer 2
	SearchMan.addSubDirectoryMatching(gameDataDir, "voices");

	// Add default file directories for Amiga & Macintosh
	// versions of The Feeble Files
	SearchMan.addSubDirectoryMatching(gameDataDir, "gfx");
	SearchMan.addSubDirectoryMatching(gameDataDir, "movies");
	SearchMan.addSubDirectoryMatching(gameDataDir, "sfx");
	SearchMan.addSubDirectoryMatching(gameDataDir, "speech");
}

Common::Error AGOSEngine::init() {
	if (getGameId() == GID_DIMP) {
		_screenWidth = 496;
		_screenHeight = 400;
	} else if (getGameType() == GType_FF || getGameType() == GType_PP) {
		_screenWidth = 640;
		_screenHeight = 480;
	} else {
		_screenWidth = 320;
		_screenHeight = 200;
	}

	_internalWidth = _screenWidth;
	_internalHeight = _screenHeight;

	if (getPlatform() == Common::kPlatformPC98) {
		_internalWidth <<= 1;
		_internalHeight <<= 1;
	}

	initGraphics(_internalWidth, _internalHeight);

	_midi = new MidiPlayer(this);

	if ((getGameType() == GType_SIMON2 && getPlatform() == Common::kPlatformWindows) ||
			(getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformWindows) ||
			((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) ||
			(getPlatform() == Common::kPlatformDOS && getGameType() != GType_PN && getGameType() != GType_FF) ||
			getPlatform() == Common::kPlatformPC98) {

		int ret = _midi->open();
		if (ret)
			warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret));

		_midiEnabled = true;
	}
	// Digital SFX are used if MIDI SFX are not available or if the "prefer
	// digital SFX" setting is set to true or is not present at all.
	// Two exceptions to this are:
	// - Elvira 2 DOS needs an optional file to enable digital SFX. If it is
	//   not present, MIDI SFX are used.
	// - Simon 1 DOS floppy has only MIDI SFX.
	// Note that MIDI SFX can be safely used if the MidiPlayer failed to
	// initialize; they just will not play.
	_useDigitalSfx = !_midiEnabled || !_midi->hasMidiSfx() || !ConfMan.hasKey("prefer_digitalsfx") || ConfMan.getBool("prefer_digitalsfx");
	if ((getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformDOS && !SearchMan.hasFile("013.VGA")) ||
			(getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformDOS && !(getFeatures() & GF_TALKIE))) {
		_useDigitalSfx = false;
	}
	if (!_useDigitalSfx && (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) && getPlatform() == Common::kPlatformDOS) {
		// Load the MIDI SFX data file for Elvira 2 and Waxworks DOS.
		loadMidiSfx();
	}

	// allocate buffers
	_backGroundBuf = new Graphics::Surface();
	_backGroundBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());

	if (getGameType() == GType_FF || getGameType() == GType_PP || (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98)) {
		_backBuf = new Graphics::Surface();
		_backBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
		_scaleBuf = new Graphics::Surface();
		_scaleBuf->create(_internalWidth, _internalHeight, Graphics::PixelFormat::createFormatCLUT8());
	}

	if (getGameType() == GType_SIMON2) {
		_window4BackScn = new Graphics::Surface();
		_window4BackScn->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
	} else if (getGameType() == GType_SIMON1) {
		_window4BackScn = new Graphics::Surface();
		_window4BackScn->create(_screenWidth, 134, Graphics::PixelFormat::createFormatCLUT8());
	} else if (getGameType() == GType_WW || getGameType() == GType_ELVIRA2) {
		_window4BackScn = new Graphics::Surface();
		_window4BackScn->create(224, 127, Graphics::PixelFormat::createFormatCLUT8());
	} else if (getGameType() == GType_ELVIRA1) {
		_window4BackScn = new Graphics::Surface();
		if (getPlatform() == Common::kPlatformAmiga && (getFeatures() & GF_DEMO)) {
			_window4BackScn->create(224, 196, Graphics::PixelFormat::createFormatCLUT8());
		} else {
			_window4BackScn->create(224, 144, Graphics::PixelFormat::createFormatCLUT8());
		}
		_window6BackScn = new Graphics::Surface();
		_window6BackScn->create(48, 80, Graphics::PixelFormat::createFormatCLUT8());
	}

	setupGame();

	setDebugger(new Debugger(this));
	_sound = new Sound(this, gss, _mixer);

	_copyProtection = ConfMan.getBool("copy_protection");
	_language = Common::parseLanguage(ConfMan.get("language"));

	if (getGameType() == GType_PP) {
		_speech = true;
		_subtitles = false;
	} else if (getFeatures() & GF_TALKIE) {
		_speech = !ConfMan.getBool("speech_mute");
		_subtitles = ConfMan.getBool("subtitles");

		if (getGameType() == GType_SIMON1) {
			// English and German versions don't have full subtitles
			if (_language == Common::EN_ANY || _language == Common::DE_DEU)
				_subtitles = false;
			// Other versions require speech to be enabled
			else
				_speech = true;
		}

		// Default to speech only, if both speech and subtitles disabled
		if (!_speech && !_subtitles)
			_speech = true;
	} else {
		_speech = false;
		_subtitles = true;
	}

	// Setup mixer
	syncSoundSettings();

	return Common::kNoError;
}

static const uint16 initialVideoWindows_Simon[20] = {
	0,  0, 20, 200,
	0,  0,  3, 136,
	17, 0,  3, 136,
	0,  0, 20, 200,
	0,  0, 20, 134
};

static const uint16 initialVideoWindows_Common[20] = {
	 3, 0, 14, 136,
	 0, 0,  3, 136,
	17, 0,  3, 136,
	 0, 0, 20, 200,
	 3, 3, 14, 127,
};

static const uint16 initialVideoWindows_PN[20] = {
	 3, 0, 14, 136,
	 0, 0,  3, 136,
	17, 0,  3, 136,
	 0, 0, 20, 200,
	 3, 2, 14, 129,
};

#ifdef ENABLE_AGOS2
void AGOSEngine_PuzzlePack::setupGame() {
	if (getGameId() == GID_DIMP) {
		gss = &dimp_settings;
	} else if (getGameId() == GID_JUMBLE) {
		gss = &jumble_settings;
	} else if (getGameId() == GID_PUZZLE) {
		gss = &puzzle_settings;
	} else if (getGameId() == GID_SWAMPY) {
		gss = &swampy_settings;
	}
	_numVideoOpcodes = 85;
	_vgaMemSize = 7500000;
	_itemMemSize = 20000;
	_tableMemSize = 200000;
	_frameCount = 1;
	_vgaBaseDelay = 5;
	_vgaPeriod = (getGameId() == GID_DIMP) ? 35 : 30;
	_numBitArray1 = 128;
	_numItemStore = 10;
	_numTextBoxes = 40;
	_numVars = 2048;

	_numZone = 450;

	AGOSEngine::setupGame();
}
#endif

void AGOSEngine_Simon2::setupGame() {
	gss = &simon2_settings;
	_tableIndexBase = 1580 / 4;
	_textIndexBase = 1500 / 4;
	_numVideoOpcodes = 75;
#if defined(__DS__)
	_vgaMemSize = 1300000;
#else
	_vgaMemSize = 2000000;
#endif
	_itemMemSize = 20000;
	_tableMemSize = 100000;
	// Check whether to use MT-32 MIDI tracks in Simon the Sorcerer 2
	if (getGameType() == GType_SIMON2 && getPlatform() == Common::kPlatformDOS && _midi->usesMT32Data())
		_musicIndexBase = MUSIC_INDEX_BASE_SIMON2_MT32;
	else
		_musicIndexBase = MUSIC_INDEX_BASE_SIMON2_GM;
	_soundIndexBase = 1660 / 4;
	_frameCount = 1;
	_vgaBaseDelay = 1;
	_vgaPeriod = 45;
	_numBitArray1 = 16;
	_numBitArray2 = 16;
	_numItemStore = 10;
	_numTextBoxes = 20;
	_numVars = 255;

	_numMusic = 93;
	_numSFX = 222;
	_numSpeech = 11997;
	_numZone = 140;

	AGOSEngine::setupGame();
}

void AGOSEngine_Simon1::setupGame() {
	gss = &simon1_settings;
	_tableIndexBase = 1576 / 4;
	_textIndexBase = 1460 / 4;
	_numVideoOpcodes = 64;
	_vgaMemSize = 1000000;
	_itemMemSize = 20000;
	_tableMemSize = 50000;
	_musicIndexBase = 1316 / 4;
	_soundIndexBase = 0;
	_frameCount = 1;
	_vgaBaseDelay = 1;
	_vgaPeriod = 50;
	_numBitArray1 = 16;
	_numBitArray2 = 16;
	_numItemStore = 10;
	_numTextBoxes = 20;
	_numVars = 255;

	_numMusic = 34;
	_numSFX = 127;
	_numSpeech = 3623;
	_numZone = 164;

	AGOSEngine::setupGame();
}

void AGOSEngine_Waxworks::setupGame() {
	gss = &simon1_settings;
	_numVideoOpcodes = 64;
	_vgaMemSize = 1000000;
	_itemMemSize = 80000;
	_tableMemSize = 50000;
	_frameCount = 4;
	_vgaBaseDelay = 1;
	_vgaPeriod = 50;
	_numBitArray1 = 16;
	_numBitArray2 = 15;
	_numItemStore = 50;
	_numTextBoxes = 10;
	_numVars = 255;

	_numMusic = 26;
	_numZone = 155;

	AGOSEngine::setupGame();
}

void AGOSEngine_Elvira2::setupGame() {
	gss = &simon1_settings;
	_numVideoOpcodes = 60;
	_vgaMemSize = 1000000;
	_itemMemSize = 64000;
	_tableMemSize = 100000;
	_frameCount = 4;
	_vgaBaseDelay = 1;
	_vgaPeriod = 50;
	_numBitArray1 = 16;
	_numBitArray2 = 15;
	_numItemStore = 50;
	_numVars = 255;

	_numMusic = 9;
	_numZone = 99;

	AGOSEngine::setupGame();
}

void AGOSEngine_Elvira1::setupGame() {
	gss = &simon1_settings;
	_numVideoOpcodes = 57;
	_vgaMemSize = 1000000;
	_itemMemSize = 64000;
	_tableMemSize = 256000;
	_frameCount = 4;
	_vgaBaseDelay = 1;
	_vgaPeriod = 50;
	_numVars = 512;

	_numMusic = 14;
	_numZone = 74;

	AGOSEngine::setupGame();
}

void AGOSEngine_PN::setupGame() {
	gss = &simon1_settings;
	_numVideoOpcodes = 57;
	_vgaMemSize = 1000000;
	_frameCount = 4;
	_vgaBaseDelay = 1;
	_vgaPeriod = 50;
	_numVars = 256;

	_numZone = 26;

	AGOSEngine::setupGame();
}

void AGOSEngine::setupGame() {
	allocItemHeap();
	allocTablesHeap();

	if (getGameType() != GType_SIMON2)
		initMouse();

	_variableArray = (int16 *)calloc(_numVars, sizeof(int16));
	_variableArrayPtr = _variableArray;
	if (getGameType() == GType_FF || getGameType() == GType_PP) {
		_variableArray2 = (int16 *)calloc(_numVars, sizeof(int16));
	}

	setupOpcodes();
	setupVgaOpcodes();

	setZoneBuffers();

	_currentMouseCursor = 255;
	_currentMouseAnim = 255;

	_lastMusicPlayed = -1;
	_nextMusicToPlay = -1;

	_noOverWrite = 0xFFFF;

	_stringIdLocalMin = 1;

	_agosMenu = 1;
	_superRoomNumber = 1;

	for (int i = 0; i < 20; i++) {
		if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
			_videoWindows[i] = initialVideoWindows_Simon[i];
		} else if (getGameType() == GType_PN) {
			_videoWindows[i] = initialVideoWindows_PN[i];
		} else {
			_videoWindows[i] = initialVideoWindows_Common[i];
		}
	}

	if (getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformAtariST) {
		_videoWindows[9] = 75;
	}
}

AGOSEngine::~AGOSEngine() {
	_system->getAudioCDManager()->stop();

	for (uint i = 0; i < _itemHeap.size(); i++) {
		delete[] _itemHeap[i];
	}
	_itemHeap.clear();

	free(_tablesHeapPtr - _tablesHeapCurPos);

	free(_mouseData);

	free(_gameOffsetsPtr);
	free(_iconFilePtr);
	free(_itemArrayPtr);
	free(_menuBase);
	free(_roomsList);
	free(_roomStates);
	free(_stringTabPtr);
	free(_strippedTxtMem);
	free(_tblList);
	free(_textMem);
	free(_xtblList);

	if (_backGroundBuf)
		_backGroundBuf->free();
	delete _backGroundBuf;
	if (_backBuf)
		_backBuf->free();
	delete _backBuf;
	free(_planarBuf);
	delete[] _pak98Buf;
	if (_scaleBuf)
		_scaleBuf->free();
	delete _scaleBuf;
	free(_zoneBuffers);

	if (_window4BackScn)
		_window4BackScn->free();
	delete _window4BackScn;
	if (_window6BackScn)
		_window6BackScn->free();
	delete _window6BackScn;

	delete _midi;

	free(_firstTimeStruct);
	free(_pendingDeleteTimeEvent);

	free(_variableArray);
	free(_variableArray2);

	delete _dummyItem1;
	delete _dummyItem2;
	delete _dummyItem3;

	delete _dummyWindow;
	delete[] _windowList;

	delete _sound;
	delete _gameFile;
}

void AGOSEngine::pauseEngineIntern(bool pauseIt) {
	if (pauseIt) {
		_keyPressed.reset();
		_pause = true;

		_midi->pause(true);
		_mixer->pauseAll(true);
	} else {
		_pause = false;

		_midi->pause(false);
		_mixer->pauseAll(false);
	}
}

void AGOSEngine::pause() {
	PauseToken pt = pauseEngine();

	while (_pause && !shouldQuit()) {
		delay(1);
		if (_keyPressed.keycode == Common::KEYCODE_PAUSE) {
			pt.clear();
			_keyPressed.reset();
		}
	}
}

Common::Error AGOSEngine::go() {
#ifdef ENABLE_AGOS2
	loadArchives();
#endif

	loadGamePcFile();

	addTimeEvent(0, 1);

	if (getFileName(GAME_GMEFILE) != nullptr) {
		openGameFile();
	}

	if (getGameType() == GType_FF) {
		loadIconData();
	} else if (getFileName(GAME_ICONFILE) != nullptr) {
		loadIconFile();
	}

	if (getFileName(GAME_MENUFILE) != nullptr) {
		loadMenuFile();
	}

	vc34_setMouseOff();

	if (getGameType() != GType_PP && getGameType() != GType_FF) {
		uint16 count = (getGameType() == GType_SIMON2) ? 5 : _frameCount;
		addVgaEvent(count, ANIMATE_INT, nullptr, 0, 0);
	}

	if (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformAtariST &&
		(getFeatures() & GF_DEMO)) {
		setWindowImage(3, 9900);
		while (!shouldQuit())
			delay(0);
	}

	if (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformAmiga &&
		(getFeatures() & GF_DEMO)) {
		playMusic(0, 0);
	}

	runSubroutine101();
	permitInput();

	while (!shouldQuit()) {
		waitForInput();
		handleVerbClicked(_verbHitArea);
		delay(100);
	}

	return Common::kNoError;
}


uint32 AGOSEngine::getTime() const {
	return _system->getMillis() / 1000;
}

void AGOSEngine::syncSoundSettings() {
	Engine::syncSoundSettings();

	int newMusicVolume = ConfMan.getInt("music_volume");
	int newEffectsVolume = ConfMan.getInt("sfx_volume");

	_musicMuted = newMusicVolume == 0;
	if (newMusicVolume != 0)
		_musicVolume = newMusicVolume;
	if (getGameType() == GType_SIMON2) {
		// Simon 2 has regular and ambient SFX, which can be toggled on and off
		// separately.
		if (newEffectsVolume == 0) {
			// Global SFX volume 0 mutes both regular and ambient SFX.
			_effectsMuted = _ambientMuted = true;
		} else {
			// If global SFX volume is > 0 and both regular and ambient SFX are
			// muted, unmute them. If only one of them is muted, the volume
			// change will only affect that type of SFX. If both are not muted,
			// it will affect both types.
			if (_effectsMuted && _ambientMuted)
				_effectsMuted = _ambientMuted = false;
			_effectsVolume = newEffectsVolume;
		}
		// Engine::syncSoundSettings applies SFX volume to all SFX handles,
		// so manage the regular and ambient handles separately here.
		_sound->effectsMute(_effectsMuted, _effectsVolume);
		_sound->ambientMute(_ambientMuted, _effectsVolume);
	} else {
		// Other games only have one SFX setting.
		_effectsMuted = newEffectsVolume == 0;
		if (newEffectsVolume != 0)
			_effectsVolume = newEffectsVolume;
	}
	_speech = !ConfMan.getBool("speech_mute");

	if (_midiEnabled)
		_midi->syncSoundSettings();
}

void AGOSEngine::syncSoundSettingsIntern() {
	ConfMan.setBool("speech_mute", !_speech);
	ConfMan.setInt("music_volume", _musicMuted ? 0 : _musicVolume);
	bool sfxMute = getGameType() == GType_SIMON2 ?
		_effectsMuted && _ambientMuted : _effectsMuted;
	ConfMan.setInt("sfx_volume", sfxMute ? 0 : _effectsVolume);

	Engine::syncSoundSettings();

	if (getGameType() == GType_SIMON2) {
		// Simon 2 has ambient sound effects, which can be toggled on and off
		// separately from the other SFX.
		// Engine::syncSoundSettings applies SFX volume to all SFX handles,
		// so manage the regular and ambient handles separately here.
		_sound->effectsMute(_effectsMuted, _effectsVolume);
		_sound->ambientMute(_ambientMuted, _effectsVolume);
	}

	if (_midiEnabled)
		_midi->syncSoundSettings();
}

} // End of namespace AGOS
