/*****************************************************************************
Open Source MIT License

Copyright (c) 2017 Matthew Ahmed

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*****************************************************************************/

// ZDialog.cpp : implementation file
//

//#include "stdafx.h"
#include "Headers.h"
#include "ZDialog.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CZDialog dialog


CZDialog::CZDialog(INT id_dlg, LPCTSTR sCapt, CWnd* pParent, CSettings * pSet,
                   ZWinAppDataClone * pzwd)
	: CDialog(id_dlg, pParent)
{
	//{{AFX_DATA_INIT(CZDialog)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT

  m_pzwd = pzwd;

	m_hIcon = NULL; // unless override in derived

	m_pSet = pSet;
  m_pParent = pParent;
//  m_ulpOldCursor = NULL;

	m_csCapt = sCapt ? sCapt : _T("Constructor called with NULL sCapt");
	m_csCaptMain = m_csCapt; // Probably override in derived

	csn = _T("\n");
	csrn = _T("\r\n");
  sDigits = _T("0123456789");

  m_hfLog = NULL;
}

// For ZDlg converted to CDialog
CZDialog::~CZDialog()
{
  if (m_hfLog) CloseHandle(m_hfLog);
}

BEGIN_MESSAGE_MAP(CZDialog, CDialog)
	//{{AFX_MSG_MAP(CZDialog)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CZDialog message handlers

BOOL CZDialog::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	if (m_hIcon)
	{
		SetIcon(m_hIcon, TRUE);			// Set big icon
//		SetIcon(m_hIcon, FALSE);		// Set small icon
	}

	SetWindowText(m_csCaptMain);
	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

void CZDialog::OnOK()
{
  //if (m_ulpOldCursor) // restore default cursor
  //{
  //  SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_ulpOldCursor);
  //  m_ulpOldCursor = NULL;
  //}
  CDialog::OnOK();
}

void CZDialog::OnCancel()
{
  //if (m_ulpOldCursor) // restore default cursor
  //{
  //  SetClassLongPtr(m_hWnd, GCLP_HCURSOR, m_ulpOldCursor);
  //  m_ulpOldCursor = NULL;
  //}
  CDialog::OnCancel();
}

void CZDialog::MB(LPCTSTR sMsg1, LPCTSTR sMsg2, LPCTSTR sMsg3, 
											LPCTSTR sMsg4, LPCTSTR sMsg5, LPCTSTR sMsg6)
{
	MB(MB_OK, sMsg1, sMsg2, sMsg3, sMsg4, sMsg5, sMsg6);
}

void CZDialog::MB(UINT uType, LPCTSTR sMsg1, LPCTSTR sMsg2, LPCTSTR sMsg3, 
											LPCTSTR sMsg4, LPCTSTR sMsg5, LPCTSTR sMsg6)
{
	CString csMsg = sMsg1;
	if (sMsg2) csMsg += csn + sMsg2;
	if (sMsg3) csMsg += csn + sMsg3;
	if (sMsg4) csMsg += csn + sMsg4;
	if (sMsg5) csMsg += csn + sMsg5;
	if (sMsg6) csMsg += csn + sMsg6;

	::MessageBox(m_hWnd, csMsg, m_csCapt, uType);
}

BOOL CZDialog::fnErr(LPCTSTR sErr1, LPCTSTR sErr2, LPCTSTR sErr3, 
											LPCTSTR sErr4, LPCTSTR sErr5, LPCTSTR sErr6)
{
	CString csErr = sErr1;
	if (sErr2) csErr += csn + sErr2;
	if (sErr3) csErr += csn + sErr3;
	if (sErr4) csErr += csn + sErr4;
	if (sErr5) csErr += csn + sErr5;
	if (sErr6) csErr += csn + sErr6;

	CString csCapt = m_csCapt + _T(" - Error");

	::MessageBox(m_hWnd, csErr, csCapt, MB_OK | MB_ICONEXCLAMATION);

	return FALSE;
}

void CZDialog::fnVoid(LPCTSTR sErr1, LPCTSTR sErr2, LPCTSTR sErr3, 
											LPCTSTR sErr4, LPCTSTR sErr5, LPCTSTR sErr6)
{ // Just fnErr with no value returned
  fnErr(sErr1, sErr2, sErr3, sErr4, sErr5, sErr6);
}

