/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-12-0/engines/m4/m4.cpp $
 * $Id: m4.cpp 32122 2008-05-14 19:42:02Z thebluegr $
 *
 */

//#define SCRIPT_TEST
//#define INTRO_TEST

#include "m4/globals.h"
#include "m4/burger_data.h"
#include "m4/m4.h"
#include "m4/resource.h"
#include "m4/sprite.h"
#include "m4/hotspot.h"
#include "m4/font.h"
#include "m4/rails.h"
#include "m4/midi.h"
#include "m4/events.h"
#include "m4/graphics.h"
#include "m4/viewmgr.h"
#include "m4/gui.h"
#include "m4/woodscript.h"
#include "m4/actor.h"
#include "m4/sound.h"
#include "m4/rails.h"
#include "m4/script.h"
#include "m4/compression.h"
#include "m4/animation.h"
#include "m4/m4_menus.h"
#include "m4/m4_views.h"
#include "m4/mads_anim.h"
#include "m4/mads_menus.h"

#include "common/file.h"
#include "common/events.h"
#include "common/endian.h"
#include "common/system.h"
#include "common/config-manager.h"
#include "engines/engine.h"
#include "graphics/surface.h"
#include "sound/mididrv.h"

namespace M4 {

// FIXME: remove global
M4Engine *_vm;

void escapeHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
	// For now, simply exit the game
	vm->_events->quitFlag = true;
}

// Temporary hotkey handler for use in testing the TextviewView class

void textviewHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
	// Deactivate the scene if it's currently active
	View *sceneView = vm->_viewManager->getView(VIEWID_SCENE);
	if (sceneView != NULL) 
		vm->_viewManager->deleteView(sceneView);

	// Activate the textview view
	vm->_font->setFont(FONT_CONVERSATION_MADS);
	TextviewView *textView = new TextviewView(vm);
	vm->_viewManager->addView(textView);
	textView->setScript("quotes", NULL);
}

void saveGameHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
	// TODO: See CreateF2SaveMenu - save menu should only be activated when
	// certain conditions are met, such as player_commands_allowed, and isInterfaceVisible
	vm->loadMenu(SAVE_MENU, true);
}

void loadGameHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
	// TODO: See CreateF3LoadMenu - save menu should only be activated when
	// certain conditions are met, such as player_commands_allowed, and isInterfaceVisible
	vm->loadMenu(LOAD_MENU, true);
}

void gameMenuHotkeyHandler(M4Engine *vm, View *view, uint32 key) {
	vm->loadMenu(GAME_MENU);
}

M4Engine::M4Engine(OSystem *syst, const M4GameDescription *gameDesc) :
	Engine(syst), _gameDescription(gameDesc) {

	// FIXME
	_vm = this;

	Common::File::addDefaultDirectory(_gameDataPath);
	Common::File::addDefaultDirectory("goodstuf");
	Common::File::addDefaultDirectory("resource");

	Common::addSpecialDebugLevel(kDebugScript, "script", "Script debug level");
	Common::addSpecialDebugLevel(kDebugConversations, "conversations", "Conversations debugging");
}


M4Engine::~M4Engine() {
	delete _globals;
	delete _midi;
	delete _saveLoad;
	delete _kernel;
	delete _player;
	delete _mouse;
	delete _events;
	delete _font;
	delete _actor;
//	delete _scene;		// deleted by the viewmanager
	delete _dialogs;
	delete _screen;
	delete _inventory;
	delete _viewManager;
	delete _rails;
	delete _converse;
	delete _script;
	delete _ws;
	delete _random;
	delete _animation;
	delete _palette;
	delete _resourceManager;
}

