#define WIN32_LEAN_AND_MEAN

#ifndef _MSC_VER
#define NONAMELESSUNION 1
#endif

#include <windows.h>
#include <string.h>
#include <assert.h>
#include <commctrl.h>
#include <commdlg.h>
#include <windowsx.h>
#include <sys/stat.h>
#include <stdio.h>
#include <tchar.h>

#include "driver.h"
#include "device.h"
#include "ui/screenshot.h"
#include "ui/datamap.h"
#include "ui/bitmask.h"
#include "ui/mame32.h"
#include "ui/directories.h"
#include "ui/m32util.h"
#include "ui/m32opts.h"
#include "resourcems.h"
#include "mess.h"
#include "utils.h"
#include "propertiesms.h"
#include "optionsms.h"
#include "ms32util.h"
#include "messopts.h"
#include "strconv.h"

static BOOL SoftwareDirectories_OnInsertBrowse(HWND hDlg, BOOL bBrowse, LPCTSTR lpItem);
static BOOL SoftwareDirectories_OnDelete(HWND hDlg);
static BOOL SoftwareDirectories_OnBeginLabelEdit(HWND hDlg, NMHDR* pNMHDR);
static BOOL SoftwareDirectories_OnEndLabelEdit(HWND hDlg, NMHDR* pNMHDR);

extern BOOL BrowseForDirectory(HWND hwnd, LPCTSTR pStartDir, TCHAR* pResult);
BOOL g_bModifiedSoftwarePaths = FALSE;



static void MarkChanged(HWND hDlg)
{
	HWND hCtrl;

	/* fake a CBN_SELCHANGE event from IDC_SIZES to force it to be changed */
	hCtrl = GetDlgItem(hDlg, IDC_SIZES);
	PostMessage(hDlg, WM_COMMAND, (CBN_SELCHANGE << 16) | IDC_SIZES, (LPARAM) hCtrl);
}



static void AppendList(HWND hList, LPCTSTR lpItem, int nItem)
{
    LV_ITEM Item;
	memset(&Item, 0, sizeof(LV_ITEM));
	Item.mask = LVIF_TEXT;
	Item.pszText = (LPTSTR) lpItem;
	Item.iItem = nItem;
	ListView_InsertItem(hList, &Item);
}



static BOOL SoftwareDirectories_OnInsertBrowse(HWND hDlg, BOOL bBrowse, LPCTSTR lpItem)
{
    int nItem;
    TCHAR inbuf[MAX_PATH];
    TCHAR outbuf[MAX_PATH];
    HWND hList;
	LPTSTR lpIn;

	g_bModifiedSoftwarePaths = TRUE;

    hList = GetDlgItem(hDlg, IDC_DIR_LIST);
    nItem = ListView_GetNextItem(hList, -1, LVNI_SELECTED);

    if (nItem == -1)
        return FALSE;

    /* Last item is placeholder for append */
    if (nItem == ListView_GetItemCount(hList) - 1)
    {
        bBrowse = FALSE;
    }

	if (!lpItem) {
		if (bBrowse) {
			ListView_GetItemText(hList, nItem, 0, inbuf, ARRAY_LENGTH(inbuf));
			lpIn = inbuf;
		}
		else {
			lpIn = NULL;
		}

		if (!BrowseForDirectory(hDlg, lpIn, outbuf))
	        return FALSE;
		lpItem = outbuf;
	}

	AppendList(hList, lpItem, nItem);
	if (bBrowse)
		ListView_DeleteItem(hList, nItem+1);
	MarkChanged(hDlg);
	return TRUE;
}



