/*
	CConfig.cc
	The BeMAME Team
	Created: 08/13/99 08:19:04
*/

#include "CConfig.h"
#include "driver.h"

#include <FindDirectory.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <Path.h>
#include <fs_attr.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define kConfig_Filename	"BeMame_data"

extern int ignorecfg;

/* from video.c */
extern int		gWindowed;
extern int		scanlines, use_tweaked, video_sync, wait_vsync, use_triplebuf;
extern int		stretch;
extern int		vgafreq, always_synced, color_depth, skiplines, skipcolumns;
extern float	osd_gamma_correction;
extern int		frameskip,autoframeskip;
extern int		gfx_width, gfx_height;

/* from input.c */
extern int		use_mouse, joystick;

/* from sound.c */
extern int		NoGaps, NoSound;
extern int		NoAdjust;

/* from cheat.c */
extern char		*cheatfile;
static char		cheatBuf[B_FILE_NAME_LENGTH];

/* from datafile.c */
extern char		*history_filename, *mameinfo_filename;
static char		historyBuf[B_FILE_NAME_LENGTH], infoBuf[B_FILE_NAME_LENGTH];

/* from fileio.c */
extern char		*hidir, *cfgdir, *stadir, *memcarddir;
static char		hidirBuf[B_PATH_NAME_LENGTH], cfgdirBuf[B_PATH_NAME_LENGTH], stadirBuf[B_PATH_NAME_LENGTH], memcarddirBuf[B_PATH_NAME_LENGTH];
extern char		*artworkdir, *screenshotdir, *alternate_name;
static char		artworkdirBuf[B_PATH_NAME_LENGTH], sshotdirBuf[B_PATH_NAME_LENGTH];

CConfig		gConfig;

extern "C" {
	void parse_cmdline(int, char **, int);
	void get_rom_sample_path(int, char **, int);
	void decompose_rom_sample_path(char *, char *);
}

static const uint32		Cmd_ConfigMessage =		'cfgM';

CConfig::CConfig()
{
	mArgc = 0;
	mArgv = NULL;
	mConfigMsg.what = Cmd_ConfigMessage;
}

/*
 * gets some boolean config value.
 * 0 = false, >0 = true, <0 = auto
 * the shortcut can only be used on the commandline
 */

int32
CConfig::GetBool(
	const char	*inGameName,
	const char	*inOption,
	const char	*inShortcut,
	int32		inDefault,
	bool		inParseCmdLine)
{
	int32		res;

	res = inDefault;

	if (!ignorecfg) {
		char	resultBuf[16];

		if (inDefault == 0) 
			strcpy(resultBuf, "no");
		else if (inDefault > 0)
			strcpy(resultBuf, "yes");
		else
			strcpy(resultBuf, "auto");
		
		if (!ReadString(inGameName, inOption, 16, resultBuf) && ::strcmp(inGameName, "global"))
			ReadString("global", inOption, 16, resultBuf);

		/* also take numerical values instead of "yes", "no" and "auto" */
		if      (strcasecmp(resultBuf, "no"  ) == 0) res = 0;
		else if (strcasecmp(resultBuf, "yes" ) == 0) res = 1;
		else if (strcasecmp(resultBuf, "auto") == 0) res = -1;
		else    res = atoi (resultBuf);
	}
	
	if (inParseCmdLine) {
		int32		i;
		
		for (i=1; i<mArgc; i++) {
			if (mArgv[i][0] != '-') continue;
			/* look for "-option" */
			if (strcasecmp(&mArgv[i][1], inOption) == 0)
				res = 1;
			/* look for "-shortcut" */
			if (inShortcut && (strcasecmp(&mArgv[i][1], inShortcut) == 0))
				res = 1;
			/* look for "-nooption" */
			if (strncasecmp(&mArgv[i][1], "no", 2) == 0)
			{
				if (strcasecmp(&mArgv[i][3], inOption) == 0)
					res = 0;
				if (inShortcut && (strcasecmp(&mArgv[i][3], inShortcut) == 0))
					res = 0;
			}
			/* look for "-autooption" */
			if (strncasecmp(&mArgv[i][1], "auto", 4) == 0)
			{
				if (strcasecmp(&mArgv[i][5], inOption) == 0)
					res = -1;
				if (inShortcut && (strcasecmp(&mArgv[i][5], inShortcut) == 0))
					res = -1;
			}
		}
	}

	return res;
}

