/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

/***********************************************************************************
  SNES9X for Mac OS (c) Copyright John Stiles

  Snes9x for Mac OS X

  (c) Copyright 2001 - 2011  zones
  (c) Copyright 2002 - 2005  107
  (c) Copyright 2002         PB1400c
  (c) Copyright 2004         Alexander and Sander
  (c) Copyright 2004 - 2005  Steven Seeger
  (c) Copyright 2005         Ryan Vogt
 ***********************************************************************************/


#include "port.h"
#include "cheats.h"

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#include "mac-prefix.h"
#include "mac-dialog.h"
#include "mac-os.h"
#include "mac-stringtools.h"
#include "mac-cheat.h"

#define	kDataBrowser	'BRSR'
#define	kCmCheckBox		'CHK_'
#define	kCmAddress		'ADDR'
#define	kCmValue		'VALU'
#define	kCmDescription	'DESC'
#define	kNewButton		'NEW_'
#define	kDelButton		'DEL_'
#define	kAllButton		'ALL_'

extern SCheatData	Cheat;

typedef struct
{
	uint32	id;
	uint32	address;
	uint8	value;
	bool8	valid;
	bool8	enabled;
	char	description[22];
}	CheatItem;

static WindowRef	wRef;
static HIViewRef	dbRef;
static CheatItem	citem[MAC_MAX_CHEATS];
static uint32		numofcheats;

static void InitCheatItems (void);
static void ImportCheatItems (void);
static void DetachCheatItems (void);
static void AddCheatItem (void);
static void DeleteCheatItem (void);
static void EnableAllCheatItems (void);
static pascal void DBItemNotificationCallBack (HIViewRef, DataBrowserItemID, DataBrowserItemNotification);
static pascal Boolean DBCompareCallBack (HIViewRef, DataBrowserItemID, DataBrowserItemID, DataBrowserPropertyID);
static pascal OSStatus DBClientDataCallback (HIViewRef, DataBrowserItemID, DataBrowserPropertyID, DataBrowserItemDataRef, Boolean);
static pascal OSStatus CheatEventHandler (EventHandlerCallRef, EventRef, void *);


static void InitCheatItems (void)
{
	for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
	{
		citem[i].id      = i + 1;
		citem[i].valid   = false;
		citem[i].enabled = false;
		citem[i].address = 0;
		citem[i].value   = 0;
		sprintf(citem[i].description, "Cheat %03" PRIu32, citem[i].id);
	}
}

static void ImportCheatItems (void)
{
    int cheat_num = std::min((int)Cheat.g.size(), MAC_MAX_CHEATS);
	for (unsigned int i = 0; i < cheat_num; i++)
	{
		citem[i].valid   = true;
		citem[i].enabled = Cheat.g[i].enabled;
		citem[i].address = Cheat.g[i].c[0].address; // mac dialog only supports one cheat per group at the moment
		citem[i].value   = Cheat.g[i].c[0].byte;
		strncpy(citem[i].description, Cheat.g[i].name, 21);
		citem[i].description[21] = '\0';
	}
}

static void DetachCheatItems (void)
{
	S9xDeleteCheats();

	for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
	{
		if (citem[i].valid)
		{
			char code[10];
			snprintf(code, 10, "%x=%x", citem[i].address, citem[i].value);
			int index = S9xAddCheatGroup(citem[i].description, code);
			if(citem[i].enabled && index >= 0)
				S9xEnableCheatGroup(index);
		}
	}
}

