/* -*- mode: C; mode: fold; -*- */
/* slnspace.c  --- Name Space implementation */
/*
Copyright (C) 2004-2021,2022 John E. Davis

This file is part of the S-Lang Library.

The S-Lang Library 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.

The S-Lang 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
General Public License for more details.

You should have received a copy of the GNU 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 "slinclud.h"

#include "slang.h"
#include "_slang.h"

#define MAP_HASH_TO_NS_INDEX(hash,ns) (unsigned int) ((hash) % (ns->table_size))

static SLang_NameSpace_Type *Namespace_Tables;

SLang_NameSpace_Type *_pSLns_find_namespace (SLCONST char *name)
{
   SLang_NameSpace_Type *table_list;

   table_list = Namespace_Tables;
   while (table_list != NULL)
     {
	if ((table_list->namespace_name != NULL)
	    && (0 == strcmp (table_list->namespace_name, name)))
	  break;
	table_list = table_list->next;
     }
   return table_list;
}

/* This function deletes the namespace.  It is up to the caller to ensure that
 * the table has already been removed from the list.
 */
void _pSLns_deallocate_namespace (SLang_NameSpace_Type *ns)
{
   SLang_Name_Type **table;
   unsigned int table_size;
   unsigned int i;

   if (ns == NULL)
     return;

   table = ns->table;
   table_size = ns->table_size;

   for (i = 0; i < table_size; i++)
     {
	SLang_Name_Type *t = table [i];
   	while (t != NULL)
	  {
	     SLang_Name_Type *t1 = t->next;
	     SLang_free_slstring ((char *) t->name);
	     SLfree ((char *) t);
	     t = t1;
	  }
     }
   SLang_free_slstring ((char *) ns->name);
   SLang_free_slstring ((char *) ns->namespace_name);
   SLang_free_slstring ((char *) ns->private_name);
   SLfree ((char *)table);
   SLfree ((char *) ns);
}

/* This function does not insert the namespace into the list */
SLang_NameSpace_Type *_pSLns_allocate_namespace (SLFUTURE_CONST char *name, unsigned int size)
{
   SLang_Name_Type **nt;
   SLang_NameSpace_Type *ns;

   if (NULL == (name = SLang_create_slstring (name)))
     return NULL;

   if (NULL == (ns = (SLang_NameSpace_Type *)
		SLcalloc (sizeof (SLang_NameSpace_Type), 1)))
     {
	SLang_free_slstring ((char *) name);
	return NULL;
     }

   if (NULL == (nt = (SLang_Name_Type **) SLcalloc (sizeof (SLang_Name_Type *), size)))
     {
	SLang_free_slstring ((char *) name);
	SLfree ((char *)ns);
	return NULL;
     }

   ns->name = name;
   ns->table = nt;
   ns->table_size = size;

   return ns;
}

/* allocate a namespace and add it to the internal list */
SLang_NameSpace_Type *_pSLns_new_namespace (SLFUTURE_CONST char *name, unsigned int size)
{
   SLang_NameSpace_Type *table_list;
   static int num;
   char namebuf[64];

   if (name == NULL)
     {
	sprintf (namebuf, " *** internal ns <%d> *** ", num);
	name = namebuf;
	num++;
     }

   if (NULL == (table_list = _pSLns_allocate_namespace (name, size)))
     return NULL;

   table_list->next = Namespace_Tables;
   Namespace_Tables = table_list;

   return table_list;
}

/* Find the private namespace associated with the object (file, etc) given by
 * `name', and whose private namespace is given by `namespace_name'.  If
 * `namespace_name' is NULL, then it is anonymous.
 */
SLang_NameSpace_Type *_pSLns_get_private_namespace (SLFUTURE_CONST char *name, SLFUTURE_CONST char *namespace_name)
{
   SLang_NameSpace_Type *ns;

   if ((namespace_name != NULL)
       && (*namespace_name == 0))
     namespace_name = NULL;

   ns = Namespace_Tables;
   while (ns != NULL)
     {
	if ((ns->namespace_name != NULL)   /* a static namespace */
	    || (0 != strcmp (ns->name, name)))
	  {
	     ns = ns->next;
	     continue;
	  }

	/* at this point, the namespace is anonymous and is associated with
	 * the correct file (given by name).
	 */
	if (ns->private_name == NULL)
	  {
	     if (namespace_name == NULL)
	       return ns;

	     ns = ns->next;
	     continue;
	  }

	if ((namespace_name != NULL)
	    && (0 == strcmp (ns->private_name, namespace_name)))
	  return ns;

	ns = ns->next;
     }

   if (NULL == (ns = _pSLns_new_namespace (name, SLSTATIC_HASH_TABLE_SIZE)))
     return ns;

   if (namespace_name == NULL)
     return ns;

   if (NULL == (namespace_name = SLang_create_slstring (namespace_name)))
     {
	SLns_delete_namespace (ns);
	return NULL;
     }
   ns->private_name = namespace_name;
   return ns;
}

