/*****************************************************************************
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.
*****************************************************************************/

// Settings.cpp: implementation of the CSettings class.
//
//////////////////////////////////////////////////////////////////////

#include "Headers.h"
// #include <windows.h> // Need these 2 .h's if not using Headers.h
// #include <stdarg.h>

#include "Settings.h"

CSettings::CSettings()
{
	m_pSects[0] = &m_sectMain;
	m_pSects[1] = &m_sectPref;
	m_pSects[2] = &m_sectCopy;
	m_pSects[3] = &m_sectOptCopy;
	m_pSects[4] = &m_sectOptLog;
	////- ADD SAME FOR ADDITIONAL SECTIONS, AND CHANGE NUM_OF_SECTS


	for (INT i = 0 ; i < NUM_OF_SECTS ; i++)
	{
		m_pSects[i]->sSectName = NULL;
		m_pSects[i]->uNextInd = 0;
	}

	ClearError();
	m_sErrKeyNotFound = _T("Attempt to save INI value, key not specified");
	m_hDlg = NULL;
	m_sErrDlgNotSet = _T("Attempt to access dialog controls with dialog not set");
}

BOOL CSettings::Init(LPCTSTR sIniDirSlash, LPCTSTR sIniFileNameWithExt)
{
	////- Can insert ini file name param here or in call to Init
	if (!GetOwnIniPath(sIniDirSlash, sIniFileNameWithExt)) return FALSE;

	if (!SetStr(m_sectMain.sSectName, _T("Shuffle Tab"))) return FALSE;
	if (!SetStr(m_sectPref.sSectName, _T("Shuffle Settings"))) return FALSE;
	if (!SetStr(m_sectCopy.sSectName, _T("Album Art Tab"))) return FALSE;
	if (!SetStr(m_sectOptCopy.sSectName, _T("Album Art Settings"))) return FALSE;
	if (!SetStr(m_sectOptLog.sSectName, _T("Logging Settings"))) return FALSE;
	////- ADD SAME FOR ADDITIONAL SECTIONS


	////- ADD INI VARIABLES:
/*  e.g.:
	if (!Add(m_sect, "bCheck", bCheck, FALSE)) return FALSE;
	if (!Add(m_sect, "sEdit", sEdit, "")) return FALSE;
	if (!Add(m_sect, "nEdit", nEdit, -12)) return FALSE;
	if (!Add(m_sect, "uEdit", uEdit, 4294967295)) return FALSE;
  // For default DlgRBsFrom/ToKey, set var to 0 for 1st RB, 1 for 2nd etc.
	if (!Add(m_sect, "uRB", uRB, 0)) return FALSE;
*/

	if (!Add(m_sectPref, _T("UseLastDir"), bTgtDirLast, TRUE)) return FALSE;
	if (!Add(m_sectPref, _T("LastDir"), sTgtDirLast, _T(""))) return FALSE;

	if (!Add(m_sectPref, _T("WmpNotify"), bWmpNotify, TRUE)) return FALSE;
	if (!Add(m_sectPref, _T("DefaultRating"), nDefStars, 2)) return FALSE;

	if (!Add(m_sectPref, _T("AddSrcName"), bSrcName, TRUE)) return FALSE;
	if (!Add(m_sectPref, _T("AddLabel"), bLabel, TRUE)) return FALSE;
	if (!Add(m_sectPref, _T("LabelPlace"), uLabelPlace, RB_BEFORE)) return FALSE;
	if (!Add(m_sectPref, _T("LabelText"), sLabel, _T("sns"))) return FALSE;
	if (!Add(m_sectPref, _T("AddDate"), bDate, FALSE)) return FALSE;
	if (!Add(m_sectPref, _T("DatePlace"), uDatePlace, RB_AFTER)) return FALSE;

  // In v2.2 not really convert, but same var name to pick up v1 pref
//	if (!Add(m_sectPref, _T("ConvertWpl"), ubConvertWpl, 0)) return FALSE;
  // Version 3.0: 0 is still leave .wpl, 1 still .m3u; add 2 = .m3u8
//	if (!Add(m_sectPref, _T("ConvertWpl"), nConvertWpl, RB_FMT_WPL)) return FALSE;
//   v3.22 force new defaults
	//if (!Add(m_sectPref, _T("PlaylistFmt"), nPlaylistFmt, RB_FMT_LAST)) return FALSE;
	if (!Add(m_sectPref, _T("PlaylistFmt"), nPlaylistFmt, RB_FMT_WPL)) return FALSE;
	if (!Add(m_sectPref, _T("PlaylistFmtLast"), nPlaylistFmtLast, RB_FMT_WPL)) return FALSE;
	if (!Add(m_sectPref, _T("SongRelativePath"), bSongRelativePath, TRUE)) return FALSE;

  if (!Add(m_sectPref, _T("Frequency1"), nReps1, UREPS1_DEFAULT)) return FALSE;
  if (!Add(m_sectPref, _T("Frequency2"), nReps2, UREPS2_DEFAULT)) return FALSE;
  if (!Add(m_sectPref, _T("Frequency3"), nReps3, UREPS3_DEFAULT)) return FALSE;
  if (!Add(m_sectPref, _T("Frequency4"), nReps4, UREPS4_DEFAULT)) return FALSE;
  if (!Add(m_sectPref, _T("Frequency5"), nReps5, UREPS5_DEFAULT)) return FALSE;
  if (!Add(m_sectPref, _T("FrequencySame"), nRepsSame, UREPS_SAME_DEFAULT))
    return FALSE;
  // Long term versions of above, not affected by latest selection
  if (!Add(m_sectPref, _T("Frequency1Default"), nReps1Default, UREPS1_DEFAULT))
    return FALSE;
  if (!Add(m_sectPref, _T("Frequency2Default"), nReps2Default, UREPS2_DEFAULT))
    return FALSE;
  if (!Add(m_sectPref, _T("Frequency3Default"), nReps3Default, UREPS3_DEFAULT))
    return FALSE;
  if (!Add(m_sectPref, _T("Frequency4Default"), nReps4Default, UREPS4_DEFAULT))
    return FALSE;
  if (!Add(m_sectPref, _T("Frequency5Default"), nReps5Default, UREPS5_DEFAULT))
    return FALSE;
  if (!Add(m_sectPref, _T("FrequencySameDefault"), nRepsSameDefault, UREPS_SAME_DEFAULT))
    return FALSE;

  if (!Add(m_sectPref, _T("RememberFrequency"), uRememberReps,
      UREPS_REMEMBER_DEFAULT)) return FALSE;
  if (!Add(m_sectPref, _T("DaysNew"), nDaysNew, NDAYSNEW_DEFAULT)) return FALSE;

  // Added in 3.0, for LB w/ both Genres & Playlists; also remember new as fixed date
  if (!Add(m_sectPref, _T("ListboxOrder"), nListboxOrder, RB_PLISTS_FIRST))
    return FALSE;
  // Var is boolean, but with just 2 RB's, ok to handle as INT
  if (!Add(m_sectPref, _T("DaysNewAsFixedDate"), bDaysNewAsDate, RB_NUM_DAYS))
    return FALSE;

  // In 3.0 now no dialog change for this. FALSE unless manually edit ini file.
  if (!Add(m_sectPref, _T("IncludeAutoPlists"), bIncludeAutoPlists, FALSE))
    return FALSE;

  if (!Add(m_sectMain, _T("RepeatsOption"), uSepOption, RB_REPEAT_DIST))
    return FALSE;
  if (!Add(m_sectMain, _T("RepeatSepOnlySize"), uSepCount, USEPONLY_DEFAULT))
    return FALSE;
  if (!Add(m_sectMain, _T("RepeatSepDistSize"), uSepDistSize, USEPDIST_DEFAULT))
    return FALSE;
  if (!Add(m_sectMain, _T("TruncateSize"), nTruncSize, NTRUNCSIZE_DEFAULT))
    return FALSE;
  if (!Add(m_sectMain, _T("TruncateOutput"), bTruncate, TRUE)) return FALSE;
  if (!Add(m_sectMain, _T("MaxReps"), nMaxReps, UMAX_REPS_DEFAULT)) return FALSE;
  if (!Add(m_sectMain, _T("LimitReps"), bLimitReps, FALSE)) return FALSE;

  // New 2.1 options
  if (!Add(m_sectMain, _T("TruncateNoReps"), bTruncNoReps, TRUE)) return FALSE;
  // Effectively boolean (2 radio buttons), but use INT for easy set & save
  if (!Add(m_sectMain, _T("WhichTruncOpt"), nUseMaxTruncSize, 0)) return FALSE;
  // No variable for Warn Overwrite, set checked at start every time
 
  // Copy tab
  if (!Add(m_sectCopy, _T("SourceJpg"), uSrcJpg, RB_JPG_F)) return FALSE;
  if (!Add(m_sectCopy, _T("TargetJpg"), uTgtJpg, RB_JPG_F)) return FALSE;
	if (!Add(m_sectCopy, _T("SourceJpgOther"), sSrcJpgOther, _T(""))) return FALSE;
	if (!Add(m_sectCopy, _T("TargetJpgOther"), sTgtJpgOther, _T(""))) return FALSE;
	if (!Add(m_sectCopy, _T("LastSourceFolder"), sArtLastSrc, _T(""))) return FALSE;

  // Copy options dlg
  if (!Add(m_sectOptCopy, _T("ArtSrcDir"), uArtSrcDir, RB_SRC_LAST)) return FALSE;
//	if (!Add(m_sectOptCopy, "ArtSrcDirOther", sArtSrcDirOth, "")) return FALSE;
  if (!Add(m_sectOptCopy, _T("ArtTgtDir"), uArtTgtDir, RB_TGT_MUS)) return FALSE;
	if (!Add(m_sectOptCopy, _T("ArtTgtDirOther"), sArtTgtDirOth, _T(""))) return FALSE;
	if (!Add(m_sectOptCopy, _T("ArtSaveHide"), bArtSaveHide, FALSE)) return FALSE;
  // These in Match dlg, keep in m_sectOptCopy:
	if (!Add(m_sectOptCopy, _T("ArtHideNoImg"), bArtHideNoImg, FALSE)) return FALSE;
	if (!Add(m_sectOptCopy, _T("ArtHideNoSrc"), bArtHideNoSrc, FALSE)) return FALSE;
	if (!Add(m_sectOptCopy, _T("ArtHideNoSrc"), bArtHideNoSrc, FALSE)) return FALSE;
	if (!Add(m_sectOptCopy, _T("ArtHideRed"), bArtHideRed, FALSE)) return FALSE;


  // In v.3 move these from m_sectPref to m_sectOptLog
  if (!Add(m_sectOptLog, _T("SaveLog"), bLog, FALSE)) return FALSE;
  if (!Add(m_sectOptLog, _T("FlushEachWrite"), bLogFlush, FALSE)) return FALSE;
  if (!Add(m_sectOptLog, _T("ShowElapsed"), bElapsed, FALSE)) return FALSE;
  // In v.3 move these from m_sectOptCopy to m_sectOptLog
  if (!Add(m_sectOptLog, _T("MatchLog"), bArtLog, FALSE)) return FALSE;
  if (!Add(m_sectOptLog, _T("MatchLogDetail"), bArtLogDetail, FALSE)) return FALSE;
  if (!Add(m_sectOptLog, _T("MatchLogFlush"), bArtLogFlush, FALSE)) return FALSE;

  //////   NOTHING AFTER HERE SHOULD NEED TO BE MASSAGED     //////

	for (INT i = 0 ; i < NUM_OF_SECTS ; i++)
	{
		if (!ReadIniSection(*m_pSects[i])) return FALSE;
	}

	return TRUE;
}

