#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
#define _WIN32_IE 0x0500
#include <windows.h>
#include <commctrl.h>
#include <commdlg.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "resource.h"
#include "s_sound.h"
#include "templates.h"
#include "cmdlib.h"
#include "c_dispatch.h"
#include "c_cvars.h"
#include "doomstat.h"

// More w32api lackings
#ifndef TTM_SETTITLE
#define TTM_SETTITLEA (WM_USER+32)
#define TTM_SETTITLEW (WM_USER+33)
#ifdef UNICODE
#define TTM_SETTITLE TTM_SETTITLEW
#else
#define TTM_SETTITLE TTM_SETTITLEA
#endif
#endif
#ifndef TTF_TRACK
#define TTF_TRACK 0x0020
#endif
#ifndef TTF_TRANSPARENT
#define TTF_TRANSPARENT 0x0100
#endif

#ifndef OFN_ENABLESIZING
#define OFN_ENABLESIZING 0x00800000
typedef struct w32apiFixedOFNA {
	DWORD lStructSize;
	HWND hwndOwner;
	HINSTANCE hInstance;
	LPCSTR lpstrFilter;
	LPSTR lpstrCustomFilter;
	DWORD nMaxCustFilter;
	DWORD nFilterIndex;
	LPSTR lpstrFile;
	DWORD nMaxFile;
	LPSTR lpstrFileTitle;
	DWORD nMaxFileTitle;
	LPCSTR lpstrInitialDir;
	LPCSTR lpstrTitle;
	DWORD Flags;
	WORD nFileOffset;
	WORD nFileExtension;
	LPCSTR lpstrDefExt;
	LPARAM lCustData;
	LPOFNHOOKPROC lpfnHook;
	LPCSTR lpTemplateName;
	void *pvReserved;
	DWORD dwReserved;
	DWORD FlagsEx;
} FIXEDOPENFILENAMEA;
#define OPENFILENAME FIXEDOPENFILENAMEA
#endif

extern HINSTANCE g_hInst;
extern HWND Window;
extern bool ForceWindowed;
EXTERN_CVAR (Bool, fullscreen)
extern ReverbContainer *ForcedEnvironment;

HWND EAXEditWindow;
HWND hPropList;
POINT PropListMaxSize;
LONG PropListHeightDiff;
POINT EditWindowSize;
POINT DoneLocation;
WNDPROC StdEditProc;
LONG NewLeft;
LONG SaveLeft;
LONG RevertLeft;
POINT TestLocation;

bool SpawnEAXWindow;

REVERB_PROPERTIES SavedProperties;
ReverbContainer *CurrentEnv;

CUSTOM_CVAR (Bool, eaxedit_test, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL)
{
	if (EAXEditWindow != 0)
	{
		CheckDlgButton (EAXEditWindow, IDC_TESTEAX, self ? BST_CHECKED : BST_UNCHECKED);
		if (self)
		{
			ForcedEnvironment = CurrentEnv;
		}
		else
		{
			ForcedEnvironment = NULL;
		}
	}
}

struct MySaveData
{
	const ReverbContainer *Default;
	const ReverbContainer **Saves;
	UINT NumSaves;

	MySaveData (const ReverbContainer *def)
		: Default (def), Saves (NULL), NumSaves (0) {}
	~MySaveData ()
	{
		if (Saves) delete[] Saves;
	}
};

struct EnvControl
{
	const char *Name;
	int EditControl;
	int SliderControl;
	int Min;
	int Max;
	float REVERB_PROPERTIES::*Float;
	int REVERB_PROPERTIES::*Int;
	HWND EditHWND;
	HWND SliderHWND;
};

struct EnvFlag
{
	const char *Name;
	int CheckboxControl;
	unsigned int Flag;
	HWND CheckboxHWND;
};

void PopulateEnvDropDown (HWND hCtl);
void SetupEnvControls (HWND hDlg);
void UpdateControls (const ReverbContainer *env, HWND hDlg);
void UpdateControl (const EnvControl *control, int value, bool slider);

EnvControl EnvControls[] =
{
	{ "Environment",				0,							0,							0, 25, 0, &REVERB_PROPERTIES::Environment },
	{ "EnvironmentSize",			IDCE_ENVIRONMENTSIZE,		IDCS_ENVIRONMENTSIZE,		1000, 100000, &REVERB_PROPERTIES::EnvSize, 0 },
	{ "EnvironmentDiffusion",		IDCE_ENVIRONMENTDIFFUSION,	IDCS_ENVIRONMENTDIFFUSION,	0, 1000, &REVERB_PROPERTIES::EnvDiffusion, 0 },
	{ "Room",						IDCE_ROOM,					IDCS_ROOM,					-10000, 0, 0, &REVERB_PROPERTIES::Room },
	{ "RoomHF",						IDCE_ROOMHF,				IDCS_ROOMHF,				-10000, 0, 0, &REVERB_PROPERTIES::RoomHF },
	{ "RoomLF",						IDCE_ROOMLF,				IDCS_ROOMLF,				-10000, 0, 0, &REVERB_PROPERTIES::RoomLF },
	{ "DecayTime",					IDCE_DECAYTIME,				IDCS_DECAYTIME,				100, 20000, &REVERB_PROPERTIES::DecayTime, 0 },
	{ "DecayHFRatio",				IDCE_DECAYHFRATIO,			IDCS_DECAYHFRATIO,			100, 2000, &REVERB_PROPERTIES::DecayHFRatio, 0 },
	{ "DecayLFRatio",				IDCE_DECAYLFRATIO,			IDCS_DECAYLFRATIO,			100, 2000, &REVERB_PROPERTIES::DecayLFRatio, 0 },
	{ "Reflections",				IDCE_REFLECTIONS,			IDCS_REFLECTIONS,			-10000, 1000, 0, &REVERB_PROPERTIES::Reflections },
	{ "ReflectionsDelay",			IDCE_REFLECTIONSDELAY,		IDCS_REFLECTIONSDELAY,		0, 300, &REVERB_PROPERTIES::ReflectionsDelay, 0 },
	{ "ReflectionsPanX",			IDCE_REFLECTIONSPANX,		IDCS_REFLECTIONSPANX,		-2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan0, 0 },
	{ "ReflectionsPanY",			IDCE_REFLECTIONSPANY,		IDCS_REFLECTIONSPANY,		-2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan1, 0 },
	{ "ReflectionsPanZ",			IDCE_REFLECTIONSPANZ,		IDCS_REFLECTIONSPANZ,		-2000000, 2000000, &REVERB_PROPERTIES::ReflectionsPan2, 0 },
	{ "Reverb",						IDCE_REVERB,				IDCS_REVERB,				-10000, 2000, 0, &REVERB_PROPERTIES::Reverb },
	{ "ReverbDelay",				IDCE_REVERBDELAY,			IDCS_REVERBDELAY,			0, 100, &REVERB_PROPERTIES::ReverbDelay, 0 },
	{ "ReverbPanX",					IDCE_REVERBPANX,			IDCS_REVERBPANX,			-2000000, 2000000, &REVERB_PROPERTIES::ReverbPan0, 0 },
	{ "ReverbPanY",					IDCE_REVERBPANY,			IDCS_REVERBPANY,			-2000000, 2000000, &REVERB_PROPERTIES::ReverbPan1, 0 },
	{ "ReverbPanZ",					IDCE_REVERBPANZ,			IDCS_REVERBPANZ,			-2000000, 2000000, &REVERB_PROPERTIES::ReverbPan2, 0 },
	{ "EchoTime",					IDCE_ECHOTIME,				IDCS_ECHOTIME,				75, 250, &REVERB_PROPERTIES::EchoTime, 0 },
	{ "EchoDepth",					IDCE_ECHODEPTH,				IDCS_ECHODEPTH,				0, 1000, &REVERB_PROPERTIES::EchoDepth, 0 },
	{ "ModulationTime",				IDCE_MODULATIONTIME,		IDCS_MODULATIONTIME,		40, 4000, &REVERB_PROPERTIES::ModulationTime, 0 },
	{ "ModulationDepth",			IDCE_MODULATIONDEPTH,		IDCS_MODULATIONDEPTH,		0, 1000, &REVERB_PROPERTIES::ModulationDepth, 0 },
	{ "AirAbsorptionHF",			IDCE_AIRABSORPTIONHF,		IDCS_AIRABSORPTIONHF,		-100000, 0, &REVERB_PROPERTIES::AirAbsorptionHF, 0 },
	{ "HFReference",				IDCE_HFREFERENCE,			IDCS_HFREFERENCE,			1000000, 20000000, &REVERB_PROPERTIES::HFReference, 0 },
	{ "LFReference",				IDCE_LFREFERENCE,			IDCS_LFREFERENCE,			20000, 1000000, &REVERB_PROPERTIES::LFReference, 0 },
	{ "RoomRolloffFactor",			IDCE_ROOMROLLOFFFACTOR,		IDCS_ROOMROLLOFFFACTOR,		0, 10000, &REVERB_PROPERTIES::RoomRolloffFactor, 0 },
	{ "Diffusion",					0,							0,							0, 100000, &REVERB_PROPERTIES::Diffusion, 0 },
	{ "Density",					0,							0,							0, 100000, &REVERB_PROPERTIES::Density, 0 },
};

