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

// Win.cpp: implementation of the ZWin class.
//
//////////////////////////////////////////////////////////////////////

//#include "Headers.h"
#include "ZWin.h"

ZWin::ZWin(HINSTANCE hInst, LPCTSTR sCapt1, LPCTSTR sCapt2,
           ZWin * pOwner, ZWinAppData * pzwd)
{
  _tcscpy(m_sCapt, sCapt1 ? sCapt1 : _T("ZWin"));
	if (!sCapt2) _tcscpy(m_sCaptLong, sCapt1);
	else _stprintf(m_sCaptLong, _T("%s  -  %s"), sCapt1, sCapt2);

  //if (pzwd)
  //{
  //  _tcscpy(m_sProgDirSlash, pzwd->sProgDirSlash);
  //}
  m_pzwd = pzwd;

	m_hInst = hInst;
	m_pOwner = pOwner;
	m_hOwner = pOwner ? pOwner->GetWnd() : NULL;
	m_hWnd = NULL;
  m_hfLog = NULL;
  m_bFlushLog = TRUE; // Default, subclass may reset

  m_sError[0] = _T('\0');
  sBS[0] = _T('\0');
  sBS2[0] = _T('\0');

  // Frequently used constants
//  cNull = _T('\0'); // Init in .h
//  _tcscpy(sRN, _T("\r\n"));
  sRN = _T("\r\n");
  sCapLetters = _T("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  sDigits = _T("0123456789");
}


ZWin::~ZWin()
{
  if (m_hfLog) CloseHandle(m_hfLog);
}

HWND ZWin::GetWnd()
{
	return m_hWnd;
}

void ZWin::CenterWnd(HWND hCenterIn)
{
	RECT r;
	RECT rOther;

	GetWindowRect(m_hWnd, &r);

	if (hCenterIn) GetWindowRect(hCenterIn, &rOther);
	else GetWindowRect(GetDesktopWindow(), &rOther);

	//										// i.e. r.left + (size dif)/2
	//MoveWindow(m_hWnd, (rOther.right + rOther.left - r.right + r.left) / 2,
	//						(rOther.bottom + rOther.top - r.bottom + r.top) / 2,
	//							r.right - r.left, r.bottom - r.top, TRUE);

  INT nHeight = r.bottom - r.top;
  INT nWidth = r.right - r.left;
  // Set left and top at one half the difference in widths
  INT nLeft = (rOther.right + rOther.left - r.right + r.left) / 2;
  INT nTop = (rOther.bottom + rOther.top - r.bottom + r.top) / 2;

  // Now adjust above setting if any part of window is off desktop.
  // Do right & bottom first, so top & left prevail if window larger than desktop
  if (hCenterIn) GetWindowRect(GetDesktopWindow(), &rOther); // else already set

  if ((nLeft + nWidth) > rOther.right) nLeft = rOther.right - nWidth;
  if (nLeft < 0) nLeft = 0;

  if ((nTop + nHeight) > rOther.bottom) nTop = rOther.bottom - nHeight;
  if (nTop < 0) nTop = 0;

	MoveWindow(m_hWnd, nLeft, nTop, nWidth, nHeight, TRUE);
}

BOOL ZWin::GetVersionNumStr(LPTSTR sVersion)
{
  LPVOID lpvVerInfo;
	DWORD dwSize;
	DWORD dwUnused;
	TCHAR sTemp[1024];
  LPTSTR spVersion;
  BOOL bRet;

  if (!GetModuleFileName(NULL, sTemp, _MAX_PATH))
    return SetSysErr(_T("GetModuleFileName failed"));

  dwSize = GetFileVersionInfoSize(sTemp, &dwUnused);
  if (!dwSize)
    return SetSysErr(_T("GetFileVersionInfoSize failed"));

  lpvVerInfo = new char[dwSize];
  if (!lpvVerInfo)
    return SetError(_T("Error allocating version info buffer"));

  bRet = GetFileVersionInfo(sTemp, dwUnused, dwSize, (LPVOID) lpvVerInfo)
         && VerQueryValue(lpvVerInfo,
                          _T("\\StringFileInfo\\040904B0\\ProductVersion"),
                          (LPVOID*) &spVersion, (PUINT) &dwUnused);

  if (bRet) _tcscpy(sVersion, spVersion);
  delete lpvVerInfo; // Releases spVersion memory
  return bRet;
}

BOOL ZWin::GetCopyrightStr(LPTSTR sCopyright)
{
  LPVOID lpvVerInfo;
	DWORD dwSize;
	DWORD dwUnused;
	TCHAR sTemp[1024];
  LPTSTR spCopyright;
  BOOL bRet;

  if (!GetModuleFileName(NULL, sTemp, _MAX_PATH))
    return SetSysErr(_T("GetModuleFileName failed"));

  dwSize = GetFileVersionInfoSize(sTemp, &dwUnused);
  if (!dwSize)
    return SetSysErr(_T("GetFileVersionInfoSize failed"));

  lpvVerInfo = new char[dwSize];
  if (!lpvVerInfo)
    return SetError(_T("Error allocating version info buffer"));

  bRet = GetFileVersionInfo(sTemp, dwUnused, dwSize, (LPVOID) lpvVerInfo)
         && VerQueryValue(lpvVerInfo,
                          _T("\\StringFileInfo\\040904B0\\LegalCopyright"),
                          (LPVOID*) &spCopyright, (PUINT) &dwUnused);

  if (bRet) _tcscpy(sCopyright, spCopyright);
  delete lpvVerInfo; // Releases spVersion memory
  return bRet;
}

BOOL ZWin::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 ZWin::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 ZWin::SetSysErr(DWORD dwErr, LPCTSTR sErr1, LPCTSTR sErr2,
									   LPCTSTR sErr3, LPCTSTR sErr4)
{
  TCHAR sSysErr[2048];
  GetSysErrorMsg(sSysErr, dwErr);
  return SetError(sErr1, sErr2, sErr3, sErr4, sSysErr);
}

BOOL ZWin::SetSysErr(LPCTSTR sErr1, LPCTSTR sErr2, LPCTSTR sErr3, LPCTSTR sErr4)
{
  return SetSysErr(0, sErr1, sErr2, sErr3, sErr4);
}

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

BOOL ZWin::fnErr(LPCTSTR sErr1, LPCTSTR sErr2, LPCTSTR sErr3,
						     LPCTSTR sErr4, LPCTSTR sErr5, LPCTSTR sErr6)
{
  _tcscpy(sTmp_ZWin_private, m_sCapt);
  _tcscat(sTmp_ZWin_private, _T(" - ERROR"));
	return fnError(m_hWnd, sTmp_ZWin_private, sErr1, sErr2,
											sErr3, sErr4, sErr5, sErr6);
}

BOOL ZWin::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

// ==> Change from prog dir for log file to subdir of My Documents
//  // 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 ZWin::ToLog(LPCTSTR sMsg1, LPCTSTR sMsg2, LPCTSTR sMsg3, BOOL bTimeStamp)
{
  if (!m_hfLog) return FALSE; // May be OK, caller logs only if turned on

  if (!WriteLineToLog(sMsg1, bTimeStamp)) 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 ZWin::ToLogNoStamp(LPCTSTR sMsg1, LPCTSTR sMsg2, LPCTSTR sMsg3)
{
  return ToLog(sMsg1, sMsg2, sMsg3, FALSE);
}

BOOL ZWin::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 ZWin::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 ? sRN : _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;
}

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

INT ZWin::WaitForMsg(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 (!msg.message) // Probably something badly handled in shutdown
      {
			  KillTimer(m_hWnd, uTimerID);
			  return WFM_ERROR;
      }

			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)

// Yield for just one message if pending.
void ZWin::fnYieldOnce()
{
  MSG msg;
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
  }
}