BOOL CZDialog::fnSysErr(LPCTSTR sErr1, LPCTSTR sErr2,
												LPCTSTR sErr3, LPCTSTR sErr4, LPCTSTR sErr5)
{
//	char sErr[1024];
  CString csMsg = sErr1 + csrn;
	if (sErr2) csMsg += sErr2 + csrn;
	if (sErr3) csMsg += sErr3 + csrn;
	if (sErr4) csMsg += sErr4 + csrn;
	if (sErr5) csMsg += sErr5 + csrn;

//	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL,
//								GetLastError(), 0, sErr, 1023, NULL);
  csMsg += _T("Windows' error message was:\n");
  csMsg += GetSysErrMsg();
//	return fnErr(csMsg, sErr);
	return fnErr(csMsg);
}

CString CZDialog::GetSysErrMsg()
{
	TCHAR sErr[1024];
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL,
								GetLastError(), 0, sErr, 1023, NULL);
  return CString(sErr);
}


/*********************************************************************************
 Hack for converting ZDlg class to MFC. Copy ZWin/ZDlg functions used to here: 
**********************************************************************************/

// Global for BROWSEINFO callback member in SHBrowseForFolder
TCHAR hackgzsSelectFolderDefault[MAX_PATH];
INT CALLBACK hackZSelectFolderCallbackProc(HWND hwnd, UINT uMsg,
                                       LPARAM lParam, LPARAM lpData)
{
  TCHAR szDir[MAX_PATH];
	switch(uMsg)
  {
    case BFFM_INITIALIZED:
	    if (lpData)
      {
		    _tcscpy(szDir, hackgzsSelectFolderDefault);
		    SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)szDir);
	    }
	    break;

    case BFFM_SELCHANGED: {
	    if (SHGetPathFromIDList((LPITEMIDLIST) lParam ,szDir))
		    SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM) szDir);
	    break;
    }
    default:
	      break;
	} // switch(uMsg)
         
	return 0;
}

// SHBrowseForFolder wrapper
BOOL CZDialog::SelectFolder(HWND hOwner, LPTSTR sReturn, LPCTSTR sTitle,
                     LPCTSTR sDefault, INT nCsidlRoot)
{
  ClearError(); // So no error msg if FALSE return on Cancel

  LPMALLOC pShMalloc; // For freeing shell allocated pointers
  HRESULT hResGetMalloc = SHGetMalloc(&pShMalloc);
  if (hResGetMalloc != NOERROR)
    return SetError(_T("SHGetMalloc failed"));

  LPITEMIDLIST pRoot = NULL; // unless reset
  if (nCsidlRoot &&
      SHGetFolderLocation(hOwner, nCsidlRoot, NULL, 0, &pRoot) != S_OK)
    return SetError(_T("SHGetFolderLocation failed"));

  TCHAR sTempRet[MAX_PATH];
  BROWSEINFO bi;
  bi.hwndOwner = hOwner;
  bi.pidlRoot = pRoot; // Root is Desktop if NULL
  bi.pszDisplayName = sTempRet; // Selected folder name only, will ignore
  bi.lpszTitle = sTitle; // OK if NULL
  bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT |
               BIF_NEWDIALOGSTYLE | BIF_NONEWFOLDERBUTTON | BIF_UAHINT;
  bi.iImage = NULL;
  bi.lpfn = hackZSelectFolderCallbackProc;

  if (!sDefault || !sDefault[0]) bi.lParam = FALSE;
  else
  {
    _tcscpy(hackgzsSelectFolderDefault, sDefault);
    bi.lParam = TRUE;  // lpData in callback function
  }

  LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
  if (!pidl)
  {
//    pShMalloc->Free(&pRoot);  // Causes error on Release? Duplicative?
    pShMalloc->Release();
    return FALSE; // Presumably Cancel button, don't set error msg
  }

  // Get full path of selected folder
  BOOL bRet = SHGetPathFromIDList(pidl, sReturn);

  // Deallocate whatever the return
// ==> Both of these commented Free's caused exceptions running Shaker in
// debug mode (Set source or target folder in Album Art Copy).
// Click "Continue" both times and it proceeds OK. Repeated calls to this fn
// with the Free's commented didn't build up memory usage. No errors all this
// time in release mode. NYC Light Bulb handling, don't try to figure it out.
  //pShMalloc->Free(&pidl);
  //pShMalloc->Free(&pRoot);
  pShMalloc->Release();

  if (!bRet)
    return SetError(_T("SHGetPathFromIDList error getting full path"), sTempRet);

  return TRUE;
}