int32
CConfig::GetInt(
	const char	*inGameName,
	const char	*inOption,
	const char	*inShortcut,
	int32		inDefault,
	bool		inParseCmdLine)
{
	int32	res;

	res = inDefault;

	if (!ignorecfg) {
		if (!ReadInt32(inGameName, inOption, &res) && ::strcmp(inGameName, "global"))
			ReadInt32("global", inOption, &res);
	}

	if (inParseCmdLine) {
		int32	i;
		
		/* get it from the commandline */
		for (i=1; i<mArgc; i++) {
			if (mArgv[i][0] != '-')
				continue;
				
			if ((strcasecmp(&mArgv[i][1], inOption) == 0) ||
				(inShortcut && (strcasecmp(&mArgv[i][1], inShortcut) == 0))) {
				i++;
				if (i < mArgc)
					res = atoi(mArgv[i]);
			}
		}
	}
	
	return res;
}

float
CConfig::GetFloat(
	const char	*inGameName,
	const char	*inOption,
	const char	*inShortcut,
	float		inDefault,
	bool		inParseCmdLine)
{
	float	res;

	res = inDefault;

	if (!ignorecfg) {
		if (!ReadFloat(inGameName, inOption, &res) && ::strcmp(inGameName, "global"))
			ReadFloat("global", inOption, &res);
	}

	if (inParseCmdLine) {
		int32	i;
		
		/* get it from the commandline */
		for (i=1; i<mArgc; i++) {
			if (mArgv[i][0] != '-')
				continue;
				
			if ((strcasecmp(&mArgv[i][1], inOption) == 0) ||
				(inShortcut && (strcasecmp(&mArgv[i][1], inShortcut) == 0))) {
				i++;
				if (i < mArgc)
					res = atof(mArgv[i]);
			}
		}
	}
	
	return res;
}

char *
CConfig::GetString(
	const char	*inGameName,
	const char	*inOption,
	const char	*inShortcut,
	char		*inDefault,
	char		*outString,
	int32		inStringLen,
	bool		inParseCmdLine)
{
	::strncpy(outString, inDefault, inStringLen);
	
	if (!ignorecfg) {
		if (!ReadString(inGameName, inOption, inStringLen, outString) && ::strcmp(inGameName, "global"))
			ReadString("global", inOption, inStringLen, outString);
	}

	if (inParseCmdLine) {
		int32	i;
		
		/* get it from the commandline */
		for (i=1; i<mArgc; i++) {
			if (mArgv[i][0] != '-')
				continue;
				
			if ((strcasecmp(&mArgv[i][1], inOption) == 0) ||
				(inShortcut && (strcasecmp(&mArgv[i][1], inShortcut) == 0))) {
				i++;
				if (i < mArgc)
					::strncpy(outString, mArgv[i], inStringLen);
			}
		}
	}
	
	return outString;
}

void
CConfig::SetBool(
	const char			*inGameName,
	const char			*inOption,
	int32				inVal)
{
	char	resultBuf[16];
	
	if (inVal == 0) 
		strcpy(resultBuf, "no");
	else if (inVal > 0)
		strcpy(resultBuf, "yes");
	else
		strcpy(resultBuf, "auto");
	
	WriteString(inGameName, inOption, resultBuf);
}

void
CConfig::SetInt(
	const char			*inGameName,
	const char			*inOption,
	int32				inVal)
{
	WriteInt32(inGameName, inOption, inVal);
}