CSettings::~CSettings()
{
	for (INT i = 0 ; i < NUM_OF_SECTS ; i++)
	{
		DeleteSection(*m_pSects[i]);
	}
}

void CSettings::DeleteSection(IniSect & sect)
{
	for (UINT uInd = 0 ; uInd < sect.uNextInd ; uInd ++)
	{
		delete sect.pKeyArr[uInd]->sKeyName;
		if (sect.pKeyArr[uInd]->ppS) delete *(sect.pKeyArr[uInd]->ppS);
		delete sect.pKeyArr[uInd];
	}
	//sdelete(sect.sSectName);
  if (sect.sSectName)
  {
	  delete [] sect.sSectName;
	  sect.sSectName = NULL;
  }
}

IniKey * CSettings::AllocateNextKey(IniSect & sect, LPCTSTR sKeyName)
{
	if (sect.uNextInd >= MAX_KEYS_IN_SECT)
		return (IniKey *) SetError(_T("Max number ini variables exceeded"));

	sect.pKeyArr[sect.uNextInd] = new IniKey;
	if (!sect.pKeyArr[sect.uNextInd])
		return (IniKey *) SetError(_T("Error allocating memory for ini key"));

	sect.pKeyArr[sect.uNextInd]->sKeyName = NULL; // to avoid delete attempt
	if (!SetStr(sect.pKeyArr[sect.uNextInd]->sKeyName, sKeyName))
	{
		delete sect.pKeyArr[sect.uNextInd];
		return NULL; // msg already
	}

	sect.uNextInd ++;
	return sect.pKeyArr[sect.uNextInd - 1];
}

