/*
 * This module provides acccess to zero-terminated subfields within an
 * indexed file.  When you open the file you include a list of field
 * names that are associated with consecutive substrings with each
 * record.  This file implicitly includes access_db.h.
 *
 * The first record of the file has special meaning, it defines the
 * field lengths and names.  All fixed fields (index keys) are at the 
 * beginning of the record and consist of all blanks plus a nul-byte
 * separator.  The first field not containing blanks is a comma-separated
 * list of the field names in the same order they will occur in the
 * record.  Additional fields names after those that correspond to the
 * fixed fields are variable length fields.
 *
 * Author:	David Jones
 * Date:	30-DEC-1995
 * Revised:	18-JAN-1996		Added delete_rec function
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "subaccess.h"

/*****************************************************************************/
/*
 *  Define routines to access an indexed file by zero-terminated subfields
 * within the records.  Caller is responsible for padding fixed fields.
 */
#include "access_db.h"

/*****************************************************************************/
/* Open indexed file and initialize context for accessing it by field.
 * Return value is 1 on success and 0 on error.
 */
int subfield_open ( char *db_name,		/* file name */
		    char *mode,
		    int rec_limit,		/* max record size. */
		    subfield_ctx ctx )
{
    int length, i, j, status, fixed_count, max_fixed, item_limit;
    size_t size_read;
    char *rec, *field_names;
    /*
     * open file and zero structure.
     */
    if (  rec_limit <= 0 ) return 20;  /* Bad parameter */
    ctx->db = ifopen ( db_name, mode );
    if ( !ctx->db ) return 0;		/* open error */
    ctx->item_count = 0;
    ctx->rec_limit = ctx->rec_size = 0;
    ctx->hdr_data[0] = '\0';
    ctx->fld_names = ctx->outrec = (char *) 0;
    ctx->key = (char *) 0;

    /*
     * Read first record (sequentially).
     */
    rec = ctx->rec = (char *) malloc ( 1+(sizeof(char) * rec_limit) );
    if ( !ctx->rec ) {
	subfield_close ( ctx );
	return 0;
    }
    ctx->rec_limit = rec_limit;
    rec[rec_limit] = '\0';
    status = ifread_rec ( rec, (size_t) rec_limit, &size_read,
	ctx->db, 0, "", 0 );
    /*
     * Locate the field list within the first record.
     */
    field_names = "";
    max_fixed = 0;
    for ( item_limit = fixed_count = i = 0; i < size_read; i++ ) {
	length = strlen ( &rec[i] );
	if ( !length ) break;		/* null string illegal */
	if ( length > max_fixed ) max_fixed = length;
	if ( length == strspn(&rec[i]," ") ) {
	    fixed_count++;
	    i += length;		/* skip remainder */
	} else {
	    /*
	     * Substring is comma-separated list of names.
	     */
	    field_names = ctx->fld_names = malloc ( length+1 );
	    if ( !ctx->fld_names ) { subfield_close ( ctx ); return 0; }
	    strcpy ( ctx->fld_names, &rec[i] );
	    for ( item_limit = 1, j = 0; field_names[j]; j++ )
		if ( field_names[j] == ',' ) item_limit++;
	    i += length + 1;
	    break;
	}
    }
    if ( item_limit <= 0 || fixed_count > item_limit ) {
	/* Invalid field definition */
	subfield_close ( ctx );
	return 0;
    }
    /*
     * Copy header data.
     */
    if ( i < size_read ) {
	for ( j = 0; (j < 31) && 
		i < size_read && rec[i]; j++ ) ctx->hdr_data[j] = rec[i++];
	ctx->hdr_data[j] = '\0';
    }
    /*
     * Allocate work areas.
     */
    ctx->key = malloc ( max_fixed + 1 );
    if ( !ctx->key ) {
	subfield_close ( ctx );
	return 0;
    }
    ctx->fld = (struct subfield_item *) malloc
		(sizeof(struct subfield_item) * item_limit );
    if ( !ctx->fld ) {
	subfield_close ( ctx );
	return 0;
    }
    /*
     * Initialize the field definitions.
     */
    length++;
    ctx->item_count = 0;
    for ( i = j = 0; j < length; j++ ) {
	if ( field_names[j] == ',' || !field_names[j] ) {
	    field_names[j] = '\0';		/* terminate string */
	    ctx->fld[ctx->item_count].size = 0;
	    ctx->fld[ctx->item_count].name = &ctx->fld_names[i];
	    ctx->fld[ctx->item_count].value = "";
	    ctx->item_count++;
	    i = j + 1;
	}
    }
    if ( item_limit != ctx->item_count ) {
	printf("Consistent check failed on field count.\n");
	return 0;
    }
    for ( i = j = 0; i < fixed_count; i++ ) {
	length = strlen ( &rec[j] );
	ctx->fld[i].size = length;
	j += length + 1;
    }
    return 1;
}
/*****************************************************************************/
/* Close indexed file and cleanup. structure.
 */
