/**************************************************************************
 *
 * perf_hash.c -- Perfect hashing functions
 * Copyright (C) 1992  Bohdan S. Majewski
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **************************************************************************
 *
 * 1994 Neil Sharman
 *      Small modifications were made to the interface so that it fitted in
 *      with mg.
 *
 **************************************************************************/

#define STRUCT

#include <time.h>

#include "sysfuncs.h"

#include "local_strings.h"
#include "memlib.h"
#include "messages.h"
#include "perf_hash.h"
#include "random.h"
#include "netorder.h"  /* [RPAP - Jan 97: Endian Ordering] */

#define FALSE 0
#define TRUE  1

#define STATIC 0

/* Random Number stuff */
static mg_s_long seed[] = {0, 0};
#define RANDOM() mgirandm(seed)
#define SEED_RANDOM(the_seed) do{ seed[0] = the_seed; }while(0)

/* Max number of keys - must be a power of 2 minus 1 */
static int MAX_M;


static int MAX_CH;


/* the width of the mapping tables  */
static int MAX_L;



#define c     1.23
static int MAX_N;

/* An arbitrary amount to add to both MAX_N and n for building tree/acyclic
graph -- John McPherson - jrm21 (22-02-2001) */
#define MAX_N_HEADROOM  100


static u_char *translate;

#define MEMBER 0
#define ERASED 1



/* direction constants for the edge oriented 3-graph representation */
#define A              0
#define B        (MAX_M)
#define C          (2*B)

/* NODEa, NODEb, NODEc, FIRST and NEXT are used to represent a 3-graph */
static int *NODEa, *NODEb, *NODEc;
static int *FIRST;
static int *NEXT;


#define norm(e) ((e) % MAX_M)
/* in general should be ((e) % MAX_M) but MAX_M = 2^k - 1 */


static char *mk;		/* for marking edges/vertices as removed */
static int *g;			/* array g defines a minimal perfect hash function */



/* stack of edges, created during the independency */
/* test, used in the assignment step               */
static struct
{
int sp;
int *st;
}
S;
#define push(s, e) s.st[s.sp++] = (e)
#define pop(s, e)  e = s.st[--s.sp]

#ifndef STRUCT
static mg_s_long **tb0, **tb1, **tb2;
#else
struct tb_entry **tb;
#endif


static int 
unique (int v)
{				/* checks if vertex v belongs to only one edge */
return (FIRST[v] != 0 && NEXT[FIRST[v]] == 0) ? FIRST[v] : 0;
}				/*unique */








static void 
delete (int v, int e)
{				/* deletes edge e from list of v */

int b, norm_b;
b = FIRST[v];
assert (norm (b) != 0);
if (norm (b) == e)
FIRST[v] = NEXT[FIRST[v]];
else
{
while (norm (NEXT[b]) != e)
{
  b = NEXT[b];
  assert (norm (b) != 0);
}
NEXT[b] = NEXT[NEXT[b]];
}
}				/*delete */






#if 0
/* recursively removes the edges of a 3-graph which have at least */
/* one unique vertex. The removed edges are placed on stack S     */
static void 
ph_remove (int e, int v)
{
int b;
mk[e] = ERASED;
push (S, e);

if (NODEa[e] != v)
delete (NODEa[e], e);
if (NODEb[e] != v)
delete (NODEb[e], e);
if (NODEc[e] != v)
delete (NODEc[e], e);

if (NODEa[e] != v && mk[b = norm (unique (NODEa[e]))] == MEMBER)
ph_remove (b, NODEa[e]);
if (NODEb[e] != v && mk[b = norm (unique (NODEb[e]))] == MEMBER)
ph_remove (b, NODEb[e]);
if (NODEc[e] != v && mk[b = norm (unique (NODEc[e]))] == MEMBER)
ph_remove (b, NODEc[e]);
}
#else


static int StackPos, StackMax;
static struct Stack
{
int e, v, m;
}
*Stack = NULL;