EnvFlag EnvFlags[] =
{
	{ "bReflectionsScale",		IDC_REFLECTIONSSCALE, REVERB_FLAGS_REFLECTIONSSCALE },
	{ "bReflectionsDelayScale",	IDC_REFLECTIONSDELAYSCALE, REVERB_FLAGS_REFLECTIONSDELAYSCALE },
	{ "bDecayTimeScale",		IDC_DECAYTIMESCALE, REVERB_FLAGS_DECAYTIMESCALE },
	{ "bDecayHFLimit",			IDC_DECAYHFLIMIT, REVERB_FLAGS_DECAYHFLIMIT },
	{ "bReverbScale",			IDC_REVERBSCALE, REVERB_FLAGS_REVERBSCALE },
	{ "bReverbDelayScale",		IDC_REVERBDELAYSCALE, REVERB_FLAGS_REVERBDELAYSCALE },
	{ "bEchoTimeScale",			IDC_ECHOTIMESCALE, REVERB_FLAGS_ECHOTIMESCALE },
	{ "bModulationTimeScale",	IDC_MODULATIONTIMESCALE, REVERB_FLAGS_MODULATIONTIMESCALE }
};

LRESULT AddEnvToDropDown (HWND hCtl, bool showID, const ReverbContainer *env)
{
	char buff[128];
	LRESULT i;

	if (showID)
	{
		sprintf (buff, "(%3d,%3d) %s", HIBYTE(env->ID), LOBYTE(env->ID), env->Name);
		i = SendMessage (hCtl, CB_ADDSTRING, 0, (LPARAM)buff);
	}
	else
	{
		i = SendMessage (hCtl, CB_ADDSTRING, 0, (LPARAM)env->Name);
	}
	SendMessage (hCtl, CB_SETITEMDATA, i, (LPARAM)env);
	return i;
}

void PopulateEnvDropDown (HWND hCtl, bool showIDs, const ReverbContainer *defEnv)
{
	const ReverbContainer *env;
	WPARAM envCount = 0;
	LPARAM strCount = 0;
	LRESULT i;
	
	for (env = Environments; env != NULL; env = env->Next)
	{
		envCount++;
		strCount += strlen (env->Name) + 1;
	}

	SendMessage (hCtl, WM_SETREDRAW, FALSE, 0);
	SendMessage (hCtl, CB_RESETCONTENT, 0, 0);
	SendMessage (hCtl, CB_INITSTORAGE, envCount, showIDs ?
		strCount + envCount * 10 : strCount);

	for (env = Environments; env != NULL; env = env->Next)
	{
		AddEnvToDropDown (hCtl, showIDs, env);
	}

	SendMessage (hCtl, WM_SETREDRAW, TRUE, 0);

	if (defEnv == NULL)
	{
		SendMessage (hCtl, CB_SETCURSEL, 0, 0);
		SetFocus (hCtl);
	}
	else
	{
		for (i = 0; i < (LPARAM)envCount; ++i)
		{
			if ((const ReverbContainer  *)SendMessage (hCtl, CB_GETITEMDATA, i, 0)
				== defEnv)
			{
				SendMessage (hCtl, CB_SETCURSEL, i, 0);
				break;
			}
		}
	}
}

void SetIDEdits (HWND hDlg, WORD id)
{
	char text[4];

	sprintf (text, "%d", HIBYTE(id));
	SendMessage (GetDlgItem (hDlg, IDC_EDITID1), WM_SETTEXT, 0, (LPARAM)text);
	sprintf (text, "%d", LOBYTE(id));
	SendMessage (GetDlgItem (hDlg, IDC_EDITID2), WM_SETTEXT, 0, (LPARAM)text);
}

void PopulateEnvList (HWND hCtl, bool show, const ReverbContainer *defEnv)
{
	const ReverbContainer *env;
	int i;
	LVITEM item;
	
	SendMessage (hCtl, WM_SETREDRAW, FALSE, 0);

	item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE;
	item.stateMask = LVIS_SELECTED|LVIS_FOCUSED;
	item.iSubItem = 0;

	for (env = Environments, i = 0; env != NULL; env = env->Next, ++i)
	{
		if (!env->Builtin || show)
		{
			item.iItem = i;
			item.pszText = (LPSTR)env->Name;
			item.lParam = (LPARAM)env;
			item.state = env == defEnv ? LVIS_SELECTED|LVIS_FOCUSED : 0;
			SendMessage (hCtl, LVM_INSERTITEM, 0, (LPARAM)&item);
		}
	}

	SendMessage (hCtl, WM_SETREDRAW, TRUE, 0);
}

