/*
 ActiveGS, Copyright 2004-2016 Olivier Goguel, https://github.com/ogoguel/ActiveGS
 Based on Kegs, Copyright 2004 Kent Dickey, https://kegs.sourceforge.net
 This code is covered by the GNU GPL licence
*/

#include "CEMulatorCtrl.h"
#include "svnversion.h"

#include "../kegs/Src/adb.h"
#include "../kegs/Src/clock.h"
#include "../kegs/Src/moremem.h"
#include "../kegs/Src/paddles.h"
#include "../kegs/Src/sim65816.h"
#include "../kegs/Src/iwm.h"
#include "../kegs/Src/SaveState.h"
#include "../kegs/Src/raster.h"
#include "../kegs/Src/graphcounter.h"
#include "../kegs/Src/driver.h"
#include "../kegs/Src/video.h"
#include "../kegs/Src/async_event.h"
extern void joystick_shut();

#define CONFIG_VERSION 3

const int	joyint[] = { (int)JOYSTICK_TYPE_NATIVE_1, (int)JOYSTICK_TYPE_KEYPAD, (int)JOYSTICK_TYPE_MOUSE, /* JOYSTICK_TYPE_NATIVE_2,*/ JOYSTICK_TYPE_NONE, JOYSTICK_TYPE_ICADE, -1 } ;
const char*	joyalias[] = { "joystick1","keypad", "mouse", /*, "Joystick2",*/ "none", "iCade", NULL  };
const char*	joydesc[] = { "Joystick1","Keypad", "Mouse", /*, "Joystick2",*/ "None", "iCade", NULL  };
const int	colorint[] = { (int)COLORMODE_AUTO, (int)COLORMODE_BW, (int)COLORMODE_GREEN, (int)COLORMODE_AMBER,(int)COLORMODE_MONO, -1 };
const char* coloralias[] = { "auto", "bw", "green", "amber", "mono",NULL  };
const char* colordesc[] = { "Colors", "B&W", "Green", "Amber", "Mono", NULL  };
const char* videoalias[] = {  "crt" ,  "lcd" ,"default", NULL  };
const char* videodesc[] = {   "CRT" , "LCD",NULL,  NULL  };
const int videoint[] = {  (int)VIDEOFX_CRT,  (int)VIDEOFX_LCD, (int)VIDEOFX_LCD, -1 };
const char* speeddesc[] = { "Normal", "Slow", "Zip", "Unlimited",  NULL  };
const char* speedalias[] = { "normal", "slow", "zip", "unlimited",  NULL  };
const int speedint[] = { (int)SPEED_GS, (int)SPEED_1MHZ, (int)SPEED_ZIP, (int)SPEED_UNLIMITED, -1 };


#define DEFAULT_AUDIO_RATE 44100
#define DEFAULT_LOCKZOOM 0
#define DEFAULT_DEBUGMODE 0
#define DEFAULT_DISKSOUND 1
#define DEFAULT_VIDEO VIDEOFX_CRT

const namevaluedef defoptions[] = 
{
	{ 1,0, "configVersion"  , CONFIG_VERSION,NULL, NULL, NULL },
	{ 1,0, "guid"  , 0, NULL, NULL, NULL },
	{ 1,1, "haltOnBadRead" , 0, NULL, NULL, NULL },
	{ 1,1, "haltOnBadAcc"  , 0, NULL, NULL, NULL },
	{ 1,1, "haltOnHalts"  , 0, NULL, NULL, NULL },
	{ 1,1, "enableRewind"  ,0, NULL, NULL, NULL },
	{ 1,1, "colorMode"  , 0, NULL, colordesc,coloralias , colorint },
	{ 1,1, "videoFX", (int)DEFAULT_VIDEO, NULL, videodesc, videoalias ,  videoint },
	{ 1,1, "joystickMode", 0, NULL, joydesc, joyalias, joyint },
	{ 0,0, "browserName"  , 0, NULL, NULL, NULL },
	{ 0,0, "browserVer"  , 0, NULL, NULL, NULL },
	{ 2,0, "latestRunBuild"  , 0, NULL, NULL, NULL },
	{ 2,0, "latestUpdateCheck"  , 0, NULL, NULL, NULL },
	{ 1,0, "doNotShowUpdateAlert"  , 0, NULL, NULL, NULL },
	{ 0,0, "doNotShowDiskStatus"  , 0, NULL, NULL, NULL },	
	{ 0,0, "PNGBorder"  , 16, NULL, NULL, NULL },	
	{ 0,0, "simulateSpace"  , 0X31, NULL, NULL, NULL }, // 0X31=SPACE KEY
	{ 0,0, "latestVersion"  , 0, NULL, NULL, NULL },
	{ 0,0, "speed"  , 0, NULL, speeddesc, speedalias, speedint },
	{ 0,0, "latestVersionDate"  , 0, NULL, NULL, NULL },
	{ 1,1, "audioRate"  , DEFAULT_AUDIO_RATE, NULL, NULL, NULL },
	{ 1,1, "memorySize"  , 2, NULL, NULL, NULL },
	{ 0,0, "joyXScale"  , 1000, NULL, NULL, NULL },
	{ 0,0, "joyYScale"  , 1000, NULL, NULL, NULL },
    { 1,1, "diskSound"  , DEFAULT_DISKSOUND, NULL, NULL, NULL },
    { 0,1, "fixedVBlank"  , 0, NULL, NULL, NULL },
	 { 0,1, "adbDelay"  , 45, NULL, NULL, NULL },
     { 0,1, "adbRate"  , 3, NULL, NULL, NULL }
#ifdef DRIVER_IOS
    ,
	{ 1,1, "frameRate"  , 0, NULL, NULL, NULL },
	{ 1,1, "lockZoom"  , DEFAULT_LOCKZOOM, NULL, NULL, NULL },
	{ 1,1, "lastTab"  , 0, NULL, NULL, NULL },
	{ 1,1, "debugMode"  , DEFAULT_DEBUGMODE, NULL, NULL, NULL },
	{ 1,1, "externalKBD"  , 0, NULL, NULL, NULL }
#endif	
#ifdef ACTIVEGSKARATEKA
	,
    { 1,1, "showIcons"  , 1, NULL, NULL, NULL },
    { 0,1, "enableCheats"  , 0, NULL, NULL, NULL },
    { 1,1, "soundMode"  , 0, NULL, NULL, NULL },
    { 1,1, "karatekaRewind"  , 1, NULL, NULL, NULL },
    
    
#endif
#ifdef VIRTUALAPPLE
    { 1,1, "debug"  , 0, NULL, NULL, NULL },
    { 1,1, "scores"  , 0, NULL, NULL, NULL },
    { 1,1, "gamecenter"  , 0, NULL, NULL, NULL },
#endif
};

COption option;

const char*	COption::getDescription(option_id _id, int _value)
{
	namevalue& nv = find(_id);;
	return nv.getDescription(_value);
}

int namevalue::belongsToValue(int _v)
{
	if (!def->convertTableInt) return 1;
	int i=0;
	while(def->convertTableInt[i]!=-1)
	{
		if (def->convertTableInt[i]==_v)
			return 1;
		i++;
	}

	return 0;
}

int namevalue::getOrder(int _value)
{
	int p = 0;
	while(def->convertTableInt[p] != -1)
	{
		if (def->convertTableInt[p]==_value)
			return p;
		p++;
	}
	return -1;
}

const char* namevalue::getDescription(int _value)
{
	const char* ret = "n/a";
	if (!def->convertTableInt) return ret;
	int o = getOrder(_value);
	if (o<0) return ret;
	return def->convertTableDesc[o];
}


CEmulator* CEmulator::theEmulator = NULL;

