/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : FetchPage.c                            */
/* Purpose: High-level page fetch related          */
/*          functions (as opposed to Fetch.c where */
/*          all the lower level stuff goes on).    */
/* Author : A.D.Hodgkinson                         */
/* History: 25-Nov-96: Created                     */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "flex.h"

#include "swis.h"

#include "URI.h"     /* URI handler API, in URILib:h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"
#include "window.h"
#include "gadgets.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "TBEvents.h"
#include "Utils.h"

#include "Browser.h"
#include "Fetch.h"
#include "Frames.h"
#include "History.h"
#include "Images.h"
#include "JavaScript.h"
#include "Memory.h"
#include "Reformat.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "FetchPage.h"

/* Locals */

char * url_buffer = NULL;

/* Static function prototypes */

static _kernel_oserror * fetchpage_process_internal (browser_data * b);
static _kernel_oserror * fetchpage_preprocessed     (browser_data * b, int record);
static _kernel_oserror * fetchpage_postprocessed    (browser_data * b, int record);

/*************************************************/
/* fetchpage_fetch()                             */
/*                                               */
/* Handles the initiation of a fetch and the     */
/* display of the result in a browser window.    */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler (this is called on null events).      */
/*************************************************/

int fetchpage_fetch(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  int tf_start, tf_now, priority;

  #ifdef TRACE
    static oldstatus;

    if ((tl & (1u<<6)) && (handle->fetch_status != oldstatus))
    {
      Printf("\nfetchpage_fetch: Called with new status %d\n",handle->fetch_status);
      oldstatus = handle->fetch_status;
    }
  #endif

  if (handle->fetch_status == BS_START) ChkError(fetch_start(handle));

  /* Call the fetcher / reformatter, allowing a certain */
  /* amount of time inside each only. Whilst still      */
  /* fetching data, relax the timing as there's no      */
  /* point hanging here waiting for stuff; otherwise,   */
  /* be somewhat more aggressive as the reformat can    */
  /* progress a lot faster.                             */

  _swix(OS_ReadMonotonicTime, _OUT(0), &tf_start);
  tf_now = tf_start;

  /* Some fairly crude load balancing */

  if (!fetch_fetching(handle)) priority = 20;
  else if (handle->fetch_status != BS_PROCESS) priority = 4;
  else priority = 10;

  while ((tf_now - tf_start < priority) && (fetch_fetching(handle) || reformat_formatting(handle)))
  {
    /* If fetching, call the reformatter. */

    if (fetch_fetching(handle)) fetch_fetcher(handle);

    /* If reformatting, call the reformatter. */

    if (reformat_formatting(handle)) reformat_reformatter(handle);
    ChkError(windows_check_tools(handle, NULL));

    _swix(OS_ReadMonotonicTime, _OUT(0), &tf_now);
  }

  /* Process images on a lower priority */

  if (image_count_specific_pending(handle)) ChkError(image_process_null(handle));

  /* Handle jumping to any specified named anchors */

  if (handle->display_request == DISPLAY_NAMED)
  {
    char    * p;
    HStream * t;

    t = 0;

    p = fetch_find_name_tag(browser_current_url(handle)) + 1;
    t = fetch_find_anchor_token(handle, p);

    if (t)
    {
      handle->display_request = t;
      handle->display_offset  = 0;
    }
  }
  else
  {
    if (
         handle->display_request &&
         browser_show_token(handle,
                            handle->display_request,
                            handle->display_offset)
       )
    {
      WimpGetWindowStateBlock s;

      s.window_handle = handle->window_handle;
      wimp_get_window_state(&s);

      if (s.yscroll != handle->display_vscroll) handle->display_vscroll = s.yscroll;
      else handle->display_request = 0, handle->display_vscroll = 0;
    }
  }

  /* Various actions as things become inactive... */

  if (!fetch_fetching(handle))
  {
    /* If we have a JavaScript onLoad command, deal with it */

    if (handle->onload) ChkError(javascript_body_onload(handle));

    /* Garbage collect images if the main page fetch has finished */

    if (handle->clear_images)
    {
//      image_discard_unused(handle);
      handle->clear_images = 0;
    }

    if (!reformat_formatting(handle))
    {
      if (!image_count_specific_pending(handle))
      {
        /* There are no pending images, so we seem to have finished - */
        /* but is there a reformat pending?                           */

        if (handle->refotime)
        {
          /* Yes, so flush the queue */

          reformat_format_from(handle, handle->refoline, 1, -1);
        }
        else
        {
          /* Nope - so get rid of null claimants */

          if (handle->fetch_handler) fetchpage_release_nulls(handle);

          /* Check the page's vertical extent is correct */

          ChkError(reformat_check_extent(handle));

          /* Ensure the status bar is up to date */

          toolbars_update_status(handle, Toolbars_Status_Viewing);
        }
      }

      /* Sort out window tool presence */

      ChkError(windows_check_tools(handle, NULL));
    }
  }

  /* Keep the buttons as up to date as possible throughout the fetch */

  toolbars_set_button_states(handle);

  return 0;
}