#define StackAdd(_e, _v, _m)						\
do {									\
if (StackPos == StackMax)						\
{									\
StackMax <<= 1;							\
if (!(Stack = Xrealloc(Stack, sizeof(*Stack) * StackMax)))	\
  return(-1);							\
}									\
Stack[StackPos].e = (_e);						\
Stack[StackPos].v = (_v);						\
Stack[StackPos++].m = (_m);						\
} while(0)


static int 
ph_remove (int e, int v)
{
if (!Stack)
{
StackMax = 256;
if (!(Stack = Xmalloc (sizeof (*Stack) * StackMax)))
return (-1);
StackPos = 0;
}
Stack[StackPos].e = e;
Stack[StackPos].v = v;
Stack[StackPos].m = 0;
StackPos++;
while (StackPos)
{
int m, b;
StackPos--;
e = Stack[StackPos].e;
v = Stack[StackPos].v;
m = Stack[StackPos].m;


switch (m)
{
case 0:
  {
    mk[e] = ERASED;
    push (S, e);

    if (NODEa[e] != v)
      delete (NODEa[e], e);
    if (NODEb[e] != v)
      delete (NODEb[e], e);
    if (NODEc[e] != v)
      delete (NODEc[e], e);

    if (NODEa[e] != v && mk[b = norm (unique (NODEa[e]))] == MEMBER)
      {
	Stack[StackPos++].m = 1;
	StackAdd (b, NODEa[e], 0);
	break;
      }
  }
case 1:
  {
    if (NODEb[e] != v && mk[b = norm (unique (NODEb[e]))] == MEMBER)
      {
	Stack[StackPos++].m = 2;
	StackAdd (b, NODEb[e], 0);
	break;
      }
  }
case 2:
  if (NODEc[e] != v && mk[b = norm (unique (NODEc[e]))] == MEMBER)
    StackAdd (b, NODEc[e], 0);
}
}
return (0);
}
#endif





/* returns TRUE if and only if set of edges created in the tree_builder */
/* procedure form a 3-graph with independent edges. Edges of a 3-graph  */
/* are independent iff repeated deletion of edges containing vertices   */
/* of degree 1 results in a graph with no edges in it.                  */
static int 
acyclic (int m, int n)
{

int v, e;
/* build list of edges for each node */
for (v = 1; v <= n; FIRST[v++] = 0);
for (e = 1; e <= m; e++)
{
NEXT[A + e] = FIRST[NODEa[e]];
FIRST[NODEa[e]] = A + e;
NEXT[B + e] = FIRST[NODEb[e]];
FIRST[NODEb[e]] = B + e;
NEXT[C + e] = FIRST[NODEc[e]];
FIRST[NODEc[e]] = C + e;

/* mark edge e as belonging to the hypergraph */
mk[e] = MEMBER;
}


S.sp = 0;			/* empty the stack */
mk[0] = ERASED;		/* a sentinel for the test below */
for (v = 1; v <= n; v++)
{
if (mk[e = norm (unique (v))] == MEMBER)
ph_remove (e, v);
}

/* check if any edges were left in the graph */
return S.sp == m;
}				/*acyclic */