WORD FirstFreeID (WORD base, bool builtin)
{
	int tryCount = 0;
	int priID = HIBYTE(base);

	// If the original sound is built-in, start searching for a new
	// primary ID at 30.
	if (builtin)
	{
		for (priID = 30; priID < 256; ++priID)
		{
			if (S_FindEnvironment (priID << 8) == NULL)
			{
				break;
			}
		}
		if (priID == 256)
		{ // Oh well.
			priID = 30;
		}
	}

	for (;;)
	{
		WORD lastID = Environments->ID;
		const ReverbContainer *env = Environments->Next;

		// Find the lowest-numbered free ID with the same primary ID as base
		// If none are available, add 100 to base's primary ID and try again.
		// If that fails, then the primary ID gets incremented
		// by 1 until a match is found. If all the IDs searchable by this
		// algorithm are in use, then you're in trouble.

		while (env != NULL)
		{
			if (HIBYTE(env->ID) > priID)
			{
				break;
			}
			if (HIBYTE(env->ID) == priID)
			{
				if (HIBYTE(lastID) == priID)
				{
					if (LOBYTE(env->ID) - LOBYTE(lastID) > 1)
					{
						return lastID+1;
					}
				}
				lastID = env->ID;
			}
			env = env->Next;
		}
		if (LOBYTE(lastID) == 255)
		{
			if (tryCount == 0)
			{
				base += 100*256;
				tryCount = 1;
			}
			else
			{
				base += 256;
			}
		}
		else if (builtin && lastID == 0)
		{
			return priID << 8;
		}
		else
		{
			return lastID+1;
		}
	}
}