int subfield_close ( subfield_ctx ctx )
{
    if ( ctx->db ) { 
	/*
	 * File open, close and free allocated subelements.
	 */
	ifclose  ( ctx->db ); ctx->db = (IFILE *) 0; 
	if ( ctx->item_count > 0 ) free ( ctx->fld );
	if ( ctx->rec_limit > 0 ) free ( ctx->rec );
	if ( ctx->fld_names ) free ( ctx->fld_names );
	if ( ctx->key ) free ( ctx->key );
	if ( ctx->outrec ) free ( ctx->outrec );
    }
    return 1;
}
/*****************************************************************************/
/* 
 * specify a key_size of zero for sequential read
 */
int subfield_read ( subfield_ctx ctx, char *key, int key_num, int key_size )
{ 
    int status, i, k, j;
    size_t size_read;
    char *rec;
    /*
     * Pad key to full size.
     */
    if ( key_size > 0 ) {
	if ( key_num > ctx->item_count ) return 20;
	if ( key_size > ctx->fld[key_num].size ) return 20;  /* too large */
	strncpy ( ctx->key, key, key_size );
	i = key_size;
	key_size = ctx->fld[key_num].size;
	while ( i < key_size ) ctx->key[i++] = ' '; ctx->key[i] = '\0';
	key = ctx->key;
    }
    /*
     * Read record.
     */
    ctx->rec_size = 0;
    rec = ctx->rec;
    status = ifread_rec ( rec, (size_t) ctx->rec_limit, &size_read,
	ctx->db, key_num, key, key_size );
    if ( (status&1) == 1 ) {
	/*
	 * Parse record into items.
	 */
	ctx->rec_size = size_read;
	for ( k = i = j = 0; j < size_read; j++ ) {
	    if ( !rec[j] ) {
		ctx->fld[k++].value = &rec[i];
		if ( k >= ctx->item_count ) break;	/* got all */
		i = j + 1;
	    }
	}
	/*
	 * Trim spaces from fixed fields.
	 */
	for ( i = 0; i < ctx->item_count; i++ ) {
	    if ( ctx->fld[i].size == 0 ) break;
	    for ( j = ctx->fld[i].size -1; j >= 0 &&
		ctx->fld[i].value[j] == ' '; --j );

	    ctx->fld[i].value[j+1] = '\0';
	}
    } else status = 0;
    return status;
}
/*****************************************************************************/
/*
 * Lookup field value in current record and return pointer to it.
 * Return null if name unknown.
 */
char *subfield_value ( subfield_ctx ctx, char *name )
{
   int i;
   for ( i = 0; i < ctx->item_count; i++ ) {
	if ( 0 == strcmp ( ctx->fld[i].name, name ) ) return ctx->fld[i].value;
   }
   return (char *) 0;	/* unknown name */
}
/*****************************************************************************/
/*
 * Record is built into contiguous area using field items.
 */