/* repeatedly maps words into edges of a 3-graph until the edges */
/* are independent.                                              */
static int 
tree_builder (int num, u_char ** keys, int *n)
{

int e, k = 0;
int i, j;
u_char *w;

do
{
/* Added variable - need to increase MAX_N on allocate as well */
*n = (int) (num * c + MAX_N_HEADROOM); /* john mcpherson 22-02-2001 */

/* initialize tables of random integers */
for (i = 0; i < MAX_CH; i++)
for (j = 0; j < MAX_L; j++)
  {
#ifndef STRUCT
    tb0[i][j] = RANDOM () % *n;
    tb1[i][j] = RANDOM () % *n;
    tb2[i][j] = RANDOM () % *n;
#else
	    struct tb_entry *tbp = &tb[i][j];
	    tbp->tb0 = RANDOM () % *n;
	    tbp->tb1 = RANDOM () % *n;
	    tbp->tb2 = RANDOM () % *n;
#endif
	  }

      /* compute an edge (NODEa[e], NODEb[e], NODEc[e]) for each word */
      for (e = 1; e <= num; e++)
	{
	  /*generate an edge corresponding to the ith word */
	  int x = 0, y = 0, z = 0, l;
	  w = keys[e - 1];

	  l = *w++;
	  j = (l - 1) % MAX_L;

	  do
	    {
#ifndef STRUCT
	      x += tb0[translate[*w]][j];
	      y += tb1[translate[*w]][j];
	      z += tb2[translate[*w]][j];
#else
	      struct tb_entry *tbp = &tb[translate[*w]][j];
	      x += tbp->tb0;
	      y += tbp->tb1;
	      z += tbp->tb2;
#endif
	      if (++j >= MAX_L)
		j = 0;
	      w++;
	    }
	  while (--l);
	  x = (x % *n) + 1;
	  y = (y % *n) + 1;
	  z = (z % *n) + 1;
	  if (y == x && ++y > *n)
	    y = 1;
	  if (z == x && ++z > *n)
	    z = 1;
	  if (z == y)
	    {
	      if (++z > *n)
		z = 1;
	      if (z == x && ++z > *n)
		z = 1;
	    }

	  NODEa[e] = x;
	  NODEb[e] = y;
	  NODEc[e] = z;
	}
      if (++k > 50)		/* make sure we will not wait for ever */
	FatalError (1, "Unable to generate the perfect hash function. "
		    "This is probably because there aren't enough words");
    }
  while (!acyclic (num, *n));
  return k;
}				/*tree_builder */









/* calculates values of the g function/array so that i-th word is */
/* placed at the (i-1)st position of a hash table.                */
static void 
assign (int n)
{

  int v, w = 0, e;

  /* mark all the vertices of the 3-graph as unassigned */
  for (v = 1; v <= n; mk[v++] = FALSE);

  while (S.sp > 0)
    {
      int xor = 0;
      pop (S, e);
      v = NODEa[e];
      if (mk[v])
	xor ^= g[v];
      else
	{
	  g[w = v] = 0;
	  mk[v] = TRUE;
	}
      v = NODEb[e];
      if (mk[v])
	xor ^= g[v];
      else
	{
	  g[w = v] = 0;
	  mk[v] = TRUE;
	}
      v = NODEc[e];
      if (mk[v])
	xor ^= g[v];
      else
	{
	  g[w = v] = 0;
	  mk[v] = TRUE;
	}
      g[w] = (e - 1) ^ xor;
    }
}				/*assign */









/*check returns 0 if no error in mphf is detected, else returns error code */
static int 
check (int m)
{

  int e, err_code = 0;

  /*for each word compute its place in hash table and check if correct */
  for (e = 1; e <= m; e++)
    {
      if ((e - 1) != (g[NODEa[e]] ^ g[NODEb[e]] ^ g[NODEc[e]]))
	{
	  (void) fprintf (stderr, "Error for %dth word detected.\n", e);
	  err_code = e;
	}
    }
  return err_code;
}				/*check */







