   /*******************************************************/
   /*      "C" Language Integrated Production System      */
   /*                                                     */
   /*             CLIPS Version 6.30  02/05/15            */
   /*                                                     */
   /*                ENVIRONMENT MODULE                   */
   /*******************************************************/

/*************************************************************/
/* Purpose: Routines for supporting multiple environments.   */
/*                                                           */
/* Principal Programmer(s):                                  */
/*      Gary D. Riley                                        */
/*                                                           */
/* Revision History:                                         */
/*                                                           */
/*      6.24: Added code to CreateEnvironment to free        */
/*            already allocated data if one of the malloc    */
/*            calls fail.                                    */
/*                                                           */
/*            Modified AllocateEnvironmentData to print a    */
/*            message if it was unable to allocate memory.   */
/*                                                           */
/*            Renamed BOOLEAN macro type to intBool.         */
/*                                                           */
/*            Added CreateRuntimeEnvironment function.       */
/*                                                           */
/*            Added support for context information when an  */
/*            environment is created (i.e a pointer from the */
/*            CLIPS environment to its parent environment).  */
/*                                                           */
/*      6.30: Added support for passing context information  */ 
/*            to user defined functions and callback         */
/*            functions.                                     */
/*                                                           */
/*            Support for hashing EXTERNAL_ADDRESS data      */
/*            type.                                          */
/*                                                           */
/*            Added const qualifiers to remove C++           */
/*            deprecation warnings.                          */
/*                                                           */
/*            Removed deallocating message parameter from    */
/*            EnvReleaseMem.                                 */
/*                                                           */
/*            Removed support for BLOCK_MEMORY.              */
/*                                                           */
/*************************************************************/

#define _ENVRNMNT_SOURCE_

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

#include "setup.h"

#include "memalloc.h"
#include "prntutil.h"
#include "router.h"
#include "engine.h"
#include "sysdep.h"
#include "utility.h"

#include "envrnmnt.h"

#define SIZE_ENVIRONMENT_HASH  131

/***************************************/
/* LOCAL INTERNAL FUNCTION DEFINITIONS */
/***************************************/

#if ALLOW_ENVIRONMENT_GLOBALS
   static void                    AddHashedEnvironment(struct environmentData *);
   static struct environmentData *FindEnvironment(unsigned long);
   static intBool                 RemoveHashedEnvironment(struct environmentData *);
   static void                    InitializeEnvironmentHashTable(void);
#endif
   static void                    RemoveEnvironmentCleanupFunctions(struct environmentData *);
   static void                   *CreateEnvironmentDriver(struct symbolHashNode **,struct floatHashNode **,
                                                          struct integerHashNode **,struct bitMapHashNode **,
                                                          struct externalAddressHashNode **);

/***************************************/
/* LOCAL INTERNAL VARIABLE DEFINITIONS */
/***************************************/

#if ALLOW_ENVIRONMENT_GLOBALS
   static unsigned long              NextEnvironmentIndex = 0;
   static struct environmentData   **EnvironmentHashTable = NULL;
   static struct environmentData    *CurrentEnvironment = NULL;
#endif

/*******************************************************/
/* AllocateEnvironmentData: Allocates environment data */
/*    for the specified environment data record.       */
/*******************************************************/
globle intBool AllocateEnvironmentData(
  void *vtheEnvironment,
  unsigned int position,
  unsigned long size,
  void (*cleanupFunction)(void *))
  {      
   struct environmentData *theEnvironment = (struct environmentData *) vtheEnvironment;

   /*===========================================*/
   /* Environment data can't be of length zero. */
   /*===========================================*/
   
   if (size <= 0)
     {
      printf("\n[ENVRNMNT1] Environment data position %d allocated with size of 0 or less.\n",position);      
      return(FALSE);
     }
     
   /*================================================================*/
   /* Check to see if the data position exceeds the maximum allowed. */
   /*================================================================*/
   
   if (position >= MAXIMUM_ENVIRONMENT_POSITIONS)
     {
      printf("\n[ENVRNMNT2] Environment data position %d exceeds the maximum allowed.\n",position);      
      return(FALSE);
     }
     
   /*============================================================*/
   /* Check if the environment data has already been registered. */
   /*============================================================*/
   
   if (theEnvironment->theData[position] != NULL)
     {
      printf("\n[ENVRNMNT3] Environment data position %d already allocated.\n",position);      
      return(FALSE);
     }
     
   /*====================*/
   /* Allocate the data. */
   /*====================*/
   
   theEnvironment->theData[position] = malloc(size);
   if (theEnvironment->theData[position] == NULL)
     {
      printf("\n[ENVRNMNT4] Environment data position %d could not be allocated.\n",position);      
      return(FALSE);
     }
   
   memset(theEnvironment->theData[position],0,size);
   
   /*=============================*/
   /* Store the cleanup function. */
   /*=============================*/
   
   theEnvironment->cleanupFunctions[position] = cleanupFunction;
   
   /*===============================*/
   /* Data successfully registered. */
   /*===============================*/
   
   return(TRUE);
  }

