/*
 * File: nav.c
 *
 * Copyright (C) 1999 James McCollough <jamesm@gtwn.net>
 * Copyright (C) 2000, 2001, 2002 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.
 */

/* Support for a navigation stack */

#include <stdio.h>
#include <gtk/gtk.h>
#include "msg.h"
#include "list.h"
#include "nav.h"
#include "history.h"
#include "web.h"
#include "menu.h"
#include "interface.h"
#include "dw_gtk_scrolled_window.h"
#include "prefs.h"
#include "commands.h"
#include "capi.h"

//#define DEBUG_LEVEL 3
#include "debug.h"

/*
 * Forward declarations
 */
static void Nav_reload(BrowserWindow *bw);
void a_Nav_cancel_expect(BrowserWindow *bw);


/*
 * Initialize the navigation structure with safe values
 */
void a_Nav_init(BrowserWindow *bw)
{
   bw->nav_stack_size = 0;
   bw->nav_stack_size_max = 16;
   bw->nav_stack = NULL;
   bw->nav_stack_ptr = -1;
   bw->nav_expecting = FALSE;
   bw->nav_expect_url = NULL;
}

/*
 * Free memory used by this module
 */
void a_Nav_free(BrowserWindow *bw)
{
   a_Nav_cancel_expect(bw);
   g_free(bw->nav_stack);
}


/* Navigation stack methods ------------------------------------------------ */

/*
 * Return current nav_stack pointer [0 based; -1 = empty]
 */
gint a_Nav_stack_ptr(BrowserWindow *bw)
{
   return bw->nav_stack_ptr;
}

/*
 * Move the nav_stack pointer
 */
static void Nav_stack_move_ptr(BrowserWindow *bw, gint offset)
{
   gint nptr;

   g_return_if_fail (bw != NULL);
   if (offset != 0) {
      nptr = bw->nav_stack_ptr + offset;
      g_return_if_fail (nptr >= 0 && nptr < bw->nav_stack_size);
      bw->nav_stack_ptr = nptr;
   }
}

/*
 * Return size of nav_stack [1 based]
 */
gint a_Nav_stack_size(BrowserWindow *bw)
{
   return bw->nav_stack_size;
}

/*
 * Add an URL-index in the navigation stack.
 */
static void Nav_stack_add(BrowserWindow *bw, gint idx)
{
   g_return_if_fail (bw != NULL);

   ++bw->nav_stack_ptr;
   if ( bw->nav_stack_ptr == bw->nav_stack_size) {
      a_List_add(bw->nav_stack, bw->nav_stack_size, bw->nav_stack_size_max);
      ++bw->nav_stack_size;
   } else {
      bw->nav_stack_size = bw->nav_stack_ptr + 1;
   }
   bw->nav_stack[bw->nav_stack_ptr] = idx;
}

/*
 * Remove an URL-index from the navigation stack.
 */
static void Nav_stack_remove(BrowserWindow *bw, gint idx)
{
   gint sz = a_Nav_stack_size(bw);

   g_return_if_fail (bw != NULL && idx >=0 && idx < sz);

   for (  ; idx < sz - 1; ++idx)
      bw->nav_stack[idx] = bw->nav_stack[idx + 1];
   if ( bw->nav_stack_ptr == --bw->nav_stack_size )
      --bw->nav_stack_ptr;
}

/*
 * Remove equal adyacent URLs at the top of the stack.
 * (It may happen with redirections)
 */
static void Nav_stack_clean(BrowserWindow *bw)
{
   gint i;

   g_return_if_fail (bw != NULL);

   if ((i = a_Nav_stack_size(bw)) >= 2 &&
       bw->nav_stack[i-2] == bw->nav_stack[i-1])
         Nav_stack_remove(bw, i - 1);
}


/* General methods --------------------------------------------------------- */

/*
 * Create a DilloWeb structure for 'url' and ask the cache to send it back.
 *  - Also set a few things related to the browser window.
 * This function requests the page's root-URL; images and related stuff
 * are fetched directly by the HTML module.
 */
