/*
    mmorph, MULTEXT morphology tool
    Version 2.3, October 1995
    Copyright (c) 1994,1995 ISSCO/SUISSETRA, Geneva, Switzerland
    Dominique Petitpierre, <petitp@divsun.unige.ch>
*/
/*
    tfs.c

    handle the storage of typed feature structures (tfs)

    Dominique Petitpierre, ISSCO, Spring 1995
*/

#include "user.h"
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <search.h>

#define TFS_TABLE_CHUNK		100

s_tfs      *tfs_table;
static s_tfs *tfs_btree;
static t_card tfs_table_size;
static t_card old_tfs_table_size;
static t_card max_tfs_table_size;
static FILE *tfs_file;

/*
    General method:
    During creation or addition, an in memory btree (tsearch) is used for
    identification of tfs (insertion if not yet encountered).
    During lookup, only a simple array index is used.
*/

static s_tfs *tfs_compare_arg;

static int
#if defined(__STDC__)
tfs_compare(const T_PTR tfs_index1, const T_PTR tfs_index2)
#else
tfs_compare(tfs_index1, tfs_index2)
T_PTR       tfs_index1;
T_PTR       tfs_index2;

#endif
{
    int         comparison;
    t_card      project_card;
    s_tfs      *tfs1;
    s_tfs      *tfs2;

    if (tfs_index1 == (T_PTR) NULL) {
	tfs1 = tfs_compare_arg;
#if defined(DEBUG) && DEBUG > 2
	print_log("2=%ld\n", (long) tfs_index2 - 1);
#endif
	tfs2 = &tfs_table[(long) tfs_index2 - 1];
    }
    else if (tfs_index2 == (T_PTR) NULL) {
#if defined(DEBUG) && DEBUG > 2
	print_log("1=%ld\n", (long) tfs_index1 - 1);
#endif
	tfs1 = &tfs_table[(long) tfs_index1 - 1];
	tfs2 = tfs_compare_arg;
    }
    else {
#if defined(DEBUG) && DEBUG > 2
	print_log("1=%ld, 2=%ld\n", (long) tfs_index1 - 1, (long) tfs_index2 - 1);
#endif
	tfs1 = &tfs_table[(long) tfs_index1 - 1];
	tfs2 = &tfs_table[(long) tfs_index2 - 1];
    }
    comparison = tfs1->type - tfs2->type;
    if (comparison)
	return (comparison);
    else {
	project_card =
	    symbol_set[Type].ref[tfs1->type]->data->type.project_card;
	comparison = memcmp((T_PTR) tfs1->att_list, (T_PTR) tfs2->att_list,
			    project_card * sizeof(t_value));
	return (comparison);
    }
}

/* insertion of a tfs if not existing */
/*
    uses the trick to store an index in tfs_table in the btree rather
    than the usual pointer.
*/

int
add_tfs(new_tfs)
s_tfs      *new_tfs;

{
    long       *tfs_index_p;
    s_tfs      *tfs;
    int         project_card;

    tfs_compare_arg = new_tfs;
    /* using NULL rather than -1 to avoid sign extension problems */
    tfs_index_p = (long *) tsearch((T_PTR) NULL,
				   (T_PTR *) & tfs_btree,
				   tfs_compare);
    if (*tfs_index_p == (long) NULL) {	/* if it did not exist yet */
	if (tfs_table_size >= max_tfs_table_size) {
	    max_tfs_table_size += TFS_TABLE_CHUNK;
	    MY_REALLOC(tfs_table, max_tfs_table_size, s_tfs);
	}
	/* TODO: avoid the copying */
	tfs = tfs_table + tfs_table_size;
	tfs->type = new_tfs->type;
	project_card =
	    (int) symbol_set[Type].ref[tfs->type]->data->type.project_card;
	/* do not add a sentinel because no need to unify */
	MY_CALLOC(tfs->att_list, project_card, t_value);
	(void) memcpy((T_PTR) tfs->att_list, (T_PTR) new_tfs->att_list,
		      project_card * sizeof(t_value));
	tfs->var_map = NULL;	/* for completeness */
	*tfs_index_p = (long) ++tfs_table_size;	/* after baiting: switch */
    }
#if defined(DEBUG) && DEBUG > 2
    print_log("r=%ld\n", (int) *tfs_index_p - 1);
#endif
    return ((int) *tfs_index_p - 1);
}

