/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Samir Gehani <sgehani@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of 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 "nscore.h"
#include "nsSetupTypeDlg.h"
#include "nsXInstaller.h"

#include <sys/types.h>
#include <dirent.h>

// need these for statfs 

#ifdef HAVE_SYS_STATVFS_H
#include <sys/statvfs.h>
#endif

#ifdef HAVE_SYS_STATFS_H
#include <sys/statfs.h>
#endif

#ifdef HAVE_STATVFS
#define STATFS statvfs
#else
#define STATFS statfs
#endif

#include <sys/wait.h>


static GtkWidget        *sBrowseBtn;
static gint             sBrowseBtnID;
static GtkWidget        *sFolder;
static GSList           *sGroup;
static GtkWidget        *sCreateDestDlg;
static GtkWidget        *sDelInstDlg;
static nsLegacyCheck    *sLegacyChecks = NULL;
static nsObjectIgnore   *sObjectsToIgnore = NULL;

nsSetupTypeDlg::nsSetupTypeDlg() :
    mMsg0(NULL),
    mSetupTypeList(NULL)
{
}

nsSetupTypeDlg::~nsSetupTypeDlg()
{
    FreeSetupTypeList();
    FreeLegacyChecks();

    XI_IF_FREE(mMsg0)
}

void 
nsSetupTypeDlg::Next(GtkWidget *aWidget, gpointer aData)
{
    DUMP("Next");
    if (aData && aData != gCtx->sdlg) return;
#ifdef MOZ_WIDGET_GTK
    if (gCtx->bMoving)
    {
        gCtx->bMoving = FALSE;
        return;
    }
#endif

    // verify selected destination directory exists
    if (OK != nsSetupTypeDlg::VerifyDestination())
        return;

    // old installation may exist: delete it
    if (OK != nsSetupTypeDlg::DeleteOldInst())
        return;

    // make sure destination directory is empty
    if (OK != nsSetupTypeDlg::CheckDestEmpty())
        return;

    // if not custom setup type verify disk space
    if (gCtx->opt->mSetupType != (gCtx->sdlg->GetNumSetupTypes() - 1))
    {
        if (OK != nsSetupTypeDlg::VerifyDiskSpace())
            return;
    }

    if (gCtx->opt->mMode == nsXIOptions::MODE_DEFAULT)
    {
        // hide this notebook page
        gCtx->sdlg->Hide();
    }

    // show the last dlg
    if (gCtx->opt->mSetupType == (gCtx->sdlg->GetNumSetupTypes() - 1))
    {
        gCtx->cdlg->Show();
    }
    else
    {
        if (gCtx->opt->mMode != nsXIOptions::MODE_SILENT)
        {
            gCtx->idlg->Show();
        }
        if (gCtx->opt->mMode != nsXIOptions::MODE_DEFAULT)
        {
            gCtx->idlg->Next((GtkWidget *)NULL, (gpointer) gCtx->idlg);
        }
    }

#ifdef MOZ_WIDGET_GTK
    // When Next() is not invoked from a signal handler, the caller passes
    // aData as NULL so we know not to do the bMoving hack. 
    if (aData)
    {
        gCtx->bMoving = TRUE;
    }
#endif
}