void
CConfig::SetFloat(
	const char			*inGameName,
	const char			*inOption,
	float				inVal)
{
	WriteFloat(inGameName, inOption, inVal);
}

void
CConfig::SetString(
	const char			*inGameName,
	const char			*inOption,
	char				*inString)
{
	WriteString(inGameName, inOption, inString);
}

// I don't store the entry because it could be deleted, moved, whatever
status_t
CConfig::FindEntry(
	BEntry *		outEntry)
{
	BPath		prefPath;
	status_t	status;

	status = B_ERROR;
	
	if ((status = find_directory(B_USER_SETTINGS_DIRECTORY, &prefPath, true)) == B_OK) {
		BDirectory	settingsDir(prefPath.Path());
		
		if (!settingsDir.Contains(kConfig_Filename, B_FILE_NODE)) {	// prefs file does not exist
			BFile	configFile;										// create it
			
			status = settingsDir.CreateFile(kConfig_Filename, &configFile);
		}
		
		if (status == B_OK)
			status = settingsDir.FindEntry(kConfig_Filename, outEntry);
	}
	
	return status;
}

bool
CConfig::ReadInt32(
	const char			*inGameName,
	const char			*inOptionName,
	int32				*ioInt32)
{
	int32		tempInt32;
	bool		wasRead = false;
	
	ConfirmMessage(inGameName);

	if (mConfigMsg.FindInt32(inOptionName, &tempInt32) == B_OK) {
		*ioInt32 = tempInt32;
		wasRead = true;
	}
	else
		mConfigMsg.AddInt32(inOptionName, *ioInt32);

	return wasRead;
}

void
CConfig::WriteInt32(
	const char			*inGameName,
	const char			*inOptionName,
	int32				inInt32)
{
	ConfirmMessage(inGameName);

	mConfigMsg.RemoveData(inOptionName);
	mConfigMsg.AddInt32(inOptionName, inInt32);
}

bool
CConfig::ReadFloat(
	const char			*inGameName,
	const char			*inOptionName,
	float				*ioFloat)
{
	float		tempFloat;
	bool		wasRead = false;
	
	ConfirmMessage(inGameName);

	if (mConfigMsg.FindFloat(inOptionName, &tempFloat) == B_OK) {
		*ioFloat = tempFloat;
		wasRead = true;
	}
	else
		mConfigMsg.AddFloat(inOptionName, *ioFloat);

	return wasRead;
}

void
CConfig::WriteFloat(
	const char			*inGameName,
	const char			*inOptionName,
	float				inFloat)
{
	ConfirmMessage(inGameName);

	mConfigMsg.RemoveData(inOptionName);
	mConfigMsg.AddFloat(inOptionName, inFloat);
}

bool
CConfig::ReadString(
	const char			*inGameName,
	const char			*inOptionName,
	int32				inStringLen,
	char				*ioString)
{
	const char	*tempStr;
	bool		wasRead = false;
	
	ConfirmMessage(inGameName);

	if (mConfigMsg.FindString(inOptionName, &tempStr) == B_OK) {
		::strncpy(ioString, tempStr, inStringLen);
		wasRead = true;
	}
	else
		mConfigMsg.AddString(inOptionName, ioString);
		
	return wasRead;
}

void
CConfig::WriteString(
	const char			*inGameName,
	const char			*inOptionName,
	char				*inString)
{
	ConfirmMessage(inGameName);

	mConfigMsg.RemoveData(inOptionName);
	mConfigMsg.AddString(inOptionName, inString);
}

ssize_t
CConfig::ReadPref(
	const char *		inAttrName,
	type_code			inType,
	off_t				inOffset,
	void				*outBuf,
	size_t				inLen)
{
	BEntry		prefsFile;
	ssize_t		readSize;
	
	if ((readSize = FindEntry(&prefsFile)) == B_OK) {
		BNode		prefsNode(&prefsFile);
		
		readSize = prefsNode.ReadAttr(inAttrName, inType, inOffset, outBuf, inLen);
	}
	
	return readSize;
}