void ConfigureCheat (void)
{
	if (!cartOpen)
		return;

	OSStatus	err;
	IBNibRef	nibRef;

	err = CreateNibReference(kMacS9XCFString, &nibRef);
	if (err == noErr)
	{
		err = CreateWindowFromNib(nibRef, CFSTR("CheatEntry"), &wRef);
		if (err == noErr)
		{
			DataBrowserCallbacks	callbacks;
			EventHandlerRef			eref;
			EventHandlerUPP			eUPP;
			EventTypeSpec			events[] = { { kEventClassCommand, kEventCommandProcess      },
												 { kEventClassCommand, kEventCommandUpdateStatus },
												 { kEventClassWindow,  kEventWindowClose         } };
			HIViewRef				ctl, root;
			HIViewID				cid;

			root = HIViewGetRoot(wRef);
			cid.id = 0;
			cid.signature = kDataBrowser;
			HIViewFindByID(root, cid, &dbRef);

		#ifdef MAC_PANTHER_SUPPORT
			if (systemVersion < 0x1040)
			{
				HISize	minSize;
				Rect	rct;

				GetWindowBounds(wRef, kWindowContentRgn, &rct);
				minSize.width  = (float) (rct.right  - rct.left);
				minSize.height = (float) (rct.bottom - rct.top );
				err = SetWindowResizeLimits(wRef, &minSize, NULL);
			}
		#endif

			callbacks.version = kDataBrowserLatestCallbacks;
			err = InitDataBrowserCallbacks(&callbacks);
			callbacks.u.v1.itemDataCallback = NewDataBrowserItemDataUPP(DBClientDataCallback);
			callbacks.u.v1.itemCompareCallback = NewDataBrowserItemCompareUPP(DBCompareCallBack);
			callbacks.u.v1.itemNotificationCallback = NewDataBrowserItemNotificationUPP(DBItemNotificationCallBack);
			err = SetDataBrowserCallbacks(dbRef, &callbacks);

			if (systemVersion >= 0x1040)
				err = DataBrowserChangeAttributes(dbRef, kDataBrowserAttributeListViewAlternatingRowColors, kDataBrowserAttributeNone);

			InitCheatItems();
			ImportCheatItems();

			DataBrowserItemID	*id;

			id = new DataBrowserItemID[MAC_MAX_CHEATS];
			if (!id)
				QuitWithFatalError(0, "cheat 01");

			numofcheats = 0;

			for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
			{
				if (citem[i].valid)
				{
					id[numofcheats] = citem[i].id;
					numofcheats++;
				}
			}

			if (numofcheats)
				err = AddDataBrowserItems(dbRef, kDataBrowserNoItem, numofcheats, id, kDataBrowserItemNoProperty);

			delete [] id;

			cid.signature = kNewButton;
			HIViewFindByID(root, cid, &ctl);
			if (numofcheats == MAC_MAX_CHEATS)
				err = DeactivateControl(ctl);
			else
				err = ActivateControl(ctl);

			cid.signature = kAllButton;
			HIViewFindByID(root, cid, &ctl);
			if (numofcheats == 0)
				err = DeactivateControl(ctl);
			else
				err = ActivateControl(ctl);

			cid.signature = kDelButton;
			HIViewFindByID(root, cid, &ctl);
			err = DeactivateControl(ctl);

			eUPP = NewEventHandlerUPP(CheatEventHandler);
			err = InstallWindowEventHandler(wRef, eUPP, GetEventTypeCount(events), events, (void *) wRef, &eref);

			err = SetKeyboardFocus(wRef, dbRef, kControlFocusNextPart);

			MoveWindowPosition(wRef, kWindowCheatEntry, true);
			ShowWindow(wRef);
			err = RunAppModalLoopForWindow(wRef);
			HideWindow(wRef);
			SaveWindowPosition(wRef, kWindowCheatEntry);

			err = RemoveEventHandler(eref);
			DisposeEventHandlerUPP(eUPP);

			DisposeDataBrowserItemNotificationUPP(callbacks.u.v1.itemNotificationCallback);
			DisposeDataBrowserItemCompareUPP(callbacks.u.v1.itemCompareCallback);
			DisposeDataBrowserItemDataUPP(callbacks.u.v1.itemDataCallback);

			CFRelease(wRef);

			DetachCheatItems();
		}

		DisposeNibReference(nibRef);
	}
}

static void AddCheatItem (void)
{
	OSStatus			err;
	HIViewRef			ctl, root;
	HIViewID			cid;
	DataBrowserItemID	id[1];
	unsigned int		i;

	if (numofcheats == MAC_MAX_CHEATS)
		return;

	for (i = 0; i < MAC_MAX_CHEATS; i++)
		if (citem[i].valid == false)
			break;

	if (i == MAC_MAX_CHEATS)
		return;

	numofcheats++;
	citem[i].valid   = true;
	citem[i].enabled = false;
	citem[i].address = 0;
	citem[i].value   = 0;
	sprintf(citem[i].description, "Cheat %03" PRIu32, citem[i].id);

	id[0] = citem[i].id;
	err = AddDataBrowserItems(dbRef, kDataBrowserNoItem, 1, id, kDataBrowserItemNoProperty);
	err = RevealDataBrowserItem(dbRef, id[0], kCmAddress, true);

	root = HIViewGetRoot(wRef);
	cid.id = 0;

	if (numofcheats == MAC_MAX_CHEATS)
	{
		cid.signature = kNewButton;
		HIViewFindByID(root, cid, &ctl);
		err = DeactivateControl(ctl);
	}

	if (numofcheats)
	{
		cid.signature = kAllButton;
		HIViewFindByID(root, cid, &ctl);
		err = ActivateControl(ctl);
	}
}

