/*
 * fman.c  -  file manager for dynamic opening/closing files
 *
 *  Programmmed by Hirotsugu Kakugawa, Hiroshima University
 *  E-Mail:  h.kakugawa@computer.org
 *
 *  Edition History
 *   6 May 1995  
 *  15 May 1995  Improved finding free fds. Added VFFM_UnIntern().
 *  15 Feb 1996  Added VFFM_Intern2() (for sony font class).
 *  12 Mar 1996  Added VFFM_Deinit().
 */


/* This file is part of VFlib
 *
 * Copyright (C) 1995-1998 Hirotsugu KAKUGAWA.   All rights reserved.
 *
 * This file is part of the VFlib Library.  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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

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

#include  "config.h"
#include  "defs.h"
#include  "_VF.h"
#include  "VF.h"

Import int  VF_MaxOpenFontFiles;

Private fm_tbl FM_Table[MAX_FONT_FILES+1];
#define SENTINEL_PORT  MAX_FONT_FILES


Private int InitCache();
Private int CacheCheck();
Private int CacheIt();


Private char  *Dum_str = "!@#$%^&We don't use such a file name!@#$%^&";
Private int   Dum_open();
Private int   Dum_close();

Private int VFFM_InternDummy();
Private int VFFM_Internal_Close();
Private int VFFM_Internal_Open();



/* (INTERNAL USE ONLY) */
int
VFFM_Init()
{
  FILE_Port   port;

  if (VF_MaxOpenFontFiles >= MAX_FONT_FILES)
    return FALSE;

  for (port = 0; port < MAX_FONT_FILES; port++){
    FM_Table[port].opened    = FALSE;
    FM_Table[port].path      = NULL;
    FM_Table[port].uu.stream = (FILE*) NULL;
    FM_Table[port].open_fun  = NULL;
    FM_Table[port].close_fun = NULL;
  }

  FM_Table[SENTINEL_PORT].path = NULL;

  InitCache(VF_MaxOpenFontFiles, HASH_SIZE);

  /* Allocate file descriptors in advance */
  if (! VFFM_InternDummy())
    return FALSE;

  return TRUE;    
}

/* (INTERNAL USE ONLY) */
int
VFFM_Deinit()
{
  FILE_Port   port;

#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf("VFFM_Deinit()\n");
#endif
  for (port = 0; port < MAX_FONT_FILES; port++){
    if (FM_Table[port].opened == TRUE){
      if (FM_Table[port].close_fun != NULL){
	(FM_Table[port].close_fun)(port, &FM_Table[port]);
      } else {
#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
	printf("VFFM_Internal_Close (VFFM_Deinit()): %s\n",
	       FM_Table[port].path);
#endif
	fclose(FM_Table[port].uu.stream);
      }
    }
    if (FM_Table[port].path != NULL){
      if (Dum_str != FM_Table[port].path)
	free(FM_Table[port].path);
    }
    FM_Table[port].path   = NULL;
    FM_Table[port].opened = FALSE;
  }
  return TRUE;    
}


Private int
VFFM_InternDummy()
{
  FILE_Port port;

  for (port = 0; port < VF_MaxOpenFontFiles; port++){
    if (port >= MAX_FONT_FILES)
      break;
    FM_Table[port].opened    = FALSE;
    FM_Table[port].uu.fd     = -1;
    FM_Table[port].path      = Dum_str;
    FM_Table[port].open_fun  = Dum_open;
    FM_Table[port].close_fun = Dum_close;
    if (CacheIt(port) < 0)
      return FALSE;
  }
  return TRUE;
}

Private int
Dum_open(port, ftp)
     FILE_Port  port;
     fm_tbl     *ftp;
{
  int  fd;

#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf("Dum_open: %d\n", (int)port);
#endif

  if ((fd = dup(0)) < 0)
    return -1;
  
#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf(" fd= %d\n", fd);
#endif

  ftp->uu.fd = fd;
  return 1;
}

Private int
Dum_close(port, ftp)
     FILE_Port  port;
     fm_tbl     *ftp;
{
#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf("Dum_close: %d\n", (int)port);
#endif

  close(ftp->uu.fd);
  ftp->uu.fd = -1;
  return 1;
}


static FILE_Port ___VFFM_Intern();