ssize_t
CConfig::WritePref(
	const char *		inAttrName,
	type_code			inType,
	off_t				inOffset,
	const void			*inBuf,
	size_t				inLen)
{
	BEntry		prefsFile;
	ssize_t		writeSize;
	
	if ((writeSize = FindEntry(&prefsFile)) == B_OK) {
		BNode		prefsNode(&prefsFile);
		
		writeSize = prefsNode.WriteAttr(inAttrName, inType, inOffset, inBuf, inLen);
	}
	
	return writeSize;
}

void
CConfig::ConfirmMessage(
	const char		*inGameName)
{
	const char	*gamename;
	
	if (mConfigMsg.FindString("gamename", &gamename) == B_OK) {
		if (::strcmp(inGameName, gamename)) {		// not the same game, 
			ResetMessage(inGameName);
		}
	}
	else {
		// Message not valid, make one
		ResetMessage(inGameName);
	}
}

void
CConfig::ResetMessage(
	const char		*inGameName)
{
	const char	*gamename;

	// write current message
	if (mConfigMsg.FindString("gamename", &gamename) == B_OK) {
		WriteMessage();
	}
	
	// read new message
	if (ReadMessage(inGameName) != B_OK) {
		// Message not valid, make one
		mConfigMsg.MakeEmpty();
		mConfigMsg.AddString("gamename", inGameName);
	}
}

void
CConfig::WriteMessage()
{
	const char	*gamename;
	
	if (mConfigMsg.FindString("gamename", &gamename) == B_OK) {
		BEntry		prefsFile;
		
		if (FindEntry(&prefsFile) == B_OK) {
			BNode		prefsNode(&prefsFile);
			ssize_t		msgSize;
			char		*msgBuf;
			
			msgSize = mConfigMsg.FlattenedSize();
			msgBuf = new char [msgSize];
			mConfigMsg.Flatten(msgBuf, msgSize);
			prefsNode.WriteAttr(gamename, B_RAW_TYPE, 0, msgBuf, msgSize);
			delete [] msgBuf;
		}
	}
}

status_t
CConfig::ReadMessage(
	const char		*inGameName)
{
	BEntry		prefsFile;
	status_t	theStatus;
	
	if (FindEntry(&prefsFile) == B_OK) {
		BNode		prefsNode(&prefsFile);
		attr_info	info;
		
		if ((theStatus = prefsNode.GetAttrInfo(inGameName, &info)) == B_OK) {
			if (info.size > 0) {
				BMessage	tempMsg;
				char		*msgBuf;
				
				msgBuf = new char [info.size];
				prefsNode.ReadAttr(inGameName, B_RAW_TYPE, 0, msgBuf, info.size);
				if ((theStatus = tempMsg.Unflatten(msgBuf)) == B_OK)
					mConfigMsg = tempMsg;
				delete [] msgBuf;
			}
			else
				theStatus = B_ERROR;
		}
	}
	
	return theStatus;
}