LRESULT CALLBACK EditControlProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	char buff[16];
	double val;
	const EnvControl *env;
	MSG *msg;

	switch (uMsg)
	{
	case WM_GETDLGCODE:
		msg = (MSG *)lParam;
		if (msg && LOWORD(msg->message) == WM_KEYDOWN)
		{
			// Do not close the dialog if Enter was pressed
			// inside an edit control.
			if (msg->wParam == VK_RETURN || msg->wParam == VK_ESCAPE)
			{
				return DLGC_WANTALLKEYS;
			}
		}
		break;

	case WM_CHAR:
		// Only allow numeric symbols.
		if (wParam >= ' ' && wParam <= 127 &&
			(wParam < '0' || wParam > '9') &&
			wParam != '.' &&
			wParam != '-' &&
			wParam != '+')
		{
			return 0;
		}
		break;

	case WM_KEYDOWN:
		if (wParam != VK_RETURN)
		{
			if (wParam == VK_ESCAPE)
			{ // Put the original value back in the edit control.
				env = (const EnvControl *)GetWindowLongPtr (hWnd, GWLP_USERDATA);
				int vali = SendMessage (env->SliderHWND, TBM_GETPOS, 0, 0);
				if (env->Float)
				{
					sprintf (buff, "%d.%03d", vali/1000, abs(vali%1000));
				}
				else
				{
					sprintf (buff, "%d", vali);
				}
				CallWindowProc (StdEditProc, hWnd, WM_SETTEXT, 0, (LPARAM)buff);
				CallWindowProc (StdEditProc, hWnd, EM_SETSEL, 0, -1);
				return 0;
			}
			break;
		}

		// intentional fallthrough

	case WM_KILLFOCUS:
		// Validate the new value and update the corresponding slider.
		env = (const EnvControl *)GetWindowLongPtr (hWnd, GWLP_USERDATA);
		val = 0.0;
		if (CallWindowProc (StdEditProc, hWnd, WM_GETTEXT, 16, (LPARAM)buff) > 0)
		{
			val = atof (buff);
		}
		if (env->Float)
		{
			val *= 1000.0;
		}
		if (val < env->Min) val = env->Min;
		else if (val > env->Max) val = env->Max;
		UpdateControl (env, int(val), true);
		break;
	}
	return CallWindowProc (StdEditProc, hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK EditControlProcNoDeci (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// This is just like the above WndProc, except it only accepts integers.
	if (uMsg == WM_CHAR && wParam == '.')
	{
		return 0;
	}
	return EditControlProc (hWnd, uMsg, wParam, lParam);
}

void SetupEnvControls (HWND hDlg)
{
	size_t i;

	for (i = 0; i < sizeof(EnvControls)/sizeof(EnvControls[0]); ++i)
	{
		if (EnvControls[i].EditControl == 0)
			continue;

		EnvControls[i].EditHWND = GetDlgItem (hDlg, EnvControls[i].EditControl);
		EnvControls[i].SliderHWND = GetDlgItem (hDlg, EnvControls[i].SliderControl);
		SendMessage (EnvControls[i].SliderHWND, TBM_SETRANGEMIN, FALSE, EnvControls[i].Min);
		SendMessage (EnvControls[i].SliderHWND, TBM_SETRANGEMAX, TRUE, EnvControls[i].Max);
		SendMessage (EnvControls[i].EditHWND, EM_LIMITTEXT, 10, 0);
		StdEditProc = (WNDPROC)SetWindowLongPtr (EnvControls[i].EditHWND, GWLP_WNDPROC,
			(LONG_PTR)(EnvControls[i].Float ? EditControlProc : EditControlProcNoDeci));
		SetWindowLongPtr (EnvControls[i].EditHWND, GWLP_USERDATA, (LONG_PTR)&EnvControls[i]);
		SetWindowLongPtr (EnvControls[i].SliderHWND, GWLP_USERDATA, (LONG_PTR)&EnvControls[i]);
	}
	for (i = 0; i < sizeof(EnvFlags)/sizeof(EnvFlags[0]); ++i)
	{
		EnvFlags[i].CheckboxHWND = GetDlgItem (hDlg, EnvFlags[i].CheckboxControl);
		SetWindowLongPtr (EnvFlags[i].CheckboxHWND, GWLP_USERDATA, (LONG_PTR)&EnvFlags[i]);
	}
}

void UpdateControl (const EnvControl *control, int value, bool slider)
{
	char buff[16];

	if (slider)
	{
		SendMessage (control->SliderHWND, TBM_SETPOS, TRUE, value);
	}
	if (control->Float)
	{
		sprintf (buff, "%d.%03d", value/1000, abs(value%1000));
		if (CurrentEnv != NULL)
		{
			CurrentEnv->Properties.*control->Float = float(value) / 1000.0;
		}
	}
	else
	{
		sprintf (buff, "%d", value);
		if (CurrentEnv != NULL)
		{
			CurrentEnv->Properties.*control->Int = value;
		}
	}
	SendMessage (control->EditHWND, WM_SETTEXT, 0, (LPARAM)buff);
	if (CurrentEnv != NULL)
	{
		CurrentEnv->Modified = true;
	}
}

void UpdateControls (ReverbContainer *env, HWND hDlg)
{
	char buff[4];
	size_t i;

	if (env == NULL)
		return;

	CurrentEnv = NULL;

	for (i = 0; i < sizeof(EnvControls)/sizeof(EnvControls[0]); ++i)
	{
		EnvControl *ctrl = &EnvControls[i];

		if (ctrl->Float)
		{
			float v = env->Properties.*ctrl->Float * 1000;
			UpdateControl (ctrl, int(v >= 0.0 ? v + 0.5 : v - 0.5), true);
		}
		else
		{
			UpdateControl (ctrl, env->Properties.*ctrl->Int, true);
		}
		EnableWindow (ctrl->EditHWND, !env->Builtin);
		EnableWindow (ctrl->SliderHWND, !env->Builtin);
	}
	for (i = 0; i < sizeof(EnvFlags)/sizeof(EnvFlags[0]); ++i)
	{
		SendMessage (EnvFlags[i].CheckboxHWND, BM_SETCHECK,
			(env->Properties.Flags & EnvFlags[i].Flag) ? BST_CHECKED : BST_UNCHECKED, 0);
		EnableWindow (EnvFlags[i].CheckboxHWND, !env->Builtin);
	}
	EnableWindow (GetDlgItem (hDlg, IDC_REVERT), !env->Builtin);

	sprintf (buff, "%d", HIBYTE(env->ID));
	SendMessage (GetDlgItem (hDlg, IDC_ID1), WM_SETTEXT, 0, (LPARAM)buff);

	sprintf (buff, "%d", LOBYTE(env->ID));
	SendMessage (GetDlgItem (hDlg, IDC_ID2), WM_SETTEXT, 0, (LPARAM)buff);

	SavedProperties = env->Properties;
	CurrentEnv = env;

	if (SendMessage (GetDlgItem (hDlg, IDC_TESTEAX), BM_GETCHECK, 0,0) == BST_CHECKED)
	{
		ForcedEnvironment = env;
	}
}

INT_PTR CALLBACK EAXProp (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	const EnvControl *env;
	SCROLLINFO si;
	int yPos;

	switch (uMsg)
	{
	case WM_INITDIALOG:
		SetupEnvControls (hDlg);
		return FALSE;

	case WM_VSCROLL:
		si.cbSize = sizeof(si);
		si.fMask = SIF_ALL;
		GetScrollInfo (hDlg, SB_VERT, &si);
		yPos = si.nPos;

		switch (LOWORD(wParam))
		{
		case SB_TOP:		si.nPos = si.nMin;		break;
		case SB_BOTTOM:		si.nPos = si.nMax;		break;
		case SB_LINEUP:		si.nPos -= 16;			break;
		case SB_LINEDOWN:	si.nPos += 16;			break;
		case SB_PAGEUP:		si.nPos -= si.nPage;	break;
		case SB_PAGEDOWN:	si.nPos += si.nPage;	break;
		case SB_THUMBTRACK:	si.nPos = si.nTrackPos;	break;
		}
		si.fMask = SIF_POS;
		SetScrollInfo (hDlg, SB_VERT, &si, TRUE);
		GetScrollInfo (hDlg, SB_VERT, &si);
		if (si.nPos != yPos)
		{
			ScrollWindow (hDlg, 0, yPos - si.nPos, NULL, NULL);
			UpdateWindow (hDlg);
		}
		return TRUE;

	case WM_HSCROLL:
		env = (const EnvControl *)GetWindowLongPtr ((HWND)lParam, GWLP_USERDATA);
		UpdateControl (env, SendMessage ((HWND)lParam, TBM_GETPOS, 0, 0), false);
		return TRUE;

	case WM_SIZE:
		if (wParam != SIZE_MAXSHOW && wParam != SIZE_MAXHIDE)
		{
			si.cbSize = sizeof(si);
			si.fMask = SIF_POS;
			GetScrollInfo (hDlg, SB_VERT, &si);
			yPos = si.nPos;

			// If we let the scroll bar disappear, then when that happens,
			// it will resize the window before SetScrollInfo returns.
			// Rather than checking for recursive WM_SIZE messages, I choose
			// to force the scroll bar to always be present. The dialog is
			// really designed with the appearance of the scroll bar in
			// mind anyway, since it does nothing to resize the controls
			// horizontally.

			si.fMask = SIF_POS|SIF_PAGE|SIF_DISABLENOSCROLL;
			si.nPage = HIWORD(lParam);

			SetScrollInfo (hDlg, SB_VERT, &si, TRUE);
			GetScrollInfo (hDlg, SB_VERT, &si);
			if (si.nPos != yPos)
			{
				ScrollWindow (hDlg, 0, yPos - si.nPos, NULL, NULL);
				UpdateWindow (hDlg);
			}
		}
		return TRUE;

	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED && CurrentEnv != NULL)
		{
			for (size_t i = 0; i < sizeof(EnvFlags)/sizeof(EnvFlags[0]); ++i)
			{
				if ((HWND)lParam == EnvFlags[i].CheckboxHWND)
				{
					if (SendMessage ((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED)
					{
						CurrentEnv->Properties.Flags |= EnvFlags[i].Flag;
					}
					else
					{
						CurrentEnv->Properties.Flags &= ~EnvFlags[i].Flag;
					}
					return TRUE;;
				}
			}
		}
		return FALSE;
	}
	return FALSE;
}

void SuggestNewName (const ReverbContainer *env, HWND hEdit)
{
	const ReverbContainer *probe;
	char text[32];
	size_t len;
	int number, numdigits;

	strncpy (text, env->Name, 31);
	text[31] = 0;

	len = strlen (text);
	while (text[len-1] >= '0' && text[len-1] <= '9')
	{
		len--;
	}
	number = atoi (text + len);
	if (number < 1)
	{
		number = 1;
	}

	if (text[len-1] != ' ' && len < 31)
	{
		text[len++] = ' ';
	}

	for (; number < 100000; ++number)
	{
			 if (number < 10)	numdigits = 1;
		else if (number < 100)	numdigits = 2;
		else if (number < 1000)	numdigits = 3;
		else if (number < 10000)numdigits = 4;
		else					numdigits = 5;
		if (len + numdigits > 31)
		{
			len = 31 - numdigits;
		}
		sprintf (text + len, "%d", number);

		probe = Environments;
		while (probe != NULL)
		{
			if (stricmp (probe->Name, text) == 0)
				break;
			probe = probe->Next;
		}
		if (probe == NULL)
		{
			break;
		}
	}

	if (probe == NULL)
	{
		SetWindowText (hEdit, text);
	}
}

void ShowErrorTip (HWND ToolTip, TOOLINFO &ti, HWND hDlg, const char *title)
{
	SendMessage (ToolTip, TTM_SETTITLE, 3, (LPARAM)title);
	
	ti.cbSize = sizeof(ti);
	ti.hinst = g_hInst;
	SendMessage (ToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
	GetClientRect (ti.hwnd, &ti.rect);
	POINT pt = { (ti.rect.left + ti.rect.right) / 2, ti.rect.bottom - 4 };
	ClientToScreen (ti.hwnd, &pt);
	SendMessage (ToolTip, TTM_TRACKACTIVATE, FALSE, 0);
	SendMessage (ToolTip, TTM_TRACKPOSITION, 0, MAKELONG(pt.x, pt.y));
	SendMessage (ToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
	SetFocus (ti.hwnd);
	SendMessage (ti.hwnd, EM_SETSEL, 0, -1);
	MessageBeep (MB_ICONEXCLAMATION);
	SetTimer (hDlg, 11223, 10000, NULL);
}

INT_PTR CALLBACK NewEAXProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	const ReverbContainer *rev;
	TOOLINFO ti;
	char buff[33], buff4[4];
	int id1, id2;
	static HWND ToolTip;
	HWND hWnd;
	int i;

	switch (uMsg)
	{
	case WM_INITDIALOG:
		hWnd = GetDlgItem (hDlg, IDC_ENVIRONMENTLIST);
		PopulateEnvList (hWnd, true, (const ReverbContainer *)lParam);
		SendMessage (GetDlgItem (hDlg, IDC_SPINID1), UDM_SETRANGE, 0, MAKELONG(255,0));
		SendMessage (GetDlgItem (hDlg, IDC_SPINID2), UDM_SETRANGE, 0, MAKELONG(255,0));
		SendMessage (GetDlgItem (hDlg, IDC_EDITID1), EM_LIMITTEXT, 3, 0);
		SendMessage (GetDlgItem (hDlg, IDC_EDITID2), EM_LIMITTEXT, 3, 0);

		hWnd = GetDlgItem (hDlg, IDC_NEWENVNAME);
		SendMessage (hWnd, EM_LIMITTEXT, 31, 0);
		SuggestNewName ((const ReverbContainer *)lParam, hWnd);
		SetIDEdits (hDlg, FirstFreeID (((const ReverbContainer *)lParam)->ID,
			((const ReverbContainer *)lParam)->Builtin));

		ToolTip = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
			WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP|TTS_BALLOON|TTS_CLOSE,
			0, 0, 0, 0,
			hDlg, NULL, g_hInst, NULL);
		if (ToolTip)
		{
			ti.cbSize = sizeof(ti);
			ti.uFlags = TTF_TRACK | TTF_TRANSPARENT;
			ti.hinst = g_hInst;
			ti.lpszText = "";
			for (i = 0; i < 3; ++i)
			{
				ti.uId = i;
				ti.hwnd = GetDlgItem (hDlg, IDC_NEWENVNAME+i);
				GetClientRect (ti.hwnd, &ti.rect);
				SendMessage (ToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
			}
		}

		return 1;

	case WM_TIMER:
		if (wParam == 11223)
		{
			SendMessage (ToolTip, TTM_TRACKACTIVATE, FALSE, 0);
			KillTimer (hDlg, 11223);
		}
		return 1;

	case WM_MOVE:
		// Just hide the tool tip if the user moves the window.
		SendMessage (ToolTip, TTM_TRACKACTIVATE, FALSE, 0);
		return 1;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			id1 = 256; id2 = 256;
			buff[0] = 0;
			if (0 == GetWindowText (GetDlgItem (hDlg, IDC_NEWENVNAME), buff, 32) ||
				S_FindEnvironment (buff) != NULL)
			{
				static CHAR *texts[2] =
				{
					"That name is already used.",
					"Please enter a name."
				};
				ti.uId = 0;
				ti.hwnd = GetDlgItem (hDlg, IDC_NEWENVNAME);
				ti.lpszText = buff[0] ? texts[0] : texts[1];
				ShowErrorTip (ToolTip, ti, hDlg, "Bad Name");
				return 0;
			}
			if (0 < GetWindowText (GetDlgItem (hDlg, IDC_EDITID1), buff4, 4))
			{
				id1 = atoi (buff4);
			}
			if (0 < GetWindowText (GetDlgItem (hDlg, IDC_EDITID2), buff4, 4))
			{
				id2 = atoi (buff4);
			}
			if (id1 > 255 || id2 > 255)
			{
				ti.uId = id1 > 255 ? 1 : 2;
				ti.hwnd = GetDlgItem (hDlg, IDC_EDITID1 + ti.uId - 1);
				ti.lpszText = "Please enter a number between 0 and 255.";
				ShowErrorTip (ToolTip, ti, hDlg, "Bad Value");
			}
			else if (NULL != (rev = S_FindEnvironment (MAKEWORD (id2, id1))))
			{
				static char foo[80];
				ti.uId = 2;
				ti.hwnd = GetDlgItem (hDlg, IDC_EDITID2);
				sprintf (foo, "This ID is already used by \"%s\".", rev->Name);
				ti.lpszText = foo;
				ShowErrorTip (ToolTip, ti, hDlg, "Bad ID");
			}
			else
			{
				LVITEM item;

				hWnd = GetDlgItem (hDlg, IDC_ENVIRONMENTLIST);
				i = SendMessage (hWnd, LVM_GETNEXTITEM, -1, LVNI_ALL|LVNI_SELECTED);

				if (i == -1)
				{
					MessageBeep (MB_ICONEXCLAMATION);
					return 0;
				}

				item.iItem = i;
				item.iSubItem = 0;
				item.mask = LVIF_PARAM;
				if (!SendMessage (hWnd, LVM_GETITEM, 0, (LPARAM)&item))
				{
					MessageBeep (MB_ICONEXCLAMATION);
					return 0;
				}

				ReverbContainer *env = new ReverbContainer;
				rev = (ReverbContainer *)item.lParam;

				env->Builtin = false;
				env->ID = MAKEWORD (id2, id1);
				env->Name = copystring (buff);
				env->Next = NULL;
				env->Properties = rev->Properties;
				S_AddEnvironment (env);

				EndDialog (hDlg, (INT_PTR)env);
			}
			return 0;

		case IDCANCEL:
			EndDialog (hDlg, 0);
			break;
		}
		return 0;

	case WM_NOTIFY:
		if (wParam == IDC_ENVIRONMENTLIST && ((LPNMHDR)lParam)->code == LVN_ITEMCHANGED)
		{
			LPNMLISTVIEW nmlv = (LPNMLISTVIEW)lParam;

			if (nmlv->iItem != -1 &&
				(nmlv->uNewState & LVIS_SELECTED) &&
				!(nmlv->uOldState & LVIS_SELECTED))
			{
				SuggestNewName ((const ReverbContainer *)nmlv->lParam, GetDlgItem (hDlg, IDC_NEWENVNAME));
				SetIDEdits (hDlg, FirstFreeID (((const ReverbContainer *)nmlv->lParam)->ID,
					((const ReverbContainer *)lParam)->Builtin));
			}
		}
		return 0;
	}
	return FALSE;
}

UINT_PTR CALLBACK SaveHookProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	static LONG ButtonsLeftOffset;
	static LONG ListRightOffset;
	static LONG GroupRightOffset;

	MySaveData *msd;
	HWND hWnd;
	LVITEM item;
	int i, count;
	RECT rect1, rect2;
	UINT ns;

	switch (msg)
	{
	case WM_INITDIALOG:
		GetClientRect (hDlg, &rect1);
		GetWindowRect (GetDlgItem (hDlg, IDC_SELECTALL), &rect2);
		ScreenToClient (hDlg, (LPPOINT)&rect2.left);
		ButtonsLeftOffset = rect2.left - rect1.right;
		GetWindowRect (GetDlgItem (hDlg, IDC_ENVLIST), &rect2);
		ListRightOffset = (rect2.right - rect2.left) - rect1.right;
		GetWindowRect (GetDlgItem (hDlg, IDC_SAVEGROUP), &rect2);
		GroupRightOffset = (rect2.right - rect2.left) - rect1.right;

		hWnd = GetDlgItem (hDlg, IDC_ENVLIST);
		PopulateEnvList (hWnd, false,
			((MySaveData *)(((LPOPENFILENAME)lParam)->lCustData))->Default);
		SetWindowLongPtr (hDlg, DWLP_USER, (LONG_PTR)((LPOPENFILENAME)lParam)->lCustData);
		return 1;

	case WM_ERASEBKGND:
		SetWindowLongPtr (hDlg, DWLP_MSGRESULT, 1);
		return 1;

	case WM_SIZE:
		if (wParam != SIZE_MAXHIDE && wParam != SIZE_MAXSHOW)
		{
			GetWindowRect (hWnd = GetDlgItem (hDlg, IDC_SAVEGROUP), &rect1);
			SetWindowPos (hWnd, NULL, 0, 0, LOWORD(lParam) + GroupRightOffset, rect1.bottom-rect1.top, SWP_NOMOVE|SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_DEFERERASE);

			GetWindowRect (hWnd = GetDlgItem (hDlg, IDC_SELECTALL), &rect1);
			ScreenToClient (hDlg, (LPPOINT)&rect1.left);
			SetWindowPos (hWnd, NULL, LOWORD(lParam)+ButtonsLeftOffset, rect1.top, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER);

			GetWindowRect (hWnd = GetDlgItem (hDlg, IDC_SELECTNONE), &rect1);
			ScreenToClient (hDlg, (LPPOINT)&rect1.left);
			SetWindowPos (hWnd, NULL, LOWORD(lParam)+ButtonsLeftOffset, rect1.top, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER);

			GetWindowRect (hWnd = GetDlgItem (hDlg, IDC_ENVLIST), &rect1);
			SetWindowPos (hWnd, NULL, 0, 0, LOWORD(lParam) + ListRightOffset, rect1.bottom-rect1.top, SWP_DEFERERASE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOOWNERZORDER);
		}
		return 1;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_SELECTALL:
		case IDC_SELECTNONE:
			hWnd = GetDlgItem (hDlg, IDC_ENVLIST);
			SendMessage (hWnd, WM_SETREDRAW, FALSE, 0);
			count = ListView_GetItemCount (hWnd);
			item.iSubItem = 0;
			item.mask = LVIF_STATE;
			item.stateMask = LVIS_SELECTED;
			item.state = LOWORD(wParam)==IDC_SELECTALL ? LVIS_SELECTED : 0;
			for (i = 0; i < count; ++i)
			{
				item.iItem = i;
				ListView_SetItem (hWnd, &item);
			}
			if (LOWORD(wParam) == IDC_SELECTALL)
			{
				SetFocus (hWnd);
			}
			SendMessage (hWnd, WM_SETREDRAW, TRUE, 0);
			return 1;
		}
		return 0;

	case WM_NOTIFY:
		switch (((LPNMHDR)lParam)->code)
		{
		case CDN_INITDONE:
			hWnd = GetParent (hDlg);
			GetWindowRect (hWnd, &rect1);
			GetWindowRect (hDlg, &rect2);
			SetWindowPos (hDlg, NULL, 0, 0, rect1.right-rect1.left, rect2.bottom-rect2.top, SWP_NOMOVE|SWP_NOZORDER|SWP_NOOWNERZORDER);
			return 1;

		case CDN_FILEOK:
			msd = (MySaveData *)GetWindowLongPtr (hDlg, DWLP_USER);
			hWnd = GetDlgItem (hDlg, IDC_ENVLIST);
			ns = ListView_GetSelectedCount (hWnd);
			if (ns == 0)
			{
				msd->NumSaves = 0;
				if (IDNO == MessageBox (hDlg,
					"You have not selected any EAX environments to save.\n"
					"Do you want to cancel the save operation?",
					"Nothing Selected",
					MB_YESNO|MB_ICONEXCLAMATION|MB_DEFBUTTON2))
				{
					SetWindowLongPtr (hDlg, DWL_MSGRESULT, 1);
					return 1;
				}
			}
			else
			{
				UINT x;
				msd->Saves = new const ReverbContainer *[ns];
				item.iItem = -1;
				item.iSubItem = 0;
				item.mask = LVIF_PARAM;

				for (x = 0; x != ns; )
				{
					item.iItem = ListView_GetNextItem (hWnd, item.iItem, LVNI_SELECTED);
					if (item.iItem != -1 && ListView_GetItem (hWnd, &item))
					{
						msd->Saves[x++] = (const ReverbContainer *)item.lParam;
					}
				}
				msd->NumSaves = x;
			}
			return 0;
		}
		return 0;

	default:
		return 0;
	}
}

