/*
 * Dillo web browser
 *
 * Copyright 1997 Raph Levien <raph@acm.org>
 * Copyright 1999-2003 Jorge Arellano Cid <jcid@dillo.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <gtk/gtk.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <locale.h>

#include <config.h>
#include "msg.h"
#include "dillo.h"
#include "misc.h"
#include "nav.h"
#include "history.h"
#include "bookmark.h"
#include "dicache.h"
#include "dns.h"
#include "IO/mime.h"
#include "prefs.h"
#include "interface.h"
#include "dw.h"
#include "cookies.h"


/*
 * Command line options structure
 */
typedef struct {
   char *sh_opt; /* must be non NULL */
   char *lg_opt; /* must be non NULL */
   int opt_argc; /* positive: mandatory, negative: optional */
   int opt_flg;  /* 1 << n */
   char *help;   /* can be NULL */
} CLI_options;

enum {
   DILLO_CLI_XID           = 1 << 1,
   DILLO_CLI_FULLWINDOW    = 1 << 2,
   DILLO_CLI_HELP          = 1 << 3,
   DILLO_CLI_VERSION       = 1 << 4,
   DILLO_CLI_LOCAL         = 1 << 5,
   DILLO_CLI_DBG_RENDERING = 1 << 6,
   DILLO_CLI_ERROR         = 1 << 31
};

enum {
   O_SEARCH, O_FOUND, O_NOTFOUND, O_OK, O_ERROR, O_DONE
};

/*
 * Global variables from command line options;
 */ 
gboolean dillo_dbg_rendering = FALSE;

/*
 * Forward declarations
 */
static void Dillo_check_home_dir(char *dir);
static guint32 Dillo_get_opt(CLI_options *options, int argc,
                             char **argv, char ***opt_arg, gint *idx);
static void Dillo_print_help(CLI_options *options);
static void Dillo_print_version(void);

/*
 * Local data
 */
static CLI_options Options[] = {
   {"-x", "--xid",        1, DILLO_CLI_XID,
    "  -x, --xid XID          Open first Dillo window in an existing\n"
    "                         GtkSocket which window ID is XID (decimal)."},
   {"-v", "--version",    0, DILLO_CLI_VERSION,
    "  -v, --version          Display version info and exit."},
   {"-h", "--help",       0, DILLO_CLI_HELP,
    "  -h, --help             Display this help text and exit."},
   {"-f", "--fullwindow", 0, DILLO_CLI_FULLWINDOW,
    "  -f, --fullwindow       Start in full window mode."},
   {"-l", "--local",      0, DILLO_CLI_LOCAL,
    "  -l, --local            Don't follow links for this URL(s)."},
   {"-D", "--debug-rendering",
                          0, DILLO_CLI_DBG_RENDERING,
    "  -D, --debug-rendering  Draw additionaly several lines in a web page,\n"
    "                         representing its structure. For debugging. "},
   {NULL, NULL, 0, 0, NULL}
};

/*
 * Return the maximum number of option arguments
 */
static gint Dillo_get_max_opt(CLI_options *options)
{
   gint i, max;

   for (i = 0, max = 0; options[i].sh_opt; i++)
      if (abs(options[i].opt_argc) > max)
         max = abs(options[i].opt_argc);
   return max;
}

/*
 * Given a command line argument, build a DilloUrl for it.
 */
static DilloUrl *Dillo_make_start_url(gchar *str)
{
   gchar *url_str, *p, *cd;
   DilloUrl *start_url;
   gint is_file = FALSE;

   /* This check is for files with non-URL characters */
   if (access(str, F_OK) == 0) {
      p = a_Misc_escape_chars(str, "% ");
      url_str = g_strdup(p ? p : str);
      is_file = TRUE;
   } else {
      /* Filter URL string */
      url_str = a_Url_string_strip_delimiters(str);
   }

   if (is_file || access(url_str, F_OK) == 0) {
      GString *UrlStr = g_string_sized_new(128);

      if (url_str[0] == '/') {
         g_string_sprintf(UrlStr, "file:%s", url_str);
      } else {
         cd = g_get_current_dir();
         p = a_Misc_escape_chars(cd, "% ");
         g_string_sprintf(UrlStr, "file:%s", p ? p : cd);
         g_free(p);
         g_free(cd);
         if (UrlStr->str[UrlStr->len - 1] != '/')
            g_string_append(UrlStr, "/");
         g_string_append(UrlStr, url_str);
      }
      start_url = a_Url_new(UrlStr->str, NULL, 0, 0);
      g_string_free(UrlStr, TRUE);
   } else {
      start_url = a_Url_new(url_str, NULL, 0, 0);
   }

   g_free(url_str);
   return start_url;
}