void
CConfig::ParseCmdLine(
	int32			inArgC,
	char			**inArgV,
	int32			inGameIdx)
{
	static float	f_beam, f_flicker;
	const char		*gamename = drivers[inGameIdx]->name;
	char			tmpres[10], resStr[32], tempStr[32];
	int				i;

	mArgc = inArgC;
	mArgv = inArgV;

	/* read graphic configuration */
	gWindowed  				= GetBool  (gamename,	"window",		NULL,	0,		true);
	scanlines  				= GetBool  (gamename,	"scanlines",	NULL,	0,		true);
	stretch					= GetBool  (gamename,	"stretch",		NULL,	0,		true);
	options.use_artwork		= GetBool  (gamename,	"artwork",		NULL,	1,		true);
	options.use_samples		= GetBool  (gamename,	"samples",		NULL,	1,		true);
	video_sync				= GetBool  (gamename,	"vsync",		NULL,	0,		true);
	wait_vsync				= GetBool  (gamename,	"waitvsync",	NULL,	0,		true);
	use_triplebuf			= GetBool  (gamename,	"triplebuffer",	NULL,	0,		true);
	use_tweaked				= GetBool  (gamename,	"tweak",		NULL,	0,		true);
	options.antialias		= GetBool  (gamename,	"antialias",	NULL,	1,		true);
	options.translucency	= GetBool  (gamename,	"translucency",	NULL,	1,		true);
	vgafreq					= GetBool  (gamename,	"vgafreq",		NULL,	-1,		true);
	always_synced			= GetBool  (gamename,	"alwayssynced",	NULL,	0,		true);
	color_depth				= GetBool  (gamename,	"depth",		NULL,	16,		true);
	skiplines				= GetBool  (gamename,	"skiplines",	NULL,	0,		true);
	skipcolumns				= GetBool  (gamename,	"skipcolumns",	NULL,	0,		true);
	f_beam					= GetFloat (gamename,	"beam",			NULL,	1.0,	true);
	f_flicker				= GetFloat (gamename,	"flicker",		NULL,	0.0,	true);
	osd_gamma_correction	= GetFloat (gamename,	"gamma",		NULL,	1.2,	true);
	
	GetString(gamename, "frameskip", "fs", "auto", tempStr, 32, true);
	if (!strcasecmp(tempStr, "auto")) {
		frameskip = 0;
		autoframeskip = 1;
	}
	else {
		frameskip = atoi(tempStr);
		autoframeskip = 0;
	}

	options.norotate		= GetBool  (gamename,	"norotate",		NULL,	0,		true);
	options.ror				= GetBool  (gamename,	"ror",			NULL,	0,		true);
	options.rol				= GetBool  (gamename,	"rol",			NULL,	0,		true);
	options.flipx			= GetBool  (gamename,	"flipx",		NULL,	0,		true);
	options.flipy			= GetBool  (gamename,	"flipy",		NULL,	0,		true);

	/* read sound configuration */
	NoSound					= GetBool  (gamename,	"nosound",		"ns",	0,		true);
	NoGaps					= GetBool  (gamename,	"nogaps",		"ng",	0,		true);
	NoAdjust				= GetBool  (gamename,	"noadjust",		"na",	-1,		true);
	options.samplerate		= GetInt   (gamename,	"samplerate",	"sr",	22050,	true);
	options.samplebits		= GetInt   (gamename,	"samplebits",	"sb",	8,		true);

	/*usestereo				= GetBool  (gamename,	"stereo",		NULL,	1,		true);*/

	/* read input configuration */
	use_mouse				= GetBool  (gamename,	"mouse",		NULL,	0,		true);
	/* Removed joystick. Was unused */

	/* misc configuration */
	options.cheat			= GetBool  (gamename,	"cheat",		NULL,	0,		true);
	options.mame_debug		= GetBool  (gamename,	"debug",		NULL,	0,		true);
	cheatfile				= GetString(gamename,	"cheatfile",	"cf",	"CHEAT.DAT",	cheatBuf,		B_FILE_NAME_LENGTH,	true);
	history_filename		= GetString(gamename,	"historyfile",	NULL,	"HISTORY.DAT",	historyBuf,		B_FILE_NAME_LENGTH,	true);
	mameinfo_filename		= GetString(gamename,	"mameinfofile",	NULL,	"MAMEINFO.DAT",	infoBuf,		B_FILE_NAME_LENGTH,	true);

	/* get resolution */
							  GetString(gamename,	"resolution",	NULL,	"auto",			resStr,		32,					true);

	/* set default subdirectories */
	hidir					= GetString(gamename,	"hidir",		NULL,	"hi",			hidirBuf,		B_PATH_NAME_LENGTH,	true);
	cfgdir					= GetString(gamename,	"cfgdir",		NULL,	"cfg",			cfgdirBuf,		B_PATH_NAME_LENGTH,	true);
	screenshotdir			= GetString(gamename,	"snapdir",		NULL,	"snap",			sshotdirBuf,	B_PATH_NAME_LENGTH,	true);
	memcarddir				= GetString(gamename,	"memcarddir",	NULL,	"memcard",		memcarddirBuf,	B_PATH_NAME_LENGTH,	true);
	stadir					= GetString(gamename,	"stadir",		NULL,	"sta",			stadirBuf,		B_PATH_NAME_LENGTH,	true);
	artworkdir				= GetString(gamename,	"artworkdir",	NULL,	"artwork",		artworkdirBuf,	B_PATH_NAME_LENGTH,	true);

	/* this is handled externally cause the audit stuff needs it, too */
	GetROMSamplePath (mArgc, mArgv, inGameIdx);

	/* process some parameters */
	options.beam = (int)(f_beam * 0x00010000);
	if (options.beam < 0x00010000)
		options.beam = 0x00010000;
	if (options.beam > 0x00100000)
		options.beam = 0x00100000;

	options.flicker = (int)(f_flicker * 2.55);
	if (options.flicker < 0)
		options.flicker = 0;
	if (options.flicker > 255)
		options.flicker = 255;


	/* any option that starts with a digit is taken as a resolution option */
	/* this is to handle the old "-wxh" commandline option. */
	for (i = 1; i < mArgc; i++)
	{
		if (mArgv[i][0] == '-' && isdigit(mArgv[i][1]) &&
	/* additional kludge to handle negative arguments to -skiplines and -skipcolumns */
	/* and to -volume */
	/* and to -centerx (req. for 15.75KHz Modes)*/
	/* and to -centery (req. for 15.75KHz Modes)*/
			(i == 1 || (strcasecmp(mArgv[i-1],"-skiplines") &&
						strcasecmp(mArgv[i-1],"-skipcolumns") &&
						strcasecmp(mArgv[i-1],"-volume") &&
						strcasecmp(mArgv[i-1],"-centerx") &&
						strcasecmp(mArgv[i-1],"-centery"))))
			strncpy(resStr, &mArgv[i][1], 32);
	}

	/* break up resolution into gfx_width and gfx_height */
	gfx_height = gfx_width = 0;
	if (strcasecmp (resStr, "auto") != 0)
	{
		char *tmp;
		strncpy (tmpres, resStr, 10);
		tmp = strtok (tmpres, "xX");
		gfx_width = atoi (tmp);
		tmp = strtok (0, "xX");
		if (tmp)
			gfx_height = atoi (tmp);
	}
}

void
CConfig::GetROMSamplePath(
	int32			inArgC,
	char			**inArgV,
	int32			inGameIdx)
{
	const char		*gamename = drivers[inGameIdx]->name;
	char			rompath[128], samplepath[128];
	int32			i;
	
	mArgc = inArgC;
	mArgv = inArgV;
	alternate_name = 0;

	GetString(gamename, "rompath", NULL, ".;roms", rompath, 128, true);
	GetString(gamename, "samplepath", NULL, ".;samples", samplepath, 128, true);

	/* handle '-romdir' hack. We should get rid of this BW */
	alternate_name = 0;
	for (i=1; i<mArgc; i++) {
		if (strcasecmp (mArgv[i], "-romdir") == 0) {
			i++;
			if (i < mArgc)
				alternate_name = mArgv[i];
		}
	}

	/* decompose paths into components (handled by fileio.c) */
	decompose_rom_sample_path(rompath, samplepath);
}

void get_rom_sample_path(
	int		argc,
	char	**argv,
	int		game_index)
{
	gConfig.GetROMSamplePath(argc, argv, game_index);
}

void parse_cmdline(
	int			argc,
	char		**argv,
	int			game_index)
{
	gConfig.ParseCmdLine(argc, argv, game_index);
}
