/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Navigator.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corp.
 * Portions created by the Initial Developer are Copyright (C) 1998-2001
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Sean Su <ssu@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include <windows.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>

#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */

#include "extern.h"
#include "extra.h"
#include "dialogs.h"
#include "xpnetHook.h"

#ifdef __cplusplus
}
#endif /* __cplusplus */

#include "nsFTPConn.h"
#include "nsHTTPConn.h"
#include "nsSocket.h"

#define UPDATE_INTERVAL_STATUS          1
#define UPDATE_INTERVAL_PROGRESS_BAR    1

/* Cancel Status */
#define CS_NONE         0x00000000
#define CS_CANCEL       0x00000001
#define CS_PAUSE        0x00000002
#define CS_RESUME       0x00000003

const int  kProxySrvrLen = 1024;
const char kHTTP[8]      = "http://";
const char kFTP[7]       = "ftp://";
const char kLoclFile[7]  = "zzzFTP";
const int  kModTimeOutValue = 3;

static nsHTTPConn       *connHTTP = NULL;
static nsFTPConn        *connFTP = NULL;
static long             glLastBytesSoFar;
static long             glAbsoluteBytesSoFar;
static long             glBytesResumedFrom;
static long             glTotalKb;
char                    gszStrCopyingFile[MAX_BUF_MEDIUM];
char                    gszCurrentDownloadPath[MAX_BUF];
char                    gszCurrentDownloadFilename[MAX_BUF_TINY];
char                    gszCurrentDownloadFileDescription[MAX_BUF_TINY];
char                    gszUrl[MAX_BUF];
char                    gszTo[MAX_BUF];
char                    gszFileInfo[MAX_BUF];
char                    *gszConfigIniFile;
BOOL                    gbDlgDownloadMinimized;
BOOL                    gbDlgDownloadJustMinimized;
BOOL                    gbUrlChanged;
BOOL                    gbShowDownloadRetryMsg;
DWORD                   gdwDownloadDialogStatus;
int                     giIndex;
int                     giTotalArchivesToDownload;
DWORD                   gdwTickStart;
BOOL                    gbStartTickCounter;

double GetPercentSoFar(void);

static void UpdateGaugeFileProgressBar(double value);
       int  ProgressCB(int aBytesSoFar, int aTotalFinalSize);
       void InitDownloadDlg(void);
       void DeInitDownloadDlg();

/* local prototypes */
siC *GetObjectFromArchiveName(char *szArchiveName);

struct DownloadFileInfo
{
  char szUrl[MAX_BUF];
  char szFile[MAX_BUF_TINY];
} dlFileInfo;

struct ExtractFilesDlgInfo
{
	HWND	hWndDlg;
	int		nMaxFileBars;	    // maximum number of bars that can be displayed
	int		nMaxArchiveBars;	// maximum number of bars that can be displayed
	int   nFileBars;	      // current number of bars to display
	int		nArchiveBars;		  // current number of bars to display
} dlgInfo;

struct TickInfo
{
  DWORD dwTickBegin;
  DWORD dwTickEnd;
  DWORD dwTickDif;
  BOOL  bTickStarted;
  BOOL  bTickDownloadResumed;
} gtiPaused;

BOOL CheckInterval(long *lModLastValue, int iInterval)
{
  BOOL bRv = FALSE;
  long lModCurrentValue;

  if(iInterval == 1)
  {
    lModCurrentValue = time(NULL);
    if(lModCurrentValue != *lModLastValue)
      bRv = TRUE;
  }
  else
  {
    lModCurrentValue = time(NULL) % iInterval;
    if((lModCurrentValue == 0) && (*lModLastValue != 0))
      bRv = TRUE;
  }

  *lModLastValue = lModCurrentValue;
  return(bRv);
}

char *GetTimeLeft(DWORD dwTimeLeft,
                  char *szTimeString,
                  DWORD dwTimeStringBufSize)
{
  DWORD      dwTimeLeftPP;
  SYSTEMTIME stTime;

  ZeroMemory(&stTime, sizeof(stTime));
  dwTimeLeftPP         = dwTimeLeft + 1;
  stTime.wHour         = (unsigned)(dwTimeLeftPP / 60 / 60);
  stTime.wMinute       = (unsigned)((dwTimeLeftPP / 60) % 60);
  stTime.wSecond       = (unsigned)(dwTimeLeftPP % 60);

  ZeroMemory(szTimeString, dwTimeStringBufSize);
  /* format time string using user's local time format information */
  GetTimeFormat(LOCALE_USER_DEFAULT,
                TIME_NOTIMEMARKER|TIME_FORCE24HOURFORMAT,
                &stTime,
                NULL,
                szTimeString,
                dwTimeStringBufSize);

  return(szTimeString);
}

DWORD AddToTick(DWORD dwTick, DWORD dwTickMoreToAdd)
{
  DWORD dwTickLeftTillWrap = 0;

  /* Since GetTickCount() is the number of milliseconds since the system
   * has been on and the return value is a DWORD, this value will wrap
   * every 49.71 days or 0xFFFFFFFF milliseconds. */
  dwTickLeftTillWrap = 0xFFFFFFFF - dwTick;
  if(dwTickMoreToAdd > dwTickLeftTillWrap)
    dwTick = dwTickMoreToAdd - dwTickLeftTillWrap;
  else
    dwTick = dwTick + dwTickMoreToAdd;

  return(dwTick);
}

DWORD GetTickDif(DWORD dwTickEnd, DWORD dwTickStart)
{
  DWORD dwTickDif;

  /* Since GetTickCount() is the number of milliseconds since the system
   * has been on and the return value is a DWORD, this value will wrap
   * every 49.71 days or 0xFFFFFFFF milliseconds. 
   *
   * Assumption: dwTickEnd has not wrapped _and_ passed dwTickStart */
  if(dwTickEnd < dwTickStart)
    dwTickDif = 0xFFFFFFFF - dwTickStart + dwTickEnd;
  else
    dwTickDif = dwTickEnd - dwTickStart;

  return(dwTickDif);
}

void InitTickInfo(void)
{
  gtiPaused.dwTickBegin          = 0;
  gtiPaused.dwTickEnd            = 0;
  gtiPaused.dwTickDif            = 0;
  gtiPaused.bTickStarted         = FALSE;
  gtiPaused.bTickDownloadResumed = FALSE;
}

DWORD RoundDouble(double dValue)
{
  if(0.5 <= (dValue - (DWORD)dValue))
    return((DWORD)dValue + 1);
  else
    return((DWORD)dValue);
}