#if 0
void 
outputh (int m, int n)
{

  int i, j, w = (int) ceil (log10 ((double) n + 1.0)) + 1;

#if 0
  char *PRG =
  "int h(char *s)\n"
  "{\n"
  "  int u, v, w, i;\n"
  "i = (strlen(s) - 1) % _HXL;\n"
  "  u = v = w = 0;\n"
  "  while(*s)\n"
  "    {\n"
  "      u += mt1[(*s) - _HFC][i];\n"
  "      v += mt2[(*s) - _HFC][i];\n"
  "      w += mt3[(*s++) - _HFC][i];\n"
  "      if(++i >= _HXL) i = 0;\n"
  "    }\n"
  "  u %= _HFN; v %= _HFN; w %= _HFN;\n"
  "  if(u == v && ++v >= _HFN) v = 0;\n"
  "  if(w == u && ++w >= _HFN) w = 0;\n"
  "  if(w == v && ++w >= _HFN) w = 0;\n"
  "  if(w == u && ++w >= _HFN) w = 0;\n"
  "  return (g[u] ^ g[v] ^ g[w]) % _HFM;\n"
  "};\n\n";
#endif

  char *PRG =
  "int h(char *s)\n"
  "{\n"
  "  int u, v, w, i;\n"
  "  i = (strlen(s) - 1) % _HXL;\n"
  "  u = v = w = 0;\n"
  "  while(*s)\n"
  "    {\n"
  "      if((u += mt1[(*s) - _HFC][i]) >= _HFN) u -= _HFN;\n"
  "      if((v += mt2[(*s) - _HFC][i]) >= _HFN) v -= _HFN;\n"
  "      if((w += mt3[(*s++) - _HFC][i]) >= _HFN) w -= _HFN;\n"
  "      if(++i >= _HXL) i = 0;\n"
  "    }\n"
  "  if(u == v && ++v >= _HFN) v = 0;\n"
  "  if(w == v) \n"
  "    {\n"
  "      if(++w >= _HFN) w = 0;\n"
  "      if(w == u && ++w >= _HFN) w = 0;\n"
  "    }\n"
  "  else\n"
  "    if(w == u)\n"
  "      {\n"
  "	     if(++w >= _HFN) w = 0;\n"
  "	     if(w == v && ++w >= _HFN) w = 0;\n"
  "      }\n"
  "  return ((g[u] ^ g[v] ^ g[w]) > _HFM)? 0 : (g[u] ^ g[v] ^ g[w]);\n"
  "}\n\n";

  printf ("#define _HFN %d\n#define _HFC %d\n", n, fc);
  printf ("#define _HFM %d\n#define _HXL %d\n", m, MAX_L);
  printf ("#define _HFA %d\n\nstatic int g[_HFN] = {\n", lc - fc + 1);
  for (i = 1; i < n; i++)
    {
      if (i % 8 == 0)
	putchar ('\n');
      printf ("%5d, ", g[i]);
    }
  printf ("%5d};\n\n", g[n]);

  printf ("static int mt1[_HFA][_HXL] = {\n");
  for (j = fc; j < lc; j++)
    {
      printf ("{");
      for (i = 0; i < MAX_L - 1; i++)
	printf ("%*d,", w, tb0[j][i]);
      printf ("%*d},\n", w, tb0[j][MAX_L - 1]);
    }
  printf ("{");
  for (i = 0; i < MAX_L - 1; i++)
    printf ("%*d,", w, tb0[lc][i]);
  printf ("%*d}\n", w, tb0[lc][MAX_L - 1]);
  printf ("};\n\n");

  printf ("static int mt2[_HFA][_HXL] = {\n");
  for (j = fc; j < lc; j++)
    {
      printf ("{");
      for (i = 0; i < MAX_L - 1; i++)
	printf ("%*d,", w, tb1[j][i]);
      printf ("%*d},\n", w, tb1[j][MAX_L - 1]);
    }
  printf ("{");
  for (i = 0; i < MAX_L - 1; i++)
    printf ("%*d,", w, tb1[lc][i]);
  printf ("%*d}\n", w, tb1[lc][MAX_L - 1]);
  printf ("};\n\n");

  printf ("static int mt3[_HFA][_HXL] = {\n");
  for (j = fc; j < lc; j++)
    {
      printf ("{");
      for (i = 0; i < MAX_L - 1; i++)
	printf ("%*d,", w, tb2[j][i]);
      printf ("%*d},\n", w, tb2[j][MAX_L - 1]);
    }
  printf ("{");
  for (i = 0; i < MAX_L - 1; i++)
    printf ("%*d,", w, tb2[lc][i]);
  printf ("%*d}\n", w, tb2[lc][MAX_L - 1]);
  printf ("};\n\n");

  printf ("%s", PRG);
}				/*outputh */
#endif







static void 
make_trans_func (int num, u_char ** keys)
{
  int i, j;

  bzero ((char *) translate, sizeof (translate));

  MAX_L = 1;
  for (i = 0; i < num; i++)
    {
      unsigned len = keys[i][0];
      u_char *s = keys[i] + 1;
      for (; len; len--, s++)
	translate[*s] = 1;
      if (i)
	{
	  int l;
	  l = prefixlen (keys[i - 1], keys[i]);
	  if (l + 1 > MAX_L)
	    MAX_L = l + 1;
	}
    }
  j = 0;
  for (i = 0; i < 256; i++)
    if (translate[i])
      translate[i] = j++;
  MAX_CH = j;
}