/*************************************************/
/* fetchpage_process_internal()                  */
/*                                               */
/* Some internal URLs involve just substituting  */
/* the internal URL for some known or easily     */
/* discoverable alternative early in the fetch   */
/* stage. This function handles such changes.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetch.            */
/*************************************************/

static _kernel_oserror * fetchpage_process_internal(browser_data * b)
{
  if (
       b->displayed == Display_Recovered_Page ||
       b->displayed == Display_Home_Page
     )
  {
    char alt_url[4096];

    memset(alt_url, 0, sizeof(alt_url));

    if (b->displayed == Display_Recovered_Page)
    {
      /* For a recovered page, try to get back to the page detailed  */
      /* in Browse$PreviousPage.                                     */
      /*                                                             */
      /* If the variable is unset / can't be read, can't load a page */
      /* so set the buffer to hold a null string.                    */

      if (
           _swix(OS_ReadVarVal,
                 _INR(0,4),

                 "Browse$PreviousPage",
                 alt_url,
                 sizeof(alt_url),
                 0,
                 4)

         )
         *alt_url = 0;
    }
    else
    {
      /* Alternatively, get the Home Page URL */

      urlutils_create_home_url(alt_url, sizeof(alt_url));
    }

    /* Ensure the URL is terminated */

    alt_url[sizeof(alt_url) - 1] = 0;

    /* Reallocate URL buffer space */

    if (url_buffer)
    {
      #ifdef TRACE
        malloccount -= (strlen(url_buffer) + 128);
        if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
      #endif

      free(url_buffer);
    }

    url_buffer = malloc(strlen(alt_url) + 128);

    if (!url_buffer)
    {
      make_no_fetch_memory_error(10);
      return &erb;
    }

    #ifdef TRACE
      malloccount += (strlen(alt_url) + 128);
      if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
    #endif

    /* Copy the new URL into the buffer */

    strcpy(url_buffer, alt_url);
  }

  return NULL;
}

/*************************************************/
/* fetchpage_preprocessed()                      */
/*                                               */
/* Fetches a URL, which must be in the           */
/* 'url_buffer' malloced block of memory.        */
/* Intended to be called from functions such     */
/* as fetchpage_new or fetchpage_new_add.        */
/*                                               */
/* If using the URI handler, the URL will be     */
/* sent through that and won't actually fetch    */
/* at this stage, therefore.                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to which the new URL refers;      */
/*                                               */
/*             1 to record the previous URL in   */
/*             the history list, else 0.         */
/*************************************************/