void SetStatusStatus(void)
{
  char        szStatusStatusLine[MAX_BUF_MEDIUM];
  char        szCurrentStatusInfo[MAX_BUF_MEDIUM];
  char        szPercentString[MAX_BUF_MEDIUM];
  char        szPercentageCompleted[MAX_BUF_MEDIUM];
  static long lModLastValue = 0;
  double        dRate;
  static double dRateCounter;
  DWORD         dwTickNow;
  DWORD         dwTickDif;
  DWORD         dwKBytesSoFar;
  DWORD         dwRoundedRate;
  char          szTimeLeft[MAX_BUF_TINY];

  /* If the user just clicked on the Resume button, then the time lapsed
   * between gdwTickStart and when the Resume button was clicked needs to
   * be subtracted taken into account when calculating dwTickDif.  So
   * "this" lapsed time needs to be added to gdwTickStart. */
  if(gtiPaused.bTickDownloadResumed)
  {
    gdwTickStart = AddToTick(gdwTickStart, gtiPaused.dwTickDif);
    InitTickInfo();
  }

  /* GetTickCount() returns time in milliseconds.  This is more accurate,
   * which will allow us to get at a 2 decimal precision value for the
   * download rate. */
  dwTickNow = GetTickCount();
  if((gdwTickStart == 0) && gbStartTickCounter)
    dwTickNow = gdwTickStart = GetTickCount();

  dwTickDif = GetTickDif(dwTickNow, gdwTickStart);

  /* Only update the UI every UPDATE_INTERVAL_STATUS interval,
   * which is currently set to 1 sec. */
  if(!CheckInterval(&lModLastValue, UPDATE_INTERVAL_STATUS))
    return;

  if(glAbsoluteBytesSoFar == 0)
    dRateCounter = 0.0;
  else
    dRateCounter = dwTickDif / 1000;

  if(dRateCounter == 0.0)
    dRate = 0.0;
  else
    dRate = (glAbsoluteBytesSoFar - glBytesResumedFrom) / dRateCounter / 1024;

  dwKBytesSoFar = glAbsoluteBytesSoFar / 1024;

  /* Use a rate that is rounded to the nearest integer.  If dRate used directly,
   * the "Time Left" will jump around quite a bit due to the rate usually 
   * varying up and down by quite a bit. The rounded rate give a "more linear"
   * count down of the "Time Left". */
  dwRoundedRate = RoundDouble(dRate);
  if(dwRoundedRate > 0)
    GetTimeLeft((glTotalKb - dwKBytesSoFar) / dwRoundedRate,
                 szTimeLeft,
                 sizeof(szTimeLeft));
  else
    lstrcpy(szTimeLeft, "00:00:00");

  if(!gbShowDownloadRetryMsg)
  {
    GetConfigIniProfileString("Strings", "Status Download", "",
                            szStatusStatusLine, sizeof(szStatusStatusLine));
    if(*szStatusStatusLine != '\0')
      sprintf(szCurrentStatusInfo,
              szStatusStatusLine,
              szTimeLeft,
              dRate,
              dwKBytesSoFar,
              glTotalKb);
    else
      sprintf(szCurrentStatusInfo,
              "%s at %.2fKB/sec (%uKB of %uKB downloaded)",
              szTimeLeft,
              dRate,
              dwKBytesSoFar,
              glTotalKb);
  }
  else
  {
    GetConfigIniProfileString("Strings", "Status Retry", "",
			      szStatusStatusLine, sizeof(szStatusStatusLine));
    if(*szStatusStatusLine != '\0')
      sprintf(szCurrentStatusInfo,
              szStatusStatusLine,
              szTimeLeft,
              dRate,
              dwKBytesSoFar,
              glTotalKb);
    else
      sprintf(szCurrentStatusInfo,
              "%s at %.2KB/sec (%uKB of %uKB downloaded)",
              szTimeLeft,
              dRate,
              dwKBytesSoFar,
              glTotalKb);
  }

  GetConfigIniProfileString("Strings", "Status Percentage Completed", "",
			    szPercentageCompleted, sizeof(szPercentageCompleted));
  wsprintf(szPercentString, szPercentageCompleted, (int)GetPercentSoFar());

  /* Set the download dialog title */
  SetDlgItemText(dlgInfo.hWndDlg, IDC_STATUS_STATUS, szCurrentStatusInfo);
  SetDlgItemText(dlgInfo.hWndDlg, IDC_PERCENTAGE, szPercentString);
}

void SetStatusFile(void)
{
  char szString[MAX_BUF];

  /* Set the download dialog status*/
  wsprintf(szString, gszFileInfo, gszCurrentDownloadFileDescription);
  SetDlgItemText(dlgInfo.hWndDlg, IDC_STATUS_FILE, szString);
  SetStatusStatus();
}

void SetStatusUrl(void)
{
  /* display the current url being processed */
  if(gbUrlChanged)
  {
    char szUrlPathBuf[MAX_BUF];
    char szToPathBuf[MAX_BUF];
    HWND hStatusUrl = NULL;
    HWND hStatusTo  = NULL;

    hStatusUrl = GetDlgItem(dlgInfo.hWndDlg, IDC_STATUS_URL);
    if(hStatusUrl)
      TruncateString(hStatusUrl, gszUrl, szUrlPathBuf, sizeof(szUrlPathBuf));
    else
      lstrcpy(szUrlPathBuf, gszUrl);

    hStatusTo = GetDlgItem(dlgInfo.hWndDlg, IDC_STATUS_TO);
    if(hStatusTo)
      TruncateString(hStatusTo, gszTo, szToPathBuf, sizeof(szToPathBuf));
    else
      lstrcpy(szToPathBuf, gszTo);

    SetDlgItemText(dlgInfo.hWndDlg, IDC_STATUS_URL, szUrlPathBuf);
    SetDlgItemText(dlgInfo.hWndDlg, IDC_STATUS_TO,  szToPathBuf);
    SetStatusFile();
    gbUrlChanged = FALSE;
  }
}

double GetPercentSoFar(void)
{
  return((double)(((double)(glAbsoluteBytesSoFar / 1024) / (double)glTotalKb) * (double)100));
}