static      bool_t
xdr_t_index(xdrs, index)
XDR        *xdrs;
t_index    *index;

{
    return (xdr_short(xdrs, (short *) index));
}

static      bool_t
xdr_t_value(xdrs, value)
XDR        *xdrs;
t_value    *value;

{
    return (xdr_u_long(xdrs, (unsigned long *) value));
}

static      bool_t
xdr_s_tfs(xdrs, tfs)
XDR        *xdrs;
s_tfs      *tfs;

{
    u_int       project_card;

    if (!xdr_t_index(xdrs, &(tfs->type)))
	return (FALSE);
    project_card =
	(u_int) symbol_set[Type].ref[tfs->type]->data->type.project_card;
    if (xdrs->x_op == XDR_DECODE) {
	tfs->var_map = NULL;	/* always NULL */
	MY_CALLOC(tfs->att_list, project_card, t_value);
    }
    return (xdr_vector(xdrs, (char *) tfs->att_list, project_card,
		       (u_int) sizeof(t_value), xdr_t_value));
}

/*
    saved_tfs_table creates or reads a "binary" file containing
    a collating sequence (order) description from or to a tfs_table
    array.
    The file should be already opened at calling time, and is not closed.
    It returns 0 if there was no problem,
	      -1 if some xdr routine failed,
	       1 if the crc_hash read is not the same.
*/

static int
saved_tfs_table(create, crc_hash)
bool_t      create;
unsigned char *crc_hash;

{
    XDR         xdrs;
    u_int       table_size;
    unsigned char hash[CRC32FILE_HASHLEN];
    char        creator[sizeof(CREATOR_NAME)];
    char        version[sizeof(VERSION_STR)];
    char       *hash_p;
    char       *creator_p;
    char       *version_p;

    if (create) {
	/* file should be created, so do encoding */
	xdrstdio_create(&xdrs, tfs_file, XDR_ENCODE);
	table_size = (u_int) tfs_table_size;
	hash_p = (char *) crc_hash;
	creator_p = CREATOR_NAME;
	version_p = VERSION_STR;
    }
    else {
	/* do decoding */
	xdrstdio_create(&xdrs, tfs_file, XDR_DECODE);
	tfs_table = NULL;	/* to force automatic memory allocation */
	hash_p = (char *) hash;
	creator_p = (char *) creator;
	version_p = (char *) version;
    }
    /*
       use xdr opaque instead of xdr_string so that the string length is not
       the first thing read from the file improving the chances to correctly
       report a bad signature (rather than corruption).
    */
    if (!(xdr_opaque(&xdrs, creator_p, sizeof(CREATOR_NAME))
	  && xdr_opaque(&xdrs, version_p, sizeof(VERSION_STR))
	  && xdr_opaque(&xdrs, hash_p, (u_int) CRC32FILE_HASHLEN)))
	return (-1);
    if (!create) {
	if (strcmp(creator, CREATOR_NAME)
	    || strcmp(version, VERSION_STR))
	    return (2);	/* bad signature */
	if (memcmp((T_PTR) crc_hash, (T_PTR) hash, CRC32FILE_HASHLEN))
	    return (1);	/* bad crc */
    }
    if (!xdr_array(&xdrs, (char **) &tfs_table, &table_size,
		   (u_int) T_CARD_MAX, (u_int) sizeof(s_tfs), xdr_s_tfs))
	return (-1);
    if (!create)
	tfs_table_size = (t_card) table_size;
    xdr_destroy(&xdrs);	/* tfs_file should be closed outside */
    return (0);
}