static _kernel_oserror * fetchpage_preprocessed(browser_data * b, int record)
{
  _kernel_oserror * e;

  if (
       !b->savelink &&
       browser_display_local_reference(b,
                                       url_buffer,
                                       browser_current_url(b))
     )
  {
    if (choices.keyboardctl) browser_move_selection(b, akbd_RightK);

    return NULL;
  }

  e = fetch_cancel(b);
  if (e) return e;

  /* If required, stop all fetching in all frames, else leave */
  /* images but stop everything else.                         */

  if (choices.brickwall) frames_abort_fetching(b, 1);
  else                   frames_abort_fetching(b, 0);

  /* Set the displayed type for internal / normal URLs, and */
  /* carry out any required special actions for the former. */

  urlutils_set_displayed(b, url_buffer);

  if (b->displayed == Display_Previous_Page) return history_fetch_backwards(b, 0);

  e = fetchpage_process_internal(b);
  if (e) return e;

  /* If merging the URL writable and status display, put */
  /* it back to status.                                  */

  if (b->merged_url)
  {
    toolbars_merged_to_status(b, toolbars_get_upper(b));
    browser_give_general_focus(b);
  }

  if (uri_module_present && strncmp(url_buffer, Internal_URL, Int_URL_Len))
  {
    /* Send the URL through the URI handler if the module is present */
    /* and the URL isn't an internal one.                            */

    return urlutils_dispatch(b,
                             url_buffer,
                             record ? URIQueue_RecordInHistory : 0);
  }
  else
  {
    /* Without the URI handler, deal with the URL immediately */

    return fetchpage_postprocessed(b, record);
  }
}

/*************************************************/
/* fetchpage_postprocessed()                     */
/*                                               */
/* Working end to fetchpage_preprocessed, which  */
/* will fetch the url in the url_buffer block.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to which the new URL refers;      */
/*                                               */
/*             1 to record the previous URL in   */
/*             the history list, else 0.         */
/*************************************************/

static _kernel_oserror * fetchpage_postprocessed(browser_data * b, int record)
{
  _kernel_oserror * e;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("fetchpage_postprocessed: Chunk CK_FURL set to %d\n",strlen(url_buffer) + 1);
  #endif

  e = memory_set_chunk_size(b, NULL, CK_FURL, strlen(url_buffer) + 1);
  if (e) return e;

  strcpy(b->urlfdata, url_buffer);

  if (
       record &&
       browser_current_url(b) &&
       strcmp(url_buffer, browser_current_url(b))
     )

     /* NULL means add browser_current_url. We're also ignoring any */
     /* errors from this call.                                      */

     history_record_local(b, NULL);

  /* Make sure the URL bar is updated with the current URL. */

  toolbars_update_url(b);

  /* Set the fetch status */

  b->fetch_status = BS_START;

//  /* Record the start of the fetch, for a parent browser window. */
//
//  if (!b->ancestor)
//  {
//    _swix(OS_SetVarVal,
//          _INR(0,4),
//
//          "Browse$CurrentFetch",
//          url_buffer,
//          strlen(url_buffer),
//          0,
//          4);
//  }

  /* Register event handlers to start off the new fetch */

  if (!b->fetch_handler) fetchpage_claim_nulls(b);

// (Re. the comments at the head of this code)... - No, we don't!
//
//  /* The extent settings may change or not; either way, want to */
//  /* start off scrolled to the top of the page.                 */
//
//  {
//    WimpGetWindowStateBlock s;
//
//    e = window_get_wimp_handle(0, b->self_id, &s.window_handle);
//    if (e) return e;
//
//    e = wimp_get_window_state(&s);
//    if (e) return e;
//
//    s.yscroll = s.xscroll = 0;
//    e = wimp_open_window((WimpOpenWindowBlock *) &s);
//  }

  return NULL;
}

/*************************************************/
/* fetchpage_postprocess_uri()                   */
/*                                               */
/* If the URI handler comes back with a          */
/* URI_MProcess message and we can handle the    */
/* URI it details, then that URI may be fetched  */
/* through this function - it is first copied    */
/* locally and then passed over to               */
/* fetchpage_postprocessed.                      */
/*                                               */
/* Parameters: Pointer to a browser_data         */
/*             struct relevant to the URI;       */
/*                                               */
/*             Pointer to the URI string;        */
/*                                               */
/*             1 to record the previous URL in   */
/*             the history list, else 0.         */
/*************************************************/

_kernel_oserror * fetchpage_postprocess_uri(browser_data * b, char * uri, int record)
{
  /* Reallocate URL buffer space */

  if (url_buffer)
  {
    #ifdef TRACE
      malloccount -= (strlen(url_buffer) + 128);
      if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
    #endif

    free(url_buffer);
  }

  url_buffer = malloc(strlen(uri) + 1);

  if (!url_buffer)
  {
    make_no_fetch_memory_error(14);
    return &erb;
  }

  /* Copy the URI over and fetch it */

  strcpy(url_buffer, uri);

  return fetchpage_postprocessed(b, record);
}