void ExportEnvironments (const char *filename, UINT count, const ReverbContainer **envs)
{
	FILE *f;

retry:
	f = fopen (filename, "w");

	if (f != NULL)
	{
		for (UINT i = 0; i < count; ++i)
		{
			const ReverbContainer *env = envs[i];
			const ReverbContainer *base;
			size_t j;

			if ((unsigned int)env->Properties.Environment < 26)
			{
				base = DefaultEnvironments[env->Properties.Environment];
			}
			else
			{
				base = NULL;
			}
			fprintf (f, "\"%s\" %u %u\n{\n", env->Name, HIBYTE(env->ID), LOBYTE(env->ID));
			for (j = 0; j < sizeof(EnvControls)/sizeof(EnvControls[0]); ++j)
			{
				const EnvControl *ctl = &EnvControls[j];
				if (ctl->Name &&
					(j == 0 ||
					(ctl->Float && base->Properties.*ctl->Float != env->Properties.*ctl->Float) ||
					(ctl->Int && base->Properties.*ctl->Int != env->Properties.*ctl->Int))
					)
				{
					fprintf (f, "\t%s ", ctl->Name);
					if (ctl->Float)
					{
						float v = env->Properties.*ctl->Float * 1000;
						int vi = int(v >= 0.0 ? v + 0.5 : v - 0.5);
						fprintf (f, "%d.%03d\n", vi/1000, abs(vi%1000));
					}
					else
					{
						fprintf (f, "%d\n", env->Properties.*ctl->Int);
					}
				}
			}
			for (j = 0; j < sizeof(EnvFlags)/sizeof(EnvFlags[0]); ++j)
			{
				if (EnvFlags[j].Flag & (env->Properties.Flags ^ base->Properties.Flags))
				{
					fprintf (f, "\t%s %s\n", EnvFlags[j].Name,
						EnvFlags[j].Flag & env->Properties.Flags ? "true" : "false");
				}
			}
			fprintf (f, "}\n\n");
		}
		fclose (f);
	}
	else
	{
		LPVOID lpMsgBuf;
		FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
			FORMAT_MESSAGE_FROM_SYSTEM |
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
			(LPTSTR)&lpMsgBuf,
			0,
			NULL);
		if (IDRETRY == MessageBox (EAXEditWindow, (LPCTSTR)lpMsgBuf, "Save Failed!", MB_RETRYCANCEL|MB_ICONERROR))
		{
			LocalFree (lpMsgBuf);
			goto retry;
		}
		LocalFree (lpMsgBuf);
	}
}