void
open_tfs_file(tfs_file_name, db_operation)
char       *tfs_file_name;
t_db_operation db_operation;

{
    char       *open_mode;

    /* assumed: db_operation & Lookup is true */
    if (db_operation & Create)
	open_mode = "w";
    else if (db_operation & Update)
	open_mode = "r+";
    else
	open_mode = "r";
    if ((tfs_file = fopen(tfs_file_name, open_mode)) == NULL)
	fatal_error("cannot open tfs file \"%s\" (errno=%d=%s)",
		    tfs_file_name, errno,
		    (errno < sys_nerr ? sys_errlist[errno] : "?"));
}


/* initialisation of tfs table and btree */
void
tfs_table_init(db_operation, gram_file_name, tfs_file_name, crc_hash)
t_db_operation db_operation;
char       *gram_file_name;
char       *tfs_file_name;
unsigned char *crc_hash;

{
    long        i;
    int         status;
    long       *tfs_index_p;

    /* assumed: db_operation & Lookup is true */
    if (db_operation & Create) {
	/* table and btree are empty */
	tfs_table_size = 0;
	old_tfs_table_size = 0;
	max_tfs_table_size = TFS_TABLE_CHUNK;
	MY_CALLOC(tfs_table, max_tfs_table_size, s_tfs);
	tfs_btree = NULL;
    }
    else {
	if ((status = saved_tfs_table(FALSE, crc_hash)))
	    if (status < 0)
		fatal_error("tfs file \"%s\" is corrupted", tfs_file_name);
	    else if (status == 1)	/* bad CRC */
		fatal_error("database file \"%s\" %s%s\"%s\": %s",
			    tfs_file_name, "does not correspond\n",
			    "to the morphology description in ",
			    gram_file_name, "please recreate it");
	    else	/* bad signature */
		fatal_error("%s \"%s\" %s %s %s %s",
			    "database file", tfs_file_name,
			    "was not created by", CREATOR_NAME,
			    "version", VERSION_STR);
	old_tfs_table_size = tfs_table_size;
	max_tfs_table_size = tfs_table_size;
	/* fill tfs btree */
	if (db_operation & Update) {
	    tfs_btree = NULL;
	    for (i = 0; i < (long) tfs_table_size; i++) {
		tfs_compare_arg = tfs_table + i;
		tfs_index_p = (long *) tsearch((T_PTR) NULL,
					       (T_PTR *) & (tfs_btree),
					       tfs_compare);
		*tfs_index_p = (long) i + 1;	/* after baiting: switch */

	    }
	}
    }
}

void
tfs_table_write(db_operation, tfs_file_name, crc_hash)
t_db_operation db_operation;
char       *tfs_file_name;
unsigned char crc_hash[];

{
    /* TODO: append new part of table, from old_tfs_table_size */
    if (db_operation & Update)
	/* overwrite with the new table */
	if (fseek(tfs_file, 0L, 0) == -1)
	    fatal_error("cannot rewind file \"%s\" (errno=%d=%s)",
			tfs_file_name, errno,
			(errno < sys_nerr ? sys_errlist[errno] : "?"));
    if (saved_tfs_table(TRUE, crc_hash) != 0)
	fatal_error("could not save tfs table in file \"%s\"",
		    tfs_file_name);
    /* flush and close */
    if (fclose(tfs_file) == EOF)
	fatal_error("cannot close tfs file \"%s\" (errno=%d=%s)",
		    tfs_file_name, errno,
		    (errno < sys_nerr ? sys_errlist[errno] : "?"));
}

void
dump_tfs_table()
{
    int         i;

    for (i = 0; i < tfs_table_size; i++)
	print_tfs_proj(&tfs_table[i]);
    if (debug & DEBUG_STAT)
	print_log("%d tfs\n", tfs_table_size);
}