BOOL CZDialog::ReadReg(HKEY hOpen, LPCTSTR sKey, LPCTSTR sValue,
											LPTSTR sTarget, BOOL bErrMsg, DWORD dwSize)
{
	HKEY hKey;
	if (RegOpenKeyEx(hOpen, sKey, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
    return bErrMsg ? fnSysErr(_T("Error opening Registry Key:"), sKey)
                   : SetSysErr(_T("Error opening Registry Key:"), sKey);

	DWORD dw = dwSize;
	LONG lRet = RegQueryValueEx(hKey, sValue, 0, NULL, (BYTE*) sTarget, &dw);
	RegCloseKey(hKey); // whether or not error

	if (lRet != ERROR_SUCCESS)
    return bErrMsg ? fnSysErr(_T("Error reading registry data:"), sValue)
                   : SetSysErr(_T("Error reading registry data:"), sValue);

	return TRUE;
}

BOOL CZDialog::StartLog(LPCTSTR sFileName, LPCTSTR sMsg, BOOL bFlush)
{
  TCHAR sWork[2048];
  TCHAR sWork2[2048];
  TCHAR sFullPath[_MAX_PATH];
  TCHAR sExeNameNoExt[200];

  m_bFlushLog = bFlush; // Flush on every write

//  // This puts just directory path with ending slash in sFullPath
//  if (!GetOwnPathParsed(sFullPath, sNameNoExt, sWork))
//    return SetError(_T("Error deriving log path"), sFullPath); // Msg in sFullPath

  _tcscpy(sFullPath, m_pzwd->sAppDocsDirSlash);
  if (!sFullPath[0])
    return SetError(_T("Error finding folder path for log file (gsAppDocsDirSlash)"));

  if (!EnsureDirExists(m_pzwd->sAppDocsDirSlash)) // Create if not already there
  {
    UtilGetError(sWork);
    return fnErr(_T("Error ensuring config dir exists"), m_pzwd->sAppDataDirSlash, sWork);
  }

  if (sFileName) _tcscat(sFullPath, sFileName);
  else
  {
    // Only care about sExeNameNoExt
    if (!GetProgPathParsed(sWork, sExeNameNoExt, sWork2))
      return SetError(_T("Error deriving log filename"), sWork); // Msg in sWork

    _tcscat(sFullPath, sExeNameNoExt);
    _tcscat(sFullPath, _T(".log"));
  }

  BOOL bAlreadyOpen = FALSE;
  if (m_hfLog) // Shouldn't happen except by mistake
  {
    if (!CloseLog()) // Already SetError
      return AppendError(_T("Was closing for new StartLog, file name:"), sFileName);
    bAlreadyOpen = TRUE; // Add log note below
  }

  m_hfLog = CreateFile(sFullPath, GENERIC_WRITE, FILE_SHARE_READ, NULL,
                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

  if (m_hfLog == INVALID_HANDLE_VALUE)
  {
    GetSysErrorMsg(sWork);
    return SetError(_T("Error opening log file:"), sFullPath, sWork);
  }

  // Assemble first line to write to log file
  //_tstrdate(sWork); // Date stamp mm/dd/yy
  //_tstrtime(sWork2); // 24 hour time hh:mi:ss
  //_tcscat(sWork, " ");
  //_tcscat(sWork, sWork2);

  // Avoid needing time.h
  SYSTEMTIME localtime;
  GetLocalTime(&localtime);
  GetDateFormat(LOCALE_USER_DEFAULT, NULL,
                &localtime, _T("MM'/'dd'/'yy' '"), sWork, 2048);
  GetTimeFormat(LOCALE_USER_DEFAULT, NULL,
                &localtime, _T("HH':'mm':'ss"), sWork2, 2048);
  _tcscat(sWork, sWork2);


  if (sMsg) _stprintf(sWork2, _T("%s  %s\r\n"), sWork, sMsg);
  else _stprintf(sWork2, _T("%s  Start %s\r\n"), sWork, sExeNameNoExt);

  if (bAlreadyOpen)
    _tcscat(sWork2, _T("\r\nHad to close m_hfLog, still open from previous log."));

  INT nBytes = _tcslen(sWork2) * sizeof(TCHAR);
  DWORD dwIgnore;

  if (!WriteFile(m_hfLog, sWork2, (DWORD) nBytes, &dwIgnore, NULL)
      || (m_bFlushLog && !FlushFileBuffers(m_hfLog)) )
  {
    GetSysErrorMsg(sWork);
    CloseHandle(m_hfLog); // Caller assumes file not open if this returns FALSE
    return SetError(_T("Error writing first line to log file:"),
                      sFullPath, sWork);
  }

  return TRUE;
}

BOOL CZDialog::ToLog(LPCTSTR sMsg1, LPCTSTR sMsg2, LPCTSTR sMsg3)
{
  if (!m_hfLog) return FALSE; // May be OK, caller logs only if turned on

  if (!WriteLineToLog(sMsg1)) return FALSE; // m_sError already set
  // bTimeStamp FALSE for subsequent lines if any
  if (sMsg2 && !WriteLineToLog(sMsg2, FALSE)) return FALSE;
  if (sMsg3 && !WriteLineToLog(sMsg3, FALSE)) return FALSE;

  if (m_bFlushLog && !FlushFileBuffers(m_hfLog))
  {
    TCHAR sError[1024];
    GetSysErrorMsg(sError);
    return SetError(_T("Error flushing log writes to disk"), sError);
  }

  return TRUE;
}

BOOL CZDialog::CloseLog(LPCTSTR sMsg)
{
  BOOL bRet = ToLog(sMsg ? sMsg : _T("Finished")); // Proceed to Close even if error
  TCHAR sSysError[1024];

  if (!CloseHandle(m_hfLog))
  {
    LPTSTR sError = _T("Error closing log file");
    GetSysErrorMsg(sSysError);

    if (bRet) return SetError(sError, sSysError);
    else return AppendError(sError, sSysError);
  }

  m_hfLog = NULL;
  return bRet; // ToLog's return
}

BOOL CZDialog::WriteLineToLog(LPCTSTR sLine, BOOL bTimeStamp, BOOL bAddRN)
{
  TCHAR sTimeStamp[16];
  UINT uWriteLen = _tcslen(sLine) + 20;
  LPTSTR spWrite = new TCHAR[uWriteLen];
  if (!spWrite)
    return SetError(_T("Error allocating string to write log"), sLine);

  if (bTimeStamp) // _tstrtime(sTimeStamp); avoid time.h // 24 hour time hh:mi:ss
    GetTimeFormat(LOCALE_USER_DEFAULT, NULL, NULL, _T("HH':'mm':'ss"), sTimeStamp, 16);
  else _tcscpy(sTimeStamp, _T("        ")); // padding same length as time stamp

  _stprintf(spWrite, _T("%s  %s%s"), sTimeStamp, sLine, bAddRN ? _T("\r\n") : _T(""));

  INT nBytes = _tcslen(spWrite) * sizeof(TCHAR);
  DWORD dwIgnore;

  if (!WriteFile(m_hfLog, spWrite, (DWORD) nBytes, &dwIgnore, NULL))
  {
    TCHAR sSysError[1024];
    GetSysErrorMsg(sSysError);
    SetError(_T("Error writing line to log file:"),
               sSysError, spWrite); // May truncate if long spWrite
    delete [] spWrite;
    return FALSE;
  }
  delete [] spWrite;

  return TRUE;
}

BOOL CZDialog::LogRaw(LPCTSTR sLine, BOOL bAddRN)
{
  UINT uWriteLen = _tcslen(sLine) + 20;
  LPTSTR spWrite = new TCHAR[uWriteLen];
  if (!spWrite)
    return SetError(_T("Error allocating string to write log"), sLine);

  _stprintf(spWrite, _T("%s%s"), sLine, bAddRN ? _T("\r\n") : _T(""));

  INT nBytes = _tcslen(spWrite) * sizeof(TCHAR);
  DWORD dwIgnore;

  if (!WriteFile(m_hfLog, spWrite, (DWORD) nBytes, &dwIgnore, NULL))
  {
    TCHAR sSysError[1024];
    GetSysErrorMsg(sSysError);
    SetError(_T("Error writing line to log file:"),
               sSysError, spWrite); // May truncate if long spWrite
    delete [] spWrite;
    return FALSE;
  }
  delete [] spWrite;

  return TRUE;
}


void CZDialog::zPeekMessage()
{
  static MSG msg; // Don't reallocate on numerous calls from loops
  while (PeekMessage(&msg , NULL, NULL, NULL, PM_REMOVE))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}

BOOL CZDialog::SetError(LPCTSTR sMsg1, LPCTSTR sMsg2, LPCTSTR sMsg3,
					          LPCTSTR sMsg4, LPCTSTR sMsg5, LPCTSTR sMsg6)
{
  // Always returns FALSE.
  m_sError[0] = _T('\0');
  return AppendError(sMsg1, sMsg2, sMsg3, sMsg4, sMsg5, sMsg6);
}

BOOL CZDialog::AppendError(LPCTSTR sMsg1, LPCTSTR sMsg2, LPCTSTR sMsg3,
					               LPCTSTR sMsg4, LPCTSTR sMsg5, LPCTSTR sMsg6)
{
  // Concat any non-null sMsgN's until run out of space.
  // StrCatWhatFits return FALSE means no more room, so discard the rest.
  if (sMsg1 && !StrCatWhatFits(m_sError, SERROR_MAX, sMsg1, TRUE)) return FALSE;
  if (sMsg2 && !StrCatWhatFits(m_sError, SERROR_MAX, sMsg2, TRUE)) return FALSE;
  if (sMsg3 && !StrCatWhatFits(m_sError, SERROR_MAX, sMsg3, TRUE)) return FALSE;
  if (sMsg4 && !StrCatWhatFits(m_sError, SERROR_MAX, sMsg4, TRUE)) return FALSE;
  if (sMsg5 && !StrCatWhatFits(m_sError, SERROR_MAX, sMsg5, TRUE)) return FALSE;
  if (sMsg6 && !StrCatWhatFits(m_sError, SERROR_MAX, sMsg6, TRUE)) return FALSE;
  return FALSE;
}

BOOL CZDialog::SetSysErr(LPCTSTR sErr1, LPCTSTR sErr2,
									     LPCTSTR sErr3, LPCTSTR sErr4)
{
  TCHAR sSysErr[2048];
  GetSysErrorMsg(sSysErr);
  return SetError(sErr1, sErr2, sErr3, sErr4, sSysErr);
}


void CZDialog::ClearError()
{
  m_sError[0] = _T('\0');
}





/*
BOOL CZDialog::PickFolder(CString & csTarget, LPCTSTR sTitle,
														LPCTSTR sText, LPCTSTR sStartDir)
{
	TCHAR sIgnore[_MAX_PATH];
	TCHAR sPath[_MAX_PATH];
	LPITEMIDLIST pidlSelected = NULL;
	BROWSEINFO bi = {0};
	LPMALLOC pMalloc = NULL;

	TCHAR slText[1024];
	if (sText) strcpy(slText, sText);
	else strcpy(slText, "Select Folder");

	CString csTitleAndStartDir;
	// Browse dlg window title, '/' delim, start folder (ok if '/' in title)
	csTitleAndStartDir.Format("%s/%s", sTitle ? sTitle : m_csCapt,
														sStartDir ? sStartDir : ""); // My Computer if ""

	SHGetMalloc(&pMalloc);

	bi.hwndOwner = m_hWnd;
	bi.pidlRoot = NULL;
	bi.pszDisplayName = sIgnore;  // Gets folder name only, not full path
	bi.lpszTitle = slText;				// static control, not window title
	bi.ulFlags = BIF_RETURNONLYFSDIRS;
//	bi.lpfn = CZDialog::BrowseForFolderCallback;
	bi.lpfn = BrowseForFolderCallback;
	bi.lParam =  (LPARAM) (LPCSTR) csTitleAndStartDir;

	pidlSelected = SHBrowseForFolder(&bi);
	
	BOOL bError = FALSE;
	BOOL bSelected = (pidlSelected != NULL);
	if (bSelected)
	{
		// Folder was selected.  Get Full Path
		bSelected = SHGetPathFromIDList(pidlSelected, sPath);
		if (!bSelected) bError = TRUE; // Clean up before fnErr
		pMalloc->Free(pidlSelected);
	}
	pMalloc->Release();

	if (bError) return fnErr("Shell error getting folder path");
	if (!bSelected) return FALSE; // Cancel button

	csTarget = sPath;
	return TRUE;
}

int CALLBACK CZDialog::BrowseForFolderCallback(HWND hwnd, UINT uMsg,
																				LPARAM lParam,  LPARAM lpData)
{
	switch(uMsg)
	{
		case BFFM_INITIALIZED:
			
			if (lpData)
			{
				CString csData = (LPTSTR) lpData;
				INT nIndex = csData.ReverseFind('/');
				CString csFolder = csData.Mid(nIndex + 1);
				CString csTitle = csData.Left(nIndex);
				if (!csTitle.IsEmpty()) ::SetWindowText(hwnd, csTitle);
				if (!csFolder.IsEmpty())
					::SendMessage(hwnd, BFFM_SETSELECTION, TRUE,
															(LPARAM) (LPCTSTR) csFolder);
			}

		break;
	}
	return 0;
}
*/
/*
BOOL CZDialog::GetShellDirName(LPCTSTR sValue, LPTSTR sDir, BOOL bCurUser)
{
	HKEY hKey;
	LONG lRet;
	DWORD dwSize = MAX_PATH; // assumes sDir[MAX_PATH]
	TCHAR * sKey = "Software\\Microsoft\\Windows\\CurrentVersion\\"
								 "Explorer\\Shell Folders";
	TCHAR sErr[256];

	if (RegOpenKeyEx(bCurUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE,
																		sKey, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
	{
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL,
								GetLastError(), 0, sErr, 200 , NULL);
		wsprintf(sDir, "Error opening registry key. Windows reports:\r\n%s", sErr);
		return FALSE;
	}

	lRet = RegQueryValueEx(hKey, sValue, 0, NULL, (BYTE*) sDir, &dwSize);

	RegCloseKey(hKey); // whether or not error

	if (lRet != ERROR_SUCCESS)
	{
		wsprintf(sDir, "Registry query error. Windows reports:\r\n%s", sErr);
		return FALSE;
	}
	return TRUE;
}

BOOL CZDialog::GetShellDirName(LPCTSTR sValue, CString & csDir, BOOL bCurUser)
{
	TCHAR sDir[_MAX_PATH];
	BOOL bRet = GetShellDirName(sValue, sDir, bCurUser);
	csDir = sDir;
	return bRet;
}

BOOL CZDialog::fnShellExecOpen(LPCTSTR lpFile)
{
  // lpFile can be a URL
  nBS = (int) ShellExecute(m_hWnd, "open", lpFile,
                           NULL, NULL, SW_SHOWNORMAL);
  if (nBS < 33)
  {
    sprintf(sBS, "ShellExecute failed, returned %d", nBS);
    return fnSysErr(sBS);
  }
  return TRUE;
}

BOOL CZDialog::fnLBSelStringExact(HWND hCtl, LPCTSTR sSearch,
                                  LRESULT & nlInd)
{
  // LB_SELECTSTRING (like LB_FINDSTRING) is case insensitive match on 
  // start of string, and also gets faked out by hyphen.  
  // N.B.:  LB_FINDSTRINGEXACT used here is still case insensitive.
  LRESULT nlFound = ::SendMessage(hCtl, LB_FINDSTRINGEXACT, -1, (LPARAM) sSearch);
  if (nlFound == LB_ERR)
    return fnErr("No listbox item found matching expected string:",
                 sSearch);

  if (::SendMessage(hCtl, LB_SETCURSEL, (WPARAM) nlFound, 0) != LB_ERR)
  {
    nlInd = nlFound;
    return TRUE;
  }
  // else:
  CString csErr;
  csErr.Format("Error setting listbox selection at index %d,\n"
               "for searched string:\n%s", nlFound, sSearch);
  return fnSysErr(csErr);
}

BOOL CZDialog::fnLBSelStringExact(HWND hCtl, LPCTSTR sSearch)
{
  LRESULT nlIgnore;
  return fnLBSelStringExact(hCtl, sSearch, nlIgnore);
}

BOOL CZDialog::fnLBSelStringExact(int nID, LPCTSTR sSearch,
                                  LRESULT & nlInd)
{
  return fnLBSelStringExact(::GetDlgItem(m_hWnd, nID), sSearch, nlInd);
}

BOOL CZDialog::fnLBSelStringExact(int nID, LPCTSTR sSearch)
{
  LRESULT nlIgnore;
  return fnLBSelStringExact(::GetDlgItem(m_hWnd, nID), sSearch, nlIgnore);
}

BOOL CZDialog::fnReadReg(HKEY hOpen, LPCSTR sKey, LPCSTR sValue,
											  LPSTR sTarget, BOOL bErrMsg, DWORD dwSize)
{
	HKEY hKey;
	if (RegOpenKeyEx(hOpen, sKey, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
		return bErrMsg ? fnSysErr("Error opening Registry Key:", sKey) : FALSE;

	DWORD dw = dwSize;
	LONG lRet = RegQueryValueEx(hKey, sValue, 0, NULL, (BYTE*) sTarget, &dw);
	RegCloseKey(hKey); // whether or not error

	if (lRet != ERROR_SUCCESS)
		return bErrMsg ? fnSysErr("Error reading registry data:", sValue) : FALSE;

	return TRUE;
}

BOOL CZDialog::fnGetExePath(LPCSTR sExe, LPSTR sTarget,
													  BOOL bDirOnly, BOOL bErrMsg)
{
	char sKey[1024];

	wsprintf(sKey, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"
								"App Paths\\%s", sExe);

//	if (!fnReadReg(HKEY_LOCAL_MACHINE, sKey, "Path", sTarget, bErrMsg))
	if (!fnReadReg(HKEY_LOCAL_MACHINE, sKey, NULL, sTarget, bErrMsg))
		return FALSE;

	if (!bDirOnly) return TRUE; // Want it including exe file name

// "Path" would get dir only, but may not be in registry (e.g. WordPad)
	char* ps = strrchr(sTarget, '\\');
	if (!ps) return FALSE;
	ps[0] = '\0';
	return TRUE;
}

int CZDialog::fnWaitForMsg(UINT uMsg, UINT uTimeOut, WPARAM * pWp,
																		LPARAM * pLp, UINT uTimerID)
{
	MSG msg;

  if (! ::SetTimer(m_hWnd, uTimerID, uTimeOut, NULL)) return WFM_ERROR;

	while (TRUE)  // Wait in PeekMessage loop until uMsg or WM_TIMER
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (uMsg && msg.message == uMsg)
			{
        ::KillTimer(m_hWnd, uTimerID);
				if (pWp) *pWp = msg.wParam;
				if (pLp) *pLp = msg.lParam;
				return WFM_GOTMSG;
			}
			else if ( (msg.message == WM_TIMER && msg.wParam == (WPARAM) uTimerID))
			{
        ::KillTimer(m_hWnd, uTimerID);
				return WFM_TIMEOUT;
			}
			else
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		} // END if (PeekMessage(&msgL, NULL, 0, 0, PM_REMOVE))
	} // END while (TRUE)

} // END int ZWin::fnWaitForMesgUINT uDelay, UINT uTimerID)

int CZDialog::PublicProcessString(LPCTSTR sStr)
{
  return 0;
}

void CZDialog::fnTruncateCtime(CTime & ct)
{
  ct = CTime::CTime(ct.GetYear(), ct.GetMonth(), ct.GetDay(), 0, 0, 0);
}

BOOL CZDialog::fnFindExtent(LPCTSTR sSource, LPCTSTR sBegin, LPCTSTR sEnd,
                            INT * npIxBegin, INT * npLenFound, BOOL bIgnoreCase)
{
  CString csSource(sSource);
  CString csBegin(sBegin);
  CString csEnd(sEnd);

  if (bIgnoreCase)
  {
    csSource.MakeLower();
    csBegin.MakeLower();
    csEnd.MakeLower();
  }

  *npIxBegin = csSource.Find(sBegin);
  if (*npIxBegin == -1) return FALSE;

  INT nLenBegin = csBegin.GetLength();
  csSource = csSource.Mid(*npIxBegin + nLenBegin);

  *npLenFound = csSource.Find(sEnd);
  if (*npLenFound == -1) return FALSE;

  *npLenFound += nLenBegin + csEnd.GetLength();
  return TRUE;
} // BOOL CZDialog::fnFindExtent(LPCTSTR sSource, LPCTSTR sBegin, LPCTSTR sEnd,
  //                     INT * npIxBegin, INT * npLenFound, BOOL bIgnoreCase)

BOOL CZDialog::fnFindExtent(LPCTSTR sSource, LPCTSTR sBegin, LPCTSTR sEnd,
         INT * npIxBegin, INT * npLenFound, CString & csFound, BOOL bIgnoreCase)
{
  if (!fnFindExtent(sSource, sBegin, sEnd, npIxBegin, npLenFound, bIgnoreCase))
    return FALSE;

  CString csSource(sSource);
  csFound = csSource.Mid(*npIxBegin, *npLenFound);
  return TRUE;
}

BOOL CZDialog::fnReplaceExtent(CString & csSource, LPCTSTR sBegin, LPCTSTR sEnd,
                               LPCTSTR sReplace, BOOL bIgnoreCase)
{
  INT nBegin;
  INT nLen;

  if (!fnFindExtent(csSource, sBegin, sEnd, &nBegin, &nLen, bIgnoreCase))
    return FALSE;

  csSource = csSource.Left(nBegin) + sReplace + csSource.Mid(nBegin + nLen);
  return TRUE;
}

BOOL CZDialog::fnReadTextFile(LPCTSTR sPath, CString & csReturn,
                              INT nMinBytes, INT nMaxBytes)
{
  CFile cf;
	CFileException ex;
  TCHAR sTemp[1024];

	if (!cf.Open(sPath, CFile::modeRead | CFile::shareExclusive, &ex))
	{
		ex.GetErrorMessage(sTemp, 512);
		cf.Abort();
		csReturn.Format("Error opening file for read.\n%s", sTemp);
    return FALSE;
	}

//	TCHAR sBuf[131072]; // Assume 128K safe enough overkill for index.htm size
  TCHAR * sBuf;
	if ((sBuf = new TCHAR[nMaxBytes]) == NULL) return FALSE;
	memset(sBuf, '\0', nMaxBytes); // Ensure null terminated string any file length

  INT nBytesRead;
	try
	{
		nBytesRead = cf.Read(sBuf, nMaxBytes - 1);
		cf.Close();
	}
	catch(CFileException* e)
	{
		e->GetErrorMessage(sTemp, 512);
		e->Delete();
		cf.Abort();
		csReturn.Format("Error reading or closing file. Windows says:\n%s", sTemp);
    delete sBuf;
    return FALSE;
	}

  if (strlen(sBuf) != nBytesRead)
  {
    csReturn.Format("File contains a null character?\nCstring length = %d\n"
                    "Bytes read from file = %d", strlen(sBuf), nBytesRead);
    delete sBuf;
    return FALSE;
  }

  csReturn = sBuf; // Unless replaced by error message below
  delete sBuf;

  if (nBytesRead == nMaxBytes - 1) // Read max, almost certainly more in file
  {
    csReturn.Format("File larger than expected, over %d bytes", nBytesRead - 1);
    return FALSE;
  }
  if (nBytesRead < nMinBytes) // No way should be this small
  {
    csReturn.Format("File smaller than expected, %d bytes", nBytesRead);
    return FALSE;
  }

  return TRUE;
} // BOOL CZDialog::fnReadTextFile(LPCTSTR csPath, CString & csReturn,
  //                               INT nMinBytes, INT nMaxBytes)

BOOL CZDialog::fnWriteTextFile(LPCTSTR sPath, LPCTSTR sText, CString & csErrMsg)
{
  CFile cf;
	CFileException ex;
  TCHAR sTemp[1024];

	if (!cf.Open(sPath, CFile::modeCreate | CFile::modeWrite |
                      CFile::shareExclusive, &ex))
	{
		ex.GetErrorMessage(sTemp, 512);
		cf.Abort();
		csErrMsg.Format("Error opening file for write.\n%s", sTemp);
    return FALSE;
	}

	try
	{
		cf.Write(sText, strlen(sText));
		cf.Close();
	}
	catch(CFileException* e)
	{
		e->GetErrorMessage(sTemp, 512);
		e->Delete();
		cf.Abort();
		csErrMsg.Format("Error writing or closing file. Windows says:\n%s", sTemp);
    return FALSE;
	}

  return TRUE;
} // BOOL CZDialog::fnWriteTextFile(LPCTSTR sPath, LPCTSTR sText, CString & csErrMsg)
*/