int M4Engine::init() {
	// Initialize backend
	_system->beginGFXTransaction();
	initCommonGFX(isM4());
	if (isM4())
		_system->initSize(640, 480);
	else
		_system->initSize(320, 200);
	_system->endGFXTransaction();

	_screen = new M4Surface(true); // Special form for creating screen reference

	int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
	bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));

	MidiDriver *driver = MidiDriver::createMidi(midiDriver);
	if (native_mt32)
		driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);

	_midi = new MidiPlayer(this, driver);
	_midi->setGM(true);
	_midi->setNativeMT32(native_mt32);

	_globals = new Globals(this);
	if (isM4())
		_resourceManager = new M4ResourceManager(this);
	else
		_resourceManager = new MADSResourceManager(this);
	_saveLoad = new SaveLoad(this);
	_palette = new Palette(this);
	_mouse = new Mouse(this);
	_events = new Events(this);
	_kernel = new Kernel(this);
	_player = new Player(this);
	_font = new Font(this);
	if (getGameType() == GType_Burger) {
		_actor = new Actor(this);
		_interfaceView = new GameInterfaceView(this);
		_conversationView = new ConversationView(this);
	} else {
		_actor = NULL;
	}
	_rails = new Rails();	// needs to be initialized before _scene
	_scene = new Scene(this);
	_dialogs = new Dialogs();
	_viewManager = new ViewManager(this);
	_inventory = new Inventory(this);
	_sound = new Sound(this, _mixer, 255);
	_converse = new Converse(this);
	_script = new ScriptInterpreter(this);
	_ws = new WoodScript(this);
	_animation = new Animation(this);
	//_callbacks = new Callbacks(this);
	_random = new Common::RandomSource();
	g_system->getEventManager()->registerRandomSource(*_random, "m4");

	return 0;
}

void M4Engine::eventHandler() {
	M4EventType event;
	uint32 keycode = 0;

	if ((event = _events->handleEvents()) != MEVENT_NO_EVENT) {
		if (_viewManager->viewCount() > 0)
			_viewManager->handleMouseEvents(event);
	}

	if (_events->kbdCheck(keycode)) 
		_viewManager->handleKeyboardEvents(keycode);
}

bool M4Engine::delay(int duration, bool keyAborts, bool clickAborts) {
	uint32 endTime = g_system->getMillis() + duration;
	M4EventType event;
	uint32 keycode = 0;

	while (!_events->quitFlag && (g_system->getMillis() < endTime)) {
		event = _events->handleEvents();
		if (clickAborts && (event == MEVENT_LEFT_RELEASE) || (event == MEVENT_RIGHT_RELEASE)) 
			return true;
			
		if (_events->kbdCheck(keycode)) {
			if (keyAborts)
				return true;
		}

		g_system->delayMillis(10);
	}

	return false;
}

void M4Engine::loadMenu(MenuType menuType, bool loadSaveFromHotkey, bool calledFromMainMenu) {
	if (isM4() && (menuType != MAIN_MENU)) {
		bool menuActive = _viewManager->getView(VIEWID_MENU) != NULL;

		if (!menuActive)
			_palette->fadeToGreen(M4_DIALOG_FADE_STEPS, M4_DIALOG_FADE_DELAY);
	}

	View *view;

	switch (menuType) {
	case MAIN_MENU:
		if (getGameType() == GType_RexNebular)
			view = new RexMainMenuView(this);
		else if (getGameType() == GType_DragonSphere)
			view = new DragonMainMenuView(this);
		else
			view = new MadsMainMenuView(this);
		break;
	case GAME_MENU:
		view = new OrionMenuView(this, 200, 100, GAME_MENU, calledFromMainMenu, loadSaveFromHotkey);
		break;
	case OPTIONS_MENU:
		view = new OrionMenuView(this, 172, 160, OPTIONS_MENU, calledFromMainMenu, loadSaveFromHotkey);
		break;
	case LOAD_MENU:
	case SAVE_MENU:
		view = new OrionMenuView(this, 145, 10, menuType, calledFromMainMenu, loadSaveFromHotkey);
		break;
	default:
		error("Unknown menu type");
		break;
	}

	_viewManager->addView(view);
	_viewManager->moveToFront(view);
}

int M4Engine::go() {
	if (isM4())
		return goM4();
	else
		return goMADS();
}