int _pSLns_set_namespace_name (SLang_NameSpace_Type *t, SLFUTURE_CONST char *name)
{
   SLang_NameSpace_Type *t1;

   t1 = _pSLns_find_namespace (name);
   if (t == t1)
     return 0;			       /* already has this name */

   if (t1 == NULL)
     t1 = t;

   if ((t != t1) || (*name == 0))
     {
	_pSLang_verror (SL_Namespace_Error, "Namespace \"%s\" already exists",
		      name);
	return -1;
     }

   if (t->namespace_name != NULL)
     {
	_pSLang_verror (SL_Namespace_Error, "An attempt was made to redefine namespace from \"%s\" to \"%s\"\n",
		      t->namespace_name, name);
	return -1;
     }

   if (NULL == (name = SLang_create_slstring (name)))
     return -1;

   SLang_free_slstring ((char *) t->namespace_name);   /* NULL ok */
   t->namespace_name = name;

   return 0;
}

SLang_Array_Type *_pSLnspace_apropos (SLang_NameSpace_Type *ns, SLFUTURE_CONST char *pat, unsigned int what)
{
   SLang_Array_Type *at;
   unsigned int table_size;
   SLang_Name_Type *t, **table;
   SLindex_Type num_matches;
   unsigned int i;
   SLRegexp_Type *reg;
   unsigned int two;

   at = NULL;

   if ((ns == NULL)
       || ((table = ns->table) == NULL))
     return NULL;

   if (NULL == (reg = SLregexp_compile (pat, 0)))
     {
	_pSLang_verror (SL_Parse_Error, "Invalid regular expression: %s", pat);
	return NULL;
     }

   table_size = ns->table_size;

   two = 2;
   while (two != 0)
     {
	two--;

	num_matches = 0;
	for (i = 0; i < table_size; i++)
	  {
	     t = table[i];
	     while (t != NULL)
	       {
		  unsigned int flags;
		  SLFUTURE_CONST char *name = t->name;

		  switch (t->name_type)
		    {
		     case SLANG_GVARIABLE:
		       flags = 8;
		       break;

		     case SLANG_ICONSTANT:
		     case SLANG_DCONSTANT:
		     case SLANG_FCONSTANT:
		     case SLANG_LLCONSTANT:
		     case SLANG_HCONSTANT:
		     case SLANG_LCONSTANT:
		     case SLANG_RVARIABLE:
		     case SLANG_IVARIABLE:
		       flags = 4;
		       break;

		     case SLANG_INTRINSIC:
		     case SLANG_MATH_UNARY:
		     case SLANG_APP_UNARY:
		     case SLANG_ARITH_UNARY:
		     case SLANG_ARITH_BINARY:
		       flags = 1;
		       break;

		     case SLANG_FUNCTION:
		       flags = 2;
		       break;

		     default:
		       flags = 0;
		       break;
		    }

		  if ((flags & what)
		      && (NULL != SLregexp_match (reg, name, strlen (name))))
		    {
		       if (at != NULL)
			 {
			    if (-1 == SLang_set_array_element (at, &num_matches, (VOID_STAR)&name))
			      goto return_error;
			 }
		       num_matches++;
		    }
		  t = t->next;
	       }
	  }

	if (at == NULL)
	  {
	     at = SLang_create_array (SLANG_STRING_TYPE, 0, NULL, &num_matches, 1);
	     if (at == NULL)
	       goto return_error;
	  }
     }

   if (reg != NULL)
     SLregexp_free (reg);
   return at;

   return_error:
   SLregexp_free (reg);
   SLang_free_array (at);
   return NULL;
}

SLang_NameSpace_Type *_pSLns_create_namespace2 (SLFUTURE_CONST char *name, SLFUTURE_CONST char *namespace_name)
{
   SLang_NameSpace_Type *ns;

   if (namespace_name == NULL)
     namespace_name = "Global";

   ns = _pSLns_find_namespace (namespace_name);
   if (ns != NULL)
     return ns;

   if (NULL == (ns = _pSLns_new_namespace (name, SLSTATIC_HASH_TABLE_SIZE)))
     return NULL;

   if (-1 == _pSLns_set_namespace_name (ns, namespace_name))
     {
	SLns_delete_namespace (ns);
	return NULL;
     }

   return ns;
}

int _pSLns_check_name (SLFUTURE_CONST char *namespace_name)
{
   if ((namespace_name == NULL) || (*namespace_name == 0))
     return 0;

   return _pSLcheck_identifier_syntax (namespace_name);
}

SLang_NameSpace_Type *SLns_create_namespace (SLFUTURE_CONST char *namespace_name)
{
   if (-1 == _pSLns_check_name (namespace_name))
     return NULL;

   return _pSLns_create_namespace2 (NULL, namespace_name);
}

void SLns_delete_namespace (SLang_NameSpace_Type *ns)
{
   if (ns == NULL)
     return;

   if (ns == Namespace_Tables)
     Namespace_Tables = ns->next;
   else
     {
	SLang_NameSpace_Type *prev = Namespace_Tables;
	while (prev != NULL)
	  {
	     if (prev->next != ns)
	       {
		  prev = prev->next;
		  continue;
	       }
	     prev->next = ns->next;
	     break;
	  }
     }
   _pSLns_deallocate_namespace (ns);
}

