/* -*- 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>
 *   Brian Ryner <bryner@brianryner.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 "nscore.h"
#include "nsXInstaller.h"
#include <gdk/gdkkeysyms.h>

nsXIContext *gCtx = NULL;
static GtkWidget *sErrDlg = NULL;

nsXInstaller::nsXInstaller()
{
}

nsXInstaller::~nsXInstaller()
{
  XI_IF_DELETE(gCtx);
}

int 
nsXInstaller::ParseArgs(int aArgc, char **aArgv)
{
  if (aArgc <= 0 || !aArgv)
    return E_PARAM;

  for (int argNum = 1; argNum < aArgc; ++argNum) {
    /* Print usage */
    if (strcmp(aArgv[argNum], "-h") == 0 ||
        strcmp(aArgv[argNum], "--help") == 0) {

      if (gCtx->Res("USAGE_MSG"))
        fprintf (stderr, gCtx->Res("USAGE_MSG"), aArgv[0], "\n", 
                 "\n", "\n", "\n", "\n", "\n", "\n", "\n");
      return E_USAGE_SHOWN;
    }

    /*
     * mode: auto  (show progress UI but assume defaults
     *              without user intervention)
     */
    else if (strcmp(aArgv[argNum], "-ma") == 0) {
      gCtx->opt->mMode = nsXIOptions::MODE_AUTO;
    }

    /*
     * mode: silent (show no UI and have no user intervention)
     */
    else if (strcmp(aArgv[argNum], "-ms") == 0) {
      gCtx->opt->mMode = nsXIOptions::MODE_SILENT;
    }

    /*
     * ignore [RunAppX] sections
     */
    else if (strcmp(aArgv[argNum], "-ira") == 0) {
      gCtx->opt->mShouldRunApps = FALSE;
    }
  }

  return OK;
}

int
nsXInstaller::ParseConfig()
{
  int err = OK;
  nsINIParser *parser = NULL; 
  char *cfg = NULL;

  XI_ERR_BAIL(InitContext());
  err = gCtx->LoadResources();
  if (err != OK)
    return err;

  cfg = nsINIParser::ResolveName(CONFIG);
  if (!cfg)
    return E_INVALID_PTR;

  parser = new nsINIParser(cfg);
  if (!parser) {
    err = E_MEM;    
    goto BAIL;
  }

  err = parser->GetError();
  if (err != nsINIParser::OK)
    return err;

  XI_ERR_BAIL(ParseGeneral(parser));
  XI_ERR_BAIL(gCtx->ldlg->Parse(parser));
  XI_ERR_BAIL(gCtx->wdlg->Parse(parser));
  //XI_ERR_BAIL(gCtx->cdlg->Parse(parser)); // components before setup type
  XI_ERR_BAIL(gCtx->sdlg->Parse(parser));
  XI_ERR_BAIL(gCtx->idlg->Parse(parser));

BAIL:
  XI_IF_FREE(cfg);
  return err;
}

int 
nsXInstaller::InitContext()
{
  int err = OK;

  gCtx = new nsXIContext();
  if (!gCtx)
    return E_MEM;

  gCtx->me = this;

  gCtx->ldlg = new nsLicenseDlg();
  gCtx->wdlg = new nsWelcomeDlg();
  gCtx->sdlg = new nsSetupTypeDlg();
  gCtx->cdlg = new nsComponentsDlg();
  gCtx->idlg = new nsInstallDlg();

  if (!(gCtx->ldlg && gCtx->wdlg && gCtx->sdlg && gCtx->cdlg && gCtx->idlg)) {
    err = E_MEM;
    goto BAIL;
  }

  return OK;
    
BAIL:
  XI_IF_DELETE(gCtx->ldlg);
  XI_IF_DELETE(gCtx->wdlg);
  XI_IF_DELETE(gCtx->sdlg);
  XI_IF_DELETE(gCtx->cdlg);
  XI_IF_DELETE(gCtx->idlg);
  XI_IF_DELETE(gCtx);

  return err;
}