int M4Engine::goMADS() {
	_palette->setMadsSystemPalette();

	_mouse->init("cursor.ss", NULL);
	_mouse->setCursorNum(0);

	// Load MADS data files
	_globals->loadMadsVocab();			// vocab.dat
	_globals->loadMadsQuotes();			// quotes.dat
	_globals->loadMadsMessagesInfo();	// messages.dat
	// TODO: objects.dat
	// TODO: hoganus.dat (what is it used for?)

	// Test code to dump all messages to the console
	//for (int i = 0; i < _globals->getMessagesSize(); i++)
	//printf("%s\n----------\n", _globals->loadMessage(i));

	if ((getGameType() == GType_RexNebular) || (getGameType() == GType_DragonSphere)) {
		loadMenu(MAIN_MENU);

	} else {
		if (getGameType() == GType_DragonSphere) {
			_scene->loadScene(FIRST_SCENE);
		} else if (getGameType() == GType_Phantom) {
			//_scene->loadScene(FIRST_SCENE);
			_scene->loadScene(106);		// a more interesting scene
		}

		_viewManager->addView(_scene);

		_font->setFont(FONT_MAIN_MADS);
		_font->setColors(2, 1, 3);
		_font->writeString(_scene->getBackgroundSurface(), "Testing the M4/MADS ScummVM engine", 5, 160, 310, 2);
		_font->writeString(_scene->getBackgroundSurface(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 5, 180, 310, 2);

		if (getGameType() == GType_DragonSphere) {
			//_scene->showMADSV2TextBox("Test", 10, 10, NULL);
		}

		_mouse->cursorOn();
	}

	_viewManager->systemHotkeys().add(Common::KEYCODE_ESCAPE, &escapeHotkeyHandler);
	_viewManager->systemHotkeys().add(Common::KEYCODE_KP_MULTIPLY, &textviewHotkeyHandler);

	// Load the general game SFX/voices
	if (getGameType() == GType_RexNebular) {
		_sound->loadDSRFile("rex009.dsr");
	} else if (getGameType() == GType_Phantom) {
		_sound->loadDSRFile("phan009.dsr");
	} else if (getGameType() == GType_DragonSphere) {
		_sound->loadDSRFile("drag009.dsr");
	}

	uint32 nextFrame = g_system->getMillis();
	while (!_events->quitFlag) {
		eventHandler();

		_animation->updateAnim();

		// Call the updateState method of all views
		_viewManager->updateState();

		if (g_system->getMillis() >= nextFrame) {

			_viewManager->refreshAll();
			nextFrame = g_system->getMillis();// + GAME_FRAME_DELAY;
		}

		g_system->delayMillis(10);
	}

	return 0;
}

int M4Engine::goM4() {

	_script->open("m4.dat");

#ifdef SCRIPT_TEST

#if 0
	ScriptFunction *func = _script->loadFunction("room_parser_142");
	_script->runFunction(func);
#endif

#if 1
	ScriptFunction *func = _script->loadFunction("room_daemon_951");
	for (int i = 1; i < 58; i++) {
		_vm->_kernel->trigger = i;
		_script->runFunction(func);
		printf("=================================\n");
		fflush(stdout);
	}
#endif

	return 0;
#endif

	// Set up the inventory

	// Set up the game interface view
	//_interfaceView->inventoryAdd("Money", "", 55);	// Sample item

	if (getGameType() == GType_Burger) {
		for (int i = 0; i < ARRAYSIZE(burger_inventory); i++) {
			char* itemName = strdup(burger_inventory[i].name);
			_inventory->registerObject(itemName, burger_inventory[i].scene,
									   burger_inventory[i].icon);
			_inventory->addToBackpack(i);	// debug: this adds ALL objects to the player's backpack
		}

		_viewManager->addView(_interfaceView);
	}

	// Show intro

	if (getGameType() == GType_Burger) {
		_kernel->newRoom = TITLE_SCENE_BURGER;
	} else {
		_scene->getBackgroundSurface()->loadBackgroundRiddle("main menu");
		_ws->setBackgroundSurface(_scene->getBackgroundSurface());
	}

	_viewManager->addView(_scene);

	// Setup game wide hotkeys. Note that Orion Burger used F2/F3 for Save/Restore,
	// but for standardisation with most other games, F5/F7 are also mapped 

	_viewManager->systemHotkeys().add(Common::KEYCODE_ESCAPE, &escapeHotkeyHandler);
	_viewManager->systemHotkeys().add(Common::KEYCODE_F2, &saveGameHotkeyHandler);
	_viewManager->systemHotkeys().add(Common::KEYCODE_F3, &loadGameHotkeyHandler);
	_viewManager->systemHotkeys().add(Common::KEYCODE_F5, &saveGameHotkeyHandler);
	_viewManager->systemHotkeys().add(Common::KEYCODE_F7, &loadGameHotkeyHandler);
	_viewManager->systemHotkeys().add(Common::KEYCODE_F9, &gameMenuHotkeyHandler);

	// Start playing Orion Burger intro music
	//_midi->playMusic("999intro", 255, false, -1, -1);

	// TODO: start playing intro animations

	// TODO: Master Lu

	// Test for mouse
	_mouse->init("cursor", NULL);
	_mouse->setCursorNum(0);
	_mouse->cursorOn();

	_ws->assets()->loadAsset("SHOW SCRIPT", NULL);
	_ws->assets()->loadAsset("STREAM SCRIPT", NULL);

#ifdef INTRO_TEST
	int curPart = 0;
	Machine *mach = NULL;
#endif

	_ws->setSurfaceView(_scene);

	uint32 nextFrame = g_system->getMillis();
	while (!_events->quitFlag) {

		// This should probably be moved to either Scene or Kernel
		if (_kernel->currentRoom != _kernel->newRoom) {

			_ws->clear();

			_kernel->currentSection = _kernel->newRoom / 100;
			_kernel->currentRoom = _kernel->newRoom;

			_scene->loadScene(_kernel->currentRoom);

			_ws->setBackgroundSurface(_scene->getBackgroundSurface());
			_ws->setInverseColorTable(_scene->getInverseColorTable());

			_kernel->loadSectionScriptFunctions();
			_kernel->loadRoomScriptFunctions();

			_kernel->roomInit();
			
#ifdef INTRO_TEST
			if (_kernel->currentRoom == 951) {
				curPart = 0;
				mach = _ws->streamSeries("PLANET X HILLTOP A", 1, 0x1000, 0);
			}
#endif

		}

		eventHandler();

		// Call the updateState method of all views
		_viewManager->updateState();

		// Handle frame updates
		if (g_system->getMillis() >= nextFrame) {
#ifdef INTRO_TEST
			// Orion Burger intro test (scene 951)
			// This is ugly and bad, machine is not deleted so there's a huge memory
			// leak too. But hey, we can see some of the intro!
			if (mach && mach->getState() == -1) {
				if (curPart == 0)
					mach = _ws->streamSeries("Planet X Low Ground Shot", 1, 0x1000, 0);
				else if (curPart == 1)
					mach = _ws->streamSeries("Planet X Hilltop B", 1, 0x1000, 0);
				else if (curPart == 2)
					mach = _ws->streamSeries("Space Station Panorama A", 1, 0x1000, 0);
				else if (curPart == 3)
					mach = _ws->streamSeries("Cargo Transfer Area A", 1, 0x1000, 0);
				else if (curPart == 4)
					mach = _ws->streamSeries("VP's Office A", 1, 0x1000, 0);
				else if (curPart == 5)
					mach = _ws->streamSeries("Hologram", 1, 0x1000, 0);
				else if (curPart == 6)
					mach = _ws->streamSeries("VP's Office B", 1, 0x1000, 0);
				else if (curPart == 7)
					mach = _ws->streamSeries("Cargo Transfer Area B", 1, 0x1000, 0);
				else if (curPart == 8)
					mach = _ws->streamSeries("Cargo Transfer Controls", 1, 0x1000, 0);
				else if (curPart == 9)
					mach = _ws->streamSeries("Space Station Panorama B", 1, 0x1000, 0);
				// This last scene is from the rolling demo
				//else if (curPart == 10)
				//	mach = _ws->streamSeries("Call To Action", 1, 0x1000, 0);
				curPart++;
			}
#endif
			_ws->update();
			_viewManager->refreshAll();
			nextFrame = g_system->getMillis();// + GAME_FRAME_DELAY;
			
			// TEST STUFF ONLY
			if (_player->commandReady) {
				_kernel->roomParser();
				_player->commandReady = false;
			}
			
		}

		g_system->delayMillis(10);
	}

	return 0;
}

void M4Engine::dumpFile(const char* filename, bool uncompress) {
	Common::SeekableReadStream *fileS = res()->get(filename);
	byte buffer[256];
	FILE *destFile = fopen(filename, "wb");
	int bytesRead = 0;
	printf("Dumping %s, size: %i\n", filename, fileS->size());

	if (!uncompress) {
		while(!fileS->eos()) {
			bytesRead = fileS->read(buffer, 256);
			fwrite(buffer, bytesRead, 1, destFile);
		}
	} else {
		MadsPack packData(fileS);
		Common::MemoryReadStream *sourceUnc;
		for (int i = 0; i < packData.getCount(); i++) {
			sourceUnc = packData.getItemStream(i);
			printf("Dumping compressed chunk %i of %i, size is %i\n", i + 1, packData.getCount(), sourceUnc->size());
			while(!sourceUnc->eos()) {
				bytesRead = sourceUnc->read(buffer, 256);
				fwrite(buffer, bytesRead, 1, destFile);
			}
			delete sourceUnc;
		}
	}

	fclose(destFile);
	res()->toss(filename);
	res()->purge();
}

} // End of namespace M4