void SetMinimizedDownloadTitle(DWORD dwPercentSoFar)
{
  static DWORD dwLastPercentSoFar = 0;
  char szDownloadTitle[MAX_BUF_MEDIUM];
  char gszCurrentDownloadInfo[MAX_BUF_MEDIUM];

  GetConfigIniProfileString("Strings", "Dialog Download Title Minimized", "", szDownloadTitle, sizeof(szDownloadTitle));

  if(*szDownloadTitle != '\0')
    wsprintf(gszCurrentDownloadInfo, szDownloadTitle, dwPercentSoFar, gszCurrentDownloadFilename);
  else
    wsprintf(gszCurrentDownloadInfo, "%d%% for all files", dwPercentSoFar);

  /* check and save the current percent so far to minimize flickering */
  if((dwLastPercentSoFar != dwPercentSoFar) || gbDlgDownloadJustMinimized)
  {
    /* When the dialog is has just been minimized, the title is not set
     * until the percentage changes, which when downloading via modem,
     * could take several seconds.  This variable allows us to tell when
     * the dialog had *just* been minimized regardless if the percentage
     * had changed or not. */
    gbDlgDownloadJustMinimized = FALSE;

    /* Set the download dialog title */
    SetWindowText(dlgInfo.hWndDlg, gszCurrentDownloadInfo);
    dwLastPercentSoFar = dwPercentSoFar;
  }
}

void SetRestoredDownloadTitle(void)
{
  /* Set the download dialog title */
  SetWindowText(dlgInfo.hWndDlg, diDownload.szTitle);
}

void GetTotalArchivesToDownload(int *iTotalArchivesToDownload, DWORD *dwTotalEstDownloadSize)
{
  int  iIndex = 0;
  char szUrl[MAX_BUF];
  char szSection[MAX_INI_SK];
  char szDownloadSize[MAX_ITOA];

  *dwTotalEstDownloadSize = 0;
  iIndex = 0;
  wsprintf(szSection, "File%d", iIndex);
  GetPrivateProfileString(szSection,
                          "url0",
                          "",
                          szUrl,
                          sizeof(szUrl),
                          gszConfigIniFile);
  while(*szUrl != '\0')
  {
    GetPrivateProfileString(szSection, "size", "", szDownloadSize, sizeof(szDownloadSize), gszConfigIniFile);
    if((lstrlen(szDownloadSize) < 32) && (*szDownloadSize != '\0'))
      /* size will be in kb.  31 bits (int minus the signed bit) of precision should sufice */
      *dwTotalEstDownloadSize += atoi(szDownloadSize);

    ++iIndex;
    wsprintf(szSection, "File%d", iIndex);
    GetPrivateProfileString(szSection,
                            "url0",
                            "",
                            szUrl,
                            sizeof(szUrl),
                            gszConfigIniFile);
  }
  *iTotalArchivesToDownload = iIndex;
}

/* 
 * Name: ProcessWndMsgCB
 *
 * Arguments: None
 *
 * Description: Callback function invoked by socket code and by FTP and HTTP layers
 *				to give the UI a chance to breath while we are in a look processing
 *				incoming data, or looping in select()
 *
 * Author: syd@netscape.com 5/11/2001
 *
*/

int
ProcessWndMsgCB()
{
  int iRv = nsFTPConn::OK;

	ProcessWindowsMessages();
  if((gdwDownloadDialogStatus == CS_CANCEL) ||
     (gdwDownloadDialogStatus == CS_PAUSE))
    iRv = nsFTPConn::E_USER_CANCEL;

	return(iRv);
}

/* Function used only to send the message stream error */
int WGet(char *szUrl,
         char *szFile,
         char *szProxyServer,
         char *szProxyPort,
         char *szProxyUser,
         char *szProxyPasswd)
{
  int        rv;
  char       proxyURL[MAX_BUF];
  nsHTTPConn *conn = NULL;

  if((szProxyServer != NULL) && (szProxyPort != NULL) &&
     (*szProxyServer != '\0') && (*szProxyPort != '\0'))
  {
    /* detected proxy information, let's use it */
    memset(proxyURL, 0, sizeof(proxyURL));
    wsprintf(proxyURL, "http://%s:%s", szProxyServer, szProxyPort);

    conn = new nsHTTPConn(proxyURL);
    if(conn == NULL)
      return(WIZ_OUT_OF_MEMORY);

    if((szProxyUser != NULL) && (*szProxyUser != '\0') &&
       (szProxyPasswd != NULL) && (*szProxyPasswd != '\0'))
      /* detected user and password info */
      conn->SetProxyInfo(szUrl, szProxyUser, szProxyPasswd);
    else
      conn->SetProxyInfo(szUrl, NULL, NULL);
  }
  else
  {
    /* no proxy information supplied. set up normal http object */
    conn = new nsHTTPConn(szUrl, ProcessWndMsgCB);
    if(conn == NULL)
      return(WIZ_OUT_OF_MEMORY);
  }
  
  rv = conn->Open();
  if(rv == WIZ_OK)
  {
    rv = conn->Get(NULL, szFile);
    conn->Close();
  }

  if(conn)
    delete(conn);

  return(rv);
}

int DownloadViaProxyOpen(char *szUrl, char *szProxyServer, char *szProxyPort, char *szProxyUser, char *szProxyPasswd)
{
  int  rv;
  char proxyURL[kProxySrvrLen];

  if((!szUrl) || (*szUrl == '\0'))
    return nsHTTPConn::E_PARAM;

  rv = nsHTTPConn::OK;
  memset(proxyURL, 0, kProxySrvrLen);
  wsprintf(proxyURL, "http://%s:%s", szProxyServer, szProxyPort);

  connHTTP = new nsHTTPConn(proxyURL, ProcessWndMsgCB);
  if(connHTTP == NULL)
  {
    char szBuf[MAX_BUF_TINY];

    GetConfigIniProfileString("Strings", "Error Out Of Memory", "", szBuf, sizeof(szBuf));
    PrintError(szBuf, ERROR_CODE_HIDE);

    return(WIZ_OUT_OF_MEMORY);
  }

  if((szProxyUser != NULL) && (*szProxyUser != '\0') &&
     (szProxyPasswd != NULL) && (*szProxyPasswd != '\0'))
    connHTTP->SetProxyInfo(szUrl, szProxyUser, szProxyPasswd);
  else
    connHTTP->SetProxyInfo(szUrl, NULL, NULL);

  rv = connHTTP->Open();
  return(rv);
}

void DownloadViaProxyClose(void)
{
  gbStartTickCounter = FALSE;
  if(connHTTP)
  {
    connHTTP->Close();
    delete(connHTTP);
    connHTTP = NULL;
  }
}

