/* 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$
 * $Id$
 *
 */

#include "common/system.h"
#include "common/file.h"

#include "engines/advancedDetector.h"	// for ADGF_DEMO

#include "sci/sci.h"
#include "sci/resource.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/kernel_types.h"
#include "sci/engine/message.h"
#include "sci/gui/gui.h"
#ifdef INCLUDE_OLDGFX
#include "sci/gfx/gfx_state_internal.h"	// required for GfxPort, GfxVisual
#include "sci/gfx/menubar.h"
#endif
#include "sci/sfx/music.h"

namespace Sci {

struct OldNewIdTableEntry {
	const char *oldId;
	const char *newId;
	bool demo;
	const char *demoCheckFile;	// if not empty and it doesn't exist, the demo flag is set
};

static const OldNewIdTableEntry s_oldNewTable[] = {
	{ "arthur",		"camelot",			false,	"resource.002" },
	{ "demo",		"christmas1988",	false,	"" },
	{ "RH Budget",	"cnick-longbow",	false,	"" },
	// iceman is the same
	{ "icedemo",	"iceman",			true,	"" },
	// longbow is the same
	{ "eco",		"ecoquest",			false,	"resource.000" },
	{ "eco2",		"ecoquest2",		true,	"" },		// EcoQuest 2 demo
	{ "rain",		"ecoquest2",		false,	"" },		// EcoQuest 2 full
	{ "fp",			"freddypharkas",	false,	"" },
	{ "emc",		"funseeker",		false,	"" },
	{ "gk",			"gk1",				false,	"" },
	{ "hoyledemo",	"hoyle1",			true,	"" },
	{ "cardgames",	"hoyle1",			false,	"" },
	{ "solitare",	"hoyle2",			false,	"" },
	// hoyle3 is the same
	// hoyle4 is the same
	{ "demo000",	"kq1sci",			true,	"" },
	{ "kq1",		"kq1sci",			false,	"" },
	{ "kq4",		"kq4sci",			false,	"" },
	{ "cb1",		"laurabow",			false,	"" },
	{ "lb2",		"laurabow2",		false,	"resource.aud" },
	{ "rh",			"longbow",			true,	"" },
	{ "ll1",		"lsl1sci",			true,	"" },
	// lsl2 is the same
	{ "lsl3",		"lsl3",				false,	"resource.003" },
	{ "ll5",		"lsl5",				true,	"" },
	// lsl5 is the same
	// lsl6 is the same
	{ "mg",			"mothergoose",		false,	"" },
	{ "twisty",		"pepper",			false,	"" },
	{ "pq1",		"pq1sci",			false,	"" },
	{ "pq",			"pq2",				false,	"" },
	{ "tales",		"fairytales",		false,	"resource.002" },
	{ "trial",		"qfg2",				false,	"" },
	{ "hq2demo",	"qfg2",				true,	"" },
	{ "thegame",	"slater",			false,	"" },
	{ "sq1demo",	"sq1sci",			true,	"" },
	{ "sq1",		"sq1sci",			false,	"" },
	// sq5 is the same

	{ "", "", false, "" }
};

const char *convertSierraGameId(const char *gameId, uint32 *gameFlags) {
	// Convert the id to lower case, so that we match all upper/lower case variants.
	Common::String sierraId = gameId;
	sierraId.toLowercase();

	// TODO: SCI32 IDs

	for (const OldNewIdTableEntry *cur = s_oldNewTable; cur->oldId[0]; ++cur) {
		if (sierraId == cur->oldId) {
			if (cur->demo)
				*gameFlags |= ADGF_DEMO;
			if (cur->demoCheckFile[0])
				if (!Common::File::exists(cur->demoCheckFile))
					*gameFlags |= ADGF_DEMO;
			return cur->newId;
		}
	}

	if (sierraId == "card") {
		// This could either be christmas1990 or christmas1992
		// christmas1990 has a "resource.001" file, whereas 
		// christmas1992 has a "resource.000" file
		return (Common::File::exists("resource.001")) ? "christmas1990" : "christmas1992";
	}
	if (sierraId == "brain") {
		// This could either be The Castle of Dr. Brain, or The Island of Dr. Brain
		// castlebrain has resource.001, whereas islandbrain doesn't
		return (Common::File::exists("resource.001")) ? "castlebrain" : "islandbrain";
	}
	if (sierraId == "lsl1") {
		// This could either be LSL1 full version, or LSL casino
		// LSL1 full has resource.000, whereas LSL casino doesn't
		return (Common::File::exists("resource.000")) ? "lsl1sci" : "cnick-lsl";
	}
	// TODO: cnick-kq and cnick-longbow (their resources can't be read)
	// TODO: cnick-sq (same files as Ms. Astro Chicken)
	if (sierraId == "pq3") {
		// The pq3 demo comes with resource.000 and resource.001
		// The full version was released with several resource.* files,
		// or one big resource.000 file
		if (Common::File::exists("resource.000") && Common::File::exists("resource.001") &&
			!Common::File::exists("resource.002"))
			*gameFlags |= ADGF_DEMO;
		return "pq3";
	}
	if (sierraId == "glory" || sierraId == "hq") {
		// This could either be qfg1 or qfg3 or qfg4
		// qfg3 has resource.aud, qfg4 has resource.sfx
		if (Common::File::exists("resource.aud"))
			return "qfg3";
		else if (Common::File::exists("resource.sfx"))
			return "qfg4";
		else
			return "qfg1";
	}
	if (sierraId == "sq3") {
		// This could either be SQ3 full version, or Astro Chicken
		// SQ3 full has resource.002, whereas Astro Chicken doesn't
		return (Common::File::exists("resource.002")) ? "sq3" : "astrochicken";
	}
	if (sierraId == "sq4") {
		// This could either be SQ4 full version, or Ms. Astro Chicken
		// SQ4 full (floppy and CD) has resource.000, whereas Ms. Astro Chicken doesn't
		return (Common::File::exists("resource.000")) ? "sq4" : "msastrochicken";
	}

	// FIXME: Evil use of strdup here (we are leaking that memory, too)
	return strdup(sierraId.c_str());
}

#ifdef INCLUDE_OLDGFX
int _reset_graphics_input(EngineState *s) {
	Resource *resource;
	int font_nr;
	gfx_color_t transparent = { PaletteEntry(), 0, -1, -1, 0 };
	debug(2, "Initializing graphics");

	if (s->resMan->getViewType() == kViewEga) {
		for (int i = 0; i < 16; i++) {
			gfxop_set_color(s->gfx_state, &(s->ega_colors[i]), gfx_sci0_image_colors[sci0_palette][i].r,
					gfx_sci0_image_colors[sci0_palette][i].g, gfx_sci0_image_colors[sci0_palette][i].b, 0, -1, -1);
			s->gfx_state->driver->getMode()->palette->makeSystemColor(i, s->ega_colors[i].visual);
		}
	} else {
		// Allocate SCI1 system colors
		gfx_color_t black = { PaletteEntry(0, 0, 0), 0, 0, 0, GFX_MASK_VISUAL };
		s->gfx_state->driver->getMode()->palette->makeSystemColor(0, black.visual);

		// Check for Amiga palette file.
		Common::File file;
		if (file.open("spal")) {
			s->gfx_state->gfxResMan->setStaticPalette(gfxr_read_pal1_amiga(file));
			file.close();
		} else {
			resource = s->resMan->findResource(ResourceId(kResourceTypePalette, 999), 1);
			if (resource) {
				if (s->resMan->getViewType() != kViewVga11)
					s->gfx_state->gfxResMan->setStaticPalette(gfxr_read_pal1(999, resource->data, resource->size));
				else
					s->gfx_state->gfxResMan->setStaticPalette(gfxr_read_pal11(999, resource->data, resource->size));
				s->resMan->unlockResource(resource);
			} else {
				debug(2, "Couldn't find the default palette!");
			}
		}
	}

	gfxop_fill_box(s->gfx_state, gfx_rect(0, 0, 320, 200), s->ega_colors[0]); // Fill screen black
	gfxop_update(s->gfx_state);

	s->pic_is_new = 0;
	s->pic_visible_map = GFX_MASK_NONE; // Other values only make sense for debugging
	s->dyn_views = NULL; // no DynViews
	s->drop_views = NULL; // And, consequently, no list for dropped views

	font_nr = -1;
	do {
		resource = s->resMan->testResource(ResourceId(kResourceTypeFont, ++font_nr));
	} while ((!resource) && (font_nr < 65536));

	if (!resource) {
		debug(2, "No text font was found.");
		return 1;
	}

	s->visual = new GfxVisual(s->gfx_state, font_nr);

	s->wm_port = new GfxPort(s->visual, s->gfx_state->pic_port_bounds, s->ega_colors[0], transparent);

	s->iconbar_port = new GfxPort(s->visual, gfx_rect(0, 0, 320, 200), s->ega_colors[0], transparent);
	s->iconbar_port->_flags |= GFXW_FLAG_NO_IMPLICIT_SWITCH;

	if (s->resMan->isVGA()) {
		// This bit sets the foreground and background colors in VGA SCI games
		gfx_color_t fgcolor;
		gfx_color_t bgcolor;
		memset(&fgcolor, 0, sizeof(gfx_color_t));
		memset(&bgcolor, 0, sizeof(gfx_color_t));

#if 0
		fgcolor.visual = s->gfx_state->resstate->static_palette[0];
		fgcolor.mask = GFX_MASK_VISUAL;
		bgcolor.visual = s->gfx_state->resstate->static_palette[255];
		bgcolor.mask = GFX_MASK_VISUAL;
#endif
		s->titlebar_port = new GfxPort(s->visual, gfx_rect(0, 0, 320, 10), fgcolor, bgcolor);
	} else {
		s->titlebar_port = new GfxPort(s->visual, gfx_rect(0, 0, 320, 10), s->ega_colors[0], s->ega_colors[15]);
	}
	s->titlebar_port->_color.mask |= GFX_MASK_PRIORITY;
	s->titlebar_port->_color.priority = 11;
	s->titlebar_port->_bgcolor.mask |= GFX_MASK_PRIORITY;
	s->titlebar_port->_bgcolor.priority = 11;
	s->titlebar_port->_flags |= GFXW_FLAG_NO_IMPLICIT_SWITCH;

	// but this is correct
	s->picture_port = new GfxPort(s->visual, s->gfx_state->pic_port_bounds, s->ega_colors[0], transparent);

	s->visual->add((GfxContainer *)s->visual, s->wm_port);
	s->visual->add((GfxContainer *)s->visual, s->titlebar_port);
	s->visual->add((GfxContainer *)s->visual, s->picture_port);
	s->visual->add((GfxContainer *)s->visual, s->iconbar_port);
	// Add ports to visual

	s->port = s->picture_port; // Currently using the picture port

#if 0
	s->titlebar_port->_bgcolor.mask |= GFX_MASK_PRIORITY;
	s->titlebar_port->_bgcolor.priority = 11; // Standard priority for the titlebar port
#endif

	s->priority_first = 42; // Priority zone 0 ends here

	if (s->usesOldGfxFunctions())
		s->priority_last = 200;
	else
		s->priority_last = 190;

	return 0;
}

int game_init_graphics(EngineState *s) {
	return _reset_graphics_input(s);
}

static void _free_graphics_input(EngineState *s) {
	debug(2, "Freeing graphics");

	delete s->visual;

	s->wm_port = s->titlebar_port = s->picture_port = NULL;
	s->visual = NULL;
	s->dyn_views = NULL;
	s->port = NULL;
}
#endif

#ifdef USE_OLD_MUSIC_FUNCTIONS
int game_init_sound(EngineState *s, int sound_flags, SciVersion soundVersion) {
	if (getSciVersion() > SCI_VERSION_0_LATE)
		sound_flags |= SFX_STATE_FLAG_MULTIPLAY;

	s->sfx_init_flags = sound_flags;
	s->_sound.sfx_init(s->resMan, sound_flags, soundVersion);

	return 0;
}
#endif

// Architectural stuff: Init/Unintialize engine
int script_init_engine(EngineState *s) {
	s->_msgState = new MessageState(s->_segMan);
	s->gc_countdown = GC_INTERVAL - 1;

	SegmentId script_000_segment = s->_segMan->getScriptSegment(0, SCRIPT_GET_LOCK);

	if (script_000_segment <= 0) {
		debug(2, "Failed to instantiate script.000");
		return 1;
	}

	s->script_000 = s->_segMan->getScript(script_000_segment);

	s->sys_strings = s->_segMan->allocateSysStrings(&s->sys_strings_segment);

	// Allocate static buffer for savegame and CWD directories
	SystemString *str = &s->sys_strings->_strings[SYS_STRING_SAVEDIR];
	str->_name = "savedir";
	str->_maxSize = MAX_SAVE_DIR_SIZE;
	str->_value = (char *)calloc(MAX_SAVE_DIR_SIZE, sizeof(char));

	s->r_acc = s->r_prev = NULL_REG;
	s->restAdjust = 0;

	s->_executionStack.clear();    // Start without any execution stack
	s->execution_stack_base = -1; // No vm is running yet

	s->restarting_flags = SCI_GAME_IS_NOT_RESTARTING;

	s->bp_list = NULL; // No breakpoints defined
	s->have_bp = 0;

	if (s->detectLofsType() == SCI_VERSION_1_MIDDLE)
		s->_segMan->setExportAreWide(true);
	else
		s->_segMan->setExportAreWide(false);

	debug(2, "Engine initialized");

#ifdef INCLUDE_OLDGFX
	s->pic_priority_table = NULL;
#endif

	return 0;
}

void script_free_breakpoints(EngineState *s) {
	Breakpoint *bp, *bp_next;

	// Free breakpoint list
	bp = s->bp_list;
	while (bp) {
		bp_next = bp->next;
		if (bp->type == BREAK_SELECTOR)
			free(bp->data.name);
		free(bp);
		bp = bp_next;
	}

	s->bp_list = NULL;
}

/*************************************************************/
/* Game instance stuff: Init/Unitialize state-dependant data */
/*************************************************************/

int game_init(EngineState *s) {
	// FIXME Use new VM instantiation code all over the place
	DataStack *stack;

	stack = s->_segMan->allocateStack(VM_STACK_SIZE, &s->stack_segment);
	s->stack_base = stack->_entries;
	s->stack_top = stack->_entries + stack->_capacity;

	if (!script_instantiate(s->resMan, s->_segMan, 0)) {
		warning("game_init(): Could not instantiate script 0");
		return 1;
	}

	s->parserIsValid = false; // Invalidate parser
	s->parser_event = NULL_REG; // Invalidate parser event

#ifdef INCLUDE_OLDGFX
	if (s->gfx_state && _reset_graphics_input(s))
		return 1;
#endif
	// Initialize menu TODO: Actually this should be another init()
	s->_gui->menuReset();

	s->successor = NULL; // No successor

	SystemString *str = &s->sys_strings->_strings[SYS_STRING_PARSER_BASE];
	str->_name = "parser-base";
	str->_maxSize = MAX_PARSER_BASE;
	str->_value = (char *)calloc(MAX_PARSER_BASE, sizeof(char));

	s->parser_base = make_reg(s->sys_strings_segment, SYS_STRING_PARSER_BASE);

	s->game_start_time = g_system->getMillis();
	s->last_wait_time = s->game_start_time;

	srand(g_system->getMillis()); // Initialize random number generator

//	script_dissect(0, s->_selectorNames);
	// The first entry in the export table of script 0 points to the game object
	s->_gameObj = s->_segMan->lookupScriptExport(0, 0);
	uint32 gameFlags = 0;	// unused
	s->_gameId = convertSierraGameId(s->_segMan->getObjectName(s->_gameObj), &gameFlags);

	debug(2, " \"%s\" at %04x:%04x", s->_gameId.c_str(), PRINT_REG(s->_gameObj));

#ifdef INCLUDE_OLDGFX
	s->_menubar = new Menubar(); // Create menu bar
#endif

#ifdef USE_OLD_MUSIC_FUNCTIONS
	if (s->sfx_init_flags & SFX_STATE_FLAG_NOSOUND)
		game_init_sound(s, 0, s->detectDoSoundType());
#endif

	// Load game language into printLang property of game object
	s->getLanguage();

	return 0;
}

int game_exit(EngineState *s) {
	s->_executionStack.clear();

	if (!s->successor) {
#ifdef USE_OLD_MUSIC_FUNCTIONS
		s->_sound.sfx_exit();
		// Reinit because some other code depends on having a valid state
		game_init_sound(s, SFX_STATE_FLAG_NOSOUND, s->detectDoSoundType());
#else
		s->_audio->stopAllAudio();
		s->_soundCmd->clearPlayList();
#endif
	}

	// Note: It's a bad idea to delete the segment manager here
	// when loading a game.
	// This function is called right after a game is loaded, and
	// the segment manager has already been initialized from the
	// save game. Deleting or resetting it here will result in
	// invalidating the loaded save state
	if (s->restarting_flags & SCI_GAME_IS_RESTARTING_NOW)
		s->_segMan->resetSegMan();

	// TODO Free parser segment here

	// TODO Free scripts here

#ifdef INCLUDE_OLDGFX
	delete s->_menubar;

	_free_graphics_input(s);
#endif

	// Close all opened file handles
	s->_fileHandles.clear();
	s->_fileHandles.resize(5);

	return 0;
}

} // End of namespace Sci