static void DeleteCheatItem (void)
{
	OSStatus	err;
	HIViewRef	ctl, root;
	HIViewID	cid;
	Handle		selectedItems;
	ItemCount	selectionCount;

	selectedItems = NewHandle(0);
	if (!selectedItems)
		return;

	err = GetDataBrowserItems(dbRef, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, selectedItems);
	selectionCount = (GetHandleSize(selectedItems) / sizeof(DataBrowserItemID));

	if (selectionCount == 0)
	{
		DisposeHandle(selectedItems);
		return;
	}

	err = RemoveDataBrowserItems(dbRef, kDataBrowserNoItem, selectionCount, (DataBrowserItemID *) *selectedItems, kDataBrowserItemNoProperty);

	for (unsigned int i = 0; i < selectionCount; i++)
	{
		citem[((DataBrowserItemID *) (*selectedItems))[i] - 1].valid   = false;
		citem[((DataBrowserItemID *) (*selectedItems))[i] - 1].enabled = false;
		numofcheats--;
	}

	DisposeHandle(selectedItems);

	root = HIViewGetRoot(wRef);
	cid.id = 0;

	if (numofcheats < MAC_MAX_CHEATS)
	{
		cid.signature = kNewButton;
		HIViewFindByID(root, cid, &ctl);
		err = ActivateControl(ctl);
	}

	if (numofcheats == 0)
	{
		cid.signature = kAllButton;
		HIViewFindByID(root, cid, &ctl);
		err = DeactivateControl(ctl);
	}
}

static void EnableAllCheatItems (void)
{
	OSStatus	err;

	for (unsigned int i = 0; i < MAC_MAX_CHEATS; i++)
		if (citem[i].valid)
			citem[i].enabled = true;

	err = UpdateDataBrowserItems(dbRef, kDataBrowserNoItem, kDataBrowserNoItem, NULL, kDataBrowserItemNoProperty, kCmCheckBox);
}

static pascal OSStatus DBClientDataCallback (HIViewRef browser, DataBrowserItemID itemID, DataBrowserPropertyID property, DataBrowserItemDataRef itemData, Boolean changeValue)
{
	OSStatus 	err, result;
	CFStringRef	str;
	Boolean		r;
	uint32		address;
	uint8		value;
	char		code[256];

	result = noErr;

	switch (property)
	{
		case kCmCheckBox:
			ThemeButtonValue	buttonValue;

            if (changeValue)
			{
				err = GetDataBrowserItemDataButtonValue(itemData, &buttonValue);
				citem[itemID - 1].enabled = (buttonValue == kThemeButtonOn) ? true : false;
 	        }
			else
				err = SetDataBrowserItemDataButtonValue(itemData, citem[itemID - 1].enabled ? kThemeButtonOn : kThemeButtonOff);

			break;

		case kCmAddress:
			if (changeValue)
			{
				err = GetDataBrowserItemDataText(itemData, &str);
				r = CFStringGetCString(str, code, 256, CFStringGetSystemEncoding());
				CFRelease(str);
				if (r)
				{
					Boolean	translated;

					if (S9xProActionReplayToRaw(code, address, value) == NULL)
						translated = true;
					else
					if (S9xGameGenieToRaw(code, address, value) == NULL)
						translated = true;
					else
					{
						translated = false;
						if (sscanf(code, "%" SCNx32, &address) != 1)
							address = 0;
						else
							address &= 0xFFFFFF;
					}

					citem[itemID - 1].address = address;
					sprintf(code, "%06" PRIX32, address);
					str = CFStringCreateWithCString(kCFAllocatorDefault, code, CFStringGetSystemEncoding());
					err = SetDataBrowserItemDataText(itemData, str);
					CFRelease(str);

					if (translated)
					{
						DataBrowserItemID	id[1];

						citem[itemID - 1].value = value;
						id[0] = itemID;
						err = UpdateDataBrowserItems(browser, kDataBrowserNoItem, 1, id, kDataBrowserItemNoProperty, kCmValue);
					}
				}
			}
			else
			{
				sprintf(code, "%06" PRIX32, citem[itemID - 1].address);
				str = CFStringCreateWithCString(kCFAllocatorDefault, code, CFStringGetSystemEncoding());
				err = SetDataBrowserItemDataText(itemData, str);
				CFRelease(str);
			}

			break;

		case kCmValue:
			if (changeValue)
			{
				err = GetDataBrowserItemDataText(itemData, &str);
				r = CFStringGetCString(str, code, 256, CFStringGetSystemEncoding());
				CFRelease(str);
				if (r)
				{
					uint32	byte;

					if (sscanf(code, "%" SCNx32, &byte) == 1)
						citem[itemID - 1].value = (uint8) byte;
					else
					{
						citem[itemID - 1].value = 0;
						err = SetDataBrowserItemDataText(itemData, CFSTR("00"));
					}
				}
			}
			else
			{
				sprintf(code, "%02" PRIX8, citem[itemID - 1].value);
				str = CFStringCreateWithCString(kCFAllocatorDefault, code, CFStringGetSystemEncoding());
				err = SetDataBrowserItemDataText(itemData, str);
				CFRelease(str);
			}

			break;

		case kCmDescription:
			if (changeValue)
			{
				code[0] = 0;
				err = GetDataBrowserItemDataText(itemData, &str);
				strcpy(code, GetMultiByteCharacters(str, 19));
				CFRelease(str);

				if (code[0] == 0)
				{
					code[0] = ' ';
					code[1] = 0;
				}

				strcpy(citem[itemID - 1].description, code);
			}
			else
			{
				str = CFStringCreateWithCString(kCFAllocatorDefault, citem[itemID - 1].description, CFStringGetSystemEncoding());
				err = SetDataBrowserItemDataText(itemData, str);
				CFRelease(str);
			}

			break;

		case kDataBrowserItemIsActiveProperty:
			err = SetDataBrowserItemDataBooleanValue(itemData, true);
			break;

		case kDataBrowserItemIsEditableProperty:
			err = SetDataBrowserItemDataBooleanValue(itemData, true);
			break;

		default:
			result = errDataBrowserPropertyNotSupported;
	}

	return (result);
}