int
nsSetupTypeDlg::Parse(nsINIParser *aParser)
{
    int err = OK;
    int bufsize = 0;
    char *showDlg = NULL;
    int i, j;
    char *defSec = NULL; // default Setup Type
    char *currSec = (char *) malloc(strlen(SETUP_TYPEd) + 1); // e.g. SetupType12
    if (!currSec) return E_MEM;
    char *currKey = (char *) malloc(1 + 3); // e.g. C0, C1, C12
    if (!currKey) return E_MEM;
    char *currOIKey = (char *) malloc(strlen(OBJECT_IGNOREd) + 1);
    if (!currOIKey) return E_MEM;
    char *currLCSec = (char *) malloc(strlen(LEGACY_CHECKd) + 1);
    if (!currLCSec) return E_MEM;
    char *currVal = NULL;
    nsObjectIgnore *currOI = NULL, *nextOI = NULL;
    char *currFile = NULL, *currMsg = NULL;
    nsLegacyCheck *currLC = NULL, *nextLC = NULL;
    nsComponent *currComp = NULL;
    int currIndex;
    int currNumComps = 0;

    nsComponentList *compList = gCtx->cdlg->GetCompList();
    DUMP("Pre-verification of comp list")
    XI_VERIFY(compList);
    DUMP("Post-verification of comp list")

    nsSetupType *currST = NULL;
    char *currDescShort = NULL;
    char *currDescLong = NULL;

    XI_VERIFY(gCtx);

    /* optional keys */
    err = aParser->GetStringAlloc(GENERAL, DEFAULT_SETUP_TYPE, &defSec, &bufsize);
    if (err != OK && err != nsINIParser::E_NO_KEY) goto BAIL; else err = OK;

    bufsize = 0;
    err = aParser->GetStringAlloc(DLG_SETUP_TYPE, MSG0, &mMsg0, &bufsize);
    if (err != OK && err != nsINIParser::E_NO_KEY) goto BAIL; else err = OK;

    bufsize = 0;
    err = aParser->GetStringAlloc(DLG_SETUP_TYPE, TITLE, &mTitle, &bufsize);
    if (err != OK && err != nsINIParser::E_NO_KEY) goto BAIL; else err = OK;
    if (bufsize == 0)
            XI_IF_FREE(mTitle); 

    /* Objects to Ignore */
    for (i = 0; i < MAX_LEGACY_CHECKS; i++)
    {
        // construct key name based on index
        memset(currOIKey, 0, (strlen(OBJECT_IGNOREd) + 1));
        sprintf(currOIKey, OBJECT_IGNOREd, i);

        // get ObjectToIgnore key
        bufsize = 0;
        err = aParser->GetStringAlloc(CLEAN_UPGRADE, currOIKey, &currFile, &bufsize);
        if (err != OK) 
        { 
            if (err != nsINIParser::E_NO_SEC &&
                err != nsINIParser::E_NO_KEY) goto BAIL; 
            else 
            {
                err = OK; 
                break; 
            } 
        }
        nextOI = new nsObjectIgnore(currFile);

        if (currOI)
        {
           currOI->SetNext(nextOI);
        }
        else if (!sObjectsToIgnore)
        {
           sObjectsToIgnore = nextOI;
        }

        currOI = nextOI;
    }

    /* legacy check */
    for (i = 0; i < MAX_LEGACY_CHECKS; i++)
    {
        // construct section name based on index
        memset(currLCSec, 0, (strlen(LEGACY_CHECKd) + 1));
        sprintf(currLCSec, LEGACY_CHECKd, i);

        // get "Filename" and "Message" keys
        bufsize = 0;
        err = aParser->GetStringAlloc(currLCSec, FILENAME, &currFile, &bufsize);
        if (err != OK) 
        { 
            if (err != nsINIParser::E_NO_SEC &&
                err != nsINIParser::E_NO_KEY) goto BAIL; 
            else 
            {
                err = OK; 
                break; 
            } 
        }

        bufsize = 0;
        err = aParser->GetStringAlloc(currLCSec, MSG, &currMsg, &bufsize);
        if (err != OK)
        {
            if (err != nsINIParser::E_NO_SEC &&
                err != nsINIParser::E_NO_KEY) goto BAIL; 
            else 
            {
                err = OK; 
                break; 
            } 
        }
        nextLC = new nsLegacyCheck(currFile, currMsg);

        if (currLC)
        {
           currLC->SetNext(nextLC);
        }
        else if (!sLegacyChecks)
        {
           sLegacyChecks = nextLC;
        }

        currLC = nextLC;
    }

    /* setup types */
    gCtx->opt->mSetupType = 0;
    for (i=0; i<MAX_SETUP_TYPES; i++)
    {
        sprintf(currSec, SETUP_TYPEd, i);

        bufsize = 0;
        err = aParser->GetStringAlloc(currSec, DESC_SHORT, &currDescShort,
                                      &bufsize);
        if (err != OK && err != nsINIParser::E_NO_SEC) goto fin_iter;
        if (bufsize == 0 || err == nsINIParser::E_NO_SEC) // no more setup types
        {
            err = OK;
            break;
        }

        if (defSec && strcasecmp(currDescShort, defSec) == 0)
            gCtx->opt->mSetupType = i;

        bufsize = 0;
        err = aParser->GetStringAlloc(currSec, DESC_LONG, &currDescLong,
                                      &bufsize);
        if (err != OK || bufsize == 0) goto fin_iter;

        currST = new nsSetupType();
        if (!currST) goto fin_iter;

        currST->SetDescShort(currDescShort);
        currST->SetDescLong(currDescLong);

        currNumComps = 0;
        for (j=0; j<MAX_COMPONENTS; j++)
        {
            sprintf(currKey, Cd, j);
            
            bufsize = 0;
            err = aParser->GetStringAlloc(currSec, currKey, &currVal, 
                                          &bufsize);
            if (err != OK && err != nsINIParser::E_NO_KEY) continue;
            if (bufsize == 0 || err == nsINIParser::E_NO_KEY) 
            {
                err = OK;
                break;
            }
        
            currIndex = atoi(currVal + strlen(COMPONENT));
            currComp = compList->GetCompByIndex(currIndex);
            if (!currComp)
            {
                err = E_OUT_OF_BOUNDS;
                goto BAIL;
            }

            currST->SetComponent(currComp);
            currNumComps++;
        }
        if (currNumComps > 0)
        {
            AddSetupType(currST);
            currST = NULL;
        }

fin_iter:
        XI_IF_DELETE(currST);
    }

    err = OK;

BAIL:
    XI_IF_FREE(currSec);
    XI_IF_FREE(currKey);
    XI_IF_FREE(currOIKey);
    XI_IF_FREE(currLCSec);

    return err;
}