/*
 * Get status code to avoid zombies!
 */
void Dillo_sigchld_handler(int signum)
{
  int pid, status, serrno;
  serrno = errno;
  while (1) {
     pid = waitpid (-1, &status, WNOHANG);
     if (pid < 0) {
        perror ("waitpid");
        break;
     }
     if (pid == 0)
       break;
     /* notice_termination (pid, status); */
  }
  errno = serrno;
}


/*
 * ******************************** MAIN *************************************
 */
gint main(int argc, char *argv[])
{
   gchar *dir, *curr_locale;
   DilloUrl *start_url;
   BrowserWindow *bw;
   guint32 xid = 0;
   guint32 options_got = 0;
   gint idx = 0;
   guint opt_flg;
   gint i;
   char **opt_argv = NULL;

   /* set locale */
   curr_locale = g_strdup(setlocale(LC_ALL, NULL));
   gtk_set_locale();
   /* Initialize GUI and parse GTK related args */
   gtk_init(&argc, &argv);
   gdk_rgb_init();

   /* Handle command line options */
   while ((opt_flg = Dillo_get_opt(Options, argc, argv, &opt_argv, &idx))) {
     options_got |= opt_flg;
     switch (opt_flg) {
     case DILLO_CLI_VERSION:
        Dillo_print_version();
        return 0;
        break;
     case DILLO_CLI_HELP:
        Dillo_print_help(Options);
        return 0;
        break;
     case DILLO_CLI_XID:
        if (opt_argv[0][0] >= '0' && opt_argv[0][0] <= '9') {
           xid = strtol(opt_argv[0], NULL, 10);
        } else {
           g_printerr("Error: the XID must be an unsigned decimal numerical "
                      "value.\nThe offending value was: %s\n", opt_argv[0]);
           return -1;
        }
        break;
     case DILLO_CLI_FULLWINDOW:
     case DILLO_CLI_LOCAL:
        break;
     case DILLO_CLI_DBG_RENDERING:
        dillo_dbg_rendering = TRUE;
        break;
     default:
        g_printerr("Error in command line options.\n");
        return -1;
        break;
     }
   }

   /* Send a delayed locale-related message */
   MSG("Setting locale to %s...\n", curr_locale);
   g_free(curr_locale);

   /* This lets threads in the file module end peacefully when aborted
    * todo: implement a cleaner mechanism (in file.c) */
   signal(SIGPIPE, SIG_IGN);

   /* This avoids making zombies when dpi-programs finish. */
   signal(SIGCHLD, Dillo_sigchld_handler);


   /* check that ~/.dillo exists, create it if it doesn't */
   dir = a_Misc_prepend_user_home(".dillo");
   Dillo_check_home_dir(dir);
   g_free(dir);

   a_Prefs_init();
   a_Dns_init();
   a_Http_init();
   a_Mime_init();
   a_Cache_init();
   a_Dicache_init();
   a_Interface_init();
   a_Dw_init();
   a_Cookies_init();

   /* -f overrides dillorc */
   if (options_got & DILLO_CLI_FULLWINDOW)
      prefs.fullwindow_start = TRUE;

   /* a_Nav_init() has been moved into this call because it needs to be
    * initialized with the new browser_window structure */
   bw = a_Interface_browser_window_new(prefs.width, prefs.height, xid);

   a_Bookmarks_init();

   /* Send dillo startup screen */
   start_url = a_Url_new("splash", "about:", 0, 0);
   a_Nav_push(bw, start_url);
   a_Url_free(start_url);

   for (i = idx; i < argc; i++) {
      /* If more than one URL/FILE, open in new window */
      if (i > idx)
         bw = a_Interface_browser_window_new(prefs.width, prefs.height, 0);

      start_url = Dillo_make_start_url(argv[i]);
      if (options_got & DILLO_CLI_LOCAL)
         a_Url_set_flags(start_url, URL_FLAGS(start_url) | URL_SpamSafe);
      a_Nav_push(bw, start_url);
      a_Url_free(start_url);
   }

   /* Start the GTK+ cycle */
   gtk_main();

   /*
    * Memory deallocating routines
    * (This can be left to the OS, but we'll do it, with a view to test
    *  and fix our memory management)
    */
   a_Cookies_freeall();
   a_Cache_freeall();
   a_Dicache_freeall();
   a_Http_freeall();
   a_Dns_freeall();
   a_Prefs_freeall();
   a_Dw_freeall();
   a_History_free();

   //a_Dpi_bye_dpid();
   MSG("Dillo: normal exit!\n");
   return 0;
}

