/* require.c: -*- C -*-  Load a package if it isn't already loaded. */

/*  Copyright (c) 1996 Brian J. Fox
    Author: Brian J. Fox (bfox@ai.mit.edu) Fri Aug 30 11:25:54 1996.

   This file is part of <Meta-HTML>(tm), a system for the rapid
   deployment of Internet and Intranet applications via the use
   of the Meta-HTML language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, 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
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include "language.h"
#include "symdump.h"

/* The name of the variable which holds the already found filenames. */
#define REQUIRE_LOADED "MHTML::REQUIRE-LOADED"

/* The name of the variable which holds the directories to search. */
#define REQUIRE_DIRECTORIES "MHTML::REQUIRE-DIRECTORIES"

/* The types of files that REQUIRE finds. */
#define require_TYPE_ANY     0
#define require_TYPE_INCLUDE 1
#define require_TYPE_LIB     2

/* Extension to filetype mapping. */
typedef struct
{
  char *ext;
  int filetype;
} EXT2FILETYPE;

static EXT2FILETYPE extensions[] =
{
  { ".mhtml",	require_TYPE_INCLUDE },
  { ".src",	require_TYPE_INCLUDE },
  { ".lib",	require_TYPE_LIB },
  { (char *)NULL, -1 }
};

static void pf_require (PFunArgs);

PFunDesc require_table[] =
{
  { "require",			0, 0, pf_require },
  { (char *)NULL,		0, 0, (PFunHandler *)NULL }
};

PACKAGE_INITIALIZER (initialize_require_functions)
void
initialize_require_functions (Package *package)
{
  register int i;
  Symbol *sym;

  for (i = 0; require_table[i].tag != (char *)NULL; i++)
    {
      sym = symbol_intern_in_package (package, require_table[i].tag);
      sym->type = symtype_FUNCTION;
      sym->values = (char **)(&require_table[i]);
    }
}

/* Return the full pathname of the file found in webspace at ROOT and LEAF,
   side-effecting FILETYPE based on the extension of that file.  For LEAFs
   which do not specify an extension, the list of possible extensions is
   used to determine the most recently written file.
   A newly consed string (or NULL) is returned; you must free it yourself.*/
static char *
require_search (char *root, char *leaf, int *filetype)
{
  register int i;
  static char *pathname = (char *)NULL;
  static int psize = 0;
  char *ext, *temp;
  int nsize = strlen (root) + strlen (leaf) + 1024;
  struct stat finfo;
  char *result = (char *)NULL;
  char *incpref = pagefunc_get_variable ("mhtml::include-prefix");
  char *relpref = pagefunc_get_variable ("mhtml::relative-prefix");

  if (nsize >= psize)
    pathname = (char *)xrealloc (pathname, psize = nsize);

  if ((root[0] == '.') && (root[1] == '\0'))
    {
      strcpy (pathname, leaf);
    }
  else
    {
      strcpy (pathname, root);
      strcat (pathname, "/");
      strcat (pathname, leaf);
    }

  temp = mhtml_canonicalize_file_name (pathname, incpref, relpref);

  /* If found and not a directory, return it immediately. */
  if ((stat (temp, &finfo) != -1) && (!S_ISDIR (finfo.st_mode)))
    {
      result = temp;
    }
  else
    {
      /* Not found under the specific name given.  If that name included
	 a file name extension, it is time to give up.  Otherwise, try all
	 of the extensions, and then take the one which was most recently
	 written. */
      char *slash = strrchr (temp, '/');

      if ((slash != (char *)NULL) &&
	  (ext = strrchr (slash, '.')) == (char *)NULL)
	{
	  time_t recent = 0;
	  int offset = strlen (temp);

	  temp = (char *)xrealloc (temp, 20 + offset);

	  for (i = 0; extensions[i].ext != (char *)NULL; i++)
	    {
	      strcpy (temp + offset, extensions[i].ext);

	      if (stat (temp, &finfo) != -1)
		{
		  if (finfo.st_mtime > recent)
		    {
		      if (result != (char *)NULL)
			free (result);

		      recent = finfo.st_mtime;
		      *filetype = extensions[i].filetype;
		      result = strdup (temp);
		    }
		}
	    }
	}
      /* Get rid of unneeded temporary. */
      free (temp);
    }

  return (result);
}