int
nsSetupTypeDlg::Show()
{
    int err = OK;
    int numSetupTypes = 0;
    int i;
    GtkWidget *stTable = NULL;
    GtkWidget *radbtns[MAX_SETUP_TYPES];
    GtkWidget *desc[MAX_SETUP_TYPES];
    nsSetupType *currST = NULL;
    GtkWidget *destTable = NULL;
    GtkWidget *frame = NULL;
    GtkWidget *hbox = NULL;

    XI_VERIFY(gCtx);
    XI_VERIFY(gCtx->notebook);

    if (mWidgetsInit == FALSE)
    {
        // create a new table and add it as a page of the notebook
        mTable = gtk_table_new(4, 1, FALSE);
        gtk_notebook_append_page(GTK_NOTEBOOK(gCtx->notebook), mTable, NULL);
        mPageNum = gtk_notebook_get_current_page(GTK_NOTEBOOK(gCtx->notebook));
        gtk_widget_show(mTable);

        // insert a static text widget in the first row
        GtkWidget *msg0 = gtk_label_new(mMsg0);
        hbox = gtk_hbox_new(FALSE, 0);
        gtk_box_pack_start(GTK_BOX(hbox), msg0, FALSE, FALSE, 0);
        gtk_widget_show(hbox);
        gtk_table_attach(GTK_TABLE(mTable), hbox, 0, 1, 1, 2,
            static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND),
            GTK_FILL, 20, 20);
        gtk_widget_show(msg0);

        // insert a [n x 2] heterogeneous table in the second row
        // where n = numSetupTypes
        numSetupTypes = GetNumSetupTypes();
        stTable = gtk_table_new(numSetupTypes, 4, FALSE);
        gtk_widget_show(stTable);
        gtk_table_attach(GTK_TABLE(mTable), stTable, 0, 1, 2, 3,
            static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL),
            static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL),
            20, 0);

        currST = GetSetupTypeList();
        if (!currST) return E_NO_SETUPTYPES;

        sGroup=NULL;

        // radio buttons
        for (i = 0; i < numSetupTypes; i++)
        {
            radbtns[i] = gtk_radio_button_new_with_label(sGroup,
                            currST->GetDescShort());
            sGroup = gtk_radio_button_group(GTK_RADIO_BUTTON(radbtns[i]));
            gtk_table_attach(GTK_TABLE(stTable), radbtns[i], 0, 1, i, i+1,
                static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND),
                static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND), 0, 0);
            gtk_signal_connect(GTK_OBJECT(radbtns[i]), "toggled",
                               GTK_SIGNAL_FUNC(RadBtnToggled),
                               reinterpret_cast<void *>(i));
            gtk_widget_show(radbtns[i]);

            desc[i] = gtk_label_new(currST->GetDescLong());
            gtk_label_set_justify(GTK_LABEL(desc[i]), GTK_JUSTIFY_LEFT);
            gtk_label_set_line_wrap(GTK_LABEL(desc[i]), TRUE);
            hbox = gtk_hbox_new(FALSE, 0);
            gtk_box_pack_start(GTK_BOX(hbox), desc[i], FALSE, FALSE, 0);
            gtk_widget_show(hbox);
            gtk_table_attach_defaults(GTK_TABLE(stTable), hbox, 1, 2, i, i+1);
            gtk_widget_show(desc[i]);

            currST = currST->GetNext();
        }

        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
                                        radbtns[gCtx->opt->mSetupType]), TRUE);

        // insert a [1 x 2] heterogeneous table in the third row
        destTable = gtk_table_new(1, 2, FALSE);
        gtk_widget_show(destTable); 

        gtk_table_attach(GTK_TABLE(mTable), destTable, 0, 1, 3, 4,
            static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL),
            static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL),
            20, 5);
        frame = gtk_frame_new(gCtx->Res("DEST_DIR"));
        gtk_table_attach_defaults(GTK_TABLE(destTable), frame, 0, 2, 0, 1);
        gtk_widget_show(frame);

        if (!gCtx->opt->mDestination)
        {
            gCtx->opt->mDestination = (char*)malloc(MAXPATHLEN * sizeof(char));
            getcwd(gCtx->opt->mDestination, MAXPATHLEN);
        }
        sFolder = gtk_label_new(gCtx->opt->mDestination);
        gtk_label_set_line_wrap(GTK_LABEL(sFolder), TRUE);
        gtk_widget_show(sFolder);
        gtk_table_attach_defaults(GTK_TABLE(destTable), sFolder, 0, 1, 0, 1);

        sBrowseBtn = gtk_button_new_with_label(gCtx->Res("BROWSE"));
        gtk_widget_show(sBrowseBtn);
        gtk_table_attach(GTK_TABLE(destTable), sBrowseBtn, 1, 2, 0, 1,
            static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL),
            GTK_SHRINK, 10, 10);

        mWidgetsInit = TRUE;
    }
    else
    {
        gtk_notebook_set_page(GTK_NOTEBOOK(gCtx->notebook), mPageNum);
        gtk_widget_show(mTable);
    }

    // signal connect the buttons
    // NOTE: back button disfunctional in this dlg since user accepted license
    gCtx->nextID = gtk_signal_connect(GTK_OBJECT(gCtx->next), "clicked",
                   GTK_SIGNAL_FUNC(nsSetupTypeDlg::Next), gCtx->sdlg);
    sBrowseBtnID = gtk_signal_connect(GTK_OBJECT(sBrowseBtn), "clicked",
                   GTK_SIGNAL_FUNC(nsSetupTypeDlg::SelectFolder), NULL);  

    GTK_WIDGET_SET_FLAGS(gCtx->next, GTK_CAN_DEFAULT);
    gtk_widget_grab_default(gCtx->next);
    gtk_widget_grab_focus(gCtx->next);

    // set up the next button.
    gCtx->nextLabel = gtk_label_new(gCtx->Res("NEXT"));
    gtk_widget_show(gCtx->nextLabel);
    gtk_container_add(GTK_CONTAINER(gCtx->next), gCtx->nextLabel);
    gtk_widget_show(gCtx->next);

    return err;
}