BOOL CSettings::Add(IniSect & sect, LPCTSTR sKeyName, LPTSTR & sVar, LPCTSTR sDef)
{
	if (!AllocateNextKey(sect, sKeyName)) return FALSE; // msg already
	sect.pKeyArr[sect.uNextInd - 1]->pN = NULL; // mark INT pointer unused

	sect.pKeyArr[sect.uNextInd - 1]->ppS = &sVar;
	sVar = NULL;	// make sure, so don't try to delete in SetStr later

	return SetStr(sVar, sDef);
}

BOOL CSettings::Add(IniSect & sect, LPCTSTR sKeyName, INT & nVar, const INT nDef)
{
	if (!AllocateNextKey(sect, sKeyName)) return FALSE;
	sect.pKeyArr[sect.uNextInd - 1]->ppS = NULL; // mark str pointer unused

	sect.pKeyArr[sect.uNextInd - 1]->pN = &nVar;
	nVar = nDef;
	return TRUE;
}

//BOOL CSettings::GetOwnIniPath(LPTSTR sIniFileName)
//{
//	TCHAR cFind;
//	LPTSTR sFind;
//	LPTSTR sAppend;
//	LPCTSTR sErr = _T("Error getting ini file path");
//
//															// Assumes sTarget[_MAX_PATH]
//	if (!GetModuleFileName(NULL, m_sIniPath, _MAX_PATH))
//		return SetError(sErr, _T("Can't get own program path"));
//
//	if (sIniFileName) // Replace whole exe name with sIniFileName
//	{
//		cFind = _T('\\');
//		sAppend = sIniFileName;
//	}
//	else //  replace .exe with .ini
//	{
//		cFind = _T('.');
//		sAppend = _T("ini");
//	}
//
//	sFind = _tcsrchr(m_sIniPath, cFind);
//	if (!sFind) return SetError(sErr, _T("Error parsing own path"));
//
//	_tcscpy(&sFind[1], sAppend);	// copy after the \ or .
//	return TRUE;
//}