int 
nsXInstaller::RunWizard(int argc, char **argv)
{
  int err = OK;

  XI_VERIFY(gCtx);

  // create the dialog window
  if (gCtx->opt->mMode != nsXIOptions::MODE_SILENT) {
    gtk_init(&argc, &argv);
    gCtx->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    XI_VERIFY(gCtx->window);

    gtk_window_set_position(GTK_WINDOW(gCtx->window), GTK_WIN_POS_CENTER);
    gtk_signal_connect(GTK_OBJECT(gCtx->window), "delete_event",
                       GTK_SIGNAL_FUNC(Kill), NULL);

    gtk_signal_connect(GTK_OBJECT(gCtx->window), "key_press_event",
                       GTK_SIGNAL_FUNC(HandleKeyPress), NULL);

    gtk_window_set_title(GTK_WINDOW(gCtx->window), gCtx->opt->mTitle);

    SetupBoxes();

    // create an area for the header image.
    // this will remain 0-height until the user advances past
    // the welcome dialog.

    gCtx->header = gtk_event_box_new();
    if (gCtx->opt->mHeaderPixmap) {
      GdkPixbuf *pb = gdk_pixbuf_new_from_file(gCtx->opt->mHeaderPixmap, NULL);
      if (pb) {
        GdkPixmap *pm = NULL;
        gdk_pixbuf_render_pixmap_and_mask(pb, &pm, NULL, 0);
        if (pm) {
          GtkStyle *newStyle =
            gtk_style_copy(gtk_widget_get_style(gCtx->header));

          newStyle->bg_pixmap[GTK_STATE_NORMAL] = pm;
          gtk_widget_set_style(gCtx->header, newStyle);

          // newStyle now owns the pixmap, so we don't unref it.
          g_object_unref(newStyle);

          // Make the initial window width be as wide as the header.
          gint width, height;
          gdk_drawable_get_size(pm, &width, &height);
          gtk_window_set_default_size(GTK_WINDOW(gCtx->window), width, -1);
        }

        // We're done with rendering the pixbuf, so destroy it.
        gdk_pixbuf_unref(pb);
      }
    }

    // The header contains a vbox with two labels
    GtkWidget *header_vbox = gtk_vbox_new(FALSE, 6);
    gtk_container_set_border_width(GTK_CONTAINER(header_vbox), 6);

    gCtx->header_title = gtk_label_new(NULL);
    gtk_misc_set_alignment(GTK_MISC(gCtx->header_title), 0.0, 0.5);
    gtk_box_pack_start(GTK_BOX(header_vbox), gCtx->header_title,
                       FALSE, FALSE, 0);

    gCtx->header_subtitle = gtk_label_new(NULL);
    gtk_misc_set_alignment(GTK_MISC(gCtx->header_subtitle), 0.0, 0.5);
    gtk_misc_set_padding(GTK_MISC(gCtx->header_subtitle), 24, 0);
    gtk_box_pack_start(GTK_BOX(header_vbox), gCtx->header_subtitle,
                       FALSE, FALSE, 0);

    gtk_container_add(GTK_CONTAINER(gCtx->header), header_vbox);

    gtk_widget_set_size_request(header_vbox, 0, 0);
    gtk_box_pack_start(GTK_BOX(gCtx->mainbox), gCtx->header, FALSE, FALSE, 0);

    // create the notebook whose pages are dlgs
    gCtx->notebook = gtk_notebook_new();
    XI_VERIFY(gCtx->notebook);
    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gCtx->notebook), FALSE);
    gtk_notebook_set_show_border(GTK_NOTEBOOK(gCtx->notebook), FALSE);
    gtk_notebook_set_scrollable(GTK_NOTEBOOK(gCtx->notebook), FALSE);
    gtk_widget_show(gCtx->notebook);
    gtk_box_pack_start(GTK_BOX(gCtx->mainbox), gCtx->notebook, TRUE, TRUE, 0);

    // create and display the nav buttons
    XI_ERR_BAIL(DrawNavButtons());
  }

  if (gCtx->opt->mMode == nsXIOptions::MODE_DEFAULT) {
    // show welcome dlg
    gCtx->wdlg->Show(nsXInstallerDlg::FORWARD_MOVE); 
    gtk_widget_show(gCtx->window);

    // pop over to main event loop
    gtk_main();
  } else {
    // show install dlg
    if (gCtx->opt->mMode == nsXIOptions::MODE_AUTO)
      gCtx->idlg->Show(nsXInstallerDlg::FORWARD_MOVE);

    gCtx->idlg->Next((GtkWidget *)NULL, (gpointer) gCtx->idlg);
  }

  return OK;