static BOOL SoftwareDirectories_OnDelete(HWND hDlg)
{
    int     nCount;
    int     nSelect;
    int     nItem;
    HWND    hList = GetDlgItem(hDlg, IDC_DIR_LIST);

	g_bModifiedSoftwarePaths = TRUE;

    nItem = ListView_GetNextItem(hList, -1, LVNI_SELECTED | LVNI_ALL);

    if (nItem == -1)
        return FALSE;

    /* Don't delete "Append" placeholder. */
    if (nItem == ListView_GetItemCount(hList) - 1)
        return FALSE;

	ListView_DeleteItem(hList, nItem);

    nCount = ListView_GetItemCount(hList);
    if (nCount <= 1)
        return FALSE;

    /* If the last item was removed, select the item above. */
    if (nItem == nCount - 1)
        nSelect = nCount - 2;
    else
        nSelect = nItem;

    ListView_SetItemState(hList, nSelect, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
	MarkChanged(hDlg);
	return TRUE;
}



static BOOL SoftwareDirectories_OnBeginLabelEdit(HWND hDlg, NMHDR* pNMHDR)
{
	BOOL          bResult = FALSE;
	NMLVDISPINFO* pDispInfo = (NMLVDISPINFO*)pNMHDR;
	LVITEM*       pItem = &pDispInfo->item;
	HWND    hList = GetDlgItem(hDlg, IDC_DIR_LIST);

	/* Last item is placeholder for append */
	if (pItem->iItem == ListView_GetItemCount(hList) - 1)
	{
		HWND hEdit = (HWND) (int) SendMessage(hList, LVM_GETEDITCONTROL, 0, 0);
		Edit_SetText(hEdit, TEXT(""));
	}

	return bResult;
}



static BOOL SoftwareDirectories_OnEndLabelEdit(HWND hDlg, NMHDR* pNMHDR)
{
    BOOL          bResult = FALSE;
    NMLVDISPINFO* pDispInfo = (NMLVDISPINFO*)pNMHDR;
    LVITEM*       pItem = &pDispInfo->item;

    if (pItem->pszText != NULL)
    {
        struct _stat file_stat;

        /* Don't allow empty entries. */
        if (!_tcscmp(pItem->pszText, TEXT("")))
        {
            return FALSE;
        }

        /* Check validity of edited directory. */
        if (_tstat(pItem->pszText, &file_stat) == 0
        &&  (file_stat.st_mode & S_IFDIR))
        {
            bResult = TRUE;
        }
        else
        {
            if (MessageBox(NULL, TEXT("Directory does not exist, continue anyway?"), TEXT(MAME32NAME), MB_OKCANCEL) == IDOK)
                bResult = TRUE;
        }
    }

    if (bResult == TRUE)
    {
		SoftwareDirectories_OnInsertBrowse(hDlg, TRUE, pItem->pszText);
    }

    return bResult;
}



BOOL PropSheetFilter_Config(const machine_config *drv, const game_driver *gamedrv)
{
	return (ram_option_count(gamedrv) > 0) || DriverHasDevice(gamedrv, IO_PRINTER);
}



INT_PTR CALLBACK GameMessOptionsProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	INT_PTR rc = 0;
	BOOL bHandled = FALSE;

	switch (Msg) {
	case WM_NOTIFY:
		switch (((NMHDR *) lParam)->code) {
		case LVN_ENDLABELEDIT:
			rc = SoftwareDirectories_OnEndLabelEdit(hDlg, (NMHDR *) lParam);
			bHandled = TRUE;
			break;

		case LVN_BEGINLABELEDIT:
			rc = SoftwareDirectories_OnBeginLabelEdit(hDlg, (NMHDR *) lParam);
			bHandled = TRUE;
			break;
		}
	}

	if (!bHandled)
		rc = GameOptionsProc(hDlg, Msg, wParam, lParam);
	return rc;
}



BOOL MessPropertiesCommand(HWND hWnd, WORD wNotifyCode, WORD wID, BOOL *changed)
{
	BOOL handled = TRUE;

	switch(wID)
	{
		case IDC_DIR_BROWSE:
			if (wNotifyCode == BN_CLICKED)
				*changed = SoftwareDirectories_OnInsertBrowse(hWnd, TRUE, NULL);
			break;

		case IDC_DIR_INSERT:
			if (wNotifyCode == BN_CLICKED)
				*changed = SoftwareDirectories_OnInsertBrowse(hWnd, FALSE, NULL);
			break;

		case IDC_DIR_DELETE:
			if (wNotifyCode == BN_CLICKED)
				*changed = SoftwareDirectories_OnDelete(hWnd);
			break;

		default:
			handled = FALSE;
			break;
	}
	return handled;
}



//============================================================
//  DATAMAP HANDLERS
//============================================================

static void DirListReadControl(datamap *map, HWND dialog, HWND control, core_options *opts, const char *option_name)
{
	int directory_count;
    LV_ITEM lvi;
	TCHAR buffer[2048];
	char *dir_list;
	int i, pos, driver_index;

	// determine the directory count; note that one item is the "<    >" entry
	directory_count = ListView_GetItemCount(control);
	if (directory_count > 0)
		directory_count--;

	buffer[0] = '\0';
	pos = 0;

	for (i = 0; i < directory_count; i++)
	{
		// append a semicolon, if we're past the first entry
		if (i > 0)
			pos += _sntprintf(&buffer[pos], ARRAY_LENGTH(buffer) - pos, TEXT(";"));

		// retrieve the next entry
		memset(&lvi, '\0', sizeof(lvi));
		lvi.mask = LVIF_TEXT;
		lvi.iItem = i;
		lvi.pszText = &buffer[pos];
		lvi.cchTextMax = ARRAY_LENGTH(buffer) - pos;
		ListView_GetItem(control, &lvi);

		// advance the position
		pos += _tcslen(&buffer[pos]);
	}

	dir_list = utf8_from_tstring(buffer);
	if (dir_list != NULL)
	{
		driver_index = PropertiesCurrentGame(dialog);
		SetExtraSoftwarePaths(driver_index, dir_list);
		free(dir_list);
	}
}