// Replace above, now put ini file in app data subfolder instead of prog dir
BOOL CSettings::GetOwnIniPath(LPCTSTR sIniDirSlash, LPCTSTR sIniFileName)
{
  // Start with App Data subfolder (or maybe prog dir if that's subdir of user profile)
  if (!sIniDirSlash) return SetError(_T("sIniDirSlash is null"));
  _tcscpy(m_sIniDirSlash, sIniDirSlash);

  if (sIniFileName)
  {
    _stprintf(m_sIniPath, _T("%s%s"), m_sIniDirSlash, sIniFileName);
    return TRUE;
  }

  // sIniFileName NULL, use exe file name but with .ini extension
  TCHAR sIgnoreProgDir[_MAX_PATH];
  TCHAR sExeNameNoExt[128];
  TCHAR sIgnoreDotExt[64];
  if (! GetProgPathParsed(sIgnoreProgDir, sExeNameNoExt, sIgnoreDotExt))
  {
    // Error msg in sIgnoreProgDir
    return SetError(_T("Error setting ini file path"), sIgnoreProgDir);
  }

  _stprintf(m_sIniPath, _T("%s%s.ini"), m_sIniDirSlash, sExeNameNoExt);
  return TRUE;
}

BOOL CSettings::SetStr(LPTSTR & sVar, LPCTSTR sValue)
{
	if (sVar) delete sVar; // always except on first call

	sVar = new TCHAR[_tcslen(sValue) + 1];
	if (!sVar) //return FALSE;
			return SetError(_T("Error allocating memory for settings string"));

	_tcscpy(sVar, sValue);
	return TRUE;
}