int
nsSetupTypeDlg::Hide()
{
    gtk_widget_hide(mTable);

    // disconnect and remove this dlg's nav btn
    gtk_signal_disconnect(GTK_OBJECT(sBrowseBtn), sBrowseBtnID);
    gtk_signal_disconnect(GTK_OBJECT(gCtx->next), gCtx->nextID);
    gtk_container_remove(GTK_CONTAINER(gCtx->next), gCtx->nextLabel); 
    gtk_widget_hide(gCtx->next);

    return OK;
}

int
nsSetupTypeDlg::SetMsg0(char *aMsg)
{
    if (!aMsg)
        return E_PARAM;

    mMsg0 = aMsg;

    return OK;
}

char *
nsSetupTypeDlg::GetMsg0()
{
    if (mMsg0)
        return mMsg0;

    return NULL;
}

int
nsSetupTypeDlg::AddSetupType(nsSetupType *aSetupType)
{
    if (!aSetupType)
        return E_PARAM;

    if (!mSetupTypeList)
    {
        mSetupTypeList = aSetupType;
        return OK;
    }

    nsSetupType *curr = mSetupTypeList;
    nsSetupType *next;
    while (curr)
    {
        next = NULL;
        next = curr->GetNext();
    
        if (!next)
        {
            return curr->SetNext(aSetupType);
        }

        curr = next;
    }

    return OK;
}

nsSetupType *
nsSetupTypeDlg::GetSetupTypeList()
{
    if (mSetupTypeList)
        return mSetupTypeList;

    return NULL;
}

int
nsSetupTypeDlg::GetNumSetupTypes()
{
    int num = 0;
    nsSetupType *curr = NULL;

    if (!mSetupTypeList)
        return 0;
    
    curr = mSetupTypeList;
    while(curr)
    {
        num++;
        curr = curr->GetNext();
    }

    return num;
}

nsSetupType *
nsSetupTypeDlg::GetSelectedSetupType()
{
    nsSetupType *curr = NULL;
    int numSetupTypes = GetNumSetupTypes();
    int setupTypeCount = 0;

    curr = GetSetupTypeList();
    while (curr && setupTypeCount < numSetupTypes)  // paranoia!
    {
        if (setupTypeCount == gCtx->opt->mSetupType)
            return curr;        

        setupTypeCount++;
        curr = curr->GetNext();
    }

    return NULL;
}

void
nsSetupTypeDlg::FreeSetupTypeList()
{
    nsSetupType *curr = mSetupTypeList;
    nsSetupType *prev;
    
    while (curr)
    {
        prev = curr;
        curr = curr->GetNext();

        XI_IF_DELETE(prev);
    }
}

void
nsSetupTypeDlg::FreeLegacyChecks()
{
    nsLegacyCheck *curr = NULL;
    nsLegacyCheck *last = NULL;

    if (sLegacyChecks)
    {
        curr = sLegacyChecks;

        while(curr)
        {
            last = curr;
            curr = last->GetNext();

            XI_IF_DELETE(last);
        }
    }
}

