/
/******************************************************************************
 *  ssm.c   (shared string manager)                                           *
 *                                                                            *
 *  Copyright(C) 1995 Melvin Smith  - Mercer University, Macon GA.            *
 *                                                                            *
 *  This code was scrapped from MUD++ in favor of a String class              *
 *  I converted it over to drop into Merc/Envy code by request of             *
 *  Jason Dinkel to go with OLC.                                              *
 *  - Fusion                                                                  *
 *  <msmith@falcon.mercer.peachnet.edu>                                       *
 ******************************************************************************/


/* Shared string manager to replace the Merc read only system.
 * Uses usage counts and 'first-fit' algorithm.
 * See p84-85 of "Modern Operating Systems", Andrew S. Tanenbaum
 */

#include <time.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <ctype.h>
#include "merc.h"

typedef struct B BufEntry;

struct B
{
  BufEntry *next;
  int  size;  /* size of the chunk (regardless of NULL_CHAR) */ 
  short int  usage;  /* keeps track of how many pointers to the string */ 
  char buf[1];    /* chunk starts here */
};


/* These are the original Merc vars in db.c */
extern char  str_empty[];
extern char *string_space;
extern char *top_string;
extern int   nAllocString;
extern int   sAllocString;

/* These are the replacement functions,
 * comment them out in db.c
 */
char *str_dup		( const char * );
void free_string	( char * );
char *fread_string	( FILE * );


/*
 * buf_head points to start of shared space,
 * buf_free points to next free block
 */ 
BufEntry *buf_head, *buf_free;

/* Increase MAX_STRING as needed, ie. when you start to
 * get bug logs about string space being full.
 */
int   MAX_STRING = 2100000;
int   HEADER_SIZE;
int   AllocMode;  /* If string space is filled this is toggled */


void init_string_space()
{
   string_space = (char *)malloc( MAX_STRING );
   if( !string_space )
   {
      bug( "Cant allocate shared string space.", 0 );
      exit(0);
   }
   top_string = string_space + MAX_STRING-1;
   buf_head = (BufEntry *)string_space;
   /*
    * Would be nice to use sizeof()-1 here but word alignment
    * adds some nice padding that puts it off. The REAL size of
    * the struct is 16 bytes (8 words) but since we are overlaying
    * the struct on the character space we need the exact offset.
    * No less portable as far as I can see. -Fusion
    */
   HEADER_SIZE = (int)((char*)buf_head->buf-(char*)buf_head);
   buf_head->usage = 0;
   buf_head->size = MAX_STRING - HEADER_SIZE;
   buf_head->next = NULL;
   buf_free = buf_head;
}


int defrag_heap()
{
   /*
    * Walk through the shared heap and merge adjacent free blocks.
    * Free blocks are merged in str_free if free->next is free but
    * if the block preceding free is free, it stays unmerged. I would
    * rather not have the heap as a DOUBLE linked list for 2 reasons...
    *  (1) Extra 4 bytes per struct uses more mem
    *  (2) Speed - don't want to bog down str_ functions with heap management
    * The "orphaned" blocks will eventually be either merged or reused.
    * The str_dup function will call defrag if it cant allocate a buf.
    */
   BufEntry *walk, *last_free, *next;
   int merges = 0;
   buf_free = NULL;
   for( walk=buf_head,last_free=NULL;walk;walk = next )
   {
      next = walk->next;
      if( walk->usage > 0 )
      {
         /* this block is in use so set last_free to NULL */
         last_free = NULL;
         continue;
      }
      else if( !last_free )
      {
         /* OK found a NEW free block, set last_free and move to next */
         last_free = walk;
         if( walk > buf_free )
            buf_free = walk; 
         continue; 
      }
      else
      {
         /* previous block is free so merge walk into last_free and move on */
         merges++;
         last_free->size += walk->size + HEADER_SIZE;
         last_free->next = walk->next;
      }
   }

   if( merges )
      bug( "defrag_heap() : made %d block merges. Good.", merges );
   else
      bug( "defrag_heap() : resulted in 0 merges.", 0 ); 
   return merges;
}


/*
 * This is meant to be a bulletproof string allocation function
 * but theres gotta be a bug or two in there.
 * Fusion
 */