BOOL CSettings::ReadIniKey(IniKey * pKey, LPCTSTR sSectName)
{
	if (pKey->ppS)
	{
		TCHAR sRead[1024];
		GetPrivateProfileString(sSectName, pKey->sKeyName, *(pKey->ppS),
																					sRead, 1024, m_sIniPath);
		return SetStr(*(pKey->ppS), sRead);
	}

	if (pKey->pN)
	{
		*(pKey->pN) = (INT) GetPrivateProfileInt(sSectName, pKey->sKeyName,
																								*(pKey->pN), m_sIniPath);
		return TRUE;
	}
	
	return SetError(_T("Attempt to read null variable from ini file"));
}

BOOL CSettings::ReadIniSection(IniSect & sect)
{
	for (UINT u = 0 ; u < sect.uNextInd ; u++)
	{
		if (!ReadIniKey(sect.pKeyArr[u], sect.sSectName)) return FALSE; 
	}
	return TRUE;
}

BOOL CSettings::WriteIniKey(IniKey * pKey, LPCTSTR sSectName)
{
	TCHAR sWrite[1024];

	if (pKey->ppS) _tcscpy(sWrite, *(pKey->ppS));
	else if(pKey->pN) wsprintf(sWrite, _T("%d"), *(pKey->pN));
	else return SetError(_T("Tried to write null variable to ini file"));

	if (WritePrivateProfileString(sSectName, pKey->sKeyName,
																			sWrite, m_sIniPath)) return TRUE;
	return SetError(_T("Error writing to ini file"));
}

BOOL CSettings::WriteIniSection(IniSect & sect)
{
	for (UINT u = 0 ; u < sect.uNextInd ; u++)
	{
		if (!WriteIniKey(sect.pKeyArr[u], sect.sSectName)) return FALSE; 
	}
	return TRUE;
}

BOOL CSettings::WriteIniAll()
{
	for (INT i = 0 ; i < NUM_OF_SECTS ; i++)
	{
		if (!WriteIniSection(*m_pSects[i])) return FALSE;
	}
	return TRUE;
}

BOOL CSettings::SetAndSaveKey(INT & nVar, INT nNewVal)
{
	for (UINT uSect = 0 ; uSect < NUM_OF_SECTS ; uSect ++)
	{
		for (UINT uInd = 0 ; uInd < m_pSects[uSect]->uNextInd ; uInd ++)
		{
			if (m_pSects[uSect]->pKeyArr[uInd]->pN != &nVar) continue;

			// Here, at the right pKeyArr member
			nVar = nNewVal;
			return WriteIniKey(m_pSects[uSect]->pKeyArr[uInd],
															m_pSects[uSect]->sSectName);
		}
	}

	return SetError(m_sErrKeyNotFound);
}

BOOL CSettings::SetAndSaveKey(LPTSTR & sVar, LPCTSTR sNewVal)
{
	for (UINT uSect = 0 ; uSect < NUM_OF_SECTS ; uSect ++)
	{
		for (UINT uInd = 0 ; uInd < m_pSects[uSect]->uNextInd ; uInd ++)
		{
			if (m_pSects[uSect]->pKeyArr[uInd]->ppS != &sVar) continue;

			// Here, at the right pKeyArr member
			if (!SetStr(sVar, sNewVal)) return FALSE; // msg already
			return WriteIniKey(m_pSects[uSect]->pKeyArr[uInd],
															m_pSects[uSect]->sSectName);
		}
	}

	return SetError(m_sErrKeyNotFound);
}

BOOL CSettings::SetError(LPCTSTR sErr1, LPCTSTR sErr2)
{
  _stprintf(m_sError, _T("INI settings class error:\r\n%s%s%s"), sErr1,
													sErr2 ? _T("\r\n") : _T(""), sErr2 ? sErr2 : _T(""));
					
	return FALSE;
}

BOOL CSettings::AddToError(LPCTSTR sErr)
{
	_tcscat(m_sError, _T("\r\n"));
	_tcscat(m_sError, sErr);
	return FALSE;
}