char bram_default[]={
(char)0x00, (char)0x00, (char)0x00, (char)0x01, (char)0x00, (char)0x00, (char)0x0D, (char)0x06, (char)0x02, (char)0x01, (char)0x01, (char)0x00, (char)0x01, (char)0x00, (char)0x00, (char)0x00,
(char)0x00, (char)0x00, (char)0x07, (char)0x06, (char)0x02, (char)0x01, (char)0x01, (char)0x00, (char)0x00, (char)0x00, (char)0x0F, (char)0x06, (char)0x06, (char)0x00, (char)0x05, (char)0x06,
(char)0x01, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x01, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x03, (char)0x02, (char)0x02, (char)0x02,
(char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x00, (char)0x01, (char)0x02, (char)0x03, (char)0x04, (char)0x05, (char)0x06,
(char)0x07, (char)0x00, (char)0x00, (char)0x01, (char)0x02, (char)0x03, (char)0x04, (char)0x05, (char)0x06, (char)0x07, (char)0x08, (char)0x09, (char)0x0A, (char)0x0B, (char)0x0C, (char)0x0D,
(char)0x0E, (char)0x0F, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF,
(char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFF, (char)0xFD, (char)0x96, (char)0x57, (char)0x3C
};

void updateBramCheckSum(unsigned char* buf)	// original code in FF/B61D
{
	register short int crc=0;			// LDA #0
	int i;									// CLC
	for(i=255-4-1;i>=0;i--)						// LDX #FA
	{
		crc = (crc << 1) | ((crc>>15) & 1);	// ROL (1-bits)
#ifdef UNDER_CE
		crc += buf[i];
		crc += buf[i+1]*256;
#else
		crc += *(unsigned short*)(buf+i);	// ADC bram,X
#endif
	}									    // DEX
											// CPX #FF
											// BNE Loop
	*(unsigned short*)(buf+252) = crc;		// TAX
	*(unsigned short*)(buf+254) = crc ^0xAAAA;	// EOR #AAAA
}


void initializeBram(unsigned char* _bram_ptr)
{
	memcpy(_bram_ptr,bram_default,256);
	updateBramParameters(_bram_ptr);
	updateBramCheckSum(_bram_ptr);
	
}


void COption::generateGUID(MyString& guid)
{

	int crc = calcCRC(CDownload::getPersistentPath());

	time_t now;
   time( &now );
   int lowtime = (int)now;
	guid.Format("%08X%08X",crc,lowtime);
	
}

void CEmulatorConfig::resetConfig()
{
	speed = SPEED_AUTO;	
	bootslot = -1;	//auto	
//	fastConfig = false;
//	pXML=NULL;

	// vire tous les slots
	for(int i=0;i<3;i++)
	{
		for(int m=0;m<MAXSLOT7DRIVES;m++)
		{
			activeImages[i][m]=CActiveImage();
			for(int a=0;a<ACTIVEGSMAXIMAGE;a++)
				localImages[i][m][a]=CSlotInfo();
		}
	}
	xmlSystemParam.Empty();
	xmlEmulatorParam.Empty();
	xmlAlreadyLoaded = 0;
	
}

int namevalue::convertToInt(const char* _value)
{
	if (!def->convertTableInt) return 0;
	int i=0;
	const char* str;
	while( (str=def->convertTableAlias[i]) != NULL )
	{
		if(!strcasecmp(_value,str))		
			return def->convertTableInt[i];
		i++;
	}
	printf("err: could not convert %s : returning %s (%d)\n",_value,def->convertTableAlias[0],def->convertTableInt[0]);
	return def->convertTableInt[0];
}

option_id	COption::findByName(const char* _name)
{
	if (!_name) return (option_id)-1;

	for (int i=0;i<NB_OPTIONS;i++)
	{
		if (!strcasecmp(_name,options[i].def->name))
			return (option_id)i;
	}
#ifdef _DEBUG
	printf("cannot find option : %s\n",_name);
#endif
	return (option_id)-1;

}

void COption::setValue(option_id _id,const char* _value,int _confOverrided)
{
   
	
	namevalue& nv = find(_id);
	int isnum=1;
	const char*p=_value;
	while(*p)
	{
		if (*p<'0' || *p>'9')
		{
			isnum=0;
			break;
		}
		p++;
	}
	int v;
	if (isnum)
		v=atoi(_value);
	
	if (!nv.def->convertTableInt)
	{
		
		if (!isnum)
		{
			nv.strvalue = _value;
			printf("> option %s: %s\n",nv.def->name,_value);
		}
		else 
		{
			nv.intvalue = v;
			printf("> option %s: (%d)\n",nv.def->name,nv.intvalue);
		}
		
		nv.confOverrided = _confOverrided;

	}
	else
	{
	
		if (isnum && nv.belongsToValue(v))
		{	
			nv.intvalue = v;
			nv.confOverrided = _confOverrided;
			printf("> option %s: %s (%d)\n",nv.def->name,nv.getDescription(v),nv.intvalue);
		}		
		else
		{
			// regarde si c'est une valeur en texte?
			int pos=0;
			const char* s;
			while( (s = nv.def->convertTableAlias[pos]) != NULL )
			{
				if (!strcasecmp(s,_value))
				{
					nv.intvalue = nv.def->convertTableInt[pos];
					nv.confOverrided = _confOverrided;
					printf("* option %s: %s (%d)\n",nv.def->name,_value,nv.intvalue);
					return ;
				}
				pos++;
			}	
			printf("non supported value:%s for option:%s\n",_value,nv.def->name);
		}
	}
}


void namevalue::getAlias(MyString& _str)
{
	if (this->strvalue.IsEmpty())
	{
		// convert to int first
		int v = this->intvalue;
		if (!this->def->convertTableInt)
			_str.Format("%d",v);
		else
		{
			int i = getOrder(v);
			_str = this->def->convertTableAlias[i];
		}
		return ;
	}
	else
		_str = this->strvalue;
}



void COption::initOptions()
{
	
     for(int i=0;i<NB_OPTIONS;i++)
     {
     
     options[i].intvalue = defoptions[i].defaultint;
     options[i].strvalue = defoptions[i].defaultstr;
     }
    initiliazed = 1;
    setDefaultOptions();
}


void COption::setDefaultOptions()
{
	
    if (!initiliazed)
        x_fatal_exit("option not initialized");
	for(int i =0;i<NB_OPTIONS;i++)
	{
		options[i].def = &defoptions[i];
		if (options[i].def->inDefault)
		{
			if (options[i].def->convertTableInt)
				options[i].intvalue =options[i].def->convertTableInt[0];
			else
			{
				options[i].intvalue = options[i].def->defaultint;
				options[i].strvalue = options[i].def->defaultstr;
			}

		}
	}
    
    if (g_driver.x_apply_default_options!=NULL)
		    g_driver.x_apply_default_options((void*)this);

	printf("*restored default options\n");
}


void COption::setIntValue(option_id _id, int _v, int _confOverrided)
{
	MyString str;

	namevalue& nv  =find(_id);
	
	if (!nv.belongsToValue(_v))
	{
		printf("err: unrecognized value %d for %s\n",_v,nv.def->name);
		return ;
	}

	nv.intvalue = _v;
	if (nv.def->convertTableInt)
		printf("option %s set to %s (%d)\n",nv.def->name,nv.getDescription(_v),_v);
	else
		printf("option %s set to (%d)\n",nv.def->name,_v);
	
	nv.confOverrided = _confOverrided;
	
}

void COption::getStrValue(MyString& _str,option_id _id)
{
	namevalue& nv =find(_id);
	
	if (nv.strvalue.IsEmpty())
	{
		if (nv.def->convertTableInt)
			_str = nv.getDescription(nv.intvalue);
		else
			_str.Format("%d",nv.intvalue);
	}
	else
		_str = nv.strvalue.c_str();
}

int COption::getIntValue(option_id _id)
{
	namevalue& nv =find(_id);

	if (!nv.def->convertTableInt || nv.strvalue.IsEmpty())
		return nv.intvalue;
	else
	{
		printf("getintvalue: mismatched type for %s\n",nv.def->name);
		return 0;
	}
}

void COption::saveOptions(int _nb)
{
	MyString filename;
	getParameterFilename(filename,_nb);
	FILE* f = fopen(filename.c_str(),"wt");
	if (!f)
	{
		outputInfo("cannot create %s for saving options\n",filename.c_str());
		return ;
	}
	for(int i=0;i<NB_OPTIONS;i++)
	{
		if (options[i].def->inConfig == _nb)
		{
			MyString line;
			MyString desc;
			options[i].getAlias(desc);
			line.Format("%s:%s\n",options[i].def->name,desc.c_str());
			fputs(line.c_str(),f);
		}
	}
	fclose(f);
	//outputInfo("options saved (%s)\n",filename.c_str());
	enableOptions();
}




void COption::loadOptions()
{
	
	int  b = loadParameters(1);
	if (!b)
	{
		// si il n'y avait pas de fichier conf - rajoute le guid au option par défaut et sauve le
		MyString guid;
		generateGUID(guid);
		setValue(OPTION_GUID,guid.c_str());
		saveOptions(1);
	}
	
	// vérifie le numéro de version
	int v = getIntValue(OPTION_CONFIGVERSION);
	if (v < CONFIG_VERSION)
	{
		// réinitialise
		initOptions();
		setIntValue(OPTION_CONFIGVERSION,CONFIG_VERSION);
		printf("upgrading config file to v" TOSTRING(CONFIG_VERSION));
		saveOptions(1);
	}
	
	loadParameters(2);
}

void COption::getParameterFilename(MyString& filename,int _nb)
{
	filename = CDownload::getPersistentPath();
	filename += ACTIVEGS_DIRECTORY_SEPARATOR ;
	filename += "activegs";
	if (_nb==1)
		filename += ".conf";
	else
		filename += ".ext";	
}

int COption::loadParameters(int _nb)
{

	MyString filename;
	getParameterFilename(filename,_nb);
	
	FILE* f = fopen(filename,"rt");
	if (f)
	{
		outputInfo("loadOptions from %s\n",filename.c_str());
		char line[1025];
		line[1024]=0;
		while(fgets(line,1024,f))
		{
			int i=0;
			while(line[i]!=':' && line[i])i++;
			if (line[i]==':')
			{
				line[i++]=0;
				option_id id = findByName(line);
				if (id == OPTION_ERROR)
					outputInfo("option %s ignored\n",line);
				else
				{
                  
					int vpos = i;
					while( line[i] 
							&&
							(line[i]!='\t') 
							&&
							(line[i]!='\r') 
							&&
							(line[i]!='\n')
                          /*
							&&
							(line[i]!=' ') */) i++;
				
					if (line[vpos])
					{
                        // trim
                        while(line[i-1]==' ')i--;
                        	line[i]=0;
						setValue(id,line+vpos);
					}
				}
			}
		}
		fclose(f);
		return 1;
	}
	else
		return 0;
}


CEmulatorConfig::CEmulatorConfig()
{
	xmlAlreadyLoaded = 0;
	pXML=NULL;
	bramParam.Empty();
	emulatorParam.Empty();
    nb_bps=0;
	resetConfig();
}

void CEmulatorConfig::setBaseURL(const char* _url)
{
	MyString p = _url;
	
	if (!strncasecmp(p.c_str(),"file://localhost/",17))
		baseURL = p.substr(16,p.length()-16); // absolute path OG 250810 - leave the trailing slash
	else
	if (!strncasecmp(p.c_str(),"file:///",8))
#ifdef WIN32
		baseURL = p.substr(8,p.length()-8); // absolute path
#else
		baseURL = p.substr(7,p.length()-7); // keep the / on mac
#endif
	else
	if (!strncasecmp(p.c_str(),"file://",7))
		baseURL = p.substr(7,p.length()-7); // absolute path
	else
		baseURL = p;

	baseURL.Replace("%20"," ");
	printf("baseURL set to %s\n",baseURL.c_str());
	/*
	CDownload dl;
	dl.setBaseURL(baseURL.c_str());
	*/
}

CSlotInfo& CEmulatorConfig::getLocalImage(int _slot, int _drive, int _pos)
{
	return localImages[_slot-5][_drive-1][_pos];
}

CActiveImage& CEmulatorConfig::getActiveImage(int _slot, int _drive)
{
	return activeImages[_slot-5][_drive-1];
}

CSlotInfo* CEmulatorConfig::setSlot(int _slot, int _disk, const char * _url_normalized, enum eSetSlotPos _pos)
{
	if  (_slot<5 || _slot > 7 || _disk < 1 || (_slot==7 && _disk > MAXSLOT7DRIVES) || (_slot!=7 && _disk > 2) )
    {
        printf("invalid slot : %d:%d\n",_slot,_disk);
			return NULL;
    }
	// normalise pour garder la compatibilité avec les version < 3.5
	MyString url = _url_normalized;
	int pos = url.ReverseFind('?');
	if (pos != -1 )
	{
		int i = url.at(pos+1);
		size_t l = url.length();
		if (l==(pos+2) && i>='0' && i<='9')
		{
			url = url.Left(pos);
			url += "#pos=";
			url += i;
		}
	}
	
	const char* _url = url.c_str();
	if (_url && !_url[0]) _url=0;
	CActiveImage& ai = activeImages[_slot-5][_disk-1 ];

	int active=0;
	if (_pos==REPLACECURRENT)	// posactive
	{
		active = ai.iActive;
		if (!ai.iNb)
		{
			// ignore SetSlot = NULL si aucun disk n'avait été préalablement chargé
			if (!_url) return NULL;
			ai.iNb=1;
		}
	}
	else
	// if (_pos==ADD)	
	{
		// regarde si le disk était déjà présent dans la liste
		for(int i =0;i<ai.iNb;i++)
		{
			CSlotInfo& ref = localImages[_slot-5][_disk-1 ][i];
			if (!ref.url.CompareNoCase(_url))
			{
				printf("image %s already in the list... ignoring!\n",_url);
				return NULL ;
			}
		}
		active = ai.iNb++;
	}
	
	
	CSlotInfo& ref = localImages[_slot-5][_disk-1 ][active];
	
	if (ref.status == MOUNTED || _url )
	{
		if (!_url)	
		{
			ref.status = EJECTED ;
			outputInfo("status set to EJECTED for S%dD%d\n",_slot,_disk);
		}
		else
		{
			size_t len = strlen(_url);
			if (len>2048)
			{
				// overflow!
				outputInfo("status set to FAILED(invalid length) for S%dD%d\n",_slot,_disk);
				ref.status = FAILED;
			}
			else
			{
				ref.url	= _url;
				ref.url.Trim();	// OG Fix ending space
				ref.shortname = getfile(_url);
				ref.status = READY2MOUNT;
				outputInfo("status set to READY2MOUNT for S%dD%d, image: %s\n",_slot,_disk,ref.shortname.c_str());
			}
		}
		need2remount=true;
		x_refresh_panel(PANEL_DISK);
	}
	return &ref;
}

const char* CEmulatorConfig::getSlot(int _slot, int _disk)
{
	if (_slot<5 || _slot > 7 || _disk < 1 || _disk > MAXSLOT7DRIVES)
			return NULL;
	CActiveImage active = activeImages[_slot-5][_disk-1 ];
	CSlotInfo& ref = localImages[_slot-5][_disk-1 ][active.iActive];
	return STRING2CONSTCHAR(ref.url);
}


void CEmulatorConfig::processCommandLine(int argc, char** argv)
{
	
	for(int i=1;i<argc;i++)
	{
		bool bOption = argv[i][0]=='-';
		if (bOption)
		{
			const char* option = argv[i]+1;
			if (!strcasecmp(option,"help"))
			{
#if defined(WIN32) && !defined(UNDER_CE)
				::MessageBox(NULL,"ActiveGS.exe [<filename>.activegsxml|<filename.2mg>|<filename.dsk>]", ACTIVEGSVERSIONSTRFULL,MB_OK);
				::PostQuitMessage(0);
#endif
			}
			else
			if ((i+1)<argc)
			{
				const char* value = argv[++i];
				if (!strcasecmp(option,"slot51"))
					this->setSlot(5,1,value,ADD);
				else
				if (!strcasecmp(option,"slot52"))
					this->setSlot(5,2,value,ADD);
				else
				if (!strcasecmp(option,"slot61"))
					this->setSlot(6,1,value,ADD);
				else
				if (!strcasecmp(option,"slot62"))
					this->setSlot(6,2,value,ADD);
				else
				if (!strcasecmp(option,"slot71"))
					this->setSlot(7,1,value,ADD);
				else
				if (!strcasecmp(option,"slot72"))
					this->setSlot(7,2,value,ADD);
				else
				if (!strcasecmp(option,"BootSlot"))
				{
					int bs = atoi(value);
					this->bootslot = bs;
				}
				else
				if (!strcasecmp(option,"Speed"))
				{
					int s = atoi(value);
					this->speed=s;
				}
				else
				if (!strcasecmp(option,"EmulatorParam"))
					this->emulatorParam=value;
				else
				if (!strcasecmp(option,"SystemParam"))
					this->systemParam=value;
				else
				if (!strcasecmp(option,"BramParam"))
					this->bramParam=value;
				else
				if (!strcasecmp(option,"XMLConfig"))
					this->xmlconfig=value;
				else
				if (!strcasecmp(option,"state"))
				{
					CDownload dl(this->baseURL);
				//	dl.setBaseURL(_path);
					MyString path;
					MyString shortn;
					dl.retrieveFile(value,path,shortn);
					this->stateFilename=path.c_str();
				}
			}
		}
		else
		{
			// vérifie l'extension (mais strip le #)
			MyString extv(argv[i]);
			int l = extv.ReverseFind("#");
			if (l>0)
				extv = extv.Left(l);
			const char* ext = getext(extv.c_str());
			if ( !strcasecmp(ext,"activegsxml") || !strcasecmp(ext,"xml"))
			{
				this->xmlconfig = argv[i] ; 
			}
			else
			{
				MyString path;
				MyString file;
			//	CDownload dl;
				int slot = 0;
				
			//	dl.deleteCachedFile(argv[i]);
				CDownload dl(this->baseURL);
				size_t size = dl.retrieveFile(argv[i],path,file);
				ext = getext(path.c_str());
                if (!strcasecmp(ext,"hdv"))
				{
					slot = 7;
                }
				else
				if (!strcasecmp(ext,"2mg") || !strcasecmp(ext,"po"))
				{
					if (size> 819712) // OG Fix 2MG size recognitioN (CF. RASTAN 819264 & POMS 7 819712)
						slot = 7;
					else
						slot = 5;
				}
				else
				if ( !strcasecmp(ext,"bin") || !strcasecmp(ext,"dsk") || !strcasecmp(ext,"nib") || !strcasecmp(ext,"po") || !strcasecmp(ext,"do") )
					slot = 6;
				if (slot)
				{
					setSlot(slot,1,path,REPLACECURRENT);
				//	bootslot = slot; OG Laisse la valeur par défaut!
					xmlconfig.Empty();
				}
				else
					outputInfo("Unknown extension .%s\n",ext);
			}
		}
	}
}

int CEmulatorConfig::processXML(const char*_path,simplexml* ptrconfig)
{
	
	const char* ver = ptrconfig->property("version");
	if (!ver || atoi(ver)!=2)
	{
		outputInfo("invalid xml version - xml config skipped\n");
		return  0;
	}
	
	int child = ptrconfig->number_of_children();
	for(int i=0;i<child;i++)
	{
		simplexml* ptr = ptrconfig->child(i);
		if (!strcasecmp(ptr->key(),"uid"))
		{
			const char* s = ptr->value();
			uid = s;
		}
		else
			if (/*!fastConfig && */!strcasecmp(ptr->key(),"image"))
			{
				int slot = atoi(ptr->property("slot"));
				int disk = atoi(ptr->property("disk"));
				if (
					( ( slot==5 || slot==6 ) && ( disk==1 || disk==2) )
					||
					( (slot==7) && ( disk>=1 || disk<=MAXSLOT7DRIVES) )
					)
				{

					MyString absp ;
					const char* s = ptr->value();
					int patch_addr = 0;
					const char* patch_value = NULL;
									
					for(int idisk=0;idisk<ptr->number_of_children();idisk++)
					{
						simplexml* ptrd = ptr->child(idisk);
						if (!strcmp(ptrd->key(),"name"))
								s = ptrd->value();
						else
						if (!strcmp(ptrd->key(),"patch"))
						{
							const char* addr_str = ptrd->property("addr");
							if (addr_str)
							{
								sscanf(addr_str,"%X",&patch_addr);
								patch_value = ptrd->value();
                                if (strlen(patch_value)&1)
                                    patch_value=NULL; // doit être pair
							}
						}
					}

					if (s)
					{
					//	CDownload dl(this->baseURL);
						CDownload::makeAbsolutePath(s,_path,absp);
						
						CSlotInfo* pslot = setSlot(slot,disk,absp.c_str(),ADD);
						if (pslot && patch_value && patch_addr)
						{
							pslot->patch_addr = patch_addr;
							pslot->patch_value = patch_value;
                            pslot->patch_value.MakeUpper();
						}
					}
				}
			}
			else		
			if (/*!fastConfig && */!strcasecmp(ptr->key(),"bootslot"))
			{
				int slot = atoi(ptr->value());
				if (slot>=5 && slot<=7)
				{
					bootslot = slot;
				}
			}
			else
            if (!strcasecmp(ptr->key(),"runtime"))
            {
                const char* mode_str = ptr->property("mode");
                if (mode_str)
                {
					enum mode_breakpoint mode = BRK_DISABLED;
                if (strchr(mode_str,'X'))
                    mode = (enum mode_breakpoint)(mode|BRK_X);
                if (strchr(mode_str,'R'))
                    mode = (enum mode_breakpoint)(mode|BRK_READ);
                if (strchr(mode_str,'W'))
                    mode = (enum mode_breakpoint)(mode|BRK_WRITE);
                if (strchr(mode_str,'P'))
                    mode = (enum mode_breakpoint)(mode|BRK_PATCH);
				 if (strchr(mode_str,'B'))
                    mode = (enum mode_breakpoint)(mode|BRK_STOP);
                 if (strchr(mode_str,'S'))
                    mode = (enum mode_breakpoint)(mode|BRK_SLOWSPEED);
				if (strchr(mode_str,'D'))
                    mode = (enum mode_breakpoint)(mode|BRK_DEFAULTSPEED);
				if (strchr(mode_str,'M'))
                    mode = (enum mode_breakpoint)(mode|BRK_MESSAGE);
				


                const char* addr_str = ptr->property("addr");
                if (addr_str && !(strlen(addr_str)&1))
                {
                    word32 patch_addr;
                    sscanf(addr_str,"%X",&patch_addr);
                    
                    const char* value_str = ptr->value();
                    if (value_str == NULL || !(strlen(addr_str)&1))
                    {
                    
                    int bp = nb_bps;
                    if (bp != MAX_BREAK_POINTS)
                    {


						const char* check = ptr->property("check");
						memset(BPs[bp].check,0,MAX_CHECK);
						if (check!=NULL)
						{
						//	printf("found check:%s\n",check);
							int i=0;
							while(i<MAX_CHECK-3)	// 3x 00 A LA FIN
							{
								char c1 = *check++;
								if (!c1) break; // done
								char c2 = *check++;
								if (!c2) break; // misformed
								if (c1>='a')
									c1 -= ('a'-10);
								else
								if (c1>='A')
									c1 -= ('A'-10);
								else
									c1 -= '0';

								if (c2>='a')
									c2 -= ('a'-10);
								else
								if (c2>='A')
									c2 -= ('A'-10);
								else
									c2 -= '0';

								BPs[bp].check[i++]= c1*16+c2;
							}

						}

                        BPs[bp].addr= patch_addr;
                        BPs[bp].value = value_str;
                        BPs[bp].mode = mode;
                        nb_bps++;
                    }
                    }
                }
                }
            }
            else
			if (!strcasecmp(ptr->key(),"speed"))
			{
				int sp = atoi(ptr->value());
				if (sp>=0 && sp<SPEED_ENUMSIZE)
				{
					speed = sp;
				}
			}
			else
			if (!strcasecmp(ptr->key(),"bramparam"))
			{
				const char* s = ptr->value();
				xmlBramParam = s; 			
			}
			else
			if (!strcasecmp(ptr->key(),"emulatorparam"))
			{
				const char* s = ptr->value();
				xmlEmulatorParam = s;
			}
			else
			if (!strcasecmp(ptr->key(),"systemparam"))
			{
				const char* s = ptr->value();
				xmlSystemParam = s;
			}
			else
			if (!strcasecmp(ptr->key(),"name"))
			{
				const char* s = ptr->value();
				name = s;
			}
			else
			if (!strcasecmp(ptr->key(),"desc"))
			{
				const char* s = ptr->value();
				desc = s;
			}
			else
			if (!strcasecmp(ptr->key(),"publisher"))
			{
				const char* s = ptr->value();
				publisher = s;
			}
			else
			if (!strcasecmp(ptr->key(),"year"))
			{
				const char* s = ptr->value();
				year = s;
			}
			if (!strcasecmp(ptr->key(),"state"))
			{
				const char* s = ptr->value();
				CDownload dl(_path);	// ???
				MyString path;
				MyString shortn;
				dl.retrieveFile(s,path,shortn);
				stateFilename=path.c_str();
			}
		
	}

	return 1;
}


void CEmulatorConfig::loadXMLFile()
{
	
	
	if (xmlconfig.IsEmpty() && !pXML)
	{
		xmlEmulatorParam.Empty();
		xmlSystemParam.Empty();
		xmlBramParam.Empty();
		return ;
	}

	if (xmlAlreadyLoaded)
	{
		printf("discard xml...\n");
		return ;
	}
	resetConfig();
	xmlAlreadyLoaded = 1;

	
	// If XML already present, use preparsed structrue
	if (pXML)
	{
		processXML(baseURL.c_str(),pXML);
		return ;
	}
	
	MyString fullpath;
	MyString fulldir;
	//CDownload dl;
	CDownload::makeAbsolutePath(STRING2CONSTCHAR(xmlconfig),baseURL.c_str(),fullpath);
	getdir(fullpath.c_str(),fulldir);
	xmlconfig = fullpath.c_str() ;
	
	ActiveGSList list;
	if (!list.loadFromFile(STRING2CONSTCHAR(xmlconfig)))
		return ;

	simplexml* ptrconfig = NULL;
	if (list.elements.size()==1)
		ptrconfig = list.elements.at(0).pXML;
	else
	{
		CStdStringA xmlconfig2 = xmlconfig;
		int pos = xmlconfig2.ReverseFind("#id=");
		if (pos==-1)
		{
			printf("No key requested with .activegsxml list format\n");
			return ;
		}

		MyString key = xmlconfig2.Mid(pos+4);
		for(unsigned int i=0;i<list.elements.size();i++)
		{
			if (list.elements.at(i).theid == key)
			{
				ptrconfig = list.elements.at(i).pXML;
				break;
			}
		}
		if (!ptrconfig)
		{
			printf("id: %s not found in %s\n",key.c_str(),STRING2CONSTCHAR(xmlconfig));
			return ;
		}
	}

	ASSERT(ptrconfig);
	processXML(fulldir.c_str(),ptrconfig);
	/*
	delete root;
	delete s;
	*/
}


//void geturldomain(const char* p,MyString& _domain);

void CEmulator::sendStats()
{
/*
#pragma message("TODO refaire les stats!")
	
#ifndef DRIVER_IOS
	MyString str="http://analytics.freetoolsassociation.com/analytics.php?";
	const char* app =ACTIVEGSAPP ;
	str +="&p=";
	str += app;
	str +="&b=";
	const char* ver = TOSTRING(ACTIVEGSMAJOR) "." TOSTRING(ACTIVEGSMINOR) "." TOSTRING(ACTIVEGSBUILD);
	str += ver;
	str +="&n=";
	MyString browser; option.getStrValue(browser,OPTION_BROWSERNAME);
	str += browser.substr(0,4);
	str +="&v=";
	MyString browseroption; option.getStrValue(browseroption,OPTION_BROWSERVERSION);
	str += browseroption;
	str +="&g=";
	MyString guid; option.getStrValue(guid,OPTION_GUID);
	str += guid;
	MyString domain;
	geturldomain(config->baseURL.c_str(),domain);
	str += "&d=";
	str += domain.c_str();
	
#ifdef ACTIVEGSPLUGIN
	CDownload dl;
	dl.GetFile(str.c_str(),NULL);
#endif
#endif
	*/
}

void CEmulator::setConfig(CEmulatorConfig* _config)
{
	config = _config;

	sendStats();
	config->loadXMLFile();
	/*
	CDownload dl;
	dl.setBaseURL(config->baseURL.c_str());
	*/
}


char read2MG(const char* ref,int block, int byte)
{
	return ref[0x40+block*512+byte];
}

int find2MG(const char* ref,int block, char header, const char* str)
{
	int byte = 4;
	
	while (1)
	{
		char r = read2MG(ref,block,byte);
		if (!r) return 0;

		if (r==header)
		{
			int c=0;
			while(str[c])
			{
				if (read2MG(ref,block,byte+c+1)!=str[c]) break;
				c++;
			}
			if (!str[c])
			{
				// found!
				return block*512+byte;
			}
		}
		byte+=0x27;
		if (byte==0x1FF)
		{
			byte =4;
			block = read2MG(ref,block,2) + read2MG(ref,block,3)*256;
			if (!block) return 0;
		}

	}
	return 0;
}



int findSetStart(const char* buffer)
{
	// start dir
	
	int sys = find2MG(buffer,2,(char)0xD6,"SYSTEM");
	if (!sys)
	{
		outputInfo("cannot find System dir!");
		return false;
	}
	
	int sysbloc = buffer[sys+0x40+0x11]+buffer[sys+0x40+0x12]*256;

	int setstart = find2MG(buffer,sysbloc,0x1D,"SETSTART.DATA");
	if (!setstart)
	{
		outputInfo("cannot find setstart file");
		return false;
	}
	
	return setstart;

}

bool CEmulator::setStartParam()
{
	// prend le nom du fichier en 71

	int slot = setStart.slot/10;
	int drive = setStart.slot%10;
	CSlotInfo& ref = getLocalIMGInfo(slot,drive);
	const char* filename= ref.filename.c_str(); //getLocalIMG(setStart.slot/10,setStart.slot%10);
	if (!filename) return false;
	FILE* f=fopen(filename,"rb");
	if (!f) return false;

	// get file size
	fseek(f,0,SEEK_END);
	int filesize=ftell(f);

	char* buffer=(char*)malloc(filesize);

	fseek(f,0,SEEK_SET);
	fread(buffer,1,filesize,f);
	fclose(f);

	// 
	int offset = findSetStart(buffer);
	if (!offset)
	{
		free(buffer);
		return false;
	}

	char *entry = buffer+offset+0x40;
		
	/*
	char *entry = buffer+setStart.addr;
	*/
	if ((entry[0]!=0x1D) || (strcmp(entry+1,"SETSTART.DATA")) )
	{
		free(buffer);
		return false;
	}
	

	
	// buffer + 17 = block
	int blockpos = (entry[17]+entry[18]*256)*512+64;
	
	// buffer + 19 = size
	int size  = setStart.file.length();
	entry[0x15]=(size+3)&0xFF;
	entry[0x16]=((size+3)>>8)&0xFF;
	entry[0x17]=0;


	char* block = buffer+blockpos;
	memset(block,0,256);
	block[0]=size&0xFF;
	block[1]=(size>>8)&0xFF;
	strcpy(block+2,setStart.file.c_str());
	block[size+2]=0x31; // ????

	f=fopen(filename,"wb");
	if (!f)
	{
		free(buffer);
		return false;
	}
	fseek(f,0,SEEK_SET);
	fwrite(buffer,1,filesize,f);
	fclose(f);
	free(buffer);

	struct param p;
		p.data = "SetStart";
		p.value = setStart.file;
		p.hex = offset+0x40;	//setStart.addr;
		addUniqueParam(p);
	//	params.push_back(p);

	x_refresh_panel(PANEL_PARAMS);
	
	return true;
}


int CEmulator::acceptEvents()
{
	return r_sim65816.get_state()==RUNNING;
}


void CEmulator::onEmulatorParamChanged(const char* _param)
{
	if (!acceptEvents())
	{
		outputInfo("### onEmulatorParamChanged Ignored");
		return;
	}
	option.addEmulatorParam(_param);
	setEmulatorParam(_param);
	option.enableOptions();
}

void CEmulator::onBootSlotChanged(int _bootslot)
{
	if (!acceptEvents())
	{
		outputInfo("### OnBootSlotChanged Ignored");
		return;
	}
	config->bootslot = _bootslot;
	relaunchEmulator();
}

void CEmulator::relaunchEmulator()
{
	outputInfo("Trying to relaunch Emulator()\n");
	
	if (this !=theEmulator)
	{
		outputInfo("Another instance was running in another process : killing it()\n");
		delete theEmulator;
	}

	forceFocus();

	if (bInThread)
	{
		outputInfo("Asking for current instance to restart\n");
		r_sim65816.restart_emulator();
	}
	else
	{
		outputInfo("reactivate emulator!\n");
		launchEmulator();
	}
}


void	CEmulator::onSpeedChanged(int _speed, bool _ignoreAcceptEvents)
{

	if (!_ignoreAcceptEvents && !acceptEvents())
	{
		outputInfo("### onSpeedChanged Ignored");
		return;
	}
		
	/*
	int sp = g_sim65816.set_limit_speed((speedenum)_speed);
	config->speed = sp;
	*/
	option.setIntValue(OPTION_SPEED,_speed);
	option.enableOptions();
	forceFocus();
}

void	CEmulator::addUniqueParam(struct param _p)
{
	int i;
	for(i=0;i<nbparams;i++)
		if (params[i].data == _p.data)
		{
			params[i].value = _p.value ;
			params[i].hex = _p.hex ;
			return ;
		}
	if (i<EMULATOR_MAX_PARAMS)
	{
		params[i].value = _p.value ;
		params[i].hex = _p.hex ;
		params[i].data = _p.data ;
		nbparams++;
	}
		
	/*
	vector <struct param> params;

	vector<struct param>::iterator v;
	v = params.begin();
	while(v!=params.end())
	{
		if (v->data == _p.data)
		{
			v->value = _p.value;
			v->hex = _p.hex;
			return ;
		}
		v++;
	}
	params.push_back(_p);
*/
}


const char* currentBuild=TOSTRING(ACTIVEGSMAJOR)"." TOSTRING(ACTIVEGSMINOR) "." TOSTRING(ACTIVEGSBUILD);



void CEmulator::checkAlerts()
{
	
	static int bRunOnlyOnce=0;
	if (bRunOnlyOnce) return ;
	bRunOnlyOnce=1;
	
#ifdef _DEBUG
	option.setValue(OPTION_LATESTVERSION,"5.1.4");
#endif
	
	if (!option.getIntValue(OPTION_DONOTSHOWUPDATEALERT))
	{	
		MyString newestBuild;  option.getStrValue(newestBuild,OPTION_LATESTVERSION);
		MyString lastestUpdatedBuild; option.getStrValue(lastestUpdatedBuild,OPTION_LATESTUPDATECHECK);
		if (	!newestBuild.IsEmpty()  
			&&	lastestUpdatedBuild.compare(newestBuild)
			&&	newestBuild.compare(currentBuild) )
			{
				option.setValue(OPTION_LATESTUPDATECHECK,newestBuild);
				option.saveOptions(2);

				x_alert("A newer version (%s) of ActiveGS is available!\nCheck http://activegs.freetoolsassociation.com for more info.",newestBuild.c_str(),currentBuild);
				
				return ; 
			}
		
		
	}
	 
}

bool CEmulator::setParam(eParamType _type, const char* _data, const char* _value)
{

	static const struct { const char* name; int* address; } variables[] =
	{ 
		
	// boot
		{ "boot", (int*)0x28 },
		
	// drive emulation
		{ "driveemulation", &g_iwm.g_fast_disk_emul },
		{ "slow", (int*)0 },
		{ "fast", (int*)1 },

	// slot
		{ "slot6", (int*)0x26 },
		{ "drive", (int*)0 },
		{ "disk", (int*)0 },
		{ "card",(int*)1},

	// columns
		{ "columns", (int*)0x19 },
		{ "40", (int*)0 },
		{ "80", (int*)1 },

	// speed
	
		{ "forcespeed", (int*)0x20 },
		{ "normal",(int*)0},
		{ "fast",(int*)1},
		
		{ "zipspeed",&g_moremem.g_zipgs_reg_c05a},
	//	{ "simulatespace",&g_adb.g_simulate_space},
		{ "emulate6502",&g_sim65816.g_6502_emulation },
		{ "apple2erom",&g_sim65816.g_use_apple2e_rom },

	// bram
		{ "font", (int*)0x1A },
		{ "background", (int*)0x1B },
		{ "border", (int*)0x1C },

		{ "black",	(int*)0x00 },
		{ "dark-blue",	(int*)0x02 },
		{ "dark-green",	(int*)0x04 },
		{ "medium-blue",	(int*)0x06 },
		{ "brown",	(int*)0x08 },
		{ "light-gray",	(int*)0x0A },
		{ "green",	(int*)0x0C },
		{ "aquamarine",	(int*)0x0E },
		{ "deep-red",	(int*)0x01 },
		{ "purple",	(int*)0x03 },
		{ "dark-gray",	(int*)0x05 },
		{ "light-blue",	(int*)0x07 },
		{ "orange",	(int*)0x09 },
		{ "pink",	(int*)0x0b },
		{ "yellow",	(int*)0x0d },
		{ "white",	(int*)0x0f },

		{ "language",	(int*)0x29 },
		{ "keyboard",	(int*)0x2a },
		{ "french",	(int*)0x02 },
		/*
* $0 =       USA                 Dvorak 
*              $1 =       UK                   USA
*              $2 =     French                 USA
*              $3 =     Danish                 USA
*              $4 =     Spanish                USA
*              $5 =     Italian                USA
*              $6 =     German                 USA 
*              $7 =     Swedish  
		*/
		{ NULL,	NULL }
	};

	int value = -1000;
	int* data = 0;
		
	if (!strcasecmp(_data,"driveemulation"))
		data=0;
	
	int i=0;
	while(variables[i].name)
	{
		if (!strcasecmp(_data,variables[i].name))
		{
			data = (int*)variables[i++].address;
			while(variables[i].name)
			{
				if (!strcasecmp(_value,variables[i].name))
				{
					value = (intptr_t)(variables[i].address);
					break;
				}
				i++;
			}
		}
		else
			i++;
	}



	if (value==-1000)
		value=atoi(_value);
	if (!data)
		data=(int*)atoi(_data);

	if ( data && (value!=-1000))
	{
		outputInfo("%s = %s (%d)\n",_data,_value,value);
		
		struct param p;
		p.data = _data;
		p.value = _value;
		p.hex = value;

		if (data==&g_moremem.g_zipgs_reg_c05a)
		{
			g_moremem.g_zipgs_reg_c05a = ((g_moremem.g_zipgs_reg_c05a&0x0F)|(value&0x0F)<<4);
			p.value.Format("%3d%%",(int)((16-((g_moremem.g_zipgs_reg_c05a>>4)&0x0F))*100/16));
		}
		else
		if ((intptr_t)data > 256)
//		if (_type==EMULATOR)
			*data = value;
		else
		{
			// bram
			int idx = (intptr_t)data;
			if (idx>0 && idx<256)
				g_clock.g_bram_ptr[idx]=value;
			else 
				printf("invalid bram index : %s (%d)\n",_data,idx);

		}
		
		addUniqueParam(p);
	}
	else
	{
#ifdef _DEBUG
		outputInfo("unknown parameters (%s)(%s)\n",_data,_value);
#endif
	}

	return true;

}

bool CEmulator::parseParam(const char* _param,eParamType _type)
{

	// Format
	// data=value;data2=value;
	
	const char* run = _param;
	if (!run) return false;
	
	MyString data;
	MyString value;
	
	while(1)
	{
		// data
		data.Empty();

		// skip ;
		while (*run == ';') run++;
		if (!*run) return false; // done

		while(*run!='=' && *run!=':')
		{
			if (!*run) return false;
			data += *run;
			run++;
		}
		run++;	// passe le =
		value.Empty();

		while(*run && *run!=';')
			value += *run++;		
		
		if (!strcasecmp(data.c_str(),"setstart"))
			extractSetStartParam(value.c_str());
		else
		{
			if (data[0]!='_')
				setParam(_type,data.c_str(),value.c_str());
		}
		if (!*run) break;
		run++;
	}

	return true;
}


bool COption::addEmulatorParam(const char* _param)
{

	const char* run = _param;
	if (!_param) return true;
//	printf("loading system param\n");

	MyString data;
	MyString value;
	
	while(1)
	{
		// data
		data.Empty();

		// ignore the initial _ (for backward compatibility)
		if (*run=='_') run++; 
		while(*run!='=' && *run!=':')
		{
			if (!*run) return false;
			data += *run;
			run++;
		}
		run++;	// passe le =
		value.Empty();

		while(*run && *run!=';')
			value += *run++;		

		// regarde si c'est une option
		option_id id = findByName(data.c_str());
		if (id!=OPTION_ERROR)
			setValue(id,value.c_str(),1); 
			/*
		if (data[0]=='_')
		{
			MyString data2;
			data2 = data.substr(1);
			option_id id = findByName(data2.c_str());
			if (id==OPTION_ERROR)
				printf("could not process : %s %s\n",data2.c_str(),value.c_str());
			else
				setValue(id,value.c_str(),1); 
		}
		*/
		
		if (!*run) break;
		run++;
	}

	return true;
}



bool CEmulator::extractSetStartParam(const char* run)
{
	// Format slot|file

	setStart.slot = 0;
	int slot;
	
	MyString data;
	while(*run!=':' && *run)
		data += *run++;
			
	slot = atoi(data.c_str());
	if ( (slot!=51) && (slot!=52) && (slot!=71) && (slot!=72) ) return false;

	data.Empty();
	while(*run)
		data += *run++;

	setStart.slot = slot;
	setStart.file = data;

	return true;
}

// CChildView


void CEmulator::updateBramParameters()
{
	if (!this) return ;

	parseParam(bramParam.c_str(),BRAM);
	x_refresh_panel(PANEL_PARAMS);
}

void CEmulator::setBramParam(const char* _p)
{
	

	if (bramParam.IsEmpty())
		bramParam = _p;
	else
	{
		bramParam += ';';
		bramParam += _p;
	}
}



void CEmulator::ejectDisk(int _slot,int _drive)
{
	if (!this) return ;
	CSlotInfo& ref = getLocalIMGInfo(_slot,_drive);
	if (ref.status == UNDEFINED) return ;
	ref.status = EJECTED ;
	outputInfo("status set to EJECTED for S%dD%d\n",_slot,_drive);
	config->need2remount=true;
	x_refresh_panel(PANEL_DISK);

     if(g_sim65816.g_enable_disk_sound)
         play_sound(SOUND_EJECTION);
}


void CEmulator::reloadDisk(int _slot,int _drive)
{
	if (!this) return ;
	CSlotInfo& ref = getLocalIMGInfo(_slot,_drive);
	if (
		(ref.status == EJECTED )||
		(ref.status == FAILED ) )
	{
			ref.status = READY2MOUNT;
			outputInfo("status set to READY2MOUNT for S%dD%d\n",_slot,_drive);
			config->need2remount=true;
			x_refresh_panel(PANEL_DISK);
	}
}



CEmulator::CEmulator(CEmulator** _parent)
{
	if (theEmulator)
		x_fatal_exit("MULTIPLE INSTANCES");
	bInThread = false;
	theEmulator= this;
	parent = _parent ;
#ifdef ACTIVEGSPLUGIN
	pInstance = NULL;
#endif
	theWindow = NULL;
	g_sim65816.halt_sim = 0;
	x=0;
	y=0;
	memset(lights,0,sizeof(lights));
	memset(tracks,0,sizeof(tracks));
	config = NULL;
}

#ifdef ACTIVEGSPLUGIN
void CEmulator::setPluginInstance(void* _instance)
{
	pInstance = _instance;
}
#endif

void CEmulator::mountDisks()
{
	config->need2remount=true;
	forceFocus();
}


int CEmulator::theThread()
{
	
#ifdef TEST_RASTER
	
	x_test_raster();
	
#else

	if (!config)
	{
		x_fatal_exit("!!!MISSING CONFIG!!!\n");
		return 0;
	}

	if (bInThread)
	{
		x_fatal_exit("THREAD ALREADY RUNNING\n");
		return 0;
	}

	
	bInThread=true;
	outputInfo("emulator thread launched!\n");
	
	while(1)
	{
		config->need2remount=true;
		mountDelayedDisk();
		
		nbparams=0;
		bramParam.Empty();
		
	
		// 1- OPTION PAR DEFAULT DE L'EMULATEUR
		
		// initialise les options par défault
		setStart.slot=0;
		g_sim65816.g_6502_emulation = 0;
		g_sim65816.g_use_apple2e_rom = 0;


		// paramètre auto!
		if (config->bootslot==-1)
		{
			// default
			config->bootslot=5;	
			for(int b = 5;b<=7;b++)
			{
				const char* s = config->getSlot(b,1);
				if (s && s[0])
				{
					printf("bootslot set to %d\n",b);
					config->bootslot=b;
					break;
				}
			}
		}

		switch(config->bootslot)
		{
			case 6:
				setEmulatorParam("driveemulation:slow;emulate6502:1;");
				setBramParam("slot6:card;boot:6;border:black;background:black;font:white");

				break;
			case 5:
				setBramParam("boot=5");
				break;
			default:
				setEmulatorParam("driveemulation:fast;");
				setBramParam("slot6:disk");
				break;
		}
	
		// 1 la vitesse

		g_moremem.g_zipgs_reg_c05a = 0x0F; // MAX SPEED
		
		int sp = config->speed;
		if (sp == SPEED_AUTO)
		{
			if (config->bootslot==6)
				sp = SPEED_1MHZ;
			else
				sp = SPEED_GS;
			printf("speed auto set to %d\n",sp);
		}

		onSpeedChanged(sp,true);

		// 2 - LES OPTIONS DU FICHIERS DE CONF
	
		option.loadOptions();
				
		// 3 - LES OPTIONS DU FICHIER XML CONFIG
	
		printf("*loading option from xml\n");
		printf("EmulatorParam: %s\n",config->xmlEmulatorParam.c_str());
		printf("BramParam: %s\n",config->xmlBramParam.c_str());
		printf("SystemParam: %s\n",config->xmlSystemParam.c_str());

		option.addEmulatorParam(config->xmlSystemParam.c_str());
		setBramParam(config->xmlSystemParam.c_str());
		setBramParam(config->xmlBramParam.c_str());
		option.addEmulatorParam(config->xmlEmulatorParam.c_str());
		setEmulatorParam(config->xmlEmulatorParam.c_str());

		// 4 -  LES OPTIONS JAVASCRIPT
		printf("*loading option from javascript\n");
		option.addEmulatorParam(STRING2CONSTCHAR(config->systemParam));
		setBramParam(STRING2CONSTCHAR(config->systemParam));
		setBramParam(STRING2CONSTCHAR(config->bramParam));
		setEmulatorParam(STRING2CONSTCHAR(config->emulatorParam));
		option.addEmulatorParam(STRING2CONSTCHAR(config->emulatorParam));
		
		option.enableOptions();

        mountDisks();

		g_adb.g_adb_repeat_delay = option.getIntValue(OPTION_ADBDELAY);
		g_adb.g_adb_repeat_rate = option.getIntValue(OPTION_ADBRATE);

		int ar = option.getIntValue(OPTION_AUDIORATE);
		if (ar)
		{
			if (ar<11025  ||  ar>44100)
				ar  = 22100;
			g_sound.g_audio_enable = 1;
			g_sound.g_audio_rate = ar;
		}
		else 
			g_sound.g_audio_enable = 0;

		int mem = option.getIntValue(OPTION_MEMORYSIZE);

		if (mem <=8 && mem>=0)
			mem *= 1024*1024;
		else
		if (mem>=128 && mem<=8*1024)
		{
			mem *= 1024;
		}
		else
		if (mem>=128*1024 && mem<=8*1024*1024)
		{
		}
		else
			mem  = 2*1024*1024;

		g_sim65816.g_mem_size_exp = mem;

		if (config->bootslot!=6 && g_sim65816.g_use_apple2e_rom)
		{
			printf("disabling apple 2e rom (only works when booting from slot6\n");
			g_sim65816.g_use_apple2e_rom = 0;
		}

		
		
		g_savestate.init();
		if (config->stateFilename.IsEmpty())
		{
		}
		else
		{
			
            printf("about to restore : %s\n",config->stateFilename.c_str());
			g_savestate.restoreState(config->stateFilename.c_str());
		}

		printf("*** notifying boot option ***\n");
		x_refresh_panel(PANEL_BOOT);
		printf("*** launching Emulator Mainloop\n");
			
#ifndef MAC
		kegsmain(0,NULL);
#else
		extern int macmain(int,char**);
		macmain(0,NULL);		
#endif
		g_savestate.shut();

		joystick_shut();	
		
	
		if (!r_sim65816.restart_requested)
			break;
		else
		{
			outputInfo("*** Restarting emulator\n");
			r_sim65816.restart_requested = 0;
		
			outputInfo("*** bRestarting\n");
			setConfig(config);
		}
	}
	
#endif // TEST_RASTER

	outputInfo("end of emulator thread\n");
	
	bInThread=false;
	return 0;

	
}

void CEmulator::forceFocus()
{
	// TODO OG
}


void CEmulator::readImageInfo(int _slot,int _drive, int _imagelen, int _imagestart, int _isprodos)
{
	if (!this) return ;
	CSlotInfo& ref = getLocalIMGInfo(_slot,_drive);
	ref.size  = _imagelen;
	MyString prefix("?");
	if (_isprodos) //_info.track0_len!=-1)
	{
		char ptr[32];
		memset(ptr,0,sizeof(ptr));
		const char* filename = ref.filename.c_str(); 
		if (filename) 
		{
			FILE* f = fopen(filename,"rb");
			if (f)
			{
				fseek(f,_imagestart+0x404,SEEK_SET);
				if (16==fread(ptr,1,16,f))
				if ( ((ptr[0]&0xF0)==0xF0) )
				{	
					int len= ptr[0]&0x0F;
					ptr[len+1] = 0;
					prefix = "/";
					prefix += (ptr+1);
				}
				
				fclose(f);
			}
		}
	}
	else
		prefix = "NON-PRODOS";
	ref.prefix = prefix;
}

int CEmulator::getLocalMultipleIMGInfo(int _slot,int _drive,CSlotInfo& info, int _pos)
{
	CActiveImage& active = config->getActiveImage(_slot,_drive);
	if (_pos>=active.iNb) return 0;
	info = config->getLocalImage(_slot,_drive,_pos); 
	if (active.iActive==_pos) return 1;
	else
		return -1;
}

int CEmulator::mountDelayedDisk()
{
	for(int slot=5;slot<=7;slot++)
		for(int disk=1;disk<=2;disk++)
		{
			CSlotInfo& ref = getLocalIMGInfo(slot,disk);
			if (ref.status==DELAYEDMOUNT)
			{
				outputInfo("status set to READY2MOUNT for S%dD%d\n",slot,disk);
				ref.status=READY2MOUNT;
			}

		}
	return 0;
}

bool CEmulator::remainingDelayedDisk()
{
	for(int slot=5;slot<=7;slot++)
		for(int disk=1;disk<=2;disk++)
		{
			CSlotInfo& ref = getLocalIMGInfo(slot,disk);
			if (ref.status==DELAYEDMOUNT)
				return true;
		}
	return false;
}

int CEmulator::getSmartSwap()
{
	int found = 0;
	for(int slot=5;slot<=6;slot++)
	{
		for(int disk=1;disk<=2;disk++)
		{
			CActiveImage& active = config->getActiveImage(slot,disk);
			if (active.iNb > 1)
			{
				if (found)
				{
					// multiple multiple : does not know how to handle
					return 0;
				}
				else
					found = slot*10+disk;
			}
		}
	}
	return found;
}

int CEmulator::smartSwap()
{
	int smart = getSmartSwap();
	if (!smart)
	{
		outputInfo("smart swap not available\n");
		return 0;
	}
	return swapDisk(smart/10,smart%10);
}



int CEmulator::swapDisk(int _slot,int _drive, int pos )
{
	CActiveImage& active = config->getActiveImage(_slot,_drive);
	if (active.iNb <=1)
	{
		outputInfo("nothing to swap\n");
		return 0;
	}

	CSlotInfo& ref = getLocalIMGInfo(_slot,_drive);
	if (ref.status != UNDEFINED) 
	{
		ref.status = EJECTED ;
		outputInfo("status set to EJECTED for S%dD%d\n",_slot,_drive);
	}
	//ejectDisk(_slot,_drive);

 
        // passe à la suivante
        if (pos<0)
            pos = active.iActive+1;
        if (pos>=active.iNb)
            pos=0;
    
	active.iActive = pos;
	outputInfo("swapping to entry %d\n",pos);

	CSlotInfo& info = getLocalIMGInfo(_slot,_drive);
	if (!info.url.IsEmpty())
	{
		outputInfo("status set to DELAYEDMOUNT for S%dD%d\n",_slot,_drive);
		info.status = DELAYEDMOUNT;
		info.delay = (int)(60*1.5);	// 5s
	}

	config->need2remount=true;
	x_refresh_panel(PANEL_DISK);
	return 1;
}

CSlotInfo& CEmulator::getLocalIMGInfo(int _slot,int _drive)
{
	CActiveImage& active = config->getActiveImage(_slot,_drive);
	return config->getLocalImage(_slot,_drive,active.iActive);
}


int CEmulator::mountImages()
{
	if (!this) return 0;

#if defined(ACTIVEGSPLUGIN) && !defined(DRIVER_IOS)
	checkAlerts();
#endif

	if (config->need2remount)
	{
		//outputInfo("mounting images...\n");
		
		int ri = 0;
		for(int slot=5;slot<=7;slot++)
		{
			int maxdrive=2;
			if (slot==7) maxdrive=MAXSLOT7DRIVES;
			for(int drive=1;drive<=maxdrive;drive++)
			{
				ri |= loadDiskImage(/*name,*/slot,drive);
				if (r_sim65816.should_emulator_terminate())
					return 0 ;
				// regarde si il faut patcher le disque systËme
				if (setStart.slot == (slot*10)+drive)
					setStartParam();
			}
		}
			
		config->need2remount = (int)remainingDelayedDisk();
		if (ri)
			x_refresh_panel(PANEL_DISK);

		return 1;	
	}
	else
		return 0;

}

int CEmulator::loadDiskImage(int _slot,int _drive)
{
	ASSERT(theEmulator==this);

	CSlotInfo& ref = getLocalIMGInfo(_slot,_drive);
	if (ref.status != READY2MOUNT) 
		return 0;

	if (ref.url.IsEmpty())
	{
		ref.status = NOTHING;
		outputInfo("status set to NOTHING for S%dD%d\n",_slot,_drive);
		ref.filename.Empty();
		ref.shortname.Empty();
		return 1;
	}

	MyString path;
	MyString shortname;

	CDownload dl(this->config->baseURL);
#ifdef DRIVER_ANDROID
	dl.fromMainThread = false;
#endif


	if (dl.retrieveFile(STRING2CONSTCHAR(ref.url),path,shortname))
	{
		ref.filename = path ;
		ref.shortname = shortname ;
		ref.status = MOUNTED ;
		outputInfo("status set to MOUNTED for S%dD%d\n",_slot,_drive);
		//ref.delay = 1;
		::showStatus("Image %s mounted [S%dD%d]",ref.shortname.c_str(),_slot,_drive);
		return 1 ;
	}
	else
	{
		ref.status = FAILED ;
		outputInfo("status set to FAILED for S%dD%d\n",_slot,_drive);
		ref.shortname = getfile(STRING2CONSTCHAR(ref.url));
		return 1;
	}

}

void CEmulator::resetLocalIMG(void)
{
}

// Interface

const char* getLocalIMG(int _slot,int _drive)
{
	CEmulator* emu = CEmulator::theEmulator;
	CSlotInfo& ref = emu->getLocalIMGInfo(_slot,_drive);
	
	if (ref.status == DELAYEDMOUNT)
	{
		ref.delay--;
		if (ref.delay<=0)
		{
			ref.status = READY2MOUNT;
			outputInfo("status set to READY2MOUNT for S%dD%d : image %s\n",_slot,_drive,ref.shortname.c_str());
		}
		emu->config->need2remount=true;
		return NULL;
	}

	if (ref.status == MOUNTED)
		return ref.filename.c_str() ;
	else
		return NULL;
}


int mountImages()
{
	return CEmulator::theEmulator->mountImages();
}

void updateBramParameters(unsigned char* bramPtr)
{
	return CEmulator::theEmulator->updateBramParameters();
}

void readImageInfo(int _slot,int _drive, int _imagelen, int _imagestart, int _isprodos)
{
	return CEmulator::theEmulator->readImageInfo(_slot,_drive,_imagelen,_imagestart,_isprodos);
}

/*
void refreshInfo()
{
	CEmulator* emu = CEmulator::theEmulator;
	if (!emu) return ;
	emu->infoRefresh();
}
*/

void disableConsole()
{
	CEmulator* emu = CEmulator::theEmulator;
	if (!emu) return ;
	emu->disableConsole();

}

int activeRead(char* buf)
{
	CEmulator* emu = CEmulator::theEmulator;
	if (!emu)
	{
		buf[0]=0;
		return 0;
	}
	else
		return (emu->activeRead(buf));

}



void	COption::enableOptions()
{

	
	r_sim65816.g_ignore_bad_acc = !getIntValue(OPTION_HALTONBADACC);
	r_sim65816.g_ignore_halts = !getIntValue(OPTION_HALTONHALTS);
	r_sim65816.g_halt_on_bad_read = getIntValue(OPTION_HALTONBADREAD);
	r_sim65816.set_rewind_enable(getIntValue(OPTION_ENABLEREWIND));
	
	r_sim65816.set_color_mode( (colormodeenum)getIntValue(OPTION_COLORMODE));
	r_sim65816.set_video_fx((videofxenum)getIntValue(OPTION_VIDEOFX));
	
	g_joystick_type = getIntValue(OPTION_JOYSTICKMODE);
#ifndef VIRTUALAPPLE
	g_sim65816.set_fixed_vblank( getIntValue(OPTION_FIXEDVBLANK),(speedenum)getIntValue(OPTION_SPEED) );
#endif
    
	g_sim65816.g_enable_disk_sound = getIntValue(OPTION_DISKSOUND);
    
	g_paddles.g_joystick_scale_factor_x = (getIntValue(OPTION_JOYXSCALE)*256)/1000; // 0x100 = 1
	g_paddles.g_joystick_scale_factor_y = (getIntValue(OPTION_JOYYSCALE)*256)/1000; // 0x100 = 1
	
    
    g_adb.g_simulate_space = getIntValue(OPTION_SIMULATESPACE);
    

	if (r_sim65816.get_state()==RUNNING)
		paddle_fixup_joystick_type();	
#ifdef DRIVER_IOS
	extern int x_frame_rate;
	extern int x_lock_zoom;	
	x_frame_rate = getIntValue(OPTION_FRAMERATE);
#warning "TODO CHANGE FPS IN REALTIME"
	x_lock_zoom = getIntValue(OPTION_LOCKZOOM);
#endif
	
	x_refresh_panel(PANEL_RUNTIME);


}


void	CEmulator::setLight(int _motorOn,int _slot, int _drive, int _track)
{
	int id=0;
	for(int slot=5;slot<=6;slot++)
		for(int drive=1; drive<=2; drive++)
		{
			if ( (_slot==slot) && (_drive==drive) )
			{
				lights[id]=_motorOn;
				tracks[id]=_track;
			}
			else
				lights[id]=0;
			id++;	
		}
	
}

//extern char targetSpeed[256];
extern char estimatedSpeed[256];


void  CEmulator::processCommand( const char* _val, MyString& ret)
{
//	outputInfo("processingCommand:%s\n",_val);
	if (!strcmp(_val,"help"))
		ret = "({ commands : [ \"help\" , \"version\", \"system\", \"status\", \"runtime\", \"reboot\",\"savestate\", \"restorestate\",  \"pause\", \"resume\" ,\"pauseresume\", \"swap\", \"screenshot\",\"saveoptions\",\"defaultoptions\",\"sendkey:ctrl;alt;esc\" ] })";
	else
	if (!strcasecmp(_val,"saveoptions"))
	{
		option.saveOptions(1);
		ret = "({ \"info\" : \"options saved\"})";
	}
	else
	if (!strcasecmp(_val,"defaultOptions"))
	{
		option.setDefaultOptions();
		option.enableOptions();
		ret = "({ \"info\" : \"default options restored\"})";
	}
	else
	if (!strncasecmp(_val,"sendkey:",8 /* sizeof ("sendkey:")*/))
	{
#if defined (DRIVER_OSX) && !defined(ACTIVEGSPLUGIN)
		ret = "({ \"info\" : \"not implemented\"})";
#else
		const char* keystr[] = { "^ctrl" , "^alt", "^esc", "^reset", "^option", NULL };
		const int keycode[] = { 0x36 , 0x37 , 0x35, 0x7F, 0x3A, 0 };
#define MAX_CODE 100
		int	code[MAX_CODE];
		int nbcode=0;
		const char* ptr = _val+8;
		MyString keysent;
		while(*ptr)
		{
			const char* p = ptr;
			while(*p && *p!=';') p++;
			int l = p-ptr;
			int k = 0;
			while(keystr[k])
			{
				if (!strncasecmp(keystr[k],ptr,l))
				{
					code[nbcode++]=k;
				//	printf("found %s (%X)\n",keystr[k],keycode[k]);
					keysent+=keystr[k];
					keysent+=";";
					break;
				}
				k++;
			}
			if (!keystr[k])
				printf("key not valid %s\n",ptr);
			ptr = p;
			if (*ptr==';') ptr++;
			if (nbcode==MAX_CODE) break;
		}

		for(int i=0;i<nbcode;i++)
		{
			add_event_key(keycode[code[i]],0);
			for(int j=0;j<30;j++)
				add_event_delay();
		}
		
		for(int i=0;i<60;i++)
				add_event_delay();

		for(int i=nbcode-1;i>=0;i--)
		{
			add_event_key(keycode[code[i]],1);
		}
	
		ret.Format("({ \"info\" : \"key sent %s\"})",keysent.c_str());
#endif
	}
	else
	if (!strcasecmp(_val,"version"))
	{
		ret = "({ \"info\" : \"" ACTIVEGSVERSIONSTRFULL "\"})";
	}
	else
		/*
	if (!strcmp(_val,"rewind"))
	{
		stateActionRequired = STATE_REWIND;
		ret = "({ \"info\" : \"rewind activated\"})";
	}
	else
	*/
	if (!strcasecmp(_val,"system"))
	{
		ret = "( { ";
		
		ret += " \"currentBuild\" : \"" TOSTRING(ACTIVEGSMAJOR) "." TOSTRING(ACTIVEGSMINOR) "." TOSTRING(ACTIVEGSBUILD) "\",";
		MyString str;
		int g_joystick_plugged = (g_joystick_native_type1 != -1) || (g_joystick_native_type2 != -1) ? 1 : 0 ;
		str.Format("\"joystickPlugged\" : %d,",g_joystick_plugged);
		ret += str.c_str();
		MyString engine;
		getEngineString(engine);
		
		str.Format("\"renderingEngine\" : \"%s\",",engine.c_str());
		ret += str.c_str();
		
		str.Format("\"memorySize\" : %d,",option.getIntValue(OPTION_MEMORYSIZE));
		ret += str.c_str();

		str.Format("\"audioRate\" : %d",option.getIntValue(OPTION_AUDIORATE));
		ret += str.c_str();

		
		ret +="})";
	}	
	else	
	if (!strcasecmp(_val,"reboot"))
	{
		// Force la relecture du fichier XML!
		config->xmlAlreadyLoaded = 0;
		onBootSlotChanged(-1);	// Bootslot Auto
		ret = "({ \"info\" : \"Rebooted!\" })";
	}	
	else
	if (!strcasecmp(_val,"status"))
	{
		MyString str;
		ret = "( {";	
		str.Format("\"vbl\" : %d,",g_sim65816.g_vbl_count);
		ret += str;
		str.Format("\"speed\" : { estimated:\"%s\", target:\"",estimatedSpeed);
		if (!g_sim65816.g_speed_fast)
			str +="1";
		else
			str += option.getDescription(OPTION_SPEED,g_sim65816.get_limit_speed());
				str +="\"},";
		ret += str;
		ret += " \"lights\" : {";
		for(int i=0;i<4;i++)
		{
			str.Format(" \"slot%d%d\" : { \"on\" : %d, \"track\" : %d }",(i/2)+5,(i&1)+1,lights[i],tracks[i]);
			ret += str;	
			if (i!=3)
				ret+=",";
	
		}
		ret += "}";
		ret += "} )";
	}	
	else
	if (!strcasecmp(_val,"runtime"))
	{
		MyString str;
		ret = "( {";
		str.Format("\"pause\" : %d,",r_sim65816.get_state()==IN_PAUSE);
		ret += str;
		str.Format("\"swap\" : %d,",getSmartSwap());
		ret += str;
		str.Format("\"rewindAvailable\" : %d,",r_sim65816.get_rewind_enable());
		ret += str.c_str();
		
		str.Format("\"savedState\" : %d,", g_savestate.getSavedStateVBL());
		ret += str.c_str();
		
		ret += "\"slots\" : [";
		int nb = 0;
		for(int slot=5;slot<=7;slot++)
		{
			int maxdrive=2;
			if (slot==7) maxdrive=MAXSLOT7DRIVES;
			for(int disk=1;disk<=maxdrive;disk++)
				{
					for(int active=0;active<ACTIVEGSMAXIMAGE;active++)
					{
					 CSlotInfo info;
					int g = getLocalMultipleIMGInfo(slot,disk,info,active);
					if (!g) continue;
					if (info.status==UNDEFINED || info.status ==NOTHING) continue;
					if (g!=1) g=0;
					MyString prefix;
					switch(info.status)
					{
						case MOUNTED:
							prefix = "MOUNTED";;
							break;
						case EJECTED:
							prefix = "EJECTED";
							break;
						case READY2MOUNT:
							prefix = "READY2MOUNT";
							break;
						case DELAYEDMOUNT:
							prefix = "DELAYEDMOUNT";
							break;
						case FAILED:
							prefix	="FAILED";
							break;
                        default:
                            prefix = "N/A";
                            break;
					}
				
					if (nb)
						ret +=",";
					MyString str;
					str.Format(" { \"slot\" : %d%d, \"active\" : %d, \"name\" : \"%s\" , \"status\" : \"%s\" }",slot,disk,g,info.shortname.c_str(),prefix.c_str());
					
					ret+=str.c_str();
					nb++;
				}
			}
		}
		ret+="]";
		str.Format(",\"joystickMode\" : \"%s\"",option.getDescription(OPTION_JOYSTICKMODE,g_joystick_type));
		ret += str.c_str();
		ret += "} )";
	}	
	else
	if (!strcasecmp(_val,"pause"))
	{
	//	extern int g_config_control_panel;
	//	if (!isInitialized())
		if (r_sim65816.get_state()==RUNNING)
		{
			r_sim65816.pause();
			ret = "({ \"info\" : \"Paused!\" , \"pause\" : 1 })";
		}
		else
		if (r_sim65816.get_state()==IN_PAUSE)
			ret = "({ \"error\" : \"already in pause\" })";
		else
			ret = "({ \"error\" : \"emulator not accepting event\" })";
		
	}	
	else
	if (!strcasecmp(_val,"pauseresume"))
	{
	
		if (r_sim65816.get_state()==RUNNING)
		{
			r_sim65816.pause();
			ret = "({ \"info\" : \"Paused!\" , \"pause\" : 1 })";
		}
		else
		if (r_sim65816.get_state()==IN_PAUSE)
		{
			r_sim65816.resume();
			ret = "({ \"info\" : \"Resumed!\" , \"pause\" : 0 })";
		}
		else
			ret = "({ \"error\" : \"emulator not accepting event\" })";
		
	}	
	else
	if (!strcasecmp(_val,"savestate"))
	{
		if (!r_sim65816.get_rewind_enable())
			ret= "({\"error\" : \"Feature not available\"})";
			g_savestate.saveState(NULL);
		//	int vbl = g_savestate.getSavedStateVBL();
			MyString str;
			str.Format("({\"info\" : \"State Saved\",})",g_sim65816.g_vbl_count);
			ret= str;
	}
	else
	if (!strcasecmp(_val,"restorestate"))
	{
		if (!r_sim65816.get_rewind_enable())
			ret= "({\"error\" : \"Feature not available\"})";
		int vbl = g_savestate.getSavedStateVBL();
		if (!vbl)
			ret= "({\"error\" : \"Nothing to restore\" })";
		else
		{
			g_savestate.restoreState(NULL);
			MyString str;
			str.Format("({\"info\" : \"State Restored\", \"vbl\" : %d })",vbl);
			ret= str;
		}
	}
	else
	if (!strcasecmp(_val,"resume"))
	{
	//	extern int g_config_control_panel;
		if (r_sim65816.get_state()==IN_PAUSE)
		{
				r_sim65816.resume();
				ret = "({ \"info\" : \"Resumed!\" , \"pause\" : 0 })";
		}
		else
		if (r_sim65816.get_state()==RUNNING)
			ret = "({ \"error\" : \"nothing to resume\" })";
		else
			ret = "({ \"error\" : \"Emulator not accepting event\" })";
	
	}	
	else
	if (!strcasecmp(_val,"screenshot"))
	{
		extern int savePNG(MyString&);
		savePNG(ret);
	}	
	else
	if (!strcasecmp(_val,"swap"))
	{
		int sw = getSmartSwap();
		if (!sw)
			ret= "({\"error\" : \"Nothing to swap\"})";
		else
		{
			smartSwap();
			CSlotInfo& info = this->getLocalIMGInfo(sw/10,sw%10);
			ret = info.shortname;
			ret.Format("({ \"info\" : \"disk %s inserted\", \"slot\" : %d, name : \"%s\" })",info.shortname.c_str(),sw,info.shortname.c_str());
		}
	}
	else
	{
		ret="({ \"error\" : \"unsupported command:";
		ret += _val ;
		ret += "\"})";
	}
}


// initialize le son depuis le thread principal
void	CEmulator::launchEmulator()
{
	x_async_sound_init();
	//CDownload::initPersistentPath();
	x_LaunchThread();
	// attend la fin de l'émulateur
//	x_WaitForEndThread();
	
}



void	CEmulator::terminateEmulator()
{
	outputInfo("terminateCEmulator (%X)\n",this);
	
	if (bInThread)
	{
		
		// envoie le signal de quit
		r_sim65816.quit_emulator();
		
		// attend la fin de l'émulateur
		x_WaitForEndThread();
		
		x_async_snd_shutdown();
		
	}
	
	// l'émulateur a quitté correctement : il est pret à redémarrer
	r_sim65816.reset_quit();
	// OG 080111 Force un redraw pour rafraichir le contenu du browser si besoin 
	refresh_video(1);
	
	
}

CEmulator::~CEmulator()
{
	outputInfo("~CEmulator (%X)",this);

	// termine le singleton
	theEmulator=NULL;

	if (parent)
		*parent = NULL;

	
}

void CEmulator::onXMLConfigChanged(const char *_xmlconfig,simplexml* _pXML)
{
	if (!acceptEvents())
	{
		outputInfo("### onXMLConfigChanged Ignored");
		return;
	}

	MyString newxmlconfig = _xmlconfig; // le copie si la valeur était déjà stockée dans xmlconfig
//	config->resetConfig(); // remets les valeur par défault
	config->xmlconfig = newxmlconfig.c_str();
	config->pXML = _pXML;
	config->xmlAlreadyLoaded=0; // pour forcer la relecture au prochain chargement
	relaunchEmulator();

}

extern void addNotificationWindow();

void addConsoleWindow(Kimage* _dst)
{
#if defined(_DEBUG) && defined(WIN32) && 0
	extern void addConsole(const char*);

//	extern char estimatedSpeed[256];
	extern	void addConsole(const char* _str);
//	extern	int getFrameRate(int _id);
	extern	void drawConsole(Kimage* _dst);
	char str[256];
	int fr0 = getFrameRate(0);
	int fr1 = getFrameRate(1);
	int fr2 = getFrameRate(2);
	int fr3 = getFrameRate(3);
	sprintf(str,"fps:%02d.%0d 60hz:%02d.%0d req:%02d.%0d c054:%02d.%0d ",fr0/10,fr0%10,fr3/10,fr3%10,fr1/10,fr1%10,fr2/10,fr2%10);
//	sprintf(str,"p1:%d p2:%d fps:%d ",getFrameRate(3),getFrameRate(4),getFrameRate(1));
	addConsole(str);
	drawConsole(_dst);
#endif
//	addNotificationWindow();
}




#ifdef TEST_RASTER
int x_test_raster()
{
	x_video_init();
	r_sim65816.set_state(RUNNING);
	
	while(!r_sim65816.should_emulator_terminate())
	{
		
		static int pos=0;
	
		// faire scroller 
		
		//	int	bitmapBytesPerRow   = (pixelsWide * 4);// 1
		int bitmapBytesPerRow = g_kimage_offscreen.width_act * (g_kimage_offscreen.mdepth >> 3);

		int destx=0;
		int desty = 0;
		int col = pos;
		
		x_wait_for_last_refresh(1.0/60.0);

		CHANGE_BORDER(0,0x00FF00);

		byte* ptrdest = (byte*)g_kimage_offscreen.data_ptr ;
		for(int i=0;i<g_kimage_offscreen.height;i+=1)
		{
			col = pos;
			for(int j=0;j<g_kimage_offscreen.width_act/2;j++)
			{
				((word32*)ptrdest)[j] = col;
				col+=10;
			}
		//	memset(ptrdest,col,bitmapBytesPerRow);
			ptrdest += bitmapBytesPerRow;
		//	col += 10;
		}
		pos+=10;

		CHANGE_BORDER(0,-1);
		x_wait_for_vbl();
		CHANGE_BORDER(0,0);
		DRAW_BORDER();
		refresh_video(1);
		
		g_sim65816.g_vbl_count++;
	}

	r_sim65816.set_state(SHUTTING_DOWN);
	
	
	x_video_shut();

	return 1;
	
}

#endif

float g_notification_y=1.0;
#ifdef _DEBUG
int g_notification_show=1;
#else
int g_notification_show=0;
#endif
int g_notification_motion = 1;
float g_notification_speed = 1.0;


#define NOTIFICATION_HEIGHT 50
#define NOTIFICATION_WIDTH 300
#define NOTIFICATION_MARGIN 20


void addNotificationWindow()
{

	if (g_kimage_offscreen.mdepth!=32) return ;
	if (!g_notification_show) return ;
	
	int bitmapBytesPerRow = g_kimage_offscreen.width_act * (g_kimage_offscreen.mdepth >> 3);

	int destx= g_kimage_offscreen.width_act - NOTIFICATION_WIDTH - NOTIFICATION_MARGIN ;
		int col = 0xFFFFFF;
		
	for(int i=0;i<NOTIFICATION_HEIGHT;i+=1)
	{
		int y = (int)g_notification_y + i;
		if (y<0) continue;
		if (y>=g_kimage_offscreen.height) continue;

		byte* ptrdest = (byte*)g_kimage_offscreen.data_ptr + y*bitmapBytesPerRow;

		for(int j=0;j<NOTIFICATION_WIDTH;j++)
		{
			int x = j + destx;
			if ( (x<0) || (x>g_kimage_offscreen.width_act ) ) continue;
			((word32*)ptrdest)[x] = col;
		}
	}
	if (g_notification_motion)
	{
		g_notification_y += g_notification_speed;
		g_notification_speed += 0.5f;
		if (g_notification_y>= g_kimage_offscreen.height-(NOTIFICATION_HEIGHT+NOTIFICATION_MARGIN))
		{
			g_notification_y = (float)(g_kimage_offscreen.height-(NOTIFICATION_HEIGHT+NOTIFICATION_MARGIN));
			if (g_notification_speed<2.0f)
			{
				g_notification_motion = 0;
				g_notification_show = 3*60;
			}
			else
				g_notification_speed *= -0.5f;		
			
		}
	}
	else
	{
		g_notification_show--;
	}

//	g_notification_y++;
	if (g_notification_y>g_kimage_offscreen.height+NOTIFICATION_HEIGHT)
		g_notification_y = -NOTIFICATION_HEIGHT;

		g_video.g_needfullrefreshfornextframe = 1;

}