char *str_dup( const char *str )
{
   BufEntry *ptr;
   int len;
   char *str_new;

   if( !str || !*str || str == &str_empty[0] )
      return &str_empty[0];

   if( str > string_space && str < top_string )
   {
      ptr = (BufEntry *)(str - HEADER_SIZE);
      ptr->usage++;
      return (char *)str;
   }
  
   len = strlen( str ) + 1;
   /* 
    * Round up to word boundary if not already.
    * Don't remove this, because when the BufEntry struct is overlaid
    * the integers must be aligned on word boundarys.
    */
   if( len & 3 ) 
     len += 4-(len&3);

   if( !AllocMode )
   {
      if( !buf_free )
      {
         bug( "string_space is full! Starting allocation mode.", 0 );
         AllocMode = 1;
         return strdup( str );
      } 
      else
      { 
         RETRY:
         for( ptr=buf_free; ptr; ptr=ptr->next )
           if( ptr->usage <= 0 && ptr->size >= len )
             break;
         if( !ptr )
         {
            int merges;
            bug( "string_space has become too fragmented!", 0 );
            bug( "Attemping to optimize heap.", 0 );
            merges = defrag_heap();
            if( merges )
            {
               /* goto is fine because defrag will return 0 next time */
               bug( "str_dup() : retrying allocation.", 0 );
               goto RETRY;
            }
            else
            {
               bug( "str_dup() : switching to Alloc mode.", 0 );
               AllocMode = 1;
               return strdup( str ); 
            }
         }
         /* If there is at least header size break it up so
          * it will be available for merging
          */
         else if( ptr->size-len >= HEADER_SIZE ) 
         {
            BufEntry *temp;
            /* WARNING! - DONT REMOVE THE CASTS BELOW! - Fusion */
            temp = (BufEntry*)((char *)ptr + HEADER_SIZE + len);
            temp->size = ptr->size - (len + HEADER_SIZE);
            temp->next = ptr->next;
            temp->usage = 0;
            ptr->size = len;
            ptr->next = temp;
            if( ptr == buf_free )
            buf_free = temp;
         } 
         ptr->usage = 1;
         str_new = ptr->buf;
         if( ptr == buf_free )
           for( buf_free=buf_head;buf_free;buf_free = buf_free->next )
             if( buf_free->usage <= 0 )
                break;
         strcpy( str_new, str ); 
         nAllocString++;
         sAllocString += len + HEADER_SIZE;
      }  
      return str_new;
   }
   else
      return strdup( str );
}



void free_string( char *str )
{
   BufEntry *ptr;
   if( !str || str == &str_empty[0] ) return;
   if( str > string_space && str < top_string )
   {
      ptr = (BufEntry *)(str - HEADER_SIZE);
      if( --ptr->usage > 0 )
         return;
      sAllocString -= (ptr->size + HEADER_SIZE);
      nAllocString--;
      if( ptr->next && ptr->next->usage <= 0 )
      {
         ptr->size += ptr->next->size + HEADER_SIZE;
         ptr->next = ptr->next->next;   
      }
      if( buf_free > ptr )
         buf_free = ptr;
      AllocMode = 0;
   }
   else
     free( str );
}


/*
 * Read and allocate space for a string from a file.
 * This replaces db.c fread_string
 */
char *fread_string( FILE *fp )
{
    char buf[ MAX_STRING_LENGTH*2 ];
    char *ptr = buf;
    char  c;

    /*
     * Skip blanks.
     * Read first char.
     */
    do
    {
	c = getc( fp );
    }
    while ( isspace( c ) );

    if ( ( *ptr++ = c ) == '~' )
	return &str_empty[0];

    for ( ;; )
    {
	/*
	 * Back off the char type lookup,
	 *   it was too dirty for portability.
	 *   -- Furey
	 */
	switch ( *ptr = getc( fp ) )
	{
	default:
	    ptr++;
	    break;

	case EOF:
	    bug( "Fread_string: EOF", 0 );
	    exit( 1 );
	    break;

	case '\n':
	    ptr++;
	    *ptr++ = '\r';
	    break;

	case '\r':
	    break;

	case '~':
	    *ptr = '\0';
            return str_dup( buf );
	}
    }
}