/*
 * Check if '~/.dillo' directory exists.
 * If not, try to create it.
 */
static void Dillo_check_home_dir(char *dir)
{
   struct stat st;

   if ( stat(dir, &st) == -1 ) {
      if ( errno == ENOENT && mkdir(dir, 0700) < 0 ) {
         MSG("Dillo: error creating directory %s: %s\n",
             dir, g_strerror(errno));
      } else
         MSG("Dillo: error reading %s: %s\n", dir, g_strerror(errno));
   }
}


/*
 * Get next command line option.
 * Return value:
 *  opt_flg of the option
 *  0 if no more options found
 * -1 if an unrecognised option is found or if a mandatory option
 *    argument is not found.
 */
static guint32
Dillo_get_opt(CLI_options *options, int argc,
              char **argv, char ***opt_argv, gint *cli_idx)
{
   gint i = 0, opt_flg = 0, n_arg, state;
   static int idx = 1;
   guint32 ret_val = 0;

   /* Allocate opt_argv */
   if (*opt_argv == NULL)
      *opt_argv = g_new0(gchar*, Dillo_get_max_opt(options) + 1);

   state = O_SEARCH;
   if (idx >= argc)
      state = O_DONE;

   while (1) {
      switch (state) {
      case O_SEARCH:
         for (i = 0; options[i].sh_opt; i++)
            if (strcmp(options[i].sh_opt, argv[idx]) == 0 ||
                strcmp(options[i].lg_opt, argv[idx]) == 0)
               break;
         state = (options[i].sh_opt) ? O_FOUND : O_NOTFOUND;
         break;
      case O_FOUND:
         ++idx;
         opt_flg  = options[i].opt_flg;
         n_arg = options[i].opt_argc;
         /* Find the required/optional arguments of the option */
         for (i = 0; idx < argc && i < abs(n_arg) && argv[idx][0] != '-'; i++)
            (*opt_argv)[i] = argv[idx++];
         (*opt_argv)[i] = NULL;

         /* Optional arguments have opt_argc < 0 */
         if (i < n_arg) {
            g_printerr("Option %s requires %d argument(s)\n",
                       argv[idx - i - 1], n_arg);
            state = O_ERROR;
         } else
            state = O_OK;
         break;
      case O_NOTFOUND:
         state = O_DONE;
         if (strcmp(argv[idx], "--") == 0)
            idx++;
         else if (argv[idx][0] == '-')
            state = O_ERROR;
         break;
      case O_OK:
         *cli_idx = idx;
         return opt_flg;
         break;
      case O_ERROR:
         ret_val = DILLO_CLI_ERROR;
         state = O_DONE;
         break;
      case O_DONE:
         g_free(*opt_argv);
         *cli_idx = idx;
         return ret_val;
         break;
      }
   }
}

/*
 * Print a short help text automatically generated from the options structure
 */
static void Dillo_print_help(CLI_options *options)
{
   g_print("\nUsage: dillo [OPTIONS] [URL|FILE]...\n"
              "Options:\n");

   for (; options && options->sh_opt; options++) {
      if (options->help)
         g_print("%s\n", options->help);
      else
         g_print("  %s, %s   *Undocumented*\n", options->sh_opt,
                 options->lg_opt);
   }
   g_print("  URL                    URL to browse.\n"
           "  FILE                   Local FILE to view.\n");
   g_print("\n");
}

/*
 * Print version (TODO: and maybe a copyright notice)
 */
static void Dillo_print_version(void)
{
   g_print("Dillo %s\n", VERSION);
}