void
nsSetupTypeDlg::SelectFolder(GtkWidget *aWidget, gpointer aData)
{
    DUMP("SelectFolder");

    GtkWidget *fileSel = NULL;
    char *selDir = gCtx->opt->mDestination;

    fileSel = gtk_file_selection_new(gCtx->Res("SELECT_DIR"));
    gtk_window_set_modal(GTK_WINDOW(fileSel), TRUE);
    gtk_file_selection_set_filename(GTK_FILE_SELECTION(fileSel), selDir);
    gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fileSel)->ok_button),
                       "clicked", (GtkSignalFunc) SelectFolderOK, fileSel);
    gtk_signal_connect_object(GTK_OBJECT(
                                GTK_FILE_SELECTION(fileSel)->cancel_button),
                                "clicked", (GtkSignalFunc) SelectFolderCancel,
                                GTK_OBJECT(fileSel));
    gtk_widget_show(fileSel); 
}

void
nsSetupTypeDlg::SelectFolderOK(GtkWidget *aWidget, GtkFileSelection *aFileSel)
{
    DUMP("SelectFolderOK");

    struct stat destStat;
    const char *selDir = gtk_file_selection_get_filename(
                    GTK_FILE_SELECTION(aFileSel));

    // put the candidate file name in the global variable, then verify it

    strcpy(gCtx->opt->mDestination, selDir);

    if (0 == stat(selDir, &destStat))
        if (!S_ISDIR(destStat.st_mode) || VerifyDestination() != OK ) /* not a directory, or we don't have access permissions, so don't tear down */
            return;

    // update folder path displayed
    gtk_label_set_text(GTK_LABEL(sFolder), gCtx->opt->mDestination);
    gtk_widget_show(sFolder);

    // tear down file sel dlg
    gtk_object_destroy(GTK_OBJECT(aFileSel)); 
}

void
nsSetupTypeDlg::SelectFolderCancel(GtkWidget *aWidget, 
                                   GtkFileSelection *aFileSel)
{
    // tear down file sel dlg
    gtk_object_destroy(GTK_OBJECT(aWidget)); 
    gtk_object_destroy(GTK_OBJECT(aFileSel)); 
}

void
nsSetupTypeDlg::RadBtnToggled(GtkWidget *aWidget, gpointer aData)
{
    DUMP("RadBtnToggled");
    
    gCtx->opt->mSetupType = NS_PTR_TO_INT32(aData);
}

int
nsSetupTypeDlg::VerifyDestination()
{
    int stat_err = 0;
    struct stat stbuf; 
    GtkWidget *yesButton, *noButton, *label;
    GtkWidget *noPermsDlg, *okButton;
    char message[MAXPATHLEN];
  
    stat_err = stat(gCtx->opt->mDestination, &stbuf);
    if (stat_err == 0)
    {
      if (access(gCtx->opt->mDestination, R_OK | W_OK | X_OK ) != 0)
      {
        if (gCtx->opt->mMode != nsXIOptions::MODE_DEFAULT) {
          ErrorHandler(E_NO_PERMS);
          return E_NO_PERMS;
        }
        sprintf(message, gCtx->Res("NO_PERMS"), gCtx->opt->mDestination);

        noPermsDlg = gtk_dialog_new();
        gtk_window_set_modal(GTK_WINDOW(noPermsDlg), TRUE);
        label = gtk_label_new(message);
        okButton = gtk_button_new_with_label(gCtx->Res("OK_LABEL"));

        if (noPermsDlg && label && okButton)
        {
          gtk_window_set_title(GTK_WINDOW(noPermsDlg), gCtx->opt->mTitle);
          gtk_window_set_position(GTK_WINDOW(noPermsDlg), 
            GTK_WIN_POS_CENTER);
          gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
          gtk_box_pack_start(GTK_BOX(
            GTK_DIALOG(noPermsDlg)->action_area), okButton, FALSE, FALSE, 10);
          gtk_signal_connect(GTK_OBJECT(okButton), "clicked", 
            GTK_SIGNAL_FUNC(NoPermsOK), noPermsDlg);
          gtk_box_pack_start(GTK_BOX(
            GTK_DIALOG(noPermsDlg)->vbox), label, FALSE, FALSE, 10);

          GTK_WIDGET_SET_FLAGS(okButton, GTK_CAN_DEFAULT);
          gtk_widget_grab_default(okButton);

          gtk_widget_show_all(noPermsDlg);
        }

        return E_NO_PERMS;
      }
      else
      {
        // perms OK, we can proceed
        return OK;
      }
    }

    if (gCtx->opt->mMode != nsXIOptions::MODE_DEFAULT)
    {
      CreateDestYes((GtkWidget *)NULL, (gpointer) gCtx->sdlg);
      return E_NO_DEST;
    }
    // destination doesn't exist so ask user if we should create it
    sprintf(message, gCtx->Res("DOESNT_EXIST"), gCtx->opt->mDestination);

    sCreateDestDlg = gtk_dialog_new();
    gtk_window_set_modal(GTK_WINDOW(sCreateDestDlg), TRUE);
    label = gtk_label_new(message);
    yesButton = gtk_button_new_with_label(gCtx->Res("YES_LABEL"));
    noButton = gtk_button_new_with_label(gCtx->Res("NO_LABEL"));

    gtk_window_set_title(GTK_WINDOW(sCreateDestDlg), gCtx->opt->mTitle);
    gtk_window_set_position(GTK_WINDOW(sCreateDestDlg), GTK_WIN_POS_CENTER);
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(sCreateDestDlg)->action_area),
                      yesButton);
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(sCreateDestDlg)->action_area),
                      noButton);
    gtk_signal_connect(GTK_OBJECT(yesButton), "clicked",
                       GTK_SIGNAL_FUNC(CreateDestYes), sCreateDestDlg);
    gtk_signal_connect(GTK_OBJECT(noButton), "clicked",
                       GTK_SIGNAL_FUNC(CreateDestNo), sCreateDestDlg);

    GTK_WIDGET_SET_FLAGS(yesButton, GTK_CAN_DEFAULT);
    gtk_widget_grab_default(yesButton);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(sCreateDestDlg)->vbox), label);
    
    gtk_widget_show_all(sCreateDestDlg);

    return E_NO_DEST;
}