BAIL:
  return err;
}

gboolean
nsXInstaller::HandleKeyPress(GtkWidget *widget, GdkEventKey *event,
                             gpointer data)
{
  if (event->keyval == GDK_Escape) {
    gtk_main_quit();
    return TRUE;
  }

  return FALSE;
}

gint
nsXInstaller::Kill(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  gtk_main_quit();
  return FALSE;
}

void
nsXInstaller::SetupBoxes()
{
  GtkWidget *mainbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(gCtx->window), mainbox);

  gtk_widget_show(mainbox);
  gCtx->mainbox = mainbox; /* mainbox = canvas + nav btns' box */
}

int
nsXInstaller::DrawNavButtons()
{
    int err = OK;
    
    GtkWidget *navbtnhbox;
    GtkWidget *navbtntable;

    XI_VERIFY(gCtx->mainbox);

    gCtx->next = gtk_button_new_from_stock(GTK_STOCK_GO_FORWARD);
    gCtx->back = gtk_button_new_from_stock(GTK_STOCK_GO_BACK);
    gCtx->cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);

    gCtx->nextLabel = gtk_label_new(gCtx->Res("NEXT"));
    gCtx->backLabel = gtk_label_new(gCtx->Res("BACK"));
    XI_VERIFY(gCtx->next);
    XI_VERIFY(gCtx->back);
    XI_VERIFY(gCtx->cancel);

    gCtx->cancelID = gtk_signal_connect(GTK_OBJECT(gCtx->cancel), "clicked",
                                        GTK_SIGNAL_FUNC(Kill), NULL);

    GTK_WIDGET_SET_FLAGS(gCtx->next, GTK_CAN_DEFAULT);

    navbtnhbox = gtk_hbox_new(TRUE, 10);
    GtkWidget *separator = gtk_hseparator_new();

    gtk_box_pack_start(GTK_BOX(gCtx->mainbox), separator, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(gCtx->mainbox), navbtnhbox, FALSE, FALSE, 0); 

    // put a table in the nav btn box
    navbtntable = gtk_table_new(1, 5, TRUE);
    gtk_box_pack_start(GTK_BOX(navbtnhbox), navbtntable, TRUE, TRUE, 0);

    gtk_table_attach(GTK_TABLE(navbtntable), gCtx->cancel, 2, 3, 0, 1,
                     static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND),
                     static_cast<GtkAttachOptions>(GTK_SHRINK),
                     5, 5);

    gtk_table_attach(GTK_TABLE(navbtntable), gCtx->back, 3, 4, 0, 1, 
                     static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND),
                     static_cast<GtkAttachOptions>(GTK_SHRINK),
                     5, 5);
    gtk_table_attach(GTK_TABLE(navbtntable), gCtx->next, 4, 5, 0, 1,
                     static_cast<GtkAttachOptions>(GTK_FILL | GTK_EXPAND),
                     static_cast<GtkAttachOptions>(GTK_SHRINK),
                     5, 5);

    gtk_widget_show_all(gCtx->mainbox);

    return err;
}

