/* $Id: elt-dirs 3.2.0.2 1998/07/26 10:48:53 stoklund Exp stoklund $ */
/* elt-dirs.c: Translate a path element to its corresponding director{y,ies}.

Copyright (C) 1993, 94, 95, 96, 97 Karl Berry.
Copyright (C) 1998 Jakob Stoklund Olesen.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#include "kpathsea:config.h"

/* This file has been partly rewritten for RISC OS,
   in particular the do_subdir() function. */

#include "kpathsea:c-pathch.h"
#include "kpathsea:expand.h"
#include "kpathsea:fn.h"
#include "kpathsea:pathsearch.h"

#include "OS:osgbpb.h"

/* To avoid giving prototypes for all the routines and then their real
   definitions, we give all the subroutines first.  The entry point is
   the last routine in the file.  */

/* Make a copy of DIR (unless it's null) and save it in L.  Ensure that
   DIR ends with a DIR_SEP for the benefit of later searches.  */

static void
dir_list_add P2C(str_llist_type *, l,  const_string, dir)
{
  char last_char = dir[strlen (dir) - 1];
  string saved_dir
    = IS_DIR_SEP (last_char) || IS_DEVICE_SEP (last_char)
      ? xstrdup (dir)
      : concat (dir, DIR_SEP_STRING);
  
  str_llist_add (l, saved_dir);
}


/* If DIR is a directory, add it to the list L.  */

static void
checked_dir_list_add P2C(str_llist_type *, l,  const_string, dir)
{
  if (dir_p (dir))
    dir_list_add (l, dir);
}

/* The cache.  Typically, several paths have the same element; for
   example, /usr/local/lib/texmf/fonts//.  We don't want to compute the
   expansion of such a thing more than once.  Even though we also cache
   the dir_links call, that's not enough -- without this path element
   caching as well, the execution time doubles.  */

typedef struct
{
  const_string key;
  str_llist_type *value;
} cache_entry;

static cache_entry *the_cache = NULL;
static unsigned cache_length = 0;


/* Associate KEY with VALUE.  We implement the cache as a simple linear
   list, since it's unlikely to ever be more than a dozen or so elements
   long.  We don't bother to check here if PATH has already been saved;
   we always add it to our list.  We copy KEY but not VALUE; not sure
   that's right, but it seems to be all that's needed.  */

static void
cache P2C(const_string, key,  str_llist_type *, value)
{
  cache_length++;
  XRETALLOC (the_cache, cache_length, cache_entry);
  the_cache[cache_length - 1].key = xstrdup (key);
  the_cache[cache_length - 1].value = value;
}


/* To retrieve, just check the list in order.  */

static str_llist_type *
cached P1C(const_string, key)
{
  unsigned p;
  
  for (p = 0; p < cache_length; p++)
    {
      if (FILESTRCASEEQ (the_cache[p].key, key))
        return the_cache[p].value;
    }
  
  return NULL;
}

/* Handle the magic path constructs.  */

/* Declare recursively called routine.  */
static void expand_elt P3H(str_llist_type *, const_string, unsigned);


/* POST is a pointer into the original element (which may no longer be
   ELT) to just after the doubled DIR_SEP, perhaps to the null.  Append
   subdirectories of ELT (up to ELT_LENGTH, which must be a /) to
   STR_LIST_PTR.  */

/* The size of the buffer passed to OS_GBPB */
#define osgbpb_bufsize 64

/* do_subdir returns flags telling which files it holds */
enum subdir_flags
  {
    subdir_files = 1,		/* files without '/' */
    subdir_slash = 2,		/* files with '/' */
    subdir_dot = 4		/* subdirs with normal files */
  };

static enum subdir_flags
do_subdir 
P4C (str_llist_type *, str_list_ptr, const_string, elt,
     unsigned, elt_length, const_string, post)
{
  osgbpb_info gbpb_buffer[osgbpb_bufsize], *object = gbpb_buffer;
  os_error *erp;
  int context, count;
  fn_type name;
  enum subdir_flags flags = 0;
  boolean already_added = false;	/* have we called dir_list_add? */

  name = fn_copy0 (elt, elt_length);

  assert (IS_DIR_SEP (elt[elt_length - 1])
	  || IS_DEVICE_SEP (elt[elt_length - 1]));

  /* Remove trailing . for OS_GBPB */
  if (IS_DIR_SEP (FN_STRING (name)[elt_length - 1]))
    FN_STRING (name)[elt_length - 1] = '\0';

  /* If we can't read entries it, quit.  */
  erp = xosgbpb_dir_entries_info (FN_STRING (name),
				  (osgbpb_info_list *) gbpb_buffer,
				  osgbpb_bufsize,
				  0, sizeof (gbpb_buffer), NULL,
				  &count, &context);
  if (erp != NULL)
    {
      fn_free (&name);
      return flags;
    }

  if (FN_STRING (name)[elt_length - 1] == '\0')
    FN_STRING (name)[elt_length - 1] = DIR_SEP;

  /* Try to match the rest before scanning */
  if (*post)
    {
      /* If we do have something to match, see if it exists.  For example,
         POST might be `pk/ljfour', and they might have a directory
         `$TEXMF/fonts/pk/ljfour' that we should find.  */
      fn_str_grow (&name, post);
      expand_elt (str_list_ptr, FN_STRING (name), elt_length);
      fn_shrink_to (&name, elt_length);
    }

  while (count > 0)
    {
      switch (object->obj_type)
	{
	case 1:		/* a file */
	  if (flags != (subdir_files | subdir_slash))
	    flags |= strchr (object->name, EXT_SEP) ?
	      subdir_slash : subdir_files;
	  break;
	case 2:
	case 3:		/* a directory or image file */
	  /* Directories starting with / are hidden */
	  if (object->name[0] == EXT_SEP)
	    break;
	  /* Add this directory before the subdirs unless we already did so */
	  if (!already_added && *post == '\0')
	    {
	      dir_list_add (str_list_ptr, FN_STRING (name));
	      already_added = true;
	    }
	  /* Construct the potential subdirectory name.  */
	  fn_str_grow (&name, object->name);
	  fn_str_grow (&name, DIR_SEP_STRING);
	  /* All criteria are met; find subdirectories.  */
	  if (do_subdir (str_list_ptr, FN_STRING (name),
			 elt_length + strlen (object->name) + 1, post)
	      & subdir_files)
	    flags |= subdir_dot;
	  /* Remove the directory entry we just checked from `name'.  */
	  fn_shrink_to (&name, elt_length);
	}
      /* Advance object to next loaded */
      object = (osgbpb_info *) ((char *) (object + 1)
				+ (strlen (object->name) & ~3));
      /* countdown and refill if necessary */
      if (--count == 0 && context != -1)
	{
	  if (IS_DIR_SEP (FN_STRING (name)[elt_length - 1]))
	    FN_STRING (name)[elt_length - 1] = '\0';
	  erp = xosgbpb_dir_entries_info (FN_STRING (name),
					  (osgbpb_info_list *) gbpb_buffer,
					  osgbpb_bufsize,
					context, sizeof (gbpb_buffer), NULL,
					  &count, &context);
	  if (erp != NULL)
	    count = 0;		/* stop on error */
	  if (FN_STRING (name)[elt_length - 1] == '\0')
	    FN_STRING (name)[elt_length - 1] = DIR_SEP;
	}
    }
  /* Add this directory if it has slash-files or subdirectories with files */
  if (!already_added && *post == '\0'
      && (flags & (subdir_slash | subdir_dot)))
    dir_list_add (str_list_ptr, FN_STRING (name));

  fn_free (&name);
  return flags;
}