void
nsSetupTypeDlg::NoPermsOK(GtkWidget *aWidget, gpointer aData)
{
    GtkWidget *noPermsDlg = (GtkWidget *) aData;

    if (!noPermsDlg)
        return;

    gtk_widget_destroy(noPermsDlg);
}

void
nsSetupTypeDlg::CreateDestYes(GtkWidget *aWidget, gpointer aData)
{
    DUMP("CreateDestYes");
    int err = 0; 
    char path[PATH_MAX + 1];
    int  pathLen = strlen(gCtx->opt->mDestination);

    if (pathLen > PATH_MAX)
        pathLen = PATH_MAX;
    memcpy(path, gCtx->opt->mDestination, pathLen);
    path[pathLen] = '/';  // for uniform handling

    struct stat buf;

    for (int i = 1; !err && i <= pathLen; i++) 
    {
        if (path[i] == '/') 
        {
            path[i] = '\0';
            if (stat(path, &buf) != 0) 
            {
                err = mkdir(path, 0755);
            }
            path[i] = '/';
        }
    }

    if (gCtx->opt->mMode == nsXIOptions::MODE_DEFAULT)
    {
        gtk_widget_destroy(sCreateDestDlg);
    }

    if (err != 0)
    {
        ErrorHandler(E_MKDIR_FAIL);
    }
    else
    {
        // try to move forward to installer dialog again
        nsSetupTypeDlg::Next((GtkWidget *)NULL, NULL);
    }
}

void
nsSetupTypeDlg::CreateDestNo(GtkWidget *aWidget, gpointer aData)
{
    DUMP("CreateDestNo");

    gtk_widget_destroy(sCreateDestDlg);
}