/* (INTERNAL USE ONLY) */
FILE_Port
VFFM_Intern(path, open_fun, close_fun)
     char  *path;
     int   (*open_fun)();
     int   (*close_fun)();
{
  return ___VFFM_Intern(path, open_fun, close_fun, 0);
}
/* (INTERNAL USE ONLY) */
FILE_Port
VFFM_InternInt(path, open_fun, close_fun)
     char  *path;
     int   (*open_fun)();
     int   (*close_fun)();
{
  return ___VFFM_Intern(path, open_fun, close_fun, 1);
}

static FILE_Port
___VFFM_Intern(path, open_fun, close_fun, type)
     char  *path;
     int   (*open_fun)();
     int   (*close_fun)();
     int   type;
{

  FILE_Port  port, tport;				/* fixed by kei */
  char       *path_str;

#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf("Intern: %s\n", path);
#endif

  /* Check whether the same path is interned or not */
  for (port = 0; port < SENTINEL_PORT; port++){
    if (FM_Table[port].path == NULL)
      continue;
    if (strcmp(path, FM_Table[port].path) == 0
      && FM_Table[port].opened == TRUE)
      return port;
  }

  /* Find a free slot */
  for (port = 0; port < SENTINEL_PORT; port++){
    if (FM_Table[port].path == NULL)
      break;
  }
  if (port == SENTINEL_PORT)
    return NULL_PORT;

  if ((path_str = malloc(strlen(path)+1)) == NULL)
    return NULL_PORT;
  strcpy(path_str, path);
  FM_Table[port].opened    = FALSE;
  FM_Table[port].path      = path_str;
  if (type == 0)
    FM_Table[port].uu.stream = NULL;
  else
    FM_Table[port].uu.fd     = -1;
  FM_Table[port].open_fun  = open_fun;
  FM_Table[port].close_fun = close_fun;

  tport = port;						/* fixed by kei */
  if (type == 0){
    if (VFFM_FStream(port) == (FILE*)NULL)
      port = NULL_PORT;
  } else {
    if (VFFM_FInt(port) < 0)
      port = NULL_PORT;
  }

  if (port == NULL_PORT){
    free(FM_Table[tport].path);				/* fixed by kei */
    FM_Table[tport].path = NULL;			/* fixed by kei */
  }
  return port;    
}


/* (INTERNAL USE ONLY) */
FILE*
VFFM_FStream(port)
     int  port;
{
  if (! CacheCheck(port))
    CacheIt(port);
  return FM_Table[port].uu.stream;
}
/* (INTERNAL USE ONLY) */
int
VFFM_FInt(port)
     int  port;
{
  if (! CacheCheck(port))
    CacheIt(port);
  return FM_Table[port].uu.fd;
}


/* (INTERNAL USE ONLY) */
int
VFFM_UnIntern(port)
     FILE_Port  port;
{
#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf("UnIntern: %d\n", (int)port);
#endif

  if (! CacheCheck(port))
    return TRUE;  /* the file is not opened */

  (void) VFFM_Internal_Close(port);

  if ((FM_Table[port].path != NULL) && (FM_Table[port].path != Dum_str))
    free(FM_Table[port].path);

  /* open dummy */
  FM_Table[port].opened    = FALSE;
  FM_Table[port].path      = Dum_str;
  FM_Table[port].uu.fd     = -1;
  FM_Table[port].open_fun  = Dum_open;
  FM_Table[port].close_fun = Dum_close;
  if (CacheIt(port) < 0)
    return FALSE;

  return TRUE;
}



/* Called by Cache Man as a default open_fun */
Private int
VFFM_Internal_Open(port)
     int  port;
{
  int  val;

  FM_Table[port].opened    = FALSE;
  if (FM_Table[port].open_fun != NULL){
    val = (FM_Table[port].open_fun)(port, &FM_Table[port]);
    if (val >= 0)
      FM_Table[port].opened    = TRUE;
    return val;
  }
    
#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf("VFFM_Internal_Open: %s\n", FM_Table[port].path);
#endif

  FM_Table[port].uu.stream = fopen(FM_Table[port].path, "rb");
  if (FM_Table[port].uu.stream != NULL){
    FM_Table[port].opened = TRUE;
    return 0;
  }
  return -1;
}

/* Called by Cache Man as a default close_fun. */
Private int
VFFM_Internal_Close(port)
     FILE_Port  port;
{
  FM_Table[port].opened = FALSE;
  if (FM_Table[port].close_fun != NULL)
    return (FM_Table[port].close_fun)(port, &FM_Table[port]);

#ifdef DYNAMIC_OPEN_CLOSE_DEBUG
  printf("VFFM_Internal_Close: %s\n", FM_Table[port].path);
#endif
  fclose(FM_Table[port].uu.stream);
  FM_Table[port].uu.stream = (FILE*)NULL;
  return TRUE;
}





/* ------------------------ */


/** 
 ** Cache
 **/ 
struct s_cache {
  struct s_cache  *h_forw;   /* forward in hash table */
  struct s_cache  *h_back;   /* backward in hash table */
  struct s_cache  *l_forw;   /* forward in LRU list */
  struct s_cache  *l_back;   /* backward in LRU list */
  struct s_cache  *free;     /* free list */
  int             port;
};

typedef struct s_cache  CACHE;
typedef int             CACHE_ID;
 
Private CACHE  *StreamCache;
Private int    CacheSize; 
Private CACHE  *HashTable;
Private int    HashSize; 
Private CACHE  CacheLRUList;
Private CACHE  CacheFreeList;

#define CACHE_OBJ(cid)      (&StreamCache[cid])
#define FREE_LIST()         CacheFreeList.free
#define CDR_FREE_LIST(x)    (x)->free

/* Hash Function */
#define HashFunction(port)     ((port)%HashSize)

Private CACHE  *RequireCache();
Private int    LRUMoveTop();
Private int    LRUPutTop();
Private int    LRUDeleteTail();
Private CACHE  *HashIsInterned();
Private int    HashInternIt();
Private int    HashUninternIt();



/* 
 * InitCache()  - Init cache.  Must specify cache size and hash table size.
 *    Returns FALSE if out of memory or other fatal error.
 *    Returns TRUE otherwise.
 */
Private int 
InitCache(cache_size, hash_size)
  int  cache_size, hash_size;
{
  int    i;
  CACHE  *cptr;

  CacheSize = cache_size;
  HashSize  = hash_size;

  /* CACHE */
  if (cache_size < 1){
    fprintf(stderr, "VFlib: InitCache() - Cache size is too small\n");
    return FALSE;
  }
  StreamCache = (CACHE*)calloc(CacheSize, sizeof(CACHE));
  if (StreamCache == (CACHE*)NULL)
    return FALSE;

  /* FREE LIST */
  FREE_LIST() = cptr = &StreamCache[0];
  for (i = 1; i < CacheSize; i++){
    CDR_FREE_LIST(cptr) = &StreamCache[i];
    cptr = CDR_FREE_LIST(cptr);
  }
  CDR_FREE_LIST(&StreamCache[CacheSize-1]) = (CACHE*)NULL;

  /* HASH */
  if (hash_size < 1){
    fprintf(stderr, 
	    "VFlib: InitCache() - Hash table size is too small\n");
    return FALSE;
  }
  HashTable = (CACHE*)calloc(HashSize, sizeof(CACHE));
  if (HashTable == (CACHE*)NULL){
    free(StreamCache);
    return FALSE;
  }
  for (i = 0; i < HashSize; i++){
    HashTable[i].h_forw = &HashTable[i];
    HashTable[i].h_back = &HashTable[i];
  }

  /* Init LRU list. */
  CacheLRUList.l_forw = &CacheLRUList;
  CacheLRUList.l_back = &CacheLRUList;

  return TRUE;
}


/*
 * CacheCheck()  
 */
Private int
CacheCheck(port)
     FILE_Port   port;
{
  CACHE   *cptr;

  if ((cptr = HashIsInterned(port)) == (CACHE*)NULL)
    return FALSE;
  (void) LRUMoveTop(cptr);
  return TRUE;
}


/*
 * CacheIt() --- cache it.  The cache slot is moved into
 *   the head of the LRU list.
 */
Private int 
CacheIt(port)
     int port;
{
  CACHE         *cptr;
  Private void  ReturnCache();

  if ((cptr = RequireCache()) == (CACHE*)NULL){
    fprintf(stderr, "VFlib: CacheIt() - error\n");
    exit(1);
  }
  cptr->port = port;
  if (LRUPutTop(cptr) < 0){
    ReturnCache(cptr);
    return -1;
  }
  HashInternIt(cptr);

  return 0;
}

/*
 * RequireCache()  --- Returns a cache block.
 *    1. if there is free cache, it will be returned.
 *    2. if there is no free cache, old cache is discarded and reused.
 */
Private CACHE*
RequireCache()
{
  CACHE  *cptr;

  if (FREE_LIST() != (CACHE*)NULL){
    cptr = FREE_LIST();
    FREE_LIST() = CDR_FREE_LIST(cptr);
  } else {
    (void) LRUDeleteTail();
    if ((cptr = FREE_LIST()) == (CACHE*)NULL){
      fprintf(stderr, 
	      "VFlib: Failed to discard old cache, FlushOldCache()\n");
      exit(1);
    }
    FREE_LIST() = CDR_FREE_LIST(cptr);
  }
  return cptr;
}

Private void
ReturnCache(cptr)
  CACHE  *cptr;
{
  CDR_FREE_LIST(cptr) = FREE_LIST();
  FREE_LIST() = cptr;
}


/**
 ** LRU LIST  
 **/

/* LRUMoveTop()  - moves a cache block into the top of LRU list.
 *   THE CACHE *MUST* BE IN LRU LIST.
 */
Private int 
LRUMoveTop(cptr)
  CACHE  *cptr;
{
  CACHE       *cptr_b, *cptr_f;
  Private int LRUPutTop2();

  cptr_b         = cptr->l_back;
  cptr_f         = cptr->l_forw;
  cptr_b->l_forw = cptr_f;
  cptr_f->l_back = cptr_b;
  return LRUPutTop2(cptr, FALSE);
}

/* LRUPutTop()  - puts a cache into the top of LRU list.
 *   THE CACHE *MUST NOT* BE IN LRU LIST.
 */
Private int  
LRUPutTop(cptr)
  CACHE  *cptr;
{
  Private int LRUPutTop2();

  return LRUPutTop2(cptr, TRUE);
}
  
Private int
LRUPutTop2(cptr, f)
     CACHE  *cptr;
     int    f;
{
  CACHE       *cptr_f;
  FILE_Port   port;
  int         val;

  cptr_f              = CacheLRUList.l_forw;
  cptr->l_forw        = cptr_f;
  cptr_f->l_back      = cptr;
  cptr->l_back        = &CacheLRUList;
  CacheLRUList.l_forw = cptr;

  val = 0;
  if (f == TRUE){
    port = cptr->port;
    if (VFFM_Internal_Open(port) < 0)
      val = -1;
  }

  return val; 
}


Private int 
LRUDeleteTail()
{
  CACHE      *cptr, *cptr_b;
  FILE_Port  port;

  if ((cptr = CacheLRUList.l_back) == &CacheLRUList)
    return FALSE;
  cptr_b              = cptr->l_back;
  cptr_b->l_forw      = &CacheLRUList;
  CacheLRUList.l_back = cptr_b;
  (void) HashUninternIt(cptr);
  CDR_FREE_LIST(cptr) = FREE_LIST();
  FREE_LIST()         = cptr;

  port = cptr->port;
  VFFM_Internal_Close(cptr->port);
  return TRUE;
}


Private CACHE*
HashIsInterned(port)
     int  port;
{
  CACHE   *cptr, *cptr0;

  cptr0 = &HashTable[HashFunction(port)];
  for (cptr = cptr0->h_forw; cptr != cptr0; cptr = cptr->h_forw){
    if (cptr->port == port)
      return cptr;
  }
  return (CACHE*)NULL;
}


Private int 
HashInternIt(cptr)
  CACHE  *cptr;
{
  int    hash;
  CACHE  *hcptr;

  hash  = HashFunction(cptr->port);
  hcptr                  = HashTable[hash].h_forw;
  cptr->h_forw           = hcptr;
  cptr->h_back           = &HashTable[hash];
  hcptr->h_back          = cptr;
  HashTable[hash].h_forw = cptr;
  return TRUE;
}

Private int 
HashUninternIt(cptr)
  CACHE  *cptr;
{
  CACHE  *cptr_b, *cptr_f;

  cptr_b         = cptr->h_back;
  cptr_f         = cptr->h_forw;
  cptr_b->h_forw = cptr_f;
  cptr_f->h_back = cptr_b;
  return TRUE;
}

/*EOF*/