void CSettings::GetError(LPTSTR sTarget, BOOL bClear)
{
	if (sTarget) _tcscpy(sTarget, m_sError);
	if (bClear) ClearError();
}

BOOL CSettings::ShowError(LPCTSTR sCapt, LPCTSTR sPre, LPCTSTR sPost)
{
#if defined UTIL_H_
	// Use utility function
	return fnError(m_hDlg, sCapt, sPre, m_sError, sPost);

#else
	// Do it from scratch
	TCHAR * sMsg;
	INT nLen = _tcslen(m_sError)	+ 1			// + 1 for terminating null
		+ sPre ? _tcslen(sPre) + 2 : 0			// +2 for \r\n
		+ sPost ? _tcslen(sPost) + 2 : 0;

	sMsg = new TCHAR[nLen];
	if (!sMsg)
	{
		::MessageBox(m_hDlg, _T("Error allocating memory for message\n\n")
								_T("Individual parts of original message will be")
								_T(" displayed in up to 3 successive Message Boxes after")
								_T(" this one."), sCapt, MB_OK | MB_ICONEXCLAMATION);
		if (sPre) ::MessageBox(m_hDlg, sPre, sCapt, MB_OK | MB_ICONEXCLAMATION);
		::MessageBox(m_hDlg, m_sError, sCapt, MB_OK | MB_ICONEXCLAMATION);
		if (sPost) ::MessageBox(m_hDlg, sPost, sCapt, MB_OK | MB_ICONEXCLAMATION);
		return FALSE;
	}
	
	sprintf(sMsg, _T("%s%s%s%s%s"), sPre ? sPre : _T(""), sPre ? _T("\r\n") :_T( ""),
									m_sError, sPost ? _T("\r\n") : _T(""), sPost ? sPost : _T(""));
	::MessageBox(m_hDlg, sMsg, sCapt, MB_OK | MB_ICONEXCLAMATION);

	delete sMsg;
	return FALSE;

#endif // i.e. !defined UTIL_H_
} // BOOL CSettings::ShowError(LPCTSTR sCapt, LPCTSTR sPre, LPCTSTR sPost)

void CSettings::ClearError()
{
	//_tcscpy(m_sError, _T("No error recorded in INI settings class"));
	m_sError[0] = _T('\0');
}


// BEGIN OPTIONAL EXTRAS FOR PREFERENCES DIALOG

#if !defined SETTINGS_LITE_	 // Extras for preferences dialog

void CSettings::SetDlg(HWND hDlg)
{
	m_hDlg = hDlg;
}

BOOL CSettings::fnEditToStr(LPTSTR sRead, HWND hEdit, BOOL bEmptyOK, INT nMax)
{
	INT nRet = ::GetWindowText(hEdit, sRead, 1023);
	
	if (!nRet && !bEmptyOK) return SetError(_T("No text in input field"));

	if (nRet > nMax) // Shouldn't happen if set max input for Edit control
	{
		_stprintf(sRead, _T("Maximum allowed input text is %d characters"), nMax);
		return SetError(sRead);
	}

	return TRUE;
}

BOOL CSettings::DlgEditToKey(LPTSTR & sVar, INT nID, BOOL bEmptyOK, INT nMax)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	TCHAR sRead[1024]; // Assumes no ini string bigger
	if (!fnEditToStr(sRead, ::GetDlgItem(m_hDlg, nID),
											bEmptyOK, nMax)) return FALSE; // msg already
	return SetStr(sVar, sRead);
}

BOOL CSettings::DlgEditToKey(INT & nVar, INT nID, BOOL bEmptyOK, INT nMax)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	LPTSTR sStop;
	TCHAR sRead[32];
	if (!fnEditToStr(sRead, ::GetDlgItem(m_hDlg, nID),
											bEmptyOK, nMax)) return FALSE; // msg already

	if (sRead[0])
	{
		nVar = _tcstol(sRead, &sStop, 10);
		if (sStop[0]) return SetError(_T("Only digits, with optional minus sign,") 
																	_T(" allowed in input field"));
	}
	else nVar = 0; // Would have returned FALSE above if !bEmptyOK

	return TRUE;
}