/***************************************************************/
/* DeallocateEnvironmentData: Deallocates all environments     */
/*   stored in the environment hash table and then deallocates */
/*   the environment hash table.                               */
/***************************************************************/
globle intBool DeallocateEnvironmentData()
  {
#if ALLOW_ENVIRONMENT_GLOBALS
   struct environmentData *theEnvironment, *nextEnvironment;
   int i, rv = TRUE;
   
   for (i = 0; i < SIZE_ENVIRONMENT_HASH; i++)
     {
      for (theEnvironment = EnvironmentHashTable[i];
           theEnvironment != NULL;
          )
        {
         nextEnvironment = theEnvironment->next;
         
         if (! DestroyEnvironment(theEnvironment))
           { rv = FALSE; }
         
         theEnvironment = nextEnvironment;
        }
     }

   free(EnvironmentHashTable);
   
   return(rv);
#else
   return(FALSE);
#endif
  }

#if ALLOW_ENVIRONMENT_GLOBALS
/*********************************************************/
/* InitializeEnvironmentHashTable: Initializes the table */
/*   entries in the environment hash table to NULL.      */
/*********************************************************/
static void InitializeEnvironmentHashTable()
  {
   int i;
    
   if (EnvironmentHashTable != NULL)
     { return; }

   EnvironmentHashTable = (struct environmentData **)
                          malloc(sizeof (struct environmentData *) * SIZE_ENVIRONMENT_HASH);

   if (EnvironmentHashTable == NULL)
     {
      printf("\n[ENVRNMNT4] Unable to initialize environment hash table.\n");      
      return;
     }

   for (i = 0; i < SIZE_ENVIRONMENT_HASH; i++) EnvironmentHashTable[i] = NULL;
  }

/*********************************************/
/* AddHashedEnvironment: Adds an environment */
/*    entry to the environment hash table.   */
/*********************************************/
static void AddHashedEnvironment(
  struct environmentData *theEnvironment)
  {
   struct environmentData *temp;
   unsigned long hashValue;
   
   if (EnvironmentHashTable == NULL)
     { InitializeEnvironmentHashTable(); }
     
   hashValue = theEnvironment->environmentIndex % SIZE_ENVIRONMENT_HASH;

   temp = EnvironmentHashTable[hashValue];
   EnvironmentHashTable[hashValue] = theEnvironment;
   theEnvironment->next = temp;
  }
  
/***************************************************/
/* RemoveHashedEnvironment: Removes an environment */
/*   entry from the environment hash table.        */
/***************************************************/
static intBool RemoveHashedEnvironment(
  struct environmentData *theEnvironment)
  {
   unsigned long hashValue;
   struct environmentData *hptr, *prev;

   hashValue = theEnvironment->environmentIndex % SIZE_ENVIRONMENT_HASH;

   for (hptr = EnvironmentHashTable[hashValue], prev = NULL;
        hptr != NULL;
        hptr = hptr->next)
     {
      if (hptr == theEnvironment)
        {
         if (prev == NULL)
           {
            EnvironmentHashTable[hashValue] = hptr->next;
            return(TRUE);
           }
         else
           {
            prev->next = hptr->next;
            return(TRUE);
           }
        }
      prev = hptr;
     }

   return(FALSE);
  }

/**********************************************************/
/* FindEnvironment: Determines if a specified environment */
/*   index has an entry in the environment hash table.    */
/**********************************************************/
static struct environmentData *FindEnvironment(
  unsigned long environmentIndex)
  {
   struct environmentData *theEnvironment;
   unsigned long hashValue;
   
   hashValue = environmentIndex % SIZE_ENVIRONMENT_HASH;
   
   for (theEnvironment = EnvironmentHashTable[hashValue];
        theEnvironment != NULL;
        theEnvironment = theEnvironment->next)
     {
      if (theEnvironment->environmentIndex == environmentIndex)
        { return(theEnvironment); }
     }

   return(NULL);
  }