static void Nav_open_url(BrowserWindow *bw, const DilloUrl *url, gint offset)
{
   DilloUrl *old_url = NULL;
   gboolean MustLoad;
   gint ClientKey;
   DilloWeb *Web;
   gboolean ForceReload = (URL_FLAGS(url) & URL_E2EReload);

   MSG("Nav_open_url: Url=>%s<\n", URL_STR_(url));

   /* Get the url of the current page */
   if ( a_Nav_stack_ptr(bw) != -1 )
      old_url = a_History_get_url(NAV_TOP(bw));

   /* Record current scrolling position
    * (the strcmp check is necessary because of redirections) */
   if (old_url &&
       !strcmp(URL_STR(old_url), a_Interface_get_location_text(bw))) {
      old_url->scrolling_position =
        a_Dw_gtk_scrolled_window_get_scrolling_position(
           GTK_DW_SCROLLED_WINDOW(bw->docwin));
   }

   /* Update navigation-stack-pointer (offset may be zero) */
   Nav_stack_move_ptr(bw, offset);

   /* Page must be reloaded, if old and new url (without anchor) differ */
   MustLoad = ForceReload || !old_url;
   if (old_url){
      MustLoad |= a_Url_cmp(old_url, url);
      MustLoad |= strcmp(URL_STR(old_url), a_Interface_get_location_text(bw));
   }

   if ( MustLoad ) {
      a_Interface_stop(bw);
      a_Interface_clean(bw);

      a_Menu_pagemarks_new(bw);

      Web = a_Web_new(url);
      Web->bw = bw;
      Web->flags |= WEB_RootUrl;
      if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) {
         a_Interface_add_client(bw, ClientKey, 1);
         a_Interface_add_url(bw, url, WEB_RootUrl);
      }
      a_Interface_set_cursor(bw, GDK_LEFT_PTR);
   }

   /* Jump to #anchor position */
   if (URL_FRAGMENT_(url)) {
      /* todo: push on stack */
      a_Dw_gtk_scrolled_window_set_anchor(
         GTK_DW_SCROLLED_WINDOW(bw->docwin), URL_FRAGMENT_(url));
   }
}

/*
 * Cancel the last expected url if present. The responsibility
 * for actually aborting the data stream remains with the caller.
 */
void a_Nav_cancel_expect(BrowserWindow *bw)
{
   if (bw->nav_expecting) {
      if (bw->nav_expect_url) {
         a_Url_free(bw->nav_expect_url);
         bw->nav_expect_url = NULL;
      }
      bw->nav_expecting = FALSE;
   }
}

/*
 * We have an answer! Set things accordingly.
 */
void a_Nav_expect_done(BrowserWindow *bw)
{
   gint idx;
   DilloUrl *url;

   g_return_if_fail(bw != NULL);

   if (bw->nav_expecting) {
      url = bw->nav_expect_url;
      /* unset E2EReload before adding this url to history */
      a_Url_set_flags(url, URL_FLAGS(url) & ~URL_E2EReload);
      idx = a_History_add_url(url);
      Nav_stack_add(bw, idx);

      a_Url_free(url);
      bw->nav_expect_url = NULL;
      bw->nav_expecting = FALSE;
   }
   Nav_stack_clean(bw);
   a_Interface_set_button_sens(bw);
}

/*
 * Remove top-URL from the navigation stack.
 * (Used to remove URLs that force redirection)
 */
void a_Nav_remove_top_url(BrowserWindow *bw)
{
   g_return_if_fail (bw != NULL);

   /* Deallocate the URL a the top of the stack */
   Nav_stack_remove(bw, a_Nav_stack_size(bw) - 1);
}

/*
 * Make 'url' the current browsed page (upon data arrival)
 * - Set bw to expect the URL data
 * - Ask the cache to feed back the requested URL (via Nav_open_url)
 */
void a_Nav_push(BrowserWindow *bw, const DilloUrl *url)
{
   g_return_if_fail (bw != NULL);

   if (bw->nav_expecting && a_Url_cmp(bw->nav_expect_url, url) == 0 &&
       URL_STRCAMP_EQ(URL_FRAGMENT_(bw->nav_expect_url), URL_FRAGMENT_(url))) {
      /* we're already expecting that url (most probably a double-click) */
      return;
   }
   a_Nav_cancel_expect(bw);
   bw->nav_expect_url = a_Url_dup(url);
   bw->nav_expecting = TRUE;
   Nav_open_url(bw, url, 0);
}