int
nsSetupTypeDlg::DeleteOldInst()
{
    DUMP("DeleteOldInst");

    const int MAXCHARS = 64; // Maximum chars per line in Delete Dialog
    const int MAXLINES = 20; // Maximum lines in Delete Dialog
    int err = OK;
    struct stat dummy;
    char path[MAXPATHLEN];
    GtkWidget *label = NULL;
    GtkWidget *deleteBtn = NULL; /* delete button */
    GtkWidget *cancelBtn = NULL; /* cancel button */
    char *msgPtr = NULL, *msgChunkPtr = NULL, *msgEndPtr = NULL;
    char msgChunk[MAXCHARS+1];
    char msg[MAXPATHLEN+512];
    nsLegacyCheck *currLC = NULL;

    currLC = sLegacyChecks;
    while (currLC)
    {
      memset(path, 0, MAXPATHLEN);
      ConstructPath(path, gCtx->opt->mDestination, currLC->GetFilename());
      DUMP(path);

      // check if old installation exists
      if (0 == stat(path, &dummy))
      {
          if (gCtx->opt->mMode != nsXIOptions::MODE_DEFAULT)
          {
              DeleteInstDelete((GtkWidget *)NULL, (gpointer) gCtx->sdlg);
              return OK;
          }

          // throw up delete dialog 
          sDelInstDlg = gtk_dialog_new();
          gtk_window_set_modal(GTK_WINDOW(sDelInstDlg), TRUE);
          gtk_window_set_title(GTK_WINDOW(sDelInstDlg), gCtx->opt->mTitle);
          gtk_window_set_position(GTK_WINDOW(sDelInstDlg), GTK_WIN_POS_CENTER);

          deleteBtn = gtk_button_new_with_label(gCtx->Res("DELETE_LABEL"));
          cancelBtn = gtk_button_new_with_label(gCtx->Res("CANCEL_LABEL"));

          gtk_container_add(GTK_CONTAINER(GTK_DIALOG(sDelInstDlg)->action_area), 
              deleteBtn);
          gtk_container_add(GTK_CONTAINER(GTK_DIALOG(sDelInstDlg)->action_area),
              cancelBtn);
          gtk_signal_connect(GTK_OBJECT(deleteBtn), "clicked",
                         GTK_SIGNAL_FUNC(DeleteInstDelete), sDelInstDlg);
          gtk_signal_connect(GTK_OBJECT(cancelBtn), "clicked",
                         GTK_SIGNAL_FUNC(DeleteInstCancel), sDelInstDlg);

          GTK_WIDGET_SET_FLAGS(cancelBtn, GTK_CAN_DEFAULT);
          gtk_widget_grab_default(cancelBtn);

          snprintf(msg, sizeof(msg), currLC->GetMessage(), gCtx->opt->mDestination);
          msgPtr = msg;
          msgEndPtr = msg + strlen(msg);
          // wrap message at MAXCHARS colums (or last space inside MAXCHARS)
          // stop at MAXLINES rows or stop after last char is reached
          for (int i = 0; i < MAXLINES && msgPtr < msgEndPtr; i++)
          {
              // get the next MAXCHARS chars
              memset(msgChunk, 0, MAXCHARS+1);
              strncpy(msgChunk, msgPtr, MAXCHARS);

              // find last space
              msgChunkPtr = strrchr(msgChunk, ' ');
              if (msgChunkPtr)
              {
                  *msgChunkPtr = '\0';
                  msgPtr += (msgChunkPtr - msgChunk + 1);
              }
              else
              {
                  msgPtr += MAXCHARS;
              }
              label = gtk_label_new(msgChunk);
              gtk_box_pack_start(GTK_BOX(GTK_DIALOG(sDelInstDlg)->vbox), label,
                  FALSE, FALSE, 0);
          }
          gtk_widget_show_all(sDelInstDlg);
      
          err = E_OLD_INST;
          break;
      }
      currLC = currLC->GetNext();    
    }

    return err;
}

void         
nsSetupTypeDlg::DeleteInstDelete(GtkWidget *aWidget, gpointer aData)
{
    DUMP("DeleteInstDelete");

    if (!fork())
    {
        execlp("rm", "rm", "-rf", gCtx->opt->mDestination, NULL);
        /* execlp shouldn't return, we need to exit in case it does */
        _exit(0);
    }
    wait(NULL);

    if (gCtx->opt->mMode == nsXIOptions::MODE_DEFAULT)
    {
       gtk_widget_destroy(sDelInstDlg);
    }

    // We just deleted the directory, so the parent exists
    // and we have appropriate perms.
    mkdir(gCtx->opt->mDestination, 0755);

    if (gCtx->opt->mMode == nsXIOptions::MODE_DEFAULT)
    {
        // Try to move forward to installer dialog again.
        nsSetupTypeDlg::Next((GtkWidget *)NULL, NULL);
    }
}

void         
nsSetupTypeDlg::DeleteInstCancel(GtkWidget *aWidget, gpointer aData)
{
    DUMP("DeleteInstCancel");

    gtk_widget_destroy(sDelInstDlg);
}

int
nsSetupTypeDlg::ConstructPath(char *aDest, char *aTrunk, char *aLeaf)
{
    int err = OK;
    int trunkLen;
    char *lastSlash = NULL;
    
    if (!aDest || !aTrunk || !aLeaf)
        return E_PARAM;

    trunkLen = strlen(aTrunk);
    lastSlash = strrchr(aTrunk, '/');
    
    strcpy(aDest, aTrunk);
    if (lastSlash != aTrunk + (trunkLen - 1))
    {
        // need to tack on a slash
        strcat(aDest, "/");
    }
    strcat(aDest, aLeaf);

    return err;
}

int
nsSetupTypeDlg::CheckDestEmpty()
{
    DUMP("CheckDestEmpty");

    DIR *destDirD;
    struct dirent *de;
    nsObjectIgnore *currOI = NULL;

    /* check if the destination directory is empty */
    destDirD = opendir(gCtx->opt->mDestination);
    while (de = readdir(destDirD))
    {
        if (strcmp(de->d_name, ".") && strcmp(de->d_name, ".."))
        {
            currOI = sObjectsToIgnore;
            while (currOI)
            {
                // check if this is an Object To Ignore
                if (!strcmp(currOI->GetFilename(),de->d_name))
                    break;

                currOI = currOI->GetNext();    
            }
            if (!currOI)
            {
                closedir(destDirD);
                ErrorHandler(E_DIR_NOT_EMPTY);
                return E_DIR_NOT_EMPTY;
            }
        }
    }
    
    closedir(destDirD);
    return OK;
}