/*************************************************/
/* fetchpage_new()                               */
/*                                               */
/* Cancels any old fetch and starts a new one    */
/* the given URL.                                */
/*                                               */
/* The URL is copied to a malloc buffer before   */
/* being used, so the pointer to it can be from  */
/* pretty much anything (though beware of flex   */
/* blocks shifting over the actual function call */
/* boundary...).                                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to which the new URL refers;      */
/*                                               */
/*             Pointer to the new URL string;    */
/*                                               */
/*             1 to record the previous URL in   */
/*             the history list, else 0.         */
/*************************************************/

_kernel_oserror * fetchpage_new(browser_data * b, const char * url, int record)
{
  /* Don't proceed unless there's something to fetch */

  if (!url || !(*url)) return fetch_cancel(b);

  /* The URL may have been passed in from the 'tokens' buffer, */
  /* and fetch cancels etc. might corrupt it. So take a copy   */
  /* of it before proceeding further, if the URL didn't come   */
  /* from this buffer already...!                              */

  if (url != url_buffer)
  {
    if (url_buffer)
    {
      #ifdef TRACE
        malloccount -= (strlen(url_buffer) + 128);
        if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
      #endif

      free(url_buffer);
    }

    url_buffer = malloc(strlen(url) + 128);

    if (!url_buffer)
    {
      make_no_fetch_memory_error(7);
      return &erb;
    }

    #ifdef TRACE
      malloccount += (strlen(url) + 128);
      if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
    #endif

    strcpy(url_buffer, url);
  }
  #ifdef TRACE
    else Printf("WARNING, used same buffer in fetchpage_new\n");
  #endif

  urlutils_fix_url(url_buffer, strlen(url_buffer) + 128);

  return fetchpage_preprocessed(b, record);
}

/*************************************************/
/* fetchpage_new_add()                           */
/*                                               */
/* As fetchpage_new(), but takes a second        */
/* string, which is data to be concatenated onto */
/* the end of the given URL. This may be useful  */
/* for imagemaps or forms data. You may also     */
/* specify whether this URL is to be fetched in  */
/* a new browser window or not.                  */
/*                                               */
/* Restrictions as for fetchpage_new().          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to which the new URL refers;      */
/*                                               */
/*             Pointer to the new URL string;    */
/*                                               */
/*             1 to record the previous URL in   */
/*             the history list, else 0;         */
/*                                               */
/*             Pointer to the data to add onto   */
/*             the end of the URL string;        */
/*                                               */
/*             1 to fetch the URL in a new       */
/*             window, else 0.                   */
/*************************************************/

_kernel_oserror * fetchpage_new_add(browser_data * b, const char * url, int record, char * add, int new_window)
{
  /* Don't proceed unless there's something to fetch */

  if (!url) return fetch_cancel(b);

  /* The URL may have been passed in from the 'tokens' buffer, */
  /* and fetch cancels etc. might corrupt it. So take a copy   */
  /* of it before proceeding further, if the URL didn't come   */
  /* from this buffer already.                                 */

  if (url != url_buffer)
  {
    if (url_buffer)
    {
      #ifdef TRACE
        malloccount -= (strlen(url_buffer) + 128);
        if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
      #endif

      free(url_buffer);
    }

    url_buffer = malloc(strlen(url) + strlen(add) + 128);

    if (!url_buffer)
    {
      make_no_fetch_memory_error(7);
      return &erb;
    }

    #ifdef TRACE
      malloccount += (strlen(url) + strlen(add) + 128);
      if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
    #endif

    strcpy(url_buffer, url);
    strcat(url_buffer, add);
  }
  #ifdef TRACE
    else Printf("WARNING, used same buffer in fetchpage_new_add\n");
  #endif

  urlutils_fix_url(url_buffer, strlen(url_buffer) + 128);

  if (!new_window || b->full_screen) return fetchpage_preprocessed(b, record);
  else                               return windows_create_browser(url_buffer, NULL, NULL, NULL);
}