#endif

/************************************************************/
/* CreateEnvironment: Creates an environment data structure */
/*   and initializes its content to zero/null.              */
/************************************************************/
globle void *CreateEnvironment()
  {
   return CreateEnvironmentDriver(NULL,NULL,NULL,NULL,NULL);
  }

/**********************************************************/
/* CreateRuntimeEnvironment: Creates an environment data  */
/*   structure and initializes its content to zero/null.  */
/**********************************************************/
globle void *CreateRuntimeEnvironment(
  struct symbolHashNode **symbolTable,
  struct floatHashNode **floatTable,
  struct integerHashNode **integerTable,
  struct bitMapHashNode **bitmapTable)
  {
   return CreateEnvironmentDriver(symbolTable,floatTable,integerTable,bitmapTable,NULL);
  }
  
/*********************************************************/
/* CreateEnvironmentDriver: Creates an environment data  */
/*   structure and initializes its content to zero/null. */
/*********************************************************/
globle void *CreateEnvironmentDriver(
  struct symbolHashNode **symbolTable,
  struct floatHashNode **floatTable,
  struct integerHashNode **integerTable,
  struct bitMapHashNode **bitmapTable,
  struct externalAddressHashNode **externalAddressTable)
  {
   struct environmentData *theEnvironment;
   void *theData;
   
   theEnvironment = (struct environmentData *) malloc(sizeof(struct environmentData));
  
   if (theEnvironment == NULL)
     {
      printf("\n[ENVRNMNT5] Unable to create new environment.\n");
      return(NULL);
     }

   theData = malloc(sizeof(void *) * MAXIMUM_ENVIRONMENT_POSITIONS);
   
   if (theData == NULL)
     {
      free(theEnvironment);
      printf("\n[ENVRNMNT6] Unable to create environment data.\n");
      return(NULL);
     }

   memset(theData,0,sizeof(void *) * MAXIMUM_ENVIRONMENT_POSITIONS);

   theEnvironment->initialized = FALSE;
   theEnvironment->theData = (void **) theData;
   theEnvironment->next = NULL;
   theEnvironment->listOfCleanupEnvironmentFunctions = NULL;
#if ALLOW_ENVIRONMENT_GLOBALS
   theEnvironment->environmentIndex = NextEnvironmentIndex++;
#else
   theEnvironment->environmentIndex = 0;
#endif
   theEnvironment->context = NULL;
   theEnvironment->routerContext = NULL;
   theEnvironment->functionContext = NULL;
   theEnvironment->callbackContext = NULL;

   /*=============================================*/
   /* Allocate storage for the cleanup functions. */
   /*=============================================*/

   theData = malloc(sizeof(void (*)(struct environmentData *)) * MAXIMUM_ENVIRONMENT_POSITIONS);
   
   if (theData == NULL)
     {
      free(theEnvironment->theData);
      free(theEnvironment);
      printf("\n[ENVRNMNT7] Unable to create environment data.\n");
      return(NULL);
     }

   memset(theData,0,sizeof(void (*)(struct environmentData *)) * MAXIMUM_ENVIRONMENT_POSITIONS);
   theEnvironment->cleanupFunctions = (void (**)(void *))theData;

#if ALLOW_ENVIRONMENT_GLOBALS
   AddHashedEnvironment(theEnvironment);
   CurrentEnvironment = theEnvironment;
#endif

   EnvInitializeEnvironment(theEnvironment,symbolTable,floatTable,integerTable,bitmapTable,externalAddressTable);

   return(theEnvironment);
  }

#if ALLOW_ENVIRONMENT_GLOBALS
/*******************************************/
/* SetCurrentEnvironment: Sets the current */
/*   environment to the one specified.     */
/*******************************************/
globle void SetCurrentEnvironment(
  void *theEnvironment)
  {
   CurrentEnvironment = (struct environmentData *) theEnvironment;
  }
  
/**************************************************/
/* SetCurrentEnvironmentByIndex: Sets the current */
/*   environment to the one having the specified  */
/*   environment index.                           */
/**************************************************/
globle intBool SetCurrentEnvironmentByIndex(
  unsigned long environmentIndex)
  {
   struct environmentData *theEnvironment;

   theEnvironment = FindEnvironment(environmentIndex);
   
   if (theEnvironment == NULL)
     { return(FALSE); }
     
   SetCurrentEnvironment(theEnvironment);
   
   return(TRUE);
  }     
   