static void 
temp_free (void)
{
  if (NODEa)
    Xfree (NODEa);
  if (NODEb)
    Xfree (NODEb);
  if (NODEc)
    Xfree (NODEc);
  if (FIRST)
    Xfree (FIRST);
  if (NEXT)
    Xfree (NEXT);
  if (S.st)
    Xfree (S.st);
  if (mk)
    Xfree (mk);
}

static void 
all_free (void)
{
  int i;
  temp_free ();
  if (translate)
    Xfree (translate);
  if (g)
    Xfree (g);
#ifndef STRUCT
  for (i = 0; i < MAX_CH; i++)
    {
      if (tb0 && tb0[i])
	Xfree (tb0[i]);
      if (tb1 && tb1[i])
	Xfree (tb1[i]);
      if (tb2 && tb2[i])
	Xfree (tb2[i]);
    }
  if (tb0)
    Xfree (tb0);
  if (tb1)
    Xfree (tb1);
  if (tb2)
    Xfree (tb2);
#else
  for (i = 0; i < MAX_CH; i++)
    if (tb && tb[i])
      Xfree (tb[i]);
  if (tb)
    Xfree (tb);
#endif
}

static int 
allocate_memory (void)
{
  int i, ok = 1;
  NODEa = Xmalloc (sizeof (int) * (MAX_M + 1));
  NODEb = Xmalloc (sizeof (int) * (MAX_M + 1));
  NODEc = Xmalloc (sizeof (int) * (MAX_M + 1));
  FIRST = Xmalloc (sizeof (int) * (MAX_N + 1));
  NEXT = Xmalloc (sizeof (int) * (MAX_M + 1) * 3);
  S.st = Xmalloc (sizeof (int) * MAX_M);
  mk = Xmalloc (sizeof (char) * (MAX_N + 1));
  g = Xmalloc (sizeof (int) * (MAX_N + 1));
#ifndef STRUCT
  tb0 = Xmalloc (sizeof (int *) * MAX_CH);
  tb1 = Xmalloc (sizeof (int *) * MAX_CH);
  tb2 = Xmalloc (sizeof (int *) * MAX_CH);
  for (i = 0; i < MAX_CH; i++)
    {
      if (tb0)
	if (!(tb0[i] = Xmalloc (sizeof (mg_s_long) * MAX_L)))
	    ok = 0;
      if (tb1)
	if (!(tb1[i] = Xmalloc (sizeof (mg_s_long) * MAX_L)))
	    ok = 0;
      if (tb2)
	if (!(tb2[i] = Xmalloc (sizeof (mg_s_long) * MAX_L)))
	    ok = 0;
    }

  if (!(NODEa && NODEb && NODEc && FIRST && NEXT && S.st && mk && g &&
	tb0 && tb1 && tb2 && ok))
    {
      all_free ();
      return (-1);
    }
#else
  tb = Xmalloc (sizeof (struct tb_entry *) * MAX_CH);
  for (i = 0; i < MAX_CH; i++)
    if (tb)
      if (!(tb[i] = Xmalloc (sizeof (struct tb_entry) * MAX_L)))
	  ok = 0;

  if (!(NODEa && NODEb && NODEc && FIRST && NEXT && S.st && mk && g &&
	tb && ok))
    {
      all_free ();
      return (-1);
    }
#endif
  return (0);
}