static void DirListPopulateControl(datamap *map, HWND dialog, HWND control, core_options *opts, const char *option_name)
{
	int driver_index, pos, new_pos, current_item;
	const char *dir_list;
	TCHAR *t_dir_list;
	TCHAR *s;
	LV_COLUMN lvc;
	RECT r;

	// access the directory list, and convert to TCHARs
	driver_index = PropertiesCurrentGame(dialog);
	dir_list = GetExtraSoftwarePaths(driver_index);
	t_dir_list = tstring_from_utf8(dir_list);
	if (!t_dir_list)
		return;

	// delete all items in the list control
	ListView_DeleteAllItems(control);

	// add the column
	GetClientRect(control, &r);
	memset(&lvc, 0, sizeof(LVCOLUMN));
	lvc.mask = LVCF_WIDTH;
	lvc.cx = r.right - r.left - GetSystemMetrics(SM_CXHSCROLL);
	ListView_InsertColumn(control, 0, &lvc);

	// add each of the directories
	pos = 0;
	current_item = 0;
	while(t_dir_list[pos] != '\0')
	{
		// parse off this item
		s = _tcschr(&t_dir_list[pos], ';');
		if (s != NULL)
		{
			*s = '\0';
			new_pos = s - &t_dir_list[pos] + 1;
		}
		else
		{
			new_pos = pos + _tcslen(&t_dir_list[pos]);
		}

		// append this item
		AppendList(control, &t_dir_list[pos], current_item);

		// advance to next item
		pos = new_pos;
		current_item++;
	}

	// finish up
	AppendList(control, TEXT(DIRLIST_NEWENTRYTEXT), current_item);
	ListView_SetItemState(control, 0, LVIS_SELECTED, LVIS_SELECTED);
	free(t_dir_list);
}



static void RamPopulateControl(datamap *map, HWND dialog, HWND control, core_options *opts, const char *option_name)
{
	int count, i, current_index, driver_index;
	const game_driver *gamedrv;
	UINT32 ram, current_ram;
	char buffer[64];
	const char *this_ram_string;
	TCHAR* t_ramstring;

	// identify the driver
	driver_index = PropertiesCurrentGame(dialog);
	gamedrv = drivers[driver_index];

	// clear out the combo box
	ComboBox_ResetContent(control);

	// identify how many options that we have
	count = ram_option_count(gamedrv);
	EnableWindow(control, count > 0);

	// we can only do something meaningful if there is more than one option
	if (count > 0)
	{
		// identify the current amount of RAM
		this_ram_string = options_get_string(opts, OPTION_RAMSIZE);
		current_ram = (this_ram_string != NULL) ? ram_parse_string(this_ram_string) : 0;
		if (current_ram == 0)
			current_ram = ram_default(gamedrv);

		// by default, assume index 0
		current_index = 0;

		// loop through all options
		for (i = 0; i < count; i++)
		{
			// identify this option
			ram = ram_option(gamedrv, i);
			this_ram_string = ram_string(buffer, ram);

			t_ramstring = tstring_from_utf8(this_ram_string);
			if( !t_ramstring )
				return;

			// add this option to the combo box
			ComboBox_InsertString(control, i, win_tstring_strdup(t_ramstring));
			ComboBox_SetItemData(control, i, ram);
			
			free(t_ramstring);

			// is this the current option?  record the index if so
			if (ram == current_ram)
				current_index = i;
		}

		// set the combo box
		ComboBox_SetCurSel(control, current_index);
	}
}



//============================================================
//  DATAMAP SETUP
//============================================================

void MessBuildDataMap(datamap *properties_datamap)
{
	// MESS specific stuff
	datamap_add(properties_datamap, IDC_DIR_LIST,				DM_STRING,	NULL);
	datamap_add(properties_datamap, IDC_RAM_COMBOBOX,			DM_INT,		OPTION_RAMSIZE);
	datamap_add(properties_datamap, IDC_SKIP_WARNINGS,			DM_BOOL,	OPTION_SKIP_WARNINGS);
	datamap_add(properties_datamap, IDC_USE_NEW_UI,				DM_BOOL,	"newui");

	// set up callbacks
	datamap_set_callback(properties_datamap, IDC_DIR_LIST,		DCT_READ_CONTROL,		DirListReadControl);
	datamap_set_callback(properties_datamap, IDC_DIR_LIST,		DCT_POPULATE_CONTROL,	DirListPopulateControl);
	datamap_set_callback(properties_datamap, IDC_RAM_COMBOBOX,	DCT_POPULATE_CONTROL,	RamPopulateControl);
}