/**************************************************/
/* GetEnvironmentByIndex: Returns the environment */
/*   having the specified environment index.      */
/**************************************************/
globle void *GetEnvironmentByIndex(
  unsigned long environmentIndex)
  {
   struct environmentData *theEnvironment;

   theEnvironment = FindEnvironment(environmentIndex);
      
   return(theEnvironment);
  }     
   
/********************************************/
/* GetCurrentEnvironment: Returns a pointer */
/*   to the current environment.            */
/********************************************/
globle void *GetCurrentEnvironment()
  {
   return(CurrentEnvironment);
  }  
  
/******************************************/
/* GetEnvironmentIndex: Returns the index */
/*   of the specified environment.        */
/******************************************/
globle unsigned long GetEnvironmentIndex(
  void *theEnvironment)
  {
   return(((struct environmentData *) theEnvironment)->environmentIndex);
  } 
  
#endif

/**********************************************/
/* GetEnvironmentContext: Returns the context */
/*   of the specified environment.            */
/**********************************************/
globle void *GetEnvironmentContext(
  void *theEnvironment)
  {
   return(((struct environmentData *) theEnvironment)->context);
  } 

/*******************************************/
/* SetEnvironmentContext: Sets the context */
/*   of the specified environment.         */
/*******************************************/
globle void *SetEnvironmentContext(
  void *theEnvironment,
  void *theContext)
  {
   void *oldContext;
   
   oldContext = ((struct environmentData *) theEnvironment)->context;
  
   ((struct environmentData *) theEnvironment)->context = theContext;
   
   return oldContext;
  } 

/***************************************************/
/* GetEnvironmentRouterContext: Returns the router */
/*   context of the specified environment.         */
/***************************************************/
globle void *GetEnvironmentRouterContext(
  void *theEnvironment)
  {
   return(((struct environmentData *) theEnvironment)->routerContext);
  } 

/************************************************/
/* SetEnvironmentRouterContext: Sets the router */
/*   context of the specified environment.      */
/************************************************/
globle void *SetEnvironmentRouterContext(
  void *theEnvironment,
  void *theRouterContext)
  {
   void *oldRouterContext;
   
   oldRouterContext = ((struct environmentData *) theEnvironment)->routerContext;
  
   ((struct environmentData *) theEnvironment)->routerContext = theRouterContext;
   
   return oldRouterContext;
  } 

/*******************************************************/
/* GetEnvironmentFunctionContext: Returns the function */
/*   context of the specified environment.             */
/*******************************************************/
globle void *GetEnvironmentFunctionContext(
  void *theEnvironment)
  {
   return(((struct environmentData *) theEnvironment)->functionContext);
  } 

/**************************************************/
/* SetEnvironmentFunctionContext: Sets the router */
/*   context of the specified environment.        */
/**************************************************/
globle void *SetEnvironmentFunctionContext(
  void *theEnvironment,
  void *theFunctionContext)
  {
   void *oldFunctionContext;
   
   oldFunctionContext = ((struct environmentData *) theEnvironment)->functionContext;
  
   ((struct environmentData *) theEnvironment)->functionContext = theFunctionContext;
   
   return oldFunctionContext;
  } 

/*******************************************************/
/* GetEnvironmentCallbackContext: Returns the callback */
/*   context of the specified environment.             */
/*******************************************************/
globle void *GetEnvironmentCallbackContext(
  void *theEnvironment)
  {
   return(((struct environmentData *) theEnvironment)->callbackContext);
  } 

/****************************************************/
/* SetEnvironmentCallbackContext: Sets the callback */
/*   context of the specified environment.          */
/****************************************************/
globle void *SetEnvironmentCallbackContext(
  void *theEnvironment,
  void *theCallbackContext)
  {
   void *oldCallbackContext;
   
   oldCallbackContext = ((struct environmentData *) theEnvironment)->callbackContext;
  
   ((struct environmentData *) theEnvironment)->callbackContext = theCallbackContext;
   
   return oldCallbackContext;
  } 
  