int subfield_set ( subfield_ctx ctx, char *name, char *value )
{
   int i;
   for ( i = 0; i < ctx->item_count; i++ ) {
	if ( 0 == strcmp ( ctx->fld[i].name, name ) ) {
	    ctx->fld[i].value = value;
	    return 1;
	}
   }
   return 0;		/* unknown name */
}
static int build_outrec ( subfield_ctx ctx, int *out_length  )
{
    int i, j, vlen, length, status;

    if ( !ctx->outrec ) {
	ctx->outrec = malloc ( ctx->rec_limit );
	if ( !ctx->outrec ) return 0;		/* allocation error */
    }
    for ( *out_length = length = i = 0; i < ctx->item_count; i++ ) {
	vlen = strlen ( ctx->fld[i].value ) + 1;
	if ( ctx->fld[i].size > 0 ) {
	     /* fixed field, padd with spaces. */
	     if ( ctx->fld[i].value[0] == ' ' ) return 20;
	     strncpy ( &ctx->outrec[length], ctx->fld[i].value,
			ctx->fld[i].size );
	     j = length + vlen  -  1;
	     length += ctx->fld[i].size + 1;
	     while ( j < length ) ctx->outrec[j++] = ' ';
	     ctx->outrec[j-1] = '\0';
	} else  if ( vlen + length > ctx->rec_limit ) {
	    return 0;
	} else {
	    strcpy ( &ctx->outrec[length], ctx->fld[i].value );
	    length += vlen;
	}
    }
    *out_length = length;
    return 1;
}
/*****************************************************************************/
/* Add/update file record.
 */
int subfield_write ( subfield_ctx ctx ) 
{
    int status, length;
    status = build_outrec ( ctx, &length );
    if ( (status&1) == 1 ) status = ifwrite_rec ( ctx->outrec,
	(size_t) length, ctx->db );
    return status;
}
int subfield_update ( subfield_ctx ctx )
{
    int status, length;
    status = build_outrec ( ctx, &length );
    if ( (status&1) == 1 ) status = ifupdate_rec ( ctx->outrec,
	(size_t) length, ctx->db );
    return status;
}
/*****************************************************************************/
/* delete file record.
 */
int subfield_delete_rec ( subfield_ctx ctx ) 
{
    int status, length;
    status = ifdelete_rec ( ctx->db );
    return status;
}
/*****************************************************************************/
/* Speical update to the header record.  Call this routine invalidates
 * the current read data.  The header record is read again and parsed and
 * the old optional data, if present is saved.
 */
int subfield_update_header ( subfield_ctx ctx, 
	int (*update_cb)(subfield_ctx) )
{
    int status, i, j, length, hdr_off;
    size_t size_read;
    char *field_names;
    /*
     * Read the header.
     */
    for ( i = 0; i < ctx->fld[0].size; i++ ) ctx->key[i] = ' ';
    status = ifread_rec ( ctx->rec, (size_t) ctx->rec_limit, &size_read,
	ctx->db, 0, ctx->key, ctx->fld[0].size );
    if ( (status&1) == 0 ) return status;
    /*
     * Locate the field list within the first record.
     */
    field_names = (char *) 0;
    for ( hdr_off = i = 0; i < size_read; i++ ) {
	length = strlen ( &ctx->rec[i] );
	if ( !length ) break;		/* null string illegal */
	if ( length == strspn(&ctx->rec[i]," ") ) {
	    i += length;		/* skip remainder */
	} else {
	    /*
	     * Skip the field name list.
	     */
	    i += length;
	    hdr_off = i + 1;	/* start of header. */
	    break;
	}
    }
    if ( hdr_off ) {
	/* Copy the old data */
	length = strlen ( &ctx->rec[hdr_off] );
	if ( length > 31 ) length = 31;
	strncpy ( ctx->hdr_data, &ctx->rec[hdr_off], length );
	ctx->hdr_data[length] = '\0';
    } else {
	/*
	 * Caculate place where header should go.
	 */
	hdr_off = size_read;
	if ( ctx->rec[hdr_off-1] ) {
	     /* terminator missing on field list */
	    ctx->rec[hdr_off++] = '\0';
	}
	ctx->hdr_data[0] = '\0';
    }
    /*
     * Call user routine to update the header data while record is locked.
     */
    update_cb ( ctx );
    /*
     * rewrite record.
     */
    length = strlen ( ctx->hdr_data );
    strcpy ( &ctx->rec[hdr_off], ctx->hdr_data );
    status = ifupdate_rec ( ctx->rec, hdr_off + length + 1, ctx->db );

    return status;
}