int DownloadViaProxy(char *szUrl, char *szProxyServer, char *szProxyPort, char *szProxyUser, char *szProxyPasswd)
{
  int  rv;
  char *file = NULL;

  rv = nsHTTPConn::OK;
  if((!szUrl) || (*szUrl == '\0'))
    return nsHTTPConn::E_PARAM;

  if(connHTTP == NULL)
  {
    rv = DownloadViaProxyOpen(szUrl,
                              szProxyServer,
                              szProxyPort,
                              szProxyUser,
                              szProxyPasswd);

    if(rv != nsHTTPConn::OK)
    {
      DownloadViaProxyClose();
      return(rv);
    }
  }

  if(connHTTP == NULL)
  {
    char szBuf[MAX_BUF_TINY];

    GetConfigIniProfileString("Strings", "Error Out Of Memory", "", szBuf, sizeof(szBuf));
    PrintError(szBuf, ERROR_CODE_HIDE);

    return(WIZ_OUT_OF_MEMORY);
  }

  if(strrchr(szUrl, '/') != (szUrl + strlen(szUrl)))
    file = strrchr(szUrl, '/') + 1; // set to leaf name

  gbStartTickCounter = TRUE;
  rv = connHTTP->Get(ProgressCB, file); // use leaf from URL
  DownloadViaProxyClose();
  return(rv);
}

int DownloadViaHTTPOpen(char *szUrl)
{
  int  rv;

  if((!szUrl) || (*szUrl == '\0'))
    return nsHTTPConn::E_PARAM;

  rv = nsHTTPConn::OK;
  connHTTP = new nsHTTPConn(szUrl, ProcessWndMsgCB);
  if(connHTTP == NULL)
  {
    char szBuf[MAX_BUF_TINY];

    GetPrivateProfileString("Strings", "Error Out Of Memory", "", szBuf, sizeof(szBuf), gszConfigIniFile);
    PrintError(szBuf, ERROR_CODE_HIDE);

    return(WIZ_OUT_OF_MEMORY);
  }
  
  rv = connHTTP->Open();
  return(rv);
}

void DownloadViaHTTPClose(void)
{
  gbStartTickCounter = FALSE;
  if(connHTTP)
  {
    connHTTP->Close();
    delete(connHTTP);
    connHTTP = NULL;
  }
}

int DownloadViaHTTP(char *szUrl)
{
  int  rv;
  char *file = NULL;

  if((!szUrl) || (*szUrl == '\0'))
    return nsHTTPConn::E_PARAM;

  if(connHTTP == NULL)
  {
    rv = DownloadViaHTTPOpen(szUrl);
    if(rv != nsHTTPConn::OK)
    {
      DownloadViaHTTPClose();
      return(rv);
    }
  }

  if(connHTTP == NULL)
  {
    char szBuf[MAX_BUF_TINY];

    GetPrivateProfileString("Strings", "Error Out Of Memory", "", szBuf, sizeof(szBuf), gszConfigIniFile);
    PrintError(szBuf, ERROR_CODE_HIDE);

    return(WIZ_OUT_OF_MEMORY);
  }
  
  rv = nsHTTPConn::OK;
  if(strrchr(szUrl, '/') != (szUrl + strlen(szUrl)))
    file = strrchr(szUrl, '/') + 1; // set to leaf name

  gbStartTickCounter = TRUE;
  rv = connHTTP->Get(ProgressCB, file);
  DownloadViaHTTPClose();
  return(rv);
}

int DownloadViaFTPOpen(char *szUrl)
{
  char *host = 0, *path = 0, *file = (char*) kLoclFile;
  int port = 21;
  int rv;

  if((!szUrl) || (*szUrl == '\0'))
    return nsFTPConn::E_PARAM;

  rv = nsHTTPConn::ParseURL(kFTP, szUrl, &host, &port, &path);

  connFTP = new nsFTPConn(host, ProcessWndMsgCB);
  if(connFTP == NULL)
  {
    char szBuf[MAX_BUF_TINY];

    GetConfigIniProfileString("Strings", "Error Out Of Memory", "", szBuf, sizeof(szBuf));
    PrintError(szBuf, ERROR_CODE_HIDE);

    return(WIZ_OUT_OF_MEMORY);
  }

  rv = connFTP->Open();
  if(host)
    free(host);
  if(path)
    free(path);

  return(rv);
}

void DownloadViaFTPClose(void)
{
  gbStartTickCounter = FALSE;
  if(connFTP)
  {
    connFTP->Close();
    delete(connFTP);
    connFTP = NULL;
  }
}

int DownloadViaFTP(char *szUrl)
{
  char *host = 0, *path = 0, *file = (char*) kLoclFile;
  int port = 21;
  int rv;

  if((!szUrl) || (*szUrl == '\0'))
    return nsFTPConn::E_PARAM;

  if(connFTP == NULL)
  {
    rv = DownloadViaFTPOpen(szUrl);
    if(rv != nsFTPConn::OK)
    {
      DownloadViaFTPClose();
      return(rv);
    }
  }

  if(connFTP == NULL)
  {
    char szBuf[MAX_BUF_TINY];

    GetConfigIniProfileString("Strings", "Error Out Of Memory", "", szBuf, sizeof(szBuf));
    PrintError(szBuf, ERROR_CODE_HIDE);

    return(WIZ_OUT_OF_MEMORY);
  }

  rv = nsHTTPConn::ParseURL(kFTP, szUrl, &host, &port, &path);

  if(strrchr(path, '/') != (path + strlen(path)))
    file = strrchr(path, '/') + 1; // set to leaf name

  gbStartTickCounter = TRUE;
  rv = connFTP->Get(path, file, nsFTPConn::BINARY, TRUE, ProgressCB);

  if(host)
    free(host);
  if(path)
    free(path);

  return(rv);
}

void PauseTheDownload(int rv, int *iFileDownloadRetries)
{
  if(rv != nsFTPConn::E_USER_CANCEL)
  {
    SendMessage(dlgInfo.hWndDlg, WM_COMMAND, IDPAUSE, 0);
    --*iFileDownloadRetries;
  }

  while(gdwDownloadDialogStatus == CS_PAUSE)
  {
    SleepEx(200, FALSE);
    ProcessWindowsMessages();
  }
}

void CloseSocket(char *szProxyServer, char *szProxyPort)
{
  /* Close the socket connection from the first attempt. */
  if((szProxyServer != NULL) && (szProxyPort != NULL) &&
     (*szProxyServer != '\0') && (*szProxyPort != '\0'))
      DownloadViaProxyClose();
  else
  {
    /* is this an HTTP URL? */
    if(strncmp(gszUrl, kHTTP, lstrlen(kHTTP)) == 0)
      DownloadViaHTTPClose();
    /* or is this an FTP URL? */
    else if(strncmp(gszUrl, kFTP, lstrlen(kFTP)) == 0)
      DownloadViaFTPClose();
  }
}