void SaveEnvironments (HWND owner, const ReverbContainer *defEnv)
{
	MySaveData msd (defEnv);
	OPENFILENAME ofn;
	char filename[PATH_MAX];

	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = owner;
	ofn.hInstance = g_hInst;
	ofn.lpstrFilter = "Text Files\0*.txt\0All Files\0*.*\0";
	ofn.lpstrCustomFilter = NULL;
	ofn.nMaxCustFilter = 0;
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = filename;
	ofn.nMaxFile = sizeof(filename);
	ofn.lpstrFileTitle = NULL;
	ofn.nMaxFileTitle = 0;
	ofn.lpstrInitialDir = NULL;
	ofn.lpstrTitle = "Save EAX Environments As...";
	ofn.Flags = OFN_ENABLEHOOK|OFN_ENABLESIZING|OFN_ENABLETEMPLATE|OFN_EXPLORER|
		OFN_NOCHANGEDIR|OFN_OVERWRITEPROMPT;
	ofn.nFileOffset = 0;
	ofn.nFileExtension = 0;
	ofn.lpstrDefExt = "txt";
	ofn.lCustData = (LPARAM)&msd;
	ofn.lpfnHook = SaveHookProc;
	ofn.lpTemplateName = MAKEINTRESOURCE(IDD_SAVEEAX);
	ofn.pvReserved = NULL;
	ofn.dwReserved = 0;
	ofn.FlagsEx = 0;

	filename[0] = 0;

	if (GetSaveFileName ((tagOFNA *)&ofn) && msd.NumSaves != 0)
	{
		ExportEnvironments (filename, msd.NumSaves, msd.Saves);
	}
}