int
nsSetupTypeDlg::VerifyDiskSpace(void)
{
    int err = OK;
    int dsAvail, dsReqd;
    char dsAvailStr[128], dsReqdStr[128];
    char message[512];
    GtkWidget *noDSDlg, *label, *okButton;

    // find disk space available at destination
    dsAvail = DSAvailable();
    if (dsAvail < 0)
        return OK; // optimistic when statfs failed
                   // or we don't have statfs

    // get disk space required
    dsReqd = DSRequired();

    if (dsReqd > dsAvail)
    {
        if (gCtx->opt->mMode == nsXIOptions::MODE_DEFAULT)
        {
            // throw up not enough ds dlg
            sprintf(dsAvailStr, gCtx->Res("DS_AVAIL"), dsAvail);
            sprintf(dsReqdStr, gCtx->Res("DS_REQD"), dsReqd);
            sprintf(message, "%s\n%s\n\n%s", dsAvailStr, dsReqdStr, 
                    gCtx->Res("NO_DISK_SPACE"));

            noDSDlg = gtk_dialog_new();
            gtk_window_set_modal(GTK_WINDOW(noDSDlg), TRUE);
            label = gtk_label_new(message);
            okButton = gtk_button_new_with_label(gCtx->Res("OK_LABEL"));

            if (noDSDlg && label && okButton)
            {
                gtk_window_set_title(GTK_WINDOW(noDSDlg), gCtx->opt->mTitle);
                gtk_window_set_position(GTK_WINDOW(noDSDlg), 
                        GTK_WIN_POS_CENTER);
                gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
                gtk_misc_set_padding(GTK_MISC(label), 20, 20);
                gtk_misc_set_alignment(GTK_MISC(label), 0.5, 1);
                gtk_box_pack_start(GTK_BOX(
                        GTK_DIALOG(noDSDlg)->action_area), okButton,
                        FALSE, FALSE, 10);
                gtk_signal_connect(GTK_OBJECT(okButton), "clicked", 
                        GTK_SIGNAL_FUNC(NoDiskSpaceOK), noDSDlg);
                gtk_box_pack_start(GTK_BOX(
                        GTK_DIALOG(noDSDlg)->vbox), label, FALSE, FALSE, 10);

                GTK_WIDGET_SET_FLAGS(okButton, GTK_CAN_DEFAULT);
                gtk_widget_grab_default(okButton);

                gtk_widget_show_all(noDSDlg);
            }
        }

        err = E_NO_DISK_SPACE;
    }

    return err;
}

int
nsSetupTypeDlg::DSAvailable(void)
{
    // returns disk space available in kilobytes

    int dsAvail = -1;

#if defined(HAVE_SYS_STATVFS_H) || defined(HAVE_SYS_STATFS_H)
    struct STATFS buf;
    int rv;

    if (gCtx->opt->mDestination)
    {
        rv = STATFS(gCtx->opt->mDestination, &buf);
        if (rv == 0)
        {
            if (buf.f_bsize > 1024 && (buf.f_bsize%1024 == 0))
            {
                // normally the block size is >= 1024 and a multiple
                // so we can shave off the last three digits before 
                // finding the product of the block size and num blocks
                // which is important for large disks

                dsAvail = (buf.f_bsize/1024) * (buf.f_bavail);
            }
            else
            {
                // attempt to stuff into a 32 bit int even though
                // we convert from bytes -> kilobytes later
                // (may fail to compute on very large disks whose
                // block size is not a multiple of 1024 -- highly 
                // improbable)

                dsAvail = (buf.f_bsize * buf.f_bavail)/1024;
            }
        }
    }
#endif // HAVE_SYS_STATVFS_H -or- HAVE_SYS_STATFS_H 

    return dsAvail;
}

int 
nsSetupTypeDlg::DSRequired(void)
{
    // returns disk space required in kilobytes 

    int dsReqd = 0;
    nsComponentList *comps;
    int bCus;

    // find setup type's component list
    bCus = (gCtx->opt->mSetupType == (gCtx->sdlg->GetNumSetupTypes() - 1));
    comps = gCtx->sdlg->GetSelectedSetupType()->GetComponents();

    // loop through all components
    nsComponent *currComp = comps->GetHead();
    while (currComp)
    {
        if ( (bCus == TRUE && currComp->IsSelected()) || (bCus == FALSE) )
        {
            // add to disk space required 
            dsReqd += currComp->GetInstallSize();
            dsReqd += currComp->GetArchiveSize();
        }

        currComp = comps->GetNext();
    }

    return dsReqd;
}

void
nsSetupTypeDlg::NoDiskSpaceOK(GtkWidget *aWidget, gpointer aData)
{
    GtkWidget *noDSDlg = (GtkWidget *) aData;

    if (!noDSDlg)
        return;

    gtk_widget_destroy(noDSDlg);
}