siC *GetObjectFromArchiveName(char *szArchiveName)
{
  DWORD dwIndex;
  siC   *siCObject = NULL;
  siC   *siCNode   = NULL;

  dwIndex = 0;
  siCObject = SiCNodeGetObject(dwIndex, TRUE, AC_ALL);
  while(siCObject)
  {
    if(lstrcmpi(szArchiveName, siCObject->szArchiveName) == 0)
    {
      siCNode = siCObject;
      break;
    }

    ++dwIndex;
    siCObject = SiCNodeGetObject(dwIndex, TRUE, AC_ALL);
  }

  return(siCNode);
}

int DownloadFiles(char *szInputIniFile,
                  char *szDownloadDir,
                  char *szProxyServer,
                  char *szProxyPort,
                  char *szProxyUser,
                  char *szProxyPasswd,
                  BOOL bShowRetryMsg,
                  BOOL bIgnoreAllNetworkErrors,
                  char *szFailedFile,
                  DWORD dwFailedFileSize)
{
  char      szBuf[MAX_BUF];
  char      szCurrentFile[MAX_BUF];
  char      szSection[MAX_INI_SK];
  char      szKey[MAX_INI_SK];
  char      szSavedCwd[MAX_BUF_MEDIUM];
  int       iCounter;
  int       rv;
  int       iFileDownloadRetries;
  int       iIgnoreFileNetworkError;
  int       iLocalTimeOutCounter;
  DWORD     dwTotalEstDownloadSize;
  char      szPartiallyDownloadedFilename[MAX_BUF];
  BOOL      bDownloadInitiated;
  char      szTempURL[MAX_BUF];
  char      szWorkingURLPathOnly[MAX_BUF];
  siC       *siCCurrentFileObj = NULL;

  ZeroMemory(szTempURL, sizeof(szTempURL));
  ZeroMemory(szWorkingURLPathOnly, sizeof(szWorkingURLPathOnly));
  if(szInputIniFile == NULL)
    return(WIZ_ERROR_UNDEFINED);

  if(szFailedFile)
    ZeroMemory(szFailedFile, dwFailedFileSize);

  InitTickInfo();
  GetCurrentDirectory(sizeof(szSavedCwd), szSavedCwd);
  SetCurrentDirectory(szDownloadDir);

  rv                        = WIZ_OK;
  dwTotalEstDownloadSize    = 0;
  giTotalArchivesToDownload = 0;
  glLastBytesSoFar          = 0;
  glAbsoluteBytesSoFar      = 0;
  glBytesResumedFrom        = 0;
  gdwTickStart              = 0; /* Initialize the counter used to
                                  * calculate download rate */
  gbStartTickCounter        = FALSE; /* used to determine when to start
                                      * the tick counter used to calculate
                                      * the download rate */
  gbUrlChanged              = TRUE;
  gbDlgDownloadMinimized    = FALSE;
  gbDlgDownloadJustMinimized = FALSE;
  gdwDownloadDialogStatus   = CS_NONE;
  gbShowDownloadRetryMsg    = bShowRetryMsg;
  gszConfigIniFile          = szInputIniFile;
  bDownloadInitiated        = FALSE;

  GetTotalArchivesToDownload(&giTotalArchivesToDownload,
                             &dwTotalEstDownloadSize);
  glTotalKb                 = dwTotalEstDownloadSize;
  GetSetupCurrentDownloadFile(szPartiallyDownloadedFilename,
                              sizeof(szPartiallyDownloadedFilename));

  InitDownloadDlg();

  for(giIndex = 0; giIndex < giTotalArchivesToDownload; giIndex++)
  {
    /* set (or reset) the counter to 0 in order to read the
     * next files's 0'th url from the .idi file */
    iCounter     = 0;
    gbUrlChanged = TRUE; /* Update the download dialog with new URL */
    wsprintf(szSection, "File%d", giIndex);
    wsprintf(szKey,     "url%d",  iCounter);
    GetPrivateProfileString(szSection,
                            szKey,
                            "",
                            szTempURL,
                            sizeof(szTempURL),
                            gszConfigIniFile);

    if(*szTempURL == '\0')
      continue;

    if(!bDownloadInitiated)
    {
      ParsePath(szTempURL,
                szWorkingURLPathOnly,
                sizeof(szWorkingURLPathOnly),
                TRUE, //use '/' as the path delimiter
                PP_PATH_ONLY);
    }

    GetPrivateProfileString(szSection,
                            "desc",
                            "",
                            gszCurrentDownloadFileDescription,
                            sizeof(gszCurrentDownloadFileDescription),
                            gszConfigIniFile);
    iIgnoreFileNetworkError = GetPrivateProfileInt(szSection,
                            "Ignore File Network Error",
                            0,
                            gszConfigIniFile);

    /* save the file name to be downloaded */
    ParsePath(szTempURL,
              szCurrentFile,
              sizeof(szCurrentFile),
              TRUE, //use '/' as the path delimiter
              PP_FILENAME_ONLY);

    RemoveSlash(szWorkingURLPathOnly);
    wsprintf(gszUrl, "%s/%s", szWorkingURLPathOnly, szCurrentFile);

    /* retrieve the file's data structure */
    siCCurrentFileObj = GetObjectFromArchiveName(szCurrentFile);

    if((*szPartiallyDownloadedFilename != 0) &&
       (lstrcmpi(szPartiallyDownloadedFilename, szCurrentFile) == 0))
    {
      struct stat statBuf;

      if(stat(szPartiallyDownloadedFilename, &statBuf) != -1)
      {
        glAbsoluteBytesSoFar += statBuf.st_size;
        glBytesResumedFrom    = statBuf.st_size;
      }
    }

    lstrcpy(gszTo, szDownloadDir);
    AppendBackSlash(gszTo, sizeof(gszTo));
    lstrcat(gszTo, szCurrentFile);

    if(gbDlgDownloadMinimized)
      SetMinimizedDownloadTitle((int)GetPercentSoFar());
    else
    {
      SetStatusUrl();
      SetRestoredDownloadTitle();
    }

    SetSetupCurrentDownloadFile(szCurrentFile);
    iFileDownloadRetries = 0;
    iLocalTimeOutCounter = 0;
    do
    {
      ProcessWindowsMessages();
      /* Download starts here */
      if((szProxyServer != NULL) && (szProxyPort != NULL) &&
         (*szProxyServer != '\0') && (*szProxyPort != '\0'))
        /* If proxy info is provided, use HTTP proxy */
        rv = DownloadViaProxy(gszUrl,
                              szProxyServer,
                              szProxyPort,
                              szProxyUser,
                              szProxyPasswd);
      else
      {
        /* is this an HTTP URL? */
        if(strncmp(gszUrl, kHTTP, lstrlen(kHTTP)) == 0)
          rv = DownloadViaHTTP(gszUrl);
        /* or is this an FTP URL? */
        else if(strncmp(gszUrl, kFTP, lstrlen(kFTP)) == 0)
          rv = DownloadViaFTP(gszUrl);
      }

      bDownloadInitiated = TRUE;
      if((rv == nsFTPConn::E_USER_CANCEL) ||
         (gdwDownloadDialogStatus == CS_PAUSE))
      {
        if(gdwDownloadDialogStatus == CS_PAUSE)
        {
          CloseSocket(szProxyServer, szProxyPort);

          /* rv needs to be set to something
           * other than E_USER_CANCEL or E_OK */
          rv = nsFTPConn::E_CMD_UNEXPECTED;

          PauseTheDownload(rv, &iFileDownloadRetries);
          bDownloadInitiated = FALSE; /* restart the download using
                                       * new socket connection */
        }
        else
        {
          /* user canceled; break out of the do loop */
          break;
        }
      }
      else if((rv != nsFTPConn::OK) &&
              (rv != nsFTPConn::E_CMD_FAIL) &&
              (rv != nsSocket::E_BIND) &&
              (rv != nsHTTPConn::E_HTTP_RESPONSE) &&
              (gdwDownloadDialogStatus != CS_CANCEL))
      {
        /* We timed out.  No response from the server, or 
         * we somehow lost connection. */

        char szTitle[MAX_BUF_SMALL];
        char szMsgDownloadPaused[MAX_BUF];

        /* Incrememt the time out counter on E_TIMEOUT */
        if(rv == nsSocket::E_TIMEOUT)
        {
          ++siCCurrentFileObj->iNetTimeOuts;
          ++iLocalTimeOutCounter;
        }

        CloseSocket(szProxyServer, szProxyPort);

        /* If the number of timeouts is %3 == 0, then let's pause
         * the download process.  Otherwise, just close the
         * connection and open a new one to see if the download
         * can be restarted automatically. */
        if((rv != nsSocket::E_TIMEOUT) ||
           (rv == nsSocket::E_TIMEOUT) && ((iLocalTimeOutCounter % kModTimeOutValue) == 0))
        {
          /* Start the pause tick counter here because we don't know how
           * long before the user will dismiss the MessageBox() */
          if(!gtiPaused.bTickStarted)
          {
            gtiPaused.dwTickBegin          = GetTickCount();
            gtiPaused.bTickStarted         = TRUE;
            gtiPaused.bTickDownloadResumed = FALSE;
          }

          /* The connection unexepectedly dropped for some reason, so inform
           * the user that the download will be Paused, and then update the
           * Download dialog to show the Paused state. */
          GetConfigIniProfileString("Messages", "MB_WARNING_STR", "",
				    szTitle, sizeof(szTitle));
          GetConfigIniProfileString("Strings", "Message Download Paused", "",
				    szMsgDownloadPaused, sizeof(szMsgDownloadPaused));
          MessageBox(dlgInfo.hWndDlg,
                     szMsgDownloadPaused,
                     szTitle,
                     MB_ICONEXCLAMATION);

          /* Let's make sure we're in a paused state */
          gdwDownloadDialogStatus = CS_PAUSE;
          PauseTheDownload(rv, &iFileDownloadRetries);
        }
        else
          /* Let's make sure we're _not_ in a paused state */
          gdwDownloadDialogStatus = CS_NONE;
      }

      /* We don't count time outs as normal failures.  We're
       * keeping track of time outs differently. */
      if(rv != nsSocket::E_TIMEOUT)
        ++iFileDownloadRetries;

      if((iFileDownloadRetries > MAX_FILE_DOWNLOAD_RETRIES) &&
         (rv != nsFTPConn::E_USER_CANCEL) &&
         (gdwDownloadDialogStatus != CS_CANCEL))
      {
        /* since the download retries maxed out, increment the counter
         * to read the next url for the current file */
        ++iCounter;
        wsprintf(szKey, "url%d",  iCounter);
        GetPrivateProfileString(szSection,
                                szKey,
                                "",
                                szTempURL,
                                sizeof(szTempURL),
                                gszConfigIniFile);
        if(*szTempURL != '\0')
        {
          /* Found more urls to download from for the current file.
           * Update the dialog to show the new url and reset the
           * file download retries to 0 since it's a new url. */
          gbUrlChanged = TRUE;
          iFileDownloadRetries = 0;
          bDownloadInitiated = FALSE; // restart the download using new socket connection
          CloseSocket(szProxyServer, szProxyPort);
          ParsePath(szTempURL,
                    szWorkingURLPathOnly,
                    sizeof(szWorkingURLPathOnly),
                    TRUE, //use '/' as the path delimiter
                    PP_PATH_ONLY);
          RemoveSlash(szWorkingURLPathOnly);
          wsprintf(gszUrl, "%s/%s", szWorkingURLPathOnly, szCurrentFile);
          SetStatusUrl();
        }
      }
    } while((rv != nsFTPConn::E_USER_CANCEL) &&
            (rv != nsFTPConn::OK) &&
            (gdwDownloadDialogStatus != CS_CANCEL) &&
            (iFileDownloadRetries <= MAX_FILE_DOWNLOAD_RETRIES));

    /* Save the number of retries for each file */
    siCCurrentFileObj->iNetRetries = iFileDownloadRetries < 1 ? 0:iFileDownloadRetries - 1;

    if((rv == nsFTPConn::E_USER_CANCEL) ||
       (gdwDownloadDialogStatus == CS_CANCEL))
    {
      /* make sure rv is E_USER_CANCEL when gdwDownloadDialogStatus
       * is CS_CANCEL */
      rv = nsFTPConn::E_USER_CANCEL;

      if(szFailedFile && ((DWORD)lstrlen(szCurrentFile) <= dwFailedFileSize))
        lstrcpy(szFailedFile, gszCurrentDownloadFileDescription);

      /* break out of for() loop */
      break;
    }

    if((rv != nsFTPConn::OK) &&
       (iFileDownloadRetries > MAX_FILE_DOWNLOAD_RETRIES) &&
       !bIgnoreAllNetworkErrors &&
       !iIgnoreFileNetworkError)
    {
      /* too many retries from failed downloads */
      char szMsg[MAX_BUF];

      if(szFailedFile && ((DWORD)lstrlen(szCurrentFile) <= dwFailedFileSize))
        lstrcpy(szFailedFile, gszCurrentDownloadFileDescription);

      GetConfigIniProfileString("Strings", "Error Too Many Network Errors", "",
				szMsg, sizeof(szMsg));
      if(*szMsg != '\0')
      {
        wsprintf(szBuf, szMsg, szCurrentFile);
        PrintError(szBuf, ERROR_CODE_HIDE);
      }

      iFileDownloadRetries = 0; // reset the file download retries counter since
                                // we'll be restarting the download again.
      bDownloadInitiated = FALSE; // restart the download using new socket connection
      CloseSocket(szProxyServer, szProxyPort);
      --giIndex; // Decrement the file index counter because we'll be trying to
                 // download the same file again.  We don't want to go to the next
                 // file just yet.

      /* Let's make sure we're in a paused state. */
      /* The pause state will be unset by DownloadDlgProc(). */
      gdwDownloadDialogStatus = CS_PAUSE;
      PauseTheDownload(rv, &iFileDownloadRetries);
    }
    else if(bIgnoreAllNetworkErrors || iIgnoreFileNetworkError)
      rv = nsFTPConn::OK;

    UnsetSetupCurrentDownloadFile();
  }

  CloseSocket(szProxyServer, szProxyPort);
  DeInitDownloadDlg();
  SetCurrentDirectory(szSavedCwd);
  return(rv);
}