int
nsXInstaller::ParseGeneral(nsINIParser *aParser)
{
  int err = OK, size = 0;
  char *buf = NULL;
 
  /* optional: main program name.   if this file exists in the destination
     directory, the user will be prompted to remove the directory or choose
     a different directory. */

  err = aParser->GetStringAlloc(GENERAL, PROGRAM_NAME, &buf, &size);
  if (err == OK && size > 0) {
    gCtx->opt->mProgramName = buf;
  }

  /* optional: destination directory can be specified in config.ini */
  err = aParser->GetStringAlloc(GENERAL, DEFAULT_LOCATION, &buf, &size);
  if (err == OK && size > 0) {
    /* malloc MAXPATHLEN for consistency in behavior if destination
     * directory is not specified in the config.ini
     */
    gCtx->opt->mDestination = (char *)malloc(MAXPATHLEN * sizeof(char));
    strncpy(gCtx->opt->mDestination, buf, size);
    XI_IF_FREE(buf);
  } else {
    err = OK; /* optional so no error if we didn't find it */
  }

  /* optional: installer app window title */
  size = 0;
  buf = NULL;
  err = aParser->GetStringAlloc(GENERAL, TITLE, &buf, &size);
  if (err == OK && size > 0) {
    gCtx->opt->mTitle = buf;
  } else {
    err = OK; /* optional so no error if we didn't find it */
    gCtx->opt->mTitle = strdup(gCtx->Res("DEFAULT_TITLE"));
  }

  /* optional: product name (substituted for %s elsewhere) */
  size = 0;
  buf = NULL;
  err = aParser->GetStringAlloc(GENERAL, PRODUCT_NAME, &buf, &size);
  if (err == OK && size > 0)
    gCtx->opt->mProductName = buf;
  else
    err = OK;

  /* optional: header image */
  size = 0;
  buf = NULL;
  err = aParser->GetStringAlloc(GENERAL, HEADER_IMAGE, &buf, &size);
  if (err == OK && size > 0)
    gCtx->opt->mHeaderPixmap = buf;
  else
    err = OK;

  return err;
}

int
main(int argc, char **argv)
{
  int err = OK;
  nsXInstaller *installer = new nsXInstaller();
  if (!installer) {
    err = E_MEM;
    goto out;
  }

  if ((err = installer->ParseConfig()) != OK)
    goto out;

  if ((err = installer->ParseArgs(argc, argv)) != OK)
    goto out;

  err = installer->RunWizard(argc, argv);

 out:
  XI_IF_DELETE(installer);
  _exit(err);
}

/*------------------------------------------------------------------*
 *   Default Error Handler
 *------------------------------------------------------------------*/
int 
ErrorHandler(int aErr, const char* aErrMsg)
{
  GtkWidget *okButton, *label;
  char msg[256];
  char newmsg[256];
  char errStr[16];
    
  sprintf(errStr, "%d", aErr); 
  if (!IsErrFatal(aErr)) {
	if (aErr == E_INSTALL) {
      if (aErrMsg) {
		sprintf(newmsg, gCtx->Res(errStr), aErrMsg);
		sprintf(msg, gCtx->Res("ERROR"), aErr, newmsg);
      }
	} else {
      sprintf(msg, gCtx->Res("ERROR"), aErr, gCtx->Res(errStr));
    }
  } else {
    sprintf(msg, gCtx->Res("FATAL_ERROR"), aErr, gCtx->Res(errStr));
  }
    
  // lack of gCtx->window indicates we have not yet run RunWizard
  // and gtk_init
  if (gCtx->opt->mMode == nsXIOptions::MODE_SILENT || !gCtx->window) {
    fprintf (stderr, "%s\n", msg);
    return aErr;
  }

  sErrDlg = gtk_dialog_new();
  gtk_window_set_title(GTK_WINDOW(sErrDlg), gCtx->Res("ERROR_TITLE"));
  okButton = gtk_button_new_with_label(gCtx->Res("OK_LABEL"));
  label = gtk_label_new(msg);

  gtk_window_set_position(GTK_WINDOW(sErrDlg), GTK_WIN_POS_CENTER);
  gtk_container_add(GTK_CONTAINER(GTK_DIALOG(sErrDlg)->action_area), okButton);
  gtk_signal_connect(GTK_OBJECT(okButton), "clicked",
                     GTK_SIGNAL_FUNC(ErrDlgOK), (void*)aErr);

  gtk_container_add(GTK_CONTAINER(GTK_DIALOG(sErrDlg)->vbox), label);

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

  gtk_main();

  return aErr;
}

void
ErrDlgOK(GtkWidget *aWidget, gpointer aData)
{
  int err = NS_PTR_TO_INT32(aData);

  if (sErrDlg) {
    gtk_widget_destroy(sErrDlg);
    sErrDlg = NULL;
  }

  gtk_main_quit();

  if (IsErrFatal(err))
    exit(err);
}

int
IsErrFatal(int aErr)
{
  int bFatal = TRUE;

  /* non-fatal errors */
  switch (aErr) {
  case -620:
  case -621:
  case -624:
  case -625:
    bFatal = FALSE;
  default:
    break; 
  }

  return bFatal;
}