BOOL CSettings::DlgEditToKey(UINT & uVar, INT nID, BOOL bEmptyOK, INT nMax)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	LPTSTR sStop;
	TCHAR sRead[32];
	if (!fnEditToStr(sRead, ::GetDlgItem(m_hDlg, nID),
											bEmptyOK, nMax)) return FALSE; // msg already

	if (sRead[0])
	{
		uVar = _tcstoul(sRead, &sStop, 10);
		if (sStop[0]) return SetError(_T("Only digits allowed in input field"));
	}
	else uVar = 0; // Would have returned FALSE above if !bEmptyOK

	return TRUE;
}

BOOL CSettings::DlgEditFromKey(LPTSTR sVar, INT nID)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);
	::SetDlgItemText(m_hDlg, nID, sVar);
	return TRUE;
}

BOOL CSettings::DlgEditFromKey(INT nVar, INT nID)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	TCHAR sWrite[32];
	_stprintf(sWrite, _T("%d"), nVar);
	::SetDlgItemText(m_hDlg, nID, sWrite);
	return TRUE;
}

BOOL CSettings::DlgEditFromKey(UINT uVar, INT nID)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	TCHAR sWrite[32];
	_stprintf(sWrite, _T("%u"), uVar);
	::SetDlgItemText(m_hDlg, nID, sWrite);
	return TRUE;
}

BOOL CSettings::DlgCheckToKey(BOOL & bVar, INT nID, BOOL bFlip)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	BOOL bCheck;
	
	if (::IsDlgButtonChecked(m_hDlg, nID) == BST_CHECKED)
		bCheck = TRUE;
	else bCheck = FALSE;

	bVar = bFlip ? (!bCheck) : bCheck;
	return TRUE;
}

BOOL CSettings::DlgCheckFromKey(BOOL bVar, INT nID, BOOL bFlip)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	BOOL bCheck = bFlip ? (!bVar) : bVar;
	if (::CheckDlgButton(m_hDlg, nID, bCheck ? BST_CHECKED : BST_UNCHECKED))
		return TRUE;
	return SetError(_T("Error setting check state in dialog button"));
}

BOOL CSettings::DlgRBsToKey(INT & nVar, INT nIdFirst, INT nIdLast,
																	INT nIdxFirst, INT nValIncrement)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	HWND hNow = ::GetDlgItem(m_hDlg, nIdFirst);
	HWND hLast = ::GetDlgItem(m_hDlg, nIdLast);
	INT nValue = nIdxFirst;
	
	INT nRealityCheck = 0;
	while (nRealityCheck++ < 500)
	{
		if (::SendMessage(hNow, BM_GETCHECK, 0, 0) == BST_CHECKED)
		{
			nVar = nValue;
			return TRUE;
		}

		if (hNow == hLast)
			return SetError(_T("Radio Button range has none checked"));

		hNow = GetNextDlgGroupItem(m_hDlg, hNow, FALSE);
    if (!hNow) return SetError(_T("Error getting HWND for next control"));

		nValue += nValIncrement;
	}

	return SetError(_T("Error finding checked Radio Button"));
}

BOOL CSettings::DlgRBsFromKey(INT & nVar, INT nIdFirst, INT nIdLast,
															INT nIdxFirst, INT nValIncrement)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	HWND hNow = ::GetDlgItem(m_hDlg, nIdFirst);
	HWND hLast = ::GetDlgItem(m_hDlg, nIdLast);
	INT nValue = nIdxFirst;
	
	INT nRealityCheck = 0;
	while (nRealityCheck++ < 100)
	{

//if (nIdFirst == 1239)
//{
//  TCHAR s[1024];
//  INT nIdNow = nIdFirst + nValue;
//  HWND hChk = ::GetDlgItem(m_hDlg, nIdNow);
//  _stprintf(s, _T("nVar = %d\nnValue =%d\nhLast = %d\nhChk = %d\nhNow = %d"),
//                  nVar, nValue, (INT) hChk, (INT) hNow);
//
//  if (nVar == nValue) _tcscat(s, _T("\nnVar == nValue => BST_CHECKED"));
//  else _tcscat(s, _T("\nnVar != nValue => BST_UNCHECKED"));
//
//  ::MessageBoxW(NULL, s, _T("DlgRBsFromKey"), MB_OK);
//}

    ::SendMessage(hNow, BM_SETCHECK,
										(nVar == nValue) ? BST_CHECKED : BST_UNCHECKED, 0);

		if (hNow == hLast) return TRUE;

		hNow = GetNextDlgGroupItem(m_hDlg, hNow, FALSE);
    if (!hNow) return SetError(_T("Error getting HWND for next control"));

		nValue += nValIncrement;
	}

  TCHAR sMsg[1024];

  _stprintf(sMsg, _T("Error setting Radio Button check state\n")
                  _T("Radio Buttons ID range %d to %d"),
            nIdFirst, nIdLast);

	return SetError(sMsg);
}