int ProgressCB(int aBytesSoFar, int aTotalFinalSize)
{
  long   lBytesDiffSoFar;
  double dPercentSoFar;
  int    iRv = nsFTPConn::OK;

  if(sgProduct.mode != SILENT)
  {
    SetStatusUrl();

    if(glTotalKb == 0)
      glTotalKb = aTotalFinalSize;

    /* Calculate the difference between the last set of bytes read against
     * the current set of bytes read.  If the value is negative, that means
     * that it started a new file, so reset lBytesDiffSoFar to aBytesSoFar */
    lBytesDiffSoFar = ((aBytesSoFar - glLastBytesSoFar) < 1) ? aBytesSoFar : (aBytesSoFar - glLastBytesSoFar);

    /* Save the current bytes read as the last set of bytes read */
    glLastBytesSoFar = aBytesSoFar;
    glAbsoluteBytesSoFar += lBytesDiffSoFar;

    dPercentSoFar = GetPercentSoFar();
    if(gbDlgDownloadMinimized)
      SetMinimizedDownloadTitle((int)dPercentSoFar);

    UpdateGaugeFileProgressBar(dPercentSoFar);
    SetStatusStatus();

    if((gdwDownloadDialogStatus == CS_CANCEL) ||
       (gdwDownloadDialogStatus == CS_PAUSE))
      iRv = nsFTPConn::E_USER_CANCEL;
  }

  ProcessWindowsMessages();
  return(iRv);
}

