/*
 * Copyright (c) 1996 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */

#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include <atalk/adouble.h>

#include "globals.h"
#include "volume.h"
#include "directory.h"
#include "fork.h"

/* we need to have a hashed list of oforks (by name). just hash
 * by first letter. */
#define OFORK_HASHSIZE  64
static struct ofork     *ofork_table[OFORK_HASHSIZE];

static struct ofork	**oforks = NULL;
static int	        nforks = 0;
static u_short		lastrefnum = 0;


/* OR some of each character for the hash*/
static __inline__ unsigned long hashfn(const char *name)
{
  unsigned long i = 0;

  while (*name) {
    i = ((i << 4) | (8*sizeof(i) - 4)) ^ *name++;
  }
  return i & (OFORK_HASHSIZE - 1);
}

static __inline__ void of_hash(struct ofork *of)
{
  struct ofork **table;

  table = &ofork_table[hashfn(of->of_name)];
  if ((of->next = *table) != NULL)
    (*table)->prevp = &of->next;
  *table = of;
  of->prevp = table;
}

static __inline__ void of_unhash(struct ofork *of)
{
  if (of->prevp) {
    if (of->next)
      of->next->prevp = of->prevp;
    *(of->prevp) = of->next;
  }
}

pforkdesc( f )
    FILE	*f;
{
    u_short	ofrefnum;

    if (!oforks)
      return;

    for ( ofrefnum = 0; ofrefnum < nforks; ofrefnum++ ) {
	if ( oforks[ ofrefnum ] != NULL ) {
	    fprintf( f, "%hu <%s>\n", ofrefnum, oforks[ ofrefnum ]->of_name);
	}
    }
}

int of_flush(const struct vol *vol)
{
    u_int16_t	refnum;

    if (!oforks)
      return 0;

    for ( refnum = 0; refnum < nforks; refnum++ ) {
	if (oforks[ refnum ] != NULL && (oforks[refnum]->of_vol == vol) &&
	     flushfork( oforks[ refnum ] ) < 0 ) {
	    syslog( LOG_ERR, "of_flush: %m" );
	}
    }
    return( 0 );
}


int of_rename(vol, olddir, oldpath, newdir, newpath)
     const struct vol *vol;
     struct dir *olddir, *newdir;
     const char *oldpath, *newpath;
{
  struct ofork *of, *next;
  
  next = ofork_table[hashfn(oldpath)];
  while (of = next) {
    next = next->next; /* so we can unhash and still be all right. */

    if ((vol == of->of_vol) && (olddir == of->of_dir) && 
	(strcmp(of->of_name, oldpath) == 0)) {
      of_unhash(of);
      strncpy( of->of_name, newpath, of->of_namelen);
      of->of_dir = newdir;
      of_hash(of); 
    }
  }

  return AFP_OK;
}

    struct ofork *
of_alloc(vol, dir, path, ofrefnum, eid )
    struct vol          *vol;
    struct dir		*dir;
    char		*path;
    u_int16_t		*ofrefnum;
    const int           eid;
{
    u_int16_t		refnum, of_refnum;
    int			i;

    if (!oforks) {
      nforks = (getdtablesize() - 10) / 2;
      oforks = (struct ofork **) calloc(nforks, sizeof(struct ofork *));
      if (!oforks)
	return NULL;
    }

    for ( refnum = lastrefnum++, i = 0; i < nforks; i++, refnum++ ) {
	if ( oforks[ refnum % nforks ] == NULL ) {
	    break;
	}
    }
    if ( i == nforks ) {
	return( NULL );
    }

    of_refnum = refnum % nforks;
    if (( oforks[ of_refnum ] =
	    (struct ofork *)malloc( sizeof( struct ofork ))) == NULL ) {
	syslog( LOG_ERR, "of_alloc: malloc: %m" );
	return NULL;
    }
    oforks[ of_refnum ]->of_vol = vol;
    oforks[ of_refnum ]->of_dir = dir;

    /* here's the deal: we allocate enough for the standard mac file length. 
     * in the future, we'll only reallocate in fairly large jumps in case
     * of long unicode names */

    if (( oforks[ of_refnum ]->of_name =(char *)malloc(MACFILELEN + 1)) ==
	NULL ) {
	syslog( LOG_ERR, "of_alloc: malloc: %m" );
	free(oforks[ of_refnum ]);
	oforks[ of_refnum ] = NULL;
	return NULL;
    }
    strncpy( oforks[ of_refnum ]->of_name, path, 
	     oforks[of_refnum]->of_namelen = MACFILELEN + 1);
    *ofrefnum = refnum;
    oforks[ of_refnum ]->of_refnum = of_refnum;
    of_hash(oforks[ of_refnum ]);

    if (eid == ADEID_DFORK)
      oforks[of_refnum]->of_flags = AFPFORK_DATA;
    else
      oforks[of_refnum]->of_flags = AFPFORK_RSRC;

    return( oforks[ of_refnum ] );
}

    struct ofork *
of_find( ofrefnum )
    const u_int16_t	ofrefnum;
{
   if (!oforks)
     return NULL;

    return( oforks[ ofrefnum % nforks ] );
}

struct ofork *
of_findname(const struct vol *vol, const struct dir *dir, const char *name)
{
    struct ofork *of;

    for (of = ofork_table[hashfn(name)]; of; of = of->next) {
      if ((vol == of->of_vol) && (dir == of->of_dir) && 
	  (strcmp(of->of_name, name) == 0))
	return of;
    }

    return NULL;
}


void of_dealloc( of )
    struct ofork	*of;
{
    if (!oforks)
      return;

    of_unhash(of);
    oforks[ of->of_refnum ] = NULL;
    free( of->of_name );
    free( of );
    return;
}