BOOL CSettings::DlgListToKey(INT & nVar, INT nMin, INT nID, BOOL bCombo)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	LRESULT nL = SendMessage(GetDlgItem(m_hDlg, nID),
														bCombo ? CB_GETCURSEL : LB_GETCURSEL, 0, 0);
	if (nL < 0) // CB_ERR or LB_ERR
		return SetError(_T("Error finding selection in list box"));
	
	nVar = nMin + (INT) nL;
	return TRUE;
}

BOOL CSettings::DlgListFromKey(INT nVar, INT nMin, INT nID, BOOL bCombo)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	if (SendDlgItemMessage(m_hDlg, nID,
													bCombo ? CB_SETCURSEL : LB_SETCURSEL,
																			(WPARAM) (nVar - nMin), 0) < 0)
		return SetError(_T("Error setting selection in list box"));
	return TRUE;
}

BOOL CSettings::DlgListToKey(LPTSTR & sVar, INT nID, BOOL bCombo)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	INT nInd;  // read like a key variable, then convert to string
	if (!DlgListToKey(nInd, 0, nID, bCombo)) return FALSE; // err set

	TCHAR sRead[1024];
	if (SendDlgItemMessage(m_hDlg, nID,
													bCombo ? CB_GETLBTEXT : LB_GETTEXT,
																(WPARAM) nInd, (LPARAM) sRead) < 0)
		return SetError(_T("Error reading text in listbox selection"));

	return SetStr(sVar, sRead);
}

BOOL CSettings::DlgListFromKey(LPTSTR sVar, INT nID, BOOL bCombo)
{
	if (!m_hDlg) return SetError(m_sErrDlgNotSet);

	LRESULT nL;
	nL = SendDlgItemMessage(m_hDlg, nID,
													bCombo ? CB_FINDSTRINGEXACT : LB_FINDSTRINGEXACT,
																												-1, (LPARAM) sVar);
	if (nL < 0) // Not necessarily an error; calling fn can clear error
    return SetError(_T("Text not found in list box:"), sVar);

	return DlgListFromKey((INT) nL, 0, nID, bCombo);
}

BOOL CSettings::CopySection(IniSect & Target, IniSect & Source)
{
	if (Target.uNextInd != Source.uNextInd // minimally verify sections match
			|| _tcscmp(Target.sSectName, Source.sSectName))
		return SetError(_T("Tried to copy non-matching ini sections"));

	for (UINT u = 0 ; u < Target.uNextInd ; u++)
	{
		if (Target.pKeyArr[u]->pN) // key is integer
		{
			*(Target.pKeyArr[u]->pN) = *(Source.pKeyArr[u]->pN);
			continue;
		}

		// if here, key is string
		if (!SetStr(*(Target.pKeyArr[u]->ppS), *(Source.pKeyArr[u]->ppS)))
			return AddToError(_T("while making copy of ini settings"));
	}

	return TRUE;
}

BOOL CSettings::CopyFrom(CSettings & Source)
{
	_tcscpy(m_sIniPath, Source.m_sIniPath); 
	for (UINT u = 0 ; u < NUM_OF_SECTS ; u ++)
	{
		if (!CopySection(*m_pSects[u], *Source.m_pSects[u])) return FALSE;
	}
	return TRUE;
}

BOOL CSettings::InitDlgCopy(HWND hDlg, CSettings & Source)
{
	m_hDlg = hDlg;
	if (!Init(Source.m_sIniDirSlash)) return FALSE;
	return CopyFrom(Source);
}


BOOL CSettings::CopyAndSaveFrom(CSettings & Source)
{
	if (!CopyFrom(Source)) return FALSE;
	return WriteIniAll();
}

#endif // !defined SETTINGS_LITE_