// Window proc for dialog
LRESULT CALLBACK
DownloadDlgProc(HWND hWndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_INITDIALOG:
      GetConfigIniProfileString("Strings", "Status File Info", "",
				gszFileInfo, sizeof(gszFileInfo));
      if(gbShowDownloadRetryMsg)
        SetDlgItemText(hWndDlg, IDC_MESSAGE0, diDownload.szMessageRetry0);
      else
        SetDlgItemText(hWndDlg, IDC_MESSAGE0, diDownload.szMessageDownload0);

      EnableWindow(GetDlgItem(hWndDlg, IDRESUME), FALSE);
      SetDlgItemText(hWndDlg, IDC_STATIC1, sgInstallGui.szStatus);
      SetDlgItemText(hWndDlg, IDC_STATIC2, sgInstallGui.szFile);
      SetDlgItemText(hWndDlg, IDC_STATIC4, sgInstallGui.szTo);
      SetDlgItemText(hWndDlg, IDC_STATIC3, sgInstallGui.szUrl);
      SetDlgItemText(hWndDlg, IDCANCEL, sgInstallGui.szCancel_);
      SetDlgItemText(hWndDlg, IDPAUSE, sgInstallGui.szPause_);
      SetDlgItemText(hWndDlg, IDRESUME, sgInstallGui.szResume_);
      SendDlgItemMessage (hWndDlg, IDC_STATIC1, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDC_STATIC2, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDC_STATIC3, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDCANCEL, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDPAUSE, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDRESUME, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDC_MESSAGE0, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDC_STATUS_STATUS, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDC_STATUS_FILE, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDC_STATUS_URL, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      SendDlgItemMessage (hWndDlg, IDC_STATUS_TO, WM_SETFONT, (WPARAM)sgInstallGui.definedFont, 0L);
      return FALSE;

    case WM_SIZE:
      switch(wParam)
      {
        case SIZE_MINIMIZED:
          SetMinimizedDownloadTitle((int)GetPercentSoFar());
          gbDlgDownloadMinimized = TRUE;
          gbDlgDownloadJustMinimized = TRUE;
          break;

        case SIZE_RESTORED:
          SetStatusUrl();
          SetRestoredDownloadTitle();
          gbDlgDownloadMinimized = FALSE;
          break;
      }
      return(FALSE);

    case WM_COMMAND:
      switch(LOWORD(wParam))
      {
        case IDCANCEL:
          if(ShouldExitSetup(hWndDlg))
            gdwDownloadDialogStatus = CS_CANCEL;
          break;

        case IDPAUSE:
          if(!gtiPaused.bTickStarted)
          {
            gtiPaused.dwTickBegin          = GetTickCount();
            gtiPaused.bTickStarted         = TRUE;
            gtiPaused.bTickDownloadResumed = FALSE;
          }

          EnableWindow(GetDlgItem(hWndDlg, IDPAUSE),  FALSE);
          EnableWindow(GetDlgItem(hWndDlg, IDRESUME), TRUE);
          gdwDownloadDialogStatus = CS_PAUSE;
          break;

        case IDRESUME:
          gtiPaused.dwTickEnd = GetTickCount();
          gtiPaused.dwTickDif = GetTickDif(gtiPaused.dwTickEnd,
                                           gtiPaused.dwTickBegin);
          gtiPaused.bTickDownloadResumed = TRUE;

          EnableWindow(GetDlgItem(hWndDlg, IDRESUME), FALSE);
          EnableWindow(GetDlgItem(hWndDlg, IDPAUSE),  TRUE);
          gdwDownloadDialogStatus = CS_NONE;
          break;

        default:
          break;
      }
      return(TRUE);
  }

  return(FALSE);  // didn't handle the message
}