/*
 * Same as a_Nav_push() but in a new window.
 */
void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url)
{
   gint width, height;
   BrowserWindow *newbw;

   gdk_window_get_size (bw->main_window->window, &width, &height);
   newbw = a_Interface_browser_window_new(width, height, 0);
   a_Nav_push(newbw, url);
}

/*
 * Wraps a_Nav_push to match 'DwPage->link' function type
 */
void a_Nav_vpush(void *vbw, const DilloUrl *url)
{
   a_Nav_push(vbw, url);
}

/*
 * Send the browser back to previous page
 */
void a_Nav_back(BrowserWindow *bw)
{
   gint idx = a_Nav_stack_ptr(bw);

   a_Nav_cancel_expect(bw);
   if ( --idx >= 0 ){
      a_Interface_msg(bw, "");
      Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), -1);
   }
}

/*
 * Send the browser to next page in the history list
 */
void a_Nav_forw(BrowserWindow *bw)
{
   gint idx = a_Nav_stack_ptr(bw);

   a_Nav_cancel_expect(bw);
   if (++idx < a_Nav_stack_size(bw)) {
      a_Interface_msg(bw, "");
      Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), +1);
   }
}

/*
 * Redirect the browser to the HOME page!
 */
void a_Nav_home(BrowserWindow *bw)
{
   a_Nav_push(bw, prefs.home);
}

/*
 * Jump to an URL within the stack history
 * NewBw: {0 = same window, 1 = new window}
 */
void a_Nav_jump_callback(GtkWidget *widget, gpointer client_data, gint NewBw)
{
   int idx;
   BrowserWindow *bw = client_data;

   idx = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT (widget), "nav_idx"));
   if (idx >= 0 && idx < a_Nav_stack_size(bw)) {
      if (NewBw == 1) {
         a_Nav_push_nw(bw, a_History_get_url(NAV_IDX(bw,idx)));
      } else {
         Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)),
                      idx - a_Nav_stack_ptr(bw));
      }
   }
}

/*
 * Callback for reload confirmation
 */
static void Nav_reload_confirmed(BrowserWindow *bw)
{
   DEBUG_MSG(3, "Nav_reload_confirmed\n");
   if ( a_Nav_stack_size(bw) &&
        bw->question_dialog_data == a_History_get_url(NAV_TOP(bw)) ) {
      /* a genuine confirmation! */
      DEBUG_MSG(3, "Nav_reload_confirmed test: OK\n");
      bw->question_dialog_data = NULL;
      Nav_reload(bw);
   }
}

/*
 * Callback for reload refusal
 */
static void Nav_reload_refused(BrowserWindow *bw)
{
   DEBUG_MSG(3, "Nav_reload_refused\n");
   bw->question_dialog_data = NULL;
}

/*
 * This one does a_Nav_reload's job!
 */
static void Nav_reload(BrowserWindow *bw)
{
   DilloUrl *url, *ReqURL;

   a_Nav_cancel_expect(bw);
   if ( a_Nav_stack_size(bw) ) {
      url = a_History_get_url(NAV_TOP(bw));
      ReqURL = a_Url_dup(a_History_get_url(NAV_TOP(bw)));
      /* Let's make reload be end-to-end */
      a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) | URL_E2EReload);
      /* This is an explicit reload, so clear the SpamSafe flag */
      a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) & ~URL_SpamSafe);
      Nav_open_url(bw, ReqURL, 0);
      a_Url_free(ReqURL);
   }
}

/*
 * Implement the RELOAD button functionality.
 * (We haven't defined it yet ;)
 */
void a_Nav_reload(BrowserWindow *bw)
{
   DilloUrl *url;

   a_Nav_cancel_expect(bw);
   if ( a_Nav_stack_size(bw) ) {
      url = a_History_get_url(NAV_TOP(bw));
      if (URL_FLAGS(url) & URL_Post) {
         /* Attempt to repost data, let's confirm... */
         bw->question_dialog_data = (gpointer)url;
         a_Interface_question_dialog(bw, "Repost form data?",
                                     Nav_reload_confirmed, bw,
                                     Nav_reload_refused, bw);

      } else {
         Nav_reload(bw);
      }
   }
}