perf_hash_data *
gen_hash_func (int num, u_char ** keys, int r)
{
  int n;
  perf_hash_data *phd;
  translate = Xmalloc (sizeof (u_char) * 256);
  bzero ((char *) translate, sizeof (u_char) * 256);  /* [RPAP - Feb 97: WIN32 Port] */
  make_trans_func (num, keys);

  if (r <= 0)
    SEED_RANDOM ((mg_s_long) time ((time_t *) NULL));
  else
    SEED_RANDOM (r);

  MAX_M = num + 1;

  /* For extremely small lexicons (<4 words), this isn't good enough! 
   -- John McPherson jrm21 22-02-2001 */
  /* MAX_N = MAX_M + MAX_M / 4; */
  MAX_N = (int)(ceil (1.25 * MAX_M)); 
  /* Add some space for "experimentation" of n in tree_builder() */
  MAX_N += MAX_N_HEADROOM;

  if (allocate_memory () == -1)
    return (NULL);

  /* construct an "acyclic" hypergraph */
  tree_builder (num, keys, &n);
  /* compute function g */
  assign (n);

  if (check (num) != 0)
    FatalError (1, "An error has been detected in the mphf.");
  temp_free ();

  if (!(phd = Xmalloc (sizeof (perf_hash_data))))
    return (NULL);

  memmove ((char *) g, (char *) &g[1],  n * sizeof (int));

  phd->MAX_L = MAX_L;
  phd->MAX_N = n;
  phd->MAX_M = num;
  phd->MAX_CH = MAX_CH;
  phd->translate = translate;
  phd->g = g;
#ifndef STRUCT
  phd->tb0 = tb0;
  phd->tb1 = tb1;
  phd->tb2 = tb2;
#else
  phd->tb = tb;
#endif
  return (phd);
}

int 
write_perf_hash_data (FILE * f, perf_hash_data * phd)
{
  int tot, i;
  /* [RPAP - Jan 97: Endian Ordering] */
  HTONSI(phd->MAX_L);
  HTONSI(phd->MAX_N);
  HTONSI(phd->MAX_M);
  HTONSI(phd->MAX_CH);

  tot = fwrite ((char *) &phd->MAX_L, sizeof (int), 1, f) * sizeof (int);
  tot += fwrite ((char *) &phd->MAX_N, sizeof (int), 1, f) * sizeof (int);
  tot += fwrite ((char *) &phd->MAX_M, sizeof (int), 1, f) * sizeof (int);
  tot += fwrite ((char *) &phd->MAX_CH, sizeof (int), 1, f) * sizeof (int);

  /* [RPAP - Jan 97: Endian Ordering] */
  NTOHSI(phd->MAX_L);
  NTOHSI(phd->MAX_N);
  NTOHSI(phd->MAX_M);
  NTOHSI(phd->MAX_CH);

  tot += fwrite ((char *) phd->translate, sizeof (u_char), 256, f);

  /* [RPAP - Jan 97: Endian Ordering] */
  for (i = 0; i < phd->MAX_N + 1; i++)
    HTONSI(phd->g[i]);

  tot += fwrite ((char *) phd->g, sizeof (int), phd->MAX_N + 1, f) * sizeof (int);

  /* [RPAP - Jan 97: Endian Ordering] */
  for (i = 0; i < phd->MAX_N + 1; i++)
    NTOHSI(phd->g[i]);

#ifndef STRUCT
  for (i = 0; i < phd->MAX_CH; i++)
    {
      /* [RPAP - Jan 97: Endian Ordering] */
      int j;
      for (j = 0; j < phd->MAX_L; j++)
	{
	  HTONSI(phd->tb0[i][j]);
	  HTONSI(phd->tb1[i][j]);
	  HTONSI(phd->tb2[i][j]);
	}

      tot += fwrite ((char *) phd->tb0[i], sizeof (int), phd->MAX_L, f) *
        sizeof (int);
      tot += fwrite ((char *) phd->tb1[i], sizeof (int), phd->MAX_L, f) *
        sizeof (int);
      tot += fwrite ((char *) phd->tb2[i], sizeof (int), phd->MAX_L, f) *
        sizeof (int);

      /* [RPAP - Jan 97: Endian Ordering] */
      for (j = 0; j < phd->MAX_L; j++)
	{
	  NTOHSI(phd->tb0[i][j]);
	  NTOHSI(phd->tb1[i][j]);
	  NTOHSI(phd->tb2[i][j]);
	}
    }
#else
  for (i = 0; i < phd->MAX_CH; i++)
    {
      /* [RPAP - Jan 97: Endian Ordering] */
      int j;
      for (j = 0; j < phd->MAX_L; j++)
	{
	  HTONSL(phd->tb[i][j].tb0);
	  HTONSL(phd->tb[i][j].tb1);
	  HTONSL(phd->tb[i][j].tb2);
	}

      tot += fwrite ((char *) phd->tb[i], sizeof (struct tb_entry), phd->MAX_L, f) *
	sizeof (struct tb_entry);

      /* [RPAP - Jan 97: Endian Ordering] */
      for (j = 0; j < phd->MAX_L; j++)
	{
	  NTOHSL(phd->tb[i][j].tb0);
	  NTOHSL(phd->tb[i][j].tb1);
	  NTOHSL(phd->tb[i][j].tb2);
	}
    }
#endif
  return (tot);
}