// This routine will update the File Gauge progress bar to the specified percentage
// (value between 0 and 100)
static void
UpdateGaugeFileProgressBar(double value)
{
	int	        nBars;
  static long lModLastValue = 0;

  if(sgProduct.mode != SILENT)
  {
    if(!CheckInterval(&lModLastValue, UPDATE_INTERVAL_PROGRESS_BAR))
      return;

    // Figure out how many bars should be displayed
    nBars = (int)(dlgInfo.nMaxFileBars * value / 100);

    // Only paint if we need to display more bars
    if((nBars > dlgInfo.nFileBars) || (dlgInfo.nFileBars == 0))
    {
      HWND	hWndGauge = GetDlgItem(dlgInfo.hWndDlg, IDC_PROGRESS_FILE);
      RECT	rect;

      // Update the gauge state before painting
      dlgInfo.nFileBars = nBars;

      // Only invalidate the part that needs updating
      GetClientRect(hWndGauge, &rect);
      InvalidateRect(hWndGauge, &rect, FALSE);
    
      // Update the whole extracting dialog. We do this because we don't
      // have a message loop to process WM_PAINT messages in case the
      // extracting dialog was exposed
      UpdateWindow(dlgInfo.hWndDlg);
    }
  }
}

// Draws a recessed border around the gauge
static void
DrawGaugeBorder(HWND hWnd)
{
	HDC		hDC = GetWindowDC(hWnd);
	RECT	rect;
	int		cx, cy;
	HPEN	hShadowPen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));
	HGDIOBJ	hOldPen;

	GetWindowRect(hWnd, &rect);
	cx = rect.right - rect.left;
	cy = rect.bottom - rect.top;

	// Draw a dark gray line segment
	hOldPen = SelectObject(hDC, (HGDIOBJ)hShadowPen);
	MoveToEx(hDC, 0, cy - 1, NULL);
	LineTo(hDC, 0, 0);
	LineTo(hDC, cx - 1, 0);

	// Draw a white line segment
	SelectObject(hDC, GetStockObject(WHITE_PEN));
	MoveToEx(hDC, 0, cy - 1, NULL);
	LineTo(hDC, cx - 1, cy - 1);
	LineTo(hDC, cx - 1, 0);

	SelectObject(hDC, hOldPen);
	DeleteObject(hShadowPen);
	ReleaseDC(hWnd, hDC);
}

// Draws the progress bar
static void
DrawProgressBar(HWND hWnd, int nBars)
{
  int         i;
	PAINTSTRUCT	ps;
	HDC         hDC;
	RECT        rect;
	HBRUSH      hBrush;

  hDC = BeginPaint(hWnd, &ps);
	GetClientRect(hWnd, &rect);
  if(nBars <= 0)
  {
    /* clear the bars */
    hBrush = CreateSolidBrush(GetSysColor(COLOR_MENU));
    FillRect(hDC, &rect, hBrush);
  }
  else
  {
  	// Draw the bars
    hBrush = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
	  rect.left     = rect.top = BAR_LIBXPNET_MARGIN;
	  rect.bottom  -= BAR_LIBXPNET_MARGIN;
	  rect.right    = rect.left + BAR_LIBXPNET_WIDTH;

	  for(i = 0; i < nBars; i++)
    {
		  RECT	dest;

		  if(IntersectRect(&dest, &ps.rcPaint, &rect))
			  FillRect(hDC, &rect, hBrush);

      OffsetRect(&rect, BAR_LIBXPNET_WIDTH + BAR_LIBXPNET_SPACING, 0);
	  }
  }

	DeleteObject(hBrush);
	EndPaint(hWnd, &ps);
}

// Adjusts the width of the gauge based on the maximum number of bars
static void
SizeToFitGauge(HWND hWnd, int nMaxBars)
{
	RECT	rect;
	int		cx;

	// Get the window size in pixels
	GetWindowRect(hWnd, &rect);

	// Size the width to fit
	cx = 2 * GetSystemMetrics(SM_CXBORDER) + 2 * BAR_LIBXPNET_MARGIN +
		nMaxBars * BAR_LIBXPNET_WIDTH + (nMaxBars - 1) * BAR_LIBXPNET_SPACING;

	SetWindowPos(hWnd, NULL, -1, -1, cx, rect.bottom - rect.top,
		SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}

// Window proc for Download gauge
BOOL CALLBACK
GaugeDownloadWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	DWORD	dwStyle;
	RECT	rect;

	switch(msg)
  {
		case WM_NCCREATE:
			dwStyle = GetWindowLong(hWnd, GWL_STYLE);
			SetWindowLong(hWnd, GWL_STYLE, dwStyle | WS_BORDER);
			return(TRUE);

		case WM_CREATE:
			// Figure out the maximum number of bars that can be displayed
			GetClientRect(hWnd, &rect);
			dlgInfo.nFileBars = 0;
			dlgInfo.nMaxFileBars = (rect.right - rect.left - 2 * BAR_LIBXPNET_MARGIN + BAR_LIBXPNET_SPACING) / (BAR_LIBXPNET_WIDTH + BAR_LIBXPNET_SPACING);

			// Size the gauge to exactly fit the maximum number of bars
			SizeToFitGauge(hWnd, dlgInfo.nMaxFileBars);
			return(FALSE);

		case WM_NCPAINT:
			DrawGaugeBorder(hWnd);
			return(FALSE);

		case WM_PAINT:
			DrawProgressBar(hWnd, dlgInfo.nFileBars);
			return(FALSE);
	}

	return(DefWindowProc(hWnd, msg, wParam, lParam));
}

void InitDownloadDlg(void)
{
	WNDCLASS	wc;

  if(sgProduct.mode != SILENT)
  {
    memset(&wc, 0, sizeof(wc));
    wc.style          = CS_GLOBALCLASS;
    wc.hInstance      = hInst;
    wc.hbrBackground  = (HBRUSH)(COLOR_BTNFACE + 1);
    wc.lpfnWndProc    = (WNDPROC)GaugeDownloadWndProc;
    wc.lpszClassName  = "GaugeFile";
    RegisterClass(&wc);

    // Display the dialog box
    dlgInfo.hWndDlg = CreateDialog(hSetupRscInst, MAKEINTRESOURCE(DLG_DOWNLOADING), hWndMain, (DLGPROC)DownloadDlgProc);
    UpdateWindow(dlgInfo.hWndDlg);
    UpdateGaugeFileProgressBar(0);
  }
}

void DeInitDownloadDlg()
{
  if(sgProduct.mode != SILENT)
  {
    DestroyWindow(dlgInfo.hWndDlg);
    UnregisterClass("GaugeFile", hInst);
  }
}
