void COSMCtrlMapOperationsDlg::UpdateZoom(int nZoomLevel)
{
  //Validate our parameters
  AFXASSUME(m_pOSMCtrl != NULL);
  
  //Display a wait cursor while we do the maths to work out how many tiles we will be working on
  CWaitCursor waitCursor;

  //Reset the tiles array
  m_Tiles.reserve(100000);
  
  //Work out how many tiles this operation will affect
  COSMCtrlPosition topLeft;
  COSMCtrlPosition bottomRight;
  int nZoom = static_cast<int>(m_pOSMCtrl->GetZoom());
  if (m_pOSMCtrl->m_SelectionPolygon.GetBoundingRect(topLeft, bottomRight))
  {
    //Iterate across all the zoom levels we will be operating on
    for (int i=nZoom; i<=nZoomLevel; i++)
    { 
      //Work our the start and end X and Y values for the tiles at this zoom level 
      double fStartX = COSMCtrlHelper::Longitude2TileX(topLeft.m_fLongitude, i);
      int nStartX = static_cast<int>(fStartX);
      double fStartY = COSMCtrlHelper::Latitude2TileY(topLeft.m_fLatitude, i);
      int nStartY = static_cast<int>(fStartY);

      double fEndX = COSMCtrlHelper::Longitude2TileX(bottomRight.m_fLongitude, i);
      int nEndX = static_cast<int>(fEndX);
      double fEndY = COSMCtrlHelper::Latitude2TileY(bottomRight.m_fLatitude, i);
      int nEndY = static_cast<int>(fEndY);
    
      //Add all the tiles for this zoom level to the array
      for (int j=nStartX; j<=nEndX; j++)
      {
        for (int k=nStartY; k<=nEndY; k++)
        {
          COSMCtrlMapOperationsDlgTile tile;
          tile.m_nTileX = j;
          tile.m_nTileY = k;
          tile.m_nZoom = i;
          m_Tiles.push_back(tile);
        }
      }
    }
  }
  
  //Free up any unused memory in the array
  m_Tiles.shrink_to_fit();

  //Set the range on the progress control  
  int nTiles = static_cast<int>(m_Tiles.size());
  m_ctrlProgress.SetPos(0);
  m_ctrlProgress.SetRange32(0, nTiles);

  //Finally update the text on the UI
  CString sStatus;
  CString sZoom1;
  sZoom1.Format(_T("%d"), nZoom);
  CString sTiles;
  sTiles.Format(_T("%d"), nTiles);
  if (nZoom == nZoomLevel)
    AfxFormatString2(sStatus, IDS_OSMCTRL_MAP_OPERATIONS_UPDATE_ZOOM2, sZoom1, sTiles);
  else
  {
    CString sZoom2;
    sZoom2.Format(_T("%d"), nZoomLevel);
    
    LPCTSTR szStrings[3];
    szStrings[0] = sZoom1;
    szStrings[1] = sZoom2;
    szStrings[2] = sTiles;
    AfxFormatStrings(sStatus, IDS_OSMCTRL_MAP_OPERATIONS_UPDATE_ZOOM3, szStrings, 3);
  }
  m_ctrlStatusText.SetWindowText(sStatus);
}
BOOL COSMCtrlMapOperationsDlg::DownloadTiles(BOOL bSkipIfTileAlreadyExists)
{
  //Validate our parameters
  AFXASSUME(m_pOSMCtrl != NULL);

  //Pull out the tile provider we will be using
  IOSMCtrlTileProvider* pTileProvider = m_pOSMCtrl->GetTileProvider();
  AFXASSUME(pTileProvider != NULL);

  //Next get the server to connect to
  CStringW sServer(pTileProvider->GetDownloadServer());

  //Accumulate how many tiles we have deleted and not deleted
  int nTilesDownloaded = 0;
  int nTilesNotDownloaded = 0;

  //Next create the WinHTTP session object
  CWinHTTPSession session;
  HRESULT hr = m_pOSMCtrl->CreateSession(session, 0);
  if (SUCCEEDED(hr))
  {
    //Now create the connection object from the session object
    CWinHTTPConnection connection;
    hr = connection.Initialize(session, sServer, pTileProvider->GetDownloadPort());
    if (SUCCEEDED(hr))
    {
      //Iterate across the array of tiles to download
      BOOL bSuccess = TRUE;
      for (std::vector<COSMCtrlMapOperationsDlgTile>::size_type i=0; i<m_Tiles.size() && bSuccess; i++)
      {
        //Pull out the next tile to download
        const COSMCtrlMapOperationsDlgTile& tile = m_Tiles[i];

        //Create the sub directories if we can
        CString sCacheDirectory(m_pOSMCtrl->GetCacheDirectory());
        CString sSubDirectory;
        sSubDirectory.Format(_T("%s\\%d"), sCacheDirectory.operator LPCTSTR(), tile.m_nZoom);
        CreateDirectory(sSubDirectory, NULL);
        sSubDirectory.Format(_T("%s\\%d\\%d"), sCacheDirectory.operator LPCTSTR(), tile.m_nZoom, tile.m_nTileX);
        CreateDirectory(sSubDirectory, NULL);

        //Form the path to the tile we will be downloading to and determine if we should do the download
        CString sFile(COSMCtrl::GetTileCachePath(sCacheDirectory, tile.m_nZoom, tile.m_nTileX, tile.m_nTileY, FALSE));
        BOOL bDownload = TRUE;
        if (bSkipIfTileAlreadyExists)
          bDownload = (GetFileAttributes(sFile) == INVALID_FILE_ATTRIBUTES);

        //Now download the specific tile to the cache if required
        COSMCtrlMapOperationsDlgEvent dlgEvent;
        dlgEvent.m_bSuccess = false;
        if (bDownload)
        {
          //We will accept any mime type
          LPCWSTR pwszAcceptTypes[2];
          pwszAcceptTypes[0] = L"*/*";
          pwszAcceptTypes[1] = NULL;
          CSyncWinHTTPDownloader winHttpRequest;
          winHttpRequest.m_sFileToDownloadInto = sFile;
          CString sObject(pTileProvider->GetDownloadObject(tile.m_nZoom, tile.m_nTileX, tile.m_nTileY));
          hr = winHttpRequest.Initialize(connection, CStringW(sObject), NULL, NULL, NULL, pwszAcceptTypes, WINHTTP_FLAG_REFRESH);
          if (FAILED(hr))
          {
            //report the error
            TRACE(_T("COSMCtrlMapOperationsDlg::DownloadTiles, Failed to create request for tile \"%s\", Error:%08X\n"), sFile.operator LPCTSTR(), hr);
            
            //Ensure any remants of a bad download file are nuked
            DeleteFile(sFile);
            
            //Update the stats
            ++nTilesNotDownloaded;
          }
          else
          {
            hr = winHttpRequest.SendRequestSync();
            if (FAILED(hr))
            {
              //report the error
              TRACE(_T("COSMCtrlMapOperationsDlg::DownloadTiles, Failed to send request for tile \"%s\", Error:%08X\n"), sFile.operator LPCTSTR(), hr);
            
              //Ensure any remants of a bad download file are nuked
              DeleteFile(sFile);
            
              //Update the stats
              ++nTilesNotDownloaded;
            }
            else
            {
              //Update the stats
              ++nTilesDownloaded;
              dlgEvent.m_bSuccess = true;
            }
          }
        }
        else
        {
          //Update the stats
          ++nTilesNotDownloaded;
        }

        //Update the UI          
        dlgEvent.m_Event = COSMCtrlMapOperationsDlgEvent::SimpleStringStatus;
        dlgEvent.m_sString = sFile;
        dlgEvent.m_nItemData = i + 1;
        AddEvent(dlgEvent);

        //Check if we have been cancelled before we loop around
        bSuccess = (WaitForSingleObject(m_WorkerTerminateEvent, 0) == WAIT_TIMEOUT);
      }
    }
  }

  //Finally add a event about how many items have been downloaded
  COSMCtrlMapOperationsDlgEvent dlgEvent;
  dlgEvent.m_Event = COSMCtrlMapOperationsDlgEvent::SimpleStringStatus;
  CString sTilesDownloaded;
  sTilesDownloaded.Format(_T("%d"), nTilesDownloaded);
  CString sTilesNotDownloaded;
  sTilesNotDownloaded.Format(_T("%d"), nTilesNotDownloaded);
  AfxFormatString2(dlgEvent.m_sString, IDS_OSMCTRL_DOWNLOAD_TILES_STATS, sTilesDownloaded, sTilesNotDownloaded);
  AddEvent(dlgEvent);

  return TRUE;
}
CEventLogRecord::CEventLogRecord(const EVENTLOGRECORD* pRecord)
{
  //Validate our parameters
  AFXASSUME(pRecord != NULL);

  //First the easy ones
  m_dwRecordNumber = pRecord->RecordNumber;
  m_dwTimeGenerated = pRecord->TimeGenerated;
  m_dwTimeWritten = pRecord->TimeWritten;
  m_dwEventID = pRecord->EventID;
  m_wEventType = pRecord->EventType;
  m_wEventCategory = pRecord->EventCategory;

  //Copy over the SID
  DWORD i = 0;
  const BYTE* pBeginRecord = reinterpret_cast<const BYTE*>(pRecord);
  AFXASSUME(pBeginRecord != NULL);
  DWORD dwCurOffset = pRecord->UserSidOffset;
  if (pRecord->UserSidLength)
    m_UserSID.SetSize(0, pRecord->UserSidLength); //Preallocate the array to improve performance
  while (i < pRecord->UserSidLength)
  {
    m_UserSID.Add(pBeginRecord[dwCurOffset + i]);
    i++;
  }
  dwCurOffset += pRecord->UserSidLength;

  //Copy over the Binary data
  i = 0;
  dwCurOffset = pRecord->DataOffset;
  if (pRecord->DataLength)
    m_Data.SetSize(0, pRecord->DataLength); //Preallocate the array to improve performance
  while (i < pRecord->DataLength)
  {
    m_Data.Add(pBeginRecord[dwCurOffset + i]);
    i++;
  }
  dwCurOffset += pRecord->DataLength;

  //Copy over the SourceName
  const TCHAR* pszBeginRecord = reinterpret_cast<const TCHAR*>(pBeginRecord + sizeof(EVENTLOGRECORD));
  dwCurOffset = 0;
  DWORD dwStartOffset = dwCurOffset;
  while (pszBeginRecord[dwCurOffset])
    dwCurOffset++;
  m_sSourceName = &pszBeginRecord[dwStartOffset];

  //Skip over the NULL 
  dwCurOffset++;

  //Copy over the ComputerName
  dwStartOffset = dwCurOffset;
  while (pszBeginRecord[dwCurOffset])
    dwCurOffset++;
  m_sComputerName = &pszBeginRecord[dwStartOffset];

  //Copy over the strings array
  int nStringsRead = 0;
  pszBeginRecord = reinterpret_cast<const TCHAR*>(pBeginRecord + pRecord->StringOffset);
  dwCurOffset = 0;
  while (nStringsRead < pRecord->NumStrings)
  {
    //Find the next string
    dwStartOffset = dwCurOffset;
    while (pszBeginRecord[dwCurOffset])
      dwCurOffset++;

    //Add it to the array
    CString sText(&pszBeginRecord[dwStartOffset]);
    m_Strings.Add(sText);

    //Increment the number of strings read
    nStringsRead++;

    //Skip over the NULL
    dwCurOffset++;
  }
}