perf_hash_data *
read_perf_hash_data (FILE * f)
{
  perf_hash_data *phd;
  int i, tot, ok = 1;
  if (!(phd = Xmalloc (sizeof (perf_hash_data))))
    return (NULL);
  tot = fread ((char *) &phd->MAX_L, sizeof (int), 1, f) * sizeof (int);
  tot += fread ((char *) &phd->MAX_N, sizeof (int), 1, f) * sizeof (int);
  tot += fread ((char *) &phd->MAX_M, sizeof (int), 1, f) * sizeof (int);
  tot += fread ((char *) &phd->MAX_CH, sizeof (int), 1, f) * sizeof (int);

  /* [RPAP - Jan 97: Endian Ordering] */
  NTOHSI(phd->MAX_L);
  NTOHSI(phd->MAX_N);
  NTOHSI(phd->MAX_M);
  NTOHSI(phd->MAX_CH);

  if (tot != 4 * sizeof (int))
      return (NULL);
  phd->translate = Xmalloc (sizeof (u_char) * 256);
  phd->g = Xmalloc (sizeof (int) * (phd->MAX_N + 1));
#ifndef STRUCT
  phd->tb0 = Xmalloc (sizeof (int *) * phd->MAX_CH);
  phd->tb1 = Xmalloc (sizeof (int *) * phd->MAX_CH);
  phd->tb2 = Xmalloc (sizeof (int *) * phd->MAX_CH);
  for (i = 0; i < phd->MAX_CH; i++)
    {
      if (phd->tb0)
	if (!(phd->tb0[i] = Xmalloc (sizeof (mg_s_long) * phd->MAX_L)))
	    ok = 0;
      if (phd->tb1)
	if (!(phd->tb1[i] = Xmalloc (sizeof (mg_s_long) * phd->MAX_L)))
	    ok = 0;
      if (phd->tb2)
	if (!(phd->tb2[i] = Xmalloc (sizeof (mg_s_long) * phd->MAX_L)))
	    ok = 0;
    }
  if (!(phd->translate && phd->g && phd->tb0 && phd->tb1 && phd->tb2 && ok))
    {
      if (phd->translate)
	Xfree (phd->translate);
      if (phd->g)
	Xfree (phd->g);
      for (i = 0; i < MAX_CH; i++)
	{
	  if (phd->tb0 && phd->tb0[i])
	    Xfree (phd->tb0[i]);
	  if (phd->tb1 && phd->tb1[i])
	    Xfree (phd->tb1[i]);
	  if (phd->tb2 && phd->tb2[i])
	    Xfree (phd->tb2[i]);
	}
      if (phd->tb0)
	Xfree (phd->tb0);
      if (phd->tb1)
	Xfree (phd->tb1);
      if (phd->tb2)
	Xfree (phd->tb2);
      Xfree (phd);
      return (NULL);
    }
  tot += fread ((char *) phd->translate, sizeof (u_char), 256, f);
  tot += fread ((char *) phd->g, sizeof (int), phd->MAX_N + 1, f) * sizeof (int);

  /* [RPAP - Jan 97: Endian Ordering] */
  for (i = 0; i < phd->MAX_N + 1; i++)
    NTOHSI(phd->g[i]);

  for (i = 0; i < phd->MAX_CH; i++)
    {
      int j;

      tot += fread ((char *) phd->tb0[i], sizeof (mg_s_long), phd->MAX_L, f) *
        sizeof (int);
      tot += fread ((char *) phd->tb1[i], sizeof (mg_s_long), phd->MAX_L, f) *
        sizeof (int);
      tot += fread ((char *) phd->tb2[i], sizeof (mg_s_long), phd->MAX_L, f) *
        sizeof (int);

      /* [RPAP - Jan 97: Endian Ordering] */
      for (j = 0; j < phd->MAX_L; j++)
	{
	  NTOHSI(phd->tb0[i][j]);
	  NTOHSI(phd->tb1[i][j]);
	  NTOHSI(phd->tb2[i][j]);
	}
    }
#else
  phd->tb = Xmalloc (sizeof (struct tb_entry *) * phd->MAX_CH);
  for (i = 0; i < phd->MAX_CH; i++)
    if (phd->tb)
      if (!(phd->tb[i] = Xmalloc (sizeof (struct tb_entry) * phd->MAX_L)))
	  ok = 0;
  if (!(phd->translate && phd->g && phd->tb && ok))
    {
      if (phd->translate)
	Xfree (phd->translate);
      if (phd->g)
	Xfree (phd->g);
      for (i = 0; i < MAX_CH; i++)
	if (phd->tb && phd->tb[i])
	  Xfree (phd->tb[i]);
      if (phd->tb)
	Xfree (phd->tb);
      Xfree (phd);
      return (NULL);
    }
  tot += fread ((char *) phd->translate, sizeof (u_char), 256, f);
  tot += fread ((char *) phd->g, sizeof (int), phd->MAX_N + 1, f) * sizeof (int);

  /* [RPAP - Jan 97: Endian Ordering] */
  for (i = 0; i < phd->MAX_N + 1; i++)
    NTOHSI(phd->g[i]);

  for (i = 0; i < phd->MAX_CH; i++)
    {
      int j;

      tot += fread ((char *) phd->tb[i], sizeof (struct tb_entry), phd->MAX_L, f) *
	sizeof (struct tb_entry);

      /* [RPAP - Jan 97: Endian Ordering] */
      for (j = 0; j < phd->MAX_L; j++)
	{
	  NTOHSL(phd->tb[i][j].tb0);
	  NTOHSL(phd->tb[i][j].tb1);
	  NTOHSL(phd->tb[i][j].tb2);
	}
    }
#endif
  return (phd);
}