SLang_Array_Type *_pSLns_list_namespaces (void)
{
   SLang_NameSpace_Type *table_list;
   SLang_Array_Type *at;
   SLindex_Type num, i;

   num = 0;
   table_list = Namespace_Tables;
   while (table_list != NULL)
     {
	if (table_list->namespace_name != NULL)
	  num++;
	table_list = table_list->next;
     }
   at = SLang_create_array (SLANG_STRING_TYPE, 0, NULL, &num, 1);
   if (at == NULL)
     return NULL;

   table_list = Namespace_Tables;
   i = 0;
   while ((table_list != NULL)
	  && (i < num))
     {
	if (table_list->namespace_name != NULL)
	  {
	     SLCONST char *name = table_list->namespace_name;
	     if (-1 == SLang_set_array_element (at, &i, (VOID_STAR)&name))
	       {
		  SLang_free_array (at);
		  return NULL;
	       }
	     i++;
	  }
	table_list = table_list->next;
     }
   return at;
}

SLang_Name_Type *
  _pSLns_locate_hashed_name (SLang_NameSpace_Type *ns, SLCONST char *name, unsigned long hash)
{
   SLang_Name_Type *t;
   char ch;

   t = ns->table [MAP_HASH_TO_NS_INDEX(hash,ns)];
   ch = *name++;

   while (t != NULL)
     {
	if ((ch == t->name[0])
	    && (0 == strcmp (t->name + 1, name)))
	  break;

	t = t->next;
     }

   return t;
}

/* It is up to the caller to make sure that the name is not already in the table */
int _pSLns_add_hashed_name (SLang_NameSpace_Type *ns, SLang_Name_Type *nt, unsigned long hash)
{
   hash = MAP_HASH_TO_NS_INDEX(hash,ns);
   nt->next = ns->table [(unsigned int)hash];
   ns->table [(unsigned int) hash] = nt;

   return 0;
}

SLang_NameSpace_Type *_pSLns_find_object_namespace (SLang_Name_Type *nt)
{
   SLang_NameSpace_Type *ns;
   SLCONST char *name;
   unsigned long hash;

   if (nt == NULL)
     return NULL;

   name = nt->name;
   hash = SLcompute_string_hash (name);

   ns = Namespace_Tables;
   while (ns != NULL)
     {
	SLang_Name_Type *t = ns->table [MAP_HASH_TO_NS_INDEX(hash,ns)];
	while (t != NULL)
	  {
	     if (t == nt)
	       return ns;

	     t = t->next;
	  }
	ns = ns->next;
     }

   return NULL;
}

SLang_Name_Type *_pSLns_locate_name (SLang_NameSpace_Type *ns, SLCONST char *name)
{
   return _pSLns_locate_hashed_name (ns, name, SLcompute_string_hash (name));
}

static void delete_namespace_objects (SLang_NameSpace_Type *ns)
{
   SLang_Name_Type **table = ns->table;
   unsigned int i, table_size = ns->table_size;

   for (i = 0; i < table_size; i++)
     {
	SLang_Name_Type *t = table[i];
	while (t != NULL)
	  {
	     SLang_Name_Type *t1 = t->next;
	     switch (t->name_type)
	       {
		case SLANG_PVARIABLE:
		case SLANG_GVARIABLE:
		  SLang_free_object (&((SLang_Global_Var_Type *)t)->obj);
		  break;

		case SLANG_PFUNCTION:
		case SLANG_FUNCTION:
		  SLang_free_function (t);
		  break;

		case SLANG_ICONSTANT:
		case SLANG_DCONSTANT:
		case SLANG_FCONSTANT:
		case SLANG_LLCONSTANT:
		case SLANG_HCONSTANT:
		case SLANG_LCONSTANT:
		case SLANG_RVARIABLE:
		case SLANG_IVARIABLE:
		case SLANG_INTRINSIC:
		case SLANG_MATH_UNARY:
		case SLANG_APP_UNARY:
		case SLANG_ARITH_UNARY:
		case SLANG_ARITH_BINARY:
		default:
		  break;
	       }
	     SLang_free_slstring (t->name);
	     t = t1;
	  }
     }
}

/* This is only called at exit.  Until version 3, I cannot delete everything
 * since the namespace contains a mixture of statically allocated table
 * and dynamically allocated ones.  The only way to tell the difference is
 * to change the API by adding an additional field to SLang_Name_Type object.
 * So, here I delete what I can safely do, but to avoid leak checkers complaining
 * about false leaks, the Namespace_Tables pointer will be left as is.
 */
void _pSLns_delete_namespaces (void)
{
   SLang_NameSpace_Type *ns;

   ns = Namespace_Tables;
   while (ns != NULL)
     {
	SLang_NameSpace_Type *next = ns->next;
	delete_namespace_objects (ns);
	SLang_free_slstring ((char *) ns->name);
	SLang_free_slstring ((char *) ns->namespace_name);
	SLang_free_slstring ((char *) ns->private_name);
	/* SLfree ((char *)ns->table); v3*/
	/* SLfree ((char *) ns); v3 */
	ns = next;
     }
   /* Namespace_Tables = NULL; v3 */
}