static int
require_already_loaded_p (char *pathname)
{
  char **loaded = symbol_get_values (REQUIRE_LOADED);
  int result = 0;

  if (loaded != (char **)NULL)
    {
      register int i;

      for (i = 0; loaded[i] != (char *)NULL; i++)
	if (strcmp (loaded[i], pathname) == 0)
	  {
	    result = 1;
	    break;
	  }
    }

  return (result);
}

static void
require_remember_pathname (char *pathname)
{
  Symbol *sym = symbol_intern (REQUIRE_LOADED);
  symbol_add_value (sym, pathname);
}

static void
pf_require (PFunArgs)
{
  register int i;
  char *arg = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char **directories = symbol_get_values (REQUIRE_DIRECTORIES);
  char *default_dirs[2] = { ".", (char *)NULL };
  char *pathname = (char *)NULL;
  char *prefix = (char *)NULL;
  int filetype = require_TYPE_ANY;

  if (directories == (char **)NULL)
    directories = &default_dirs[0];

  if (arg == (char *)NULL)
    return;

  if (*arg == '/')
    {
      pathname = require_search ("", arg, &filetype);
    }
  else
    {
      for (i = 0; (prefix = directories[i]) != (char *)NULL; i++)
	{
	  pathname = require_search (prefix, arg, &filetype);
	  if (pathname != (char *)NULL)
	    break;
	}
    }

  if (pathname == (char *)NULL)
    {
      page_debug ("<REQUIRE %s>: No file found in the search path", arg);
    }
  else if (!require_already_loaded_p (pathname))
    {
      switch (filetype)
	{
	case require_TYPE_ANY:
	case require_TYPE_INCLUDE:
	  {
	    PAGE *file_contents = page_read_template (pathname);

	    if (file_contents != (PAGE *)NULL)
	      {
		require_remember_pathname (pathname);

#if defined (NOT_BINARY_COMPATIBLE)
		bprintf_insert (page, start, "%s", file_contents->buffer);
#else
		/* Manually insert the file instead of letting bprintf
		   do it for us.  This is because the file could contain
		   binary data, and then file->bindex wouldn't necessarily
		   reflect the length of what was inserted. */
		if ((file_contents->bindex + page->bindex) >= page->bsize)
		  page->buffer = (char *)xrealloc
		  (page->buffer, (page->bsize += (file_contents->bindex + 100)));

		memmove (page->buffer + start + file_contents->bindex,
			 page->buffer + start, (page->bindex + 1) - start);

		memcpy (page->buffer + start, file_contents->buffer,
			file_contents->bindex);
		page->bindex += file_contents->bindex;
#endif /* BINARY_COMPATIBLE */
		page_free_page (file_contents);
	      }
	    else
	      page_debug ("<REQUIRE %s> Failed in read of %s", arg, pathname);
	  }
	  break;

	case require_TYPE_LIB:
	  {
	    int fd = os_open (pathname, O_RDONLY, 0666);
	    Package *pack;

	    if (fd > -1)
	      {
		require_remember_pathname (pathname);
		while ((pack = symbol_load_package (fd)) != (Package *)NULL);
		if (mhtml_user_keywords == (Package *)NULL)
		  mhtml_user_keywords = 
		    symbol_lookup_package ("*user-functions*");
		close (fd);
	      }
	    else
	      page_debug ("<REQUIRE %s> Failed in read of %s", arg, pathname);
	  }
	  break;

	default:
	  page_debug ("<REQUIRE %s> (%s) Don't understand files of type %d",
		      arg, pathname, filetype);
	  break;
	}
    }
  if (pathname) free (pathname);
  if (arg) free (arg);
}