/*************************************************/
/* fetchpage_new_raw()                           */
/*                                               */
/* Starts a fetch of a given URL, without doing  */
/* anything to that URL at all except copying it */
/* over to a malloc buffer (to ensure it doesn't */
/* move around, as it would in a flex block).    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to which the new URL refers;      */
/*                                               */
/*             Pointer to the new URL string;    */
/*                                               */
/*             1 to record the previous URL in   */
/*             the history list, else 0.         */
/*************************************************/

_kernel_oserror * fetchpage_new_raw(browser_data * b, const char * url, int record)
{
  /* Don't proceed unless there's something to fetch */

  if (!url || !(*url)) return fetch_cancel(b);

  /* The URL may have been passed in from the 'tokens' buffer, */
  /* and fetch cancels etc. might corrupt it. So take a copy   */
  /* of it before proceeding further, if the URL didn't come   */
  /* from this buffer already...!                              */

  if (url != url_buffer)
  {
    if (url_buffer)
    {
      #ifdef TRACE
        malloccount -= (strlen(url_buffer) + 128);
        if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
      #endif

      free(url_buffer);
    }

    url_buffer = malloc(strlen(url) + 128);

    if (!url_buffer)
    {
      make_no_fetch_memory_error(7);
      return &erb;
    }

    #ifdef TRACE
      malloccount += (strlen(url) + 128);
      if (tl & (1u<<13)) Printf("** malloccount: %d\n",malloccount);
    #endif

    strcpy(url_buffer, url);
  }
  #ifdef TRACE
    else Printf("WARNING, used same buffer in fetchpage_new_raw\n");
  #endif

  return fetchpage_preprocessed(b, record);
}

/*************************************************/
/* fetchpage_claim_nulls()                       */
/*                                               */
/* Installs the relevant null event handlers so  */
/* that a fetch may proceed in the Desktop.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetch.            */
/*************************************************/

void fetchpage_claim_nulls(browser_data * b)
{
  /* Don't register the same handler twice... */

  if (!b->fetch_handler)
  {
    register_null_claimant(Wimp_ENull,(WimpEventHandler *) fetchpage_fetch,b);
    b->fetch_handler = 1;
  }

  /* Animations only apply to an ancestor window, not frames */

  if (b->ancestor) b = b->ancestor;

  /* If the 'drift' handler, to advance the animation to the */
  /* first frame and then stop, is active, remove it as the  */
  /* full-time animation handler is about to take over.      */

  if (b->anim_drift)
  {
    deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation_drift,b);
    b->anim_drift = 0;
  }

  /* Register the full time animation handler */

  if (!b->anim_handler)
  {
    register_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation,b);
    b->anim_handler = 1;
  }

  /* Record the usage of the animation handler. This will increment */
  /* once in the ancestor object for every child fetch, so that     */
  /* the animation handler can finally be released when all the     */
  /* child fetches have stopped.                                    */

  b->current_fetches++;
}

/*************************************************/
/* fetchpage_release_nulls()                     */
/*                                               */
/* Releases all relevant null event handlers     */
/* used for a fetch in the Desktop.              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetch.            */
/*************************************************/

void fetchpage_release_nulls(browser_data * b)
{
  /* Don't register the same handler twice... */

  if (b->fetch_handler)
  {
    deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) fetchpage_fetch,b);
    b->fetch_handler = 0;
  }

  /* Animations only apply to an ancestor window, not frames */

  if (b->ancestor) b = b->ancestor;

  /* Only remove the handlers if there are no fetches in any */
  /* children, etc. (see fetchpage_claim_nulls comments).    */

  b->current_fetches--;

  if (!b->current_fetches && choices.anim_drift != 2)
  {
    /* Deregister the full time animation handler */

    if (b->anim_handler)
    {
      deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation,b);
      b->anim_handler = 0;
    }

    /* If the choices say to do install the 'drift' handler to ensure */
    /* the animation finishes on the first frame, and that handler is */
    /* not already installed, install it.                             */

    if (choices.anim_drift && !b->anim_drift)
    {
      register_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation_drift,b);
      b->anim_drift = 1;
    }
  }
}

/*************************************************/