/* Assume ELT is non-empty and non-NULL.  Return list of corresponding
   directories (with no terminating NULL entry) in STR_LIST_PTR.  Start
   looking for magic constructs at START.  */

static void
expand_elt P3C(str_llist_type *, str_list_ptr,  const_string, elt,
               unsigned, start)
{
  const_string dir = elt + start, post;
  
  while (*dir != 0)
    {
      if (IS_DIR_SEP (*dir) || IS_DEVICE_SEP (*dir))
        {
          /* If two or more consecutive /'s, find subdirectories.  */
          if (IS_DIR_SEP (dir[1]))
            {
	      for (post = dir + 1; IS_DIR_SEP (*post); post++) ;
              do_subdir (str_list_ptr, elt, dir - elt + 1, post);
	      return;
            }

          /* No special stuff at this slash.  Keep going.  */
        }
      
      dir++;
    }
  
  /* When we reach the end of ELT, it will be a normal filename.  */
  checked_dir_list_add (str_list_ptr, elt);
}

/* Here is the entry point.  Returns directory list for ELT.  */

str_llist_type *
kpse_element_dirs P1C(const_string, elt)
{
  str_llist_type *ret;

  /* If given nothing, return nothing.  */
  if (!elt || !*elt)
    return NULL;

  /* If we've already cached the answer for ELT, return it.  */
  ret = cached (elt);
  if (ret)
    return ret;

  /* We're going to have a real directory list to return.  */
  ret = XTALLOC1 (str_llist_type);
  *ret = NULL;

  /* We handle the hard case in a subroutine.  */
  expand_elt (ret, elt, 0);

  /* Remember the directory list we just found, in case future calls are
     made with the same ELT.  */
  cache (elt, ret);

#ifdef KPSE_DEBUG
  if (KPSE_DEBUG_P (KPSE_DEBUG_EXPAND))
    {
      DEBUGF1 ("path element %s =>", elt);
      if (ret)
        {
          str_llist_elt_type *e;
          for (e = *ret; e; e = STR_LLIST_NEXT (*e))
            fprintf (stderr, " %s", STR_LLIST (*e));
        }
      putc ('\n', stderr);
      fflush (stderr);
    }
#endif /* KPSE_DEBUG */

  return ret;
}

#ifdef TEST

void
print_element_dirs (const_string elt)
{
  str_llist_type *dirs;
  
  printf ("Directories of %s:\n", elt ? elt : "(nil)");
  fflush (stdout);
  
  dirs = kpse_element_dirs (elt);
  
  if (!dirs)
    printf ("(nil)\n");
  else
    {
      str_llist_elt_type *dir;
      for (dir = *dirs; dir; dir = STR_LLIST_NEXT (*dir))
        {
          string d = STR_LLIST (*dir);
          printf ("    %s\n", *d ? d : "`'\n");
        }
    }
}

int
main ()
{
  /* DEBUG_SET (DEBUG_STAT); */
  /* All lists end with NULL.  */
  print_element_dirs (NULL);	/* */
  print_element_dirs ("");	/* ./ */
  print_element_dirs ("k");	/* */
  print_element_dirs ("/k");	/* */
  print_element_dirs ("@..");	/* ./ ./archive/ */
  print_element_dirs ("..archive");	/* ./ ./archive/ */
  print_element_dirs ("texmf:fonts.."); /* lots */
  print_element_dirs ("texmf:fonts..public.."); /* just one */
  print_element_dirs ("texmf:");	/* texmf: */
  print_element_dirs ("texmf:.");	/* texmf: and all subdirs */
  print_element_dirs ("texmf:tex..local");      /* lots */
  return 0;
}

#endif /* TEST */


/*
Local variables:
test-compile-command: "gcc -g -I. -I.. -DTEST elt-dirs.c kpathsea.a"
End:
*/