INT_PTR CALLBACK EAXProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	SCROLLINFO scrollInfo;
	HWND hWnd;
	RECT rect;
	POINT ul;
	ReverbContainer *env;

	switch (uMsg)
	{
	case WM_INITDIALOG:
		hPropList = CreateDialog (g_hInst, MAKEINTRESOURCE(IDD_EAXPROPERTYLIST), hDlg, EAXProp);
		hWnd = GetDlgItem (hDlg, IDC_DUMMY);
		GetWindowRect (hPropList, &rect);
		PropListMaxSize.x = rect.right - rect.left;
		PropListMaxSize.y = rect.bottom - rect.top;
		GetWindowRect (hWnd, &rect);
		DestroyWindow (hWnd);
		ul.x = rect.left;
		ul.y = rect.top;
		ScreenToClient (hDlg, &ul);
		PropListMaxSize.x = rect.right - rect.left;

		scrollInfo.cbSize = sizeof(scrollInfo);
		scrollInfo.fMask = SIF_RANGE|SIF_POS|SIF_PAGE|SIF_DISABLENOSCROLL;
		scrollInfo.nMin = 0;
		scrollInfo.nMax = PropListMaxSize.y;
		scrollInfo.nPos = 0;
		scrollInfo.nPage = rect.bottom - rect.top;
		SetScrollInfo (hPropList, SB_VERT, &scrollInfo, TRUE);

		MoveWindow (hPropList, ul.x, ul.y, PropListMaxSize.x, rect.bottom - rect.top, FALSE);
		ShowWindow (hPropList, SW_SHOW);

		// Windows should have a layout control like MUI so that I don't have
		// to do any extra work to make the dialog resizable.

		GetClientRect (hDlg, &rect);
		PropListHeightDiff = (rect.bottom - rect.top) - scrollInfo.nPage;

		// Using a scroll bar to create a size box seems like an odd way of
		// doing things.

		hWnd = CreateWindow(
			"Scrollbar", 
			(LPSTR)NULL,
			WS_CHILD | WS_VISIBLE | SBS_SIZEGRIP | SBS_SIZEBOXBOTTOMRIGHTALIGN | WS_CLIPSIBLINGS,
			0,0,rect.right,rect.bottom,
			hDlg,
			(HMENU)IDC_SIZEBOX,
			g_hInst,
			NULL);

		TestLocation.x = DoneLocation.x = rect.right;
		TestLocation.y = DoneLocation.y = rect.bottom;
		GetWindowRect (GetDlgItem (hDlg, IDOK), &rect);
		ScreenToClient (hDlg, (LPPOINT)&rect.left);
		DoneLocation.x -= rect.left;
		DoneLocation.y -= rect.top;

		GetWindowRect (GetDlgItem (hDlg, IDC_TESTEAX), &rect);
		ScreenToClient (hDlg, (LPPOINT)&rect.left);
		TestLocation.x -= rect.left;
		TestLocation.y -= rect.top;

		GetWindowRect (hDlg, &rect);
		EditWindowSize.x = rect.right - rect.left;
		EditWindowSize.y = (rect.bottom - rect.top) - scrollInfo.nPage;

		GetWindowRect (GetDlgItem (hDlg, IDC_NEW), &rect);
		ScreenToClient (hDlg, (LPPOINT)&rect.left);
		NewLeft = rect.left;

		GetWindowRect (GetDlgItem (hDlg, IDC_SAVE), &rect);
		ScreenToClient (hDlg, (LPPOINT)&rect.left);
		SaveLeft = rect.left;

		GetWindowRect (GetDlgItem (hDlg, IDC_REVERT), &rect);
		ScreenToClient (hDlg, (LPPOINT)&rect.left);
		RevertLeft = rect.left;

		hWnd = GetDlgItem (hDlg, IDC_CURRENTENVIRONMENT);
		PopulateEnvDropDown (hWnd, IsDlgButtonChecked (hDlg, IDC_SHOWIDS)==BST_CHECKED, NULL);
		EAXProc (hDlg, WM_COMMAND, MAKELONG(IDC_CURRENTENVIRONMENT,CBN_SELENDOK), (LPARAM)hWnd);

		CheckDlgButton (hDlg, IDC_TESTEAX, eaxedit_test ? BST_CHECKED : BST_UNCHECKED);
		return 0;

	case WM_SIZE:
		if (wParam != SIZE_MAXHIDE && wParam != SIZE_MAXSHOW)
		{
			GetClientRect (hWnd = GetDlgItem (hDlg, IDC_SIZEBOX), &rect);
			SetWindowPos (hWnd, HWND_BOTTOM, LOWORD(lParam)-rect.right, HIWORD(lParam)-rect.bottom, 0, 0, SWP_NOSIZE);

			SetWindowPos (hPropList, NULL, 0, 0, PropListMaxSize.x, HIWORD(lParam)-PropListHeightDiff, SWP_NOMOVE|SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_DEFERERASE);
			SetWindowPos (GetDlgItem (hDlg, IDOK), NULL, LOWORD(lParam)-DoneLocation.x, HIWORD(lParam)-DoneLocation.y, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOSIZE);
			SetWindowPos (GetDlgItem (hDlg, IDC_NEW), NULL, NewLeft, HIWORD(lParam)-DoneLocation.y, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOSIZE);
			SetWindowPos (GetDlgItem (hDlg, IDC_SAVE), NULL, SaveLeft, HIWORD(lParam)-DoneLocation.y, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOSIZE);
			SetWindowPos (GetDlgItem (hDlg, IDC_REVERT), NULL, RevertLeft, HIWORD(lParam)-DoneLocation.y, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOSIZE);
			SetWindowPos (GetDlgItem (hDlg, IDC_TESTEAX), NULL, LOWORD(lParam)-TestLocation.x, HIWORD(lParam)-TestLocation.y, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOSIZE);
		}
		return 0;

	case WM_NCDESTROY:
		EAXEditWindow = 0;
		//new FS_Switcher;
		ForceWindowed = false;
		if (fullscreen)
		{
			setmodeneeded = true;
		}
		ForcedEnvironment = NULL;
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
		case IDCANCEL:
			DestroyWindow (hPropList);
			DestroyWindow (hDlg);
            return 1;

		case IDC_NEW:
			hWnd = GetDlgItem (hDlg, IDC_CURRENTENVIRONMENT);
			env = (ReverbContainer *)DialogBoxParam (g_hInst,
				MAKEINTRESOURCE(IDD_NEWEAX), hDlg, NewEAXProc,
				SendMessage (hWnd, CB_GETITEMDATA,
					SendMessage (hWnd, CB_GETCURSEL, 0, 0), 0));
			if (env != NULL)
			{
				LRESULT i = AddEnvToDropDown (hWnd,
					SendMessage (GetDlgItem (hDlg, IDC_SHOWIDS), BM_GETCHECK, 0, 0)==BST_CHECKED,
					env);
				SendMessage (hWnd, CB_SETCURSEL, i, 0);
				UpdateControls (env, hDlg);

				hWnd = GetDlgItem (hPropList, IDCE_ENVIRONMENTSIZE);
				SetFocus (hWnd);
				SendMessage (hWnd, EM_SETSEL, 0, -1);
			}
			return 0;

		case IDC_REVERT:
			hWnd = GetDlgItem (hDlg, IDC_CURRENTENVIRONMENT);
			env = (ReverbContainer *)SendMessage (hWnd, CB_GETITEMDATA,
				SendMessage (hWnd, CB_GETCURSEL, 0, 0), 0);
			env->Properties = SavedProperties;
			UpdateControls (env, hDlg);
			return 0;

		case IDC_SAVE:
			hWnd = GetDlgItem (hDlg, IDC_CURRENTENVIRONMENT);
			SaveEnvironments (hDlg,
				(ReverbContainer *)SendMessage (hWnd, CB_GETITEMDATA,
					SendMessage (hWnd, CB_GETCURSEL, 0, 0), 0));
			return 0;

		case IDC_TESTEAX:
			if (HIWORD(wParam) == BN_CLICKED)
			{
				eaxedit_test = (SendMessage ((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED);
			}
			break;
			
		case IDC_SHOWIDS:
			if (HIWORD(wParam) == BN_CLICKED)
			{
				hWnd = GetDlgItem (hDlg, IDC_CURRENTENVIRONMENT);
				PopulateEnvDropDown (hWnd,
					SendMessage ((HWND)lParam, BM_GETCHECK, 0, 0)==BST_CHECKED,
					(ReverbContainer *)SendMessage (hWnd, CB_GETITEMDATA,
						SendMessage (hWnd, CB_GETCURSEL, 0, 0), 0));
				EAXProc (hDlg, WM_COMMAND, MAKELONG(IDC_CURRENTENVIRONMENT,CBN_SELENDOK),
					(LPARAM)hWnd);
				return 0;
			}
			break;

		case IDC_CURRENTENVIRONMENT:
			if (HIWORD(wParam) == CBN_SELENDOK)
			{
				env = (ReverbContainer *)SendMessage ((HWND)lParam, CB_GETITEMDATA,
					SendMessage ((HWND)lParam, CB_GETCURSEL, 0, 0), 0);
				UpdateControls (env, hDlg);
			}
			return 0;
		}
		return 1;

	case WM_GETMINMAXINFO:
		((MINMAXINFO *)lParam)->ptMaxTrackSize.x =
		((MINMAXINFO *)lParam)->ptMinTrackSize.x = EditWindowSize.x;
		((MINMAXINFO *)lParam)->ptMaxTrackSize.y = EditWindowSize.y + PropListMaxSize.y + 5;
		GetClientRect (GetDlgItem (hDlg, IDOK), &rect);
		((MINMAXINFO *)lParam)->ptMinTrackSize.y = rect.bottom * 10;
		return 0;
	}
	return FALSE;
}

void ShowEAXEditor ()
{
	EAXEditWindow = CreateDialog (g_hInst, MAKEINTRESOURCE(IDD_EAXEDIT), Window, EAXProc);
}

CCMD (eaxedit)
{
	if (EAXEditWindow != 0)
	{
		SetForegroundWindow (EAXEditWindow);
	}
	else
	{
		ForceWindowed = true;
		if (fullscreen)
		{
			setmodeneeded = true;
			SpawnEAXWindow = true;
		}
		else
		{
			ShowEAXEditor ();
		}
	}
}