/**********************************************/
/* DestroyEnvironment: Destroys the specified */
/*   environment returning all of its memory. */
/**********************************************/
globle intBool DestroyEnvironment(
  void *vtheEnvironment)
  {   
   struct environmentCleanupFunction *cleanupPtr;
   int i;
   struct memoryData *theMemData;
   intBool rv = TRUE;
   struct environmentData *theEnvironment = (struct environmentData *) vtheEnvironment;
   /*
   if (EvaluationData(theEnvironment)->CurrentExpression != NULL)
     { return(FALSE); }
     
#if DEFRULE_CONSTRUCT
   if (EngineData(theEnvironment)->ExecutingRule != NULL)
     { return(FALSE); }
#endif
*/
   theMemData = MemoryData(theEnvironment);

   EnvReleaseMem(theEnvironment,-1);

   for (i = 0; i < MAXIMUM_ENVIRONMENT_POSITIONS; i++)
     {
      if (theEnvironment->cleanupFunctions[i] != NULL)
        { (*theEnvironment->cleanupFunctions[i])(theEnvironment); }
     }
     
   free(theEnvironment->cleanupFunctions);
     
   for (cleanupPtr = theEnvironment->listOfCleanupEnvironmentFunctions;
        cleanupPtr != NULL;
        cleanupPtr = cleanupPtr->next)
     { (*cleanupPtr->func)(theEnvironment); }

   RemoveEnvironmentCleanupFunctions(theEnvironment);
   
   EnvReleaseMem(theEnvironment,-1);

#if ALLOW_ENVIRONMENT_GLOBALS
   RemoveHashedEnvironment(theEnvironment);
#endif
     
   if ((theMemData->MemoryAmount != 0) || (theMemData->MemoryCalls != 0))
     {
      printf("\n[ENVRNMNT8] Environment data not fully deallocated.\n"); 
      printf("\n[ENVRNMNT8] MemoryAmount = %ld.\n",(long) theMemData->MemoryAmount); 
      printf("\n[ENVRNMNT8] MemoryCalls = %ld.\n",(long) theMemData->MemoryCalls); 
      rv = FALSE;     
     }

#if (MEM_TABLE_SIZE > 0)
   free(theMemData->MemoryTable);
#endif

   for (i = 0; i < MAXIMUM_ENVIRONMENT_POSITIONS; i++)
     {
      if (theEnvironment->theData[i] != NULL)
        {
         free(theEnvironment->theData[i]);
         theEnvironment->theData[i] = NULL;
        }
     }
     
   free(theEnvironment->theData);
   
#if ALLOW_ENVIRONMENT_GLOBALS
   if (CurrentEnvironment == theEnvironment)
     { CurrentEnvironment = NULL; }
#endif

   free(theEnvironment);
   
   return(rv);
  } 
 
/**************************************************/
/* AddEnvironmentCleanupFunction: Adds a function */
/*   to the ListOfCleanupEnvironmentFunctions.    */
/**************************************************/
globle intBool AddEnvironmentCleanupFunction(
  void *vtheEnv,
  const char *name,
  void (*functionPtr)(void *),
  int priority)
  {
   struct environmentCleanupFunction *newPtr, *currentPtr, *lastPtr = NULL;
   struct environmentData *theEnv = (struct environmentData *) vtheEnv;
     
   newPtr = (struct environmentCleanupFunction *) malloc(sizeof(struct environmentCleanupFunction));
   if (newPtr == NULL)
     { return(FALSE); }

   newPtr->name = name;
   newPtr->func = functionPtr;
   newPtr->priority = priority;

   if (theEnv->listOfCleanupEnvironmentFunctions == NULL)
     {
      newPtr->next = NULL;
      theEnv->listOfCleanupEnvironmentFunctions = newPtr;
      return(TRUE);
     }

   currentPtr = theEnv->listOfCleanupEnvironmentFunctions;
   while ((currentPtr != NULL) ? (priority < currentPtr->priority) : FALSE)
     {
      lastPtr = currentPtr;
      currentPtr = currentPtr->next;
     }

   if (lastPtr == NULL)
     {
      newPtr->next = theEnv->listOfCleanupEnvironmentFunctions;
      theEnv->listOfCleanupEnvironmentFunctions = newPtr;
     }
   else
     {
      newPtr->next = currentPtr;
      lastPtr->next = newPtr;
     }
     
   return(TRUE);
  }

/**************************************************/
/* RemoveEnvironmentCleanupFunctions: Removes the */
/*   list of environment cleanup functions.       */
/**************************************************/
static void RemoveEnvironmentCleanupFunctions(
  struct environmentData *theEnv)
  {   
   struct environmentCleanupFunction *nextPtr;
      
   while (theEnv->listOfCleanupEnvironmentFunctions != NULL)
     { 
      nextPtr = theEnv->listOfCleanupEnvironmentFunctions->next;
      free(theEnv->listOfCleanupEnvironmentFunctions);
      theEnv->listOfCleanupEnvironmentFunctions = nextPtr;
     }
  } 