void 
free_perf_hash (perf_hash_data * phd)
{
  int i;
  if (phd->translate)
    Xfree (phd->translate);
  if (phd->g)
    Xfree (phd->g);
  for (i = 0; i < phd->MAX_CH; i++)
    if (phd->tb && phd->tb[i])
      Xfree (phd->tb[i]);
  if (phd->tb)
    Xfree (phd->tb);
  Xfree (phd);
}


int 
perf_hash (perf_hash_data * phd, u_char * s)
{
  int u, v, w, i, l;
  l = *s++;
  i = (l - 1) % phd->MAX_L;
  u = v = w = 0;
  while (l)
    {
#ifndef STRUCT
      if ((u += phd->tb0[phd->translate[*s]][i]) >= phd->MAX_N)
	u -= phd->MAX_N;
      if ((v += phd->tb1[phd->translate[*s]][i]) >= phd->MAX_N)
	v -= phd->MAX_N;
      if ((w += phd->tb2[phd->translate[*s]][i]) >= phd->MAX_N)
	w -= phd->MAX_N;
#else
      struct tb_entry *tbp = &phd->tb[phd->translate[*s]][i];
      if ((u += tbp->tb0) >= phd->MAX_N)
	u -= phd->MAX_N;
      if ((v += tbp->tb1) >= phd->MAX_N)
	v -= phd->MAX_N;
      if ((w += tbp->tb2) >= phd->MAX_N)
	w -= phd->MAX_N;
#endif
      if (++i >= phd->MAX_L)
	i = 0;
      l--;
      s++;
    }
  if (u == v && ++v >= phd->MAX_N)
    v = 0;
  if (w == v)
    {
      if (++w >= phd->MAX_N)
	w = 0;
      if (w == u && ++w >= phd->MAX_N)
	w = 0;
    }
  else if (w == u)
    {
      if (++w >= phd->MAX_N)
	w = 0;
      if (w == v && ++w >= phd->MAX_N)
	w = 0;
    }
  return ((phd->g[u] ^ phd->g[v] ^ phd->g[w]) > phd->MAX_M) ? 0 :
    (phd->g[u] ^ phd->g[v] ^ phd->g[w]);
}