////- REGISITRY EXTRAS TO UNCOMMENT HERE AND IN .h IF WANTED


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

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

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

	return TRUE;
}


/********************** BEGIN CONDITIONAL INCLUDE *************************/


#if defined(_INC_COMMCTRL)

void ZWin::ProcessProgress(MSG * pMsg, INT nProgress, INT nInfo)
{
  // Virtual function for ZWinUtil to call
}

INT ZWin::GetListviewSingleSelected(HWND hList)
{
  // When no selection found, can be error or just none selected.
  // Same -1 return either way; caller may test m_sError empty to distinguish.
  ClearError();

  // With the single-selection style ListView, returned count is 1 or 0
  INT nSelected = ListView_GetSelectedCount(hList);
  if (!nSelected) return -1; // None selected, not an error
  if (nSelected > 1)
  {
    TCHAR sMsg[1024];
    _stprintf(sMsg, _T("%d rows selected\r\n")
                 _T("GetListviewSingleSelected is not for multiple selection."),
              nSelected);
    SetError(sMsg);
    return -1;
  }

  // Here, know one row is selected. No listview equivalent to LB_GETCURSEL,
  // so loop through list checking each row too see if it's the selected one.
  INT nItemCount = ListView_GetItemCount(hList);
  for (INT nInd = 0; nInd < nItemCount; nInd++)
  {
    // Need to mask return value  to test for selected?
    // From MS Help: The only valid bits in the return value are those
    // that correspond to the bits set in the mask parameter.
    if (ListView_GetItemState(hList, nInd, LVIS_SELECTED)
        & LVIS_SELECTED)
    {
      return nInd;
      break;
    }
  } // for (INT nInd = 0; nInd < nItemCount; nInd++)

  // If here, went through loop without finding the selected row
  SetError(_T("Error finding the selected item in the list"));
  return -1;
} // INT ZWin::GetListviewSingleSelected(HWND hList)

#endif // #if defined(_INC_COMMCTRL)


#if defined(_WIN32_DCOM) // Assumes if defined, various related includes

// Global for BROWSEINFO callback member in SHBrowseForFolder
TCHAR gzsSelectFolderDefault[MAX_PATH];
INT CALLBACK ZSelectFolderCallbackProc(HWND hwnd, UINT uMsg,
                                       LPARAM lParam, LPARAM lpData)
{
  TCHAR szDir[MAX_PATH];
	switch(uMsg)
  {
    case BFFM_INITIALIZED:
	    if (lpData)
      {
		    _tcscpy(szDir, gzsSelectFolderDefault);
		    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 ZWin::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 = ZSelectFolderCallbackProc;

  if (!sDefault || !sDefault[0]) bi.lParam = FALSE;
  else
  {
    _tcscpy(gzsSelectFolderDefault, 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
  pShMalloc->Free(&pidl);
  pShMalloc->Free(&pRoot);
  pShMalloc->Release();

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

  return TRUE;
}

BOOL ZWin::CoInitDcom() // Always called at start Minimal.cpp
{
  // Required before using various functions of associated includes.
  return (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) == S_OK);
}

#endif // _WIN32_DCOM