static pascal Boolean DBCompareCallBack (HIViewRef browser, DataBrowserItemID itemOne, DataBrowserItemID itemTwo, DataBrowserPropertyID sortProperty)
{
	Boolean	result = false;

	switch (sortProperty)
	{
		case kCmCheckBox:
			result = (citem[itemOne - 1].enabled && !(citem[itemTwo - 1].enabled)) ? true : false;
			break;

		case kCmAddress:
			result = (citem[itemOne - 1].address <    citem[itemTwo - 1].address)  ? true : false;
			break;

		case kCmValue:
			result = (citem[itemOne - 1].value   <    citem[itemTwo - 1].value)    ? true : false;
			break;

		case kCmDescription:
			result = (strcmp(citem[itemOne - 1].description, citem[itemTwo - 1].description) < 0) ? true : false;
	}

	return (result);
}

static pascal void DBItemNotificationCallBack (HIViewRef browser, DataBrowserItemID itemID, DataBrowserItemNotification message)
{
	OSStatus	err;
	HIViewRef	ctl;
	HIViewID	cid = { kDelButton, 0 };
	ItemCount	selectionCount;

	switch (message)
	{
		case kDataBrowserSelectionSetChanged:
			HIViewFindByID(HIViewGetRoot(wRef), cid, &ctl);

			err = GetDataBrowserItemCount(browser, kDataBrowserNoItem, true, kDataBrowserItemIsSelected, &selectionCount);
			if (selectionCount == 0)
				err = DeactivateControl(ctl);
			else
				err = ActivateControl(ctl);
	}
}

static pascal OSStatus CheatEventHandler (EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
	OSStatus	err, result = eventNotHandledErr;
	WindowRef	tWindowRef;

	tWindowRef = (WindowRef) inUserData;

	switch (GetEventClass(inEvent))
	{
		case kEventClassWindow:
			switch (GetEventKind(inEvent))
			{
				case kEventWindowClose:
					QuitAppModalLoopForWindow(tWindowRef);
					result = noErr;
					break;
			}

			break;

		case kEventClassCommand:
			switch (GetEventKind(inEvent))
			{
				HICommand	tHICommand;

				case kEventCommandUpdateStatus:
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr && tHICommand.commandID == 'clos')
					{
						UpdateMenuCommandStatus(true);
						result = noErr;
					}

					break;

				case kEventCommandProcess:
					err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &tHICommand);
					if (err == noErr)
					{
						switch (tHICommand.commandID)
						{
							case kNewButton:
								AddCheatItem();
								result = noErr;
								break;

							case kDelButton:
								DeleteCheatItem();
								result = noErr;
								break;

							case kAllButton:
								EnableAllCheatItems();
								result = noErr;
						}
					}
			}
	}

	return (result);
}