/* Types.c */

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

#define EXTBUF_SIZE 1024
#define TCITAB_SIZE 32
#define HASHTAB_SIZE 57

struct HashNode {
  char            *hn_Key;   /* extension string */
  int              hn_Idx;   /* index in TCInfo table */
  struct HashNode *hn_Next;  /* next node in collision list */
};

static char *extBuf = NULL, *extPtr;
static struct TCInfo *tciTab;
static int tciIdx;
static struct HashNode **hashTab = NULL;

static void ScanID(char **cp, ULONG *id)
{
  *id = 0;
  while(**cp && **cp == ' ')
    (*cp)++;
  while(**cp && **cp != ' ' && **cp != '\n') {
    *id = (*id << 8) + **cp;
    (*cp)++;
  }
}

static char *NextExt(char **cp)
{
  char *extStrt;
  
  while(**cp && **cp == ' ') (*cp)++;
  extStrt = **cp ? *cp : NULL;
  while(**cp && **cp != ' ' && **cp != '|') (*cp)++;
  if(**cp == '|') {
    **cp = 0;
    (*cp)++;
  }
  else
    **cp = 0;
  return extStrt;
}

static int Hash(char *str)
{
  int h;
  for(h=0; *str; str++)
    h = (64*h + *str) % HASHTAB_SIZE;
  return h;
}

static struct HashNode *NewNode(char *ext)
{
  struct HashNode *hn = malloc(sizeof(struct HashNode));
  if(extPtr >= extBuf+EXTBUF_SIZE) {
    printf("Error: extension buffer overflow.\n");
    exit(EXIT_FAILURE);
  }
  strcpy(extPtr,ext);
  hn->hn_Key = extPtr;
  hn->hn_Idx = 0;
  hn->hn_Next = NULL;
  extPtr += strlen(ext)+1;
  return hn;
}

static struct HashNode *InsertExt(char *ext)
{
  struct HashNode *hn,*lhn;
  
  int idx = Hash(ext);
  hn = hashTab[idx];
  if(hn) {
    lhn = hn;
    while(hn && strcmp(hn->hn_Key,ext)) {
      lhn = hn;
      hn = hn->hn_Next;
    }
    if(!hn) {
      hn = NewNode(ext);
      lhn->hn_Next = hn;
    }
    return hn;
  }
  else 
    return (hashTab[idx] = NewNode(ext));
}


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

void ReadTypes(char *cfgFileName)
{
  FILE *f;
  char ln[81], *nxtExt;
  int i;
  
  tciTab = malloc(sizeof(struct TCInfo)*TCITAB_SIZE);
  tciTab->tci_Type = 0;
  tciTab->tci_Creator = 0;
  tciIdx = 1;
  if(cfgFileName && (f = fopen(cfgFileName,"r"))) {
    extBuf = malloc(EXTBUF_SIZE);
    extPtr = extBuf;
    hashTab = malloc(sizeof(struct HashNode)*HASHTAB_SIZE);
    for(i=0; i<HASHTAB_SIZE; i++) hashTab[i] = NULL;
    while(fgets(ln,81,f)) {
      if(strlen(ln) < 3 || !strncmp(ln,"//",2)) continue;
      if(!strncmp(ln,"...",3)) {
        if(tciTab->tci_Type) {
          printf("Error: default extension already assigned.\n");
          continue;
        }
        else {
          char *p = ln+3;
          ScanID(&p,&tciTab->tci_Type);
          ScanID(&p,&tciTab->tci_Creator);
        }
      }
      else {
        ULONG newType,newCreator;
        char *p = ln;
        while(*p && *p == ' ') p++; /* skip blanks before extensions*/
        while(*p && *p != ' ') p++; /* skip extensions */
        ScanID(&p,&newType);
        ScanID(&p,&newCreator);
        if(tciIdx == TCITAB_SIZE) {
          printf("Error: too many types & creators.\n");
          break;
        }
        tciTab[tciIdx].tci_Type = newType;
        tciTab[tciIdx].tci_Creator = newCreator;
        tciIdx++;
        p = ln;
        while(nxtExt = NextExt(&p)) {
          struct HashNode *hn = InsertExt(nxtExt);
          if(hn->hn_Idx)
            printf("Error: extension \"%s\" already assigned.\n",nxtExt);
          else
            hn->hn_Idx = tciIdx-1;
        }
      }
    }
    fclose(f);
  }
  if(!tciTab->tci_Type) {
    tciTab->tci_Type = TYPE_TEXT;
    tciTab->tci_Creator = CREATOR_UNKNOWN;
  }
}

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

void FreeTypes(void)
{
  struct HashNode *hn,*dhn;
  int i;
  
  free(tciTab);
  if(extBuf) free(extBuf);
  if(hashTab) {
    for(i=0; i<HASHTAB_SIZE; i++) {
      hn = hashTab[i];
      while(hn) {
        dhn = hn;
        hn = hn->hn_Next;
        free(dhn);
      }
    }
    free(hashTab);
  }
}

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

struct TCInfo *TCInfoFor(char *fileName)
{
  char *ext;
  struct HashNode *hn;
  
  if(tciIdx == 1)
    return tciTab;
  else
    if(ext = strrchr(fileName,'.')) {
      if(hn = hashTab[Hash(++ext)])
        return tciTab + hn->hn_Idx;
      else
        return tciTab;
    }
    else
      return tciTab;
}
