/*
 * Define routines for handling indices
 *
 *   int bki_create_context ( void *bkfctx, void **context );
 *   int bki_delete_context ( void *ctx );
 *   int bki_find_index ( void *ctx, char *pattern, int pat_type,
 *		char name[256], int *type, int *count);
 *   int bki_find_index_end ( void *ctx )
 *
 *   int bki_open_index ( void *ctx, char *name );   
 *   int bki_read_index ( void *ctx, char name[256] );
 *   int bki_close_index ( void *ctx );
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ssdef.h>

#include "bookreader_recdef.h"
#include "bookfile_io.h"
#include "bookfile_index.h"	/* validate prototypes */

struct index_context {
    void *bkf;			/* bkf_open context for doing I/O */
    bkrdr_recptr root;		/* pointer to root part for file */
    bkrdr_recptr cur;		/* Current open index page */
    int cur_alloc;		/* Allocation size of cur */
    int offset;			/* Current read offset in cur */
    int find_context;		/* Offset for find_index */
};
typedef struct index_context *bkictx;
static bkrdr_recptr lookup_index ( bkictx ctx, char *name );	/* fwd ref */
static int strmatchwild ( char *cand, char *pattern );

int bki_create_context ( void *bkf, void **context )
{
    bkictx ctx;
    int status, page_length, length;
    /*
     * allocate context block.
     */
    ctx = (bkictx) malloc ( sizeof(struct index_context) );
    if ( !ctx ) return SS$_INSFMEM;
    ctx->bkf = bkf;
    ctx->cur_alloc = 0;
    ctx->offset = 0;
    ctx->find_context = 0;
    /*
     * Make local copy of pointer to root part of file (part 0).  We assume
     * bkf_read returns address of a static structure for part 0.
     */
    status = bkf_read_page ( bkf, 0, &page_length, &ctx->root, &length );
    *context = (void *) ctx;
    return status;
}

int bki_delete_context ( void *context )
{
    bkictx ctx;
    /*
     * Deallocate any structures and sub-structures allocated.
     */
    ctx = (bkictx) context;

    if ( ctx->cur_alloc > 0 ) {
	free ( ctx->cur );
	ctx->cur_alloc = 0;
    }
    free ( ctx );
    return 1;
}
/*****************************************************************************/
/* Scan indexes listed in the root part for index with matching name(s).
 */
int bki_find_index ( void *context, char *pattern, int pat_type, 
	char name[256], int *type, int *count )
{
    bkictx ctx;
    bkrdr_recptr root, subrec;
    int status, has_wild, i;

    ctx = (bkictx) context;
    root = ctx->root;
    /*
     *  See if find_index is in progress.
     */
    if ( ctx->find_context == 0 ) {
	/* 
	 * Start new search, Examine for wildcards.
	 */
	for ( i = has_wild = 0; pattern[i]; i++ ) 
		if ( pattern[i] == '*' || pattern[i] == '%' ) {
	    has_wild = 1;
	    break;
	}
	if ( pat_type == -1 ) has_wild = 1;
	if ( has_wild ) {
	    /* Set find_context to offset of first sub-record */
	    ctx->find_context = sizeof(root->first);
	} else {
	    /* Do direct match */
	    subrec = lookup_index ( ctx, name );
	    if ( !subrec ) return 0;

	    *type = subrec->table.keyid;
	    *count = subrec->table.count;
	    strncpy ( name, subrec->table.title, subrec->table.tit_len );
	    return 1;
	}
    }
    /*
     * Continue search.
     */
    for ( i = ctx->find_context; i < root->gen.length;
		i += subrec->gen.length ) {
	subrec = (bkrdr_recptr) &root->reloff[i];
	if ( subrec->gen.length <= 0 ) break;	/* invalid subrec */
        /*
	 * Test only the table header records.
	 */
	if ( subrec->gen.type == BKSBREC_TABLE ) {
	    if ( pat_type >= 0 ) 	/* make sure type matches */
			if ( pat_type != subrec->table.keyid ) continue;
	    if ( 0 == strmatchwild ( subrec->table.title, pattern ) ) {
		*type = subrec->table.keyid;
		*count = subrec->table.count;
		strncpy ( name, subrec->table.title, subrec->table.tit_len );
		name[subrec->table.tit_len] = '\0';
		ctx->find_context = i + subrec->gen.length;
		return 1;
	    }
	}
    }
    /*
     * No more entires.
     */
    ctx->find_context = i;
    return 2;
}
/*
 * Return pointer to matching index record.
 */
static bkrdr_recptr lookup_index ( bkictx ctx, char *name )
{
    bkrdr_recptr subrec, root;
    int i, namlen;

    root = ctx->root;
    namlen = strlen ( name ) + 1;
    /*
     * Scan subrecords of root.
     */
    for ( i = sizeof(root->first); i < root->gen.length; 
		i+= subrec->gen.length ) {
	subrec = (bkrdr_recptr) &root->reloff[i];
	if ( subrec->gen.length <= 0 ) break;
	/*
	 * Check table records found for matching name.
	 */
	if ( subrec->gen.type == BKSBREC_TABLE ) {
	    if ( subrec->table.tit_len == namlen ) if 
		( 0==strncmp(subrec->table.title, name, namlen)) return subrec;
	}
    }
   return (bkrdr_recptr) 0;
}
/**************************************************************************/
/* Reset find context.
 */
int bki_find_index_end ( void *context )
{
    bkictx ctx;

    ctx = (bkictx) context;
    ctx->find_context = 0;
    return 1;
}

/************************************************************************/
/* Emulate VMS wildcard matching routine for c strings.  Algorithm obtained
 * by decoding VAX version of STR$MATCH_WILD.  A successful match returns
 * 0 to correspond with success status of tu_strncmp() routine.
 */
#define WC_FAIL 1
#define WC_MATCH 0
static int strmatchwild ( char *cand, char *pattern )
{
   char *save_cand, *save_pattern;
   /*
    * Main loop, check each character in pattern string.
    */
   for ( save_cand = (char *) 0; *pattern; pattern++ ) {
	if ( *pattern == '*' ) {
	    /*
	     * Wildcard character, if it is last character in pattern we know
	     * we have match.
	     */
	    if ( pattern[1] == '\0' ) return WC_MATCH;
	    /*
	     * checkpoint the current position of
	     * both strings so we can resume at the succeeding candidate
	     * string character if substring starting at current position
	     * fails.
	     */
	    save_cand = cand; 
	    save_pattern = pattern;

	} else if ( *cand++ != *pattern ) if ( *pattern != '%' ) {
	    /*
	     * Characters did not match, if no '*'s in preceding pattern,
	     * then strings do not match.
	     */
	    if ( !save_cand ) return WC_FAIL;
	    /*
	     * Resume search at next candidate character, updating
	     * the candidate checkpoint position.  If no more characters,
	     * then strings do not match.
	     */
	    if ( *save_cand++ == '\0' ) return WC_FAIL;
	    cand = save_cand;
	    pattern = save_pattern;
	}
   }
   /*
    * If pattern matched to this point, we succeed if candidate also at end.
    */
   if ( *cand == '\0' ) return WC_MATCH;
   return WC_FAIL;
}
/**************************************************************************/
int bki_open_index ( void *context, char *name )
{
    bkictx ctx;
    bkrdr_recptr tblrec, ndxrec;
    int status, page_length, length;

    /*
     * Lookup index.
     */
    ctx = (bkictx) context;
    tblrec = lookup_index ( ctx, name );
    if ( !tblrec ) return 0;
    /*
     * read page and make copy.
     */
    status = bkf_read_page ( ctx->bkf, tblrec->table.part, &page_length,
	&ndxrec, &length );
    if ( (status&1) == 0 ) return status;
    if ( ndxrec->gen.type != BKREC_INDEX ) return SS$_BUGCHECK;

    if ( ctx->cur_alloc <= page_length ) {
	/* Allocate buffer */
	if ( ctx->cur_alloc > 0 ) {
	    ctx->cur_alloc = 0;
	    free ( ctx->cur );
	}
	ctx->cur = (bkrdr_recptr) malloc ( page_length + 15000 );
	if ( !ctx->cur ) return SS$_INSFMEM;
	ctx->cur_alloc = page_length + 15000;
    }
    memcpy ( ctx->cur, ndxrec, page_length );
    ndxrec = ctx->cur;
    /*
     * Initalize offset.
     */
    ctx->offset = sizeof(ndxrec->gen);
    return 1;
}
int bki_close_index ( void *context )
{
    bkictx ctx;

    ctx = (bkictx) context;
    ctx->offset = 0;
    return 1;		/* nothing to do */
}
/****************************************************************************/
/* Read table index entry and current offset and return data to caller.
 */
int bki_read_index ( void *context, 
	short hdr[9], 		/* Index entry attributes + hor,ver of name */
	unsigned char attr[4],  /* Fontno, x, y, length of name */
	char name[256], 	/* Entry name */
	char **desc, 		/* Display descricption. */
	long *value )		/* Section number or special value */
{

    bkictx ctx;
    bkrdr_recptr ndxrec, subrec;
    struct text_rec *trec;
    int status, offset, i, j, sublen, namlen;
    long *vptr;
    /*
     * Recover context and do consitency checks.
     */
    ctx = (bkictx) context;
    offset = ctx->offset;
    if ( offset <= 0 ) return SS$_BADPARAM;
    ndxrec = ctx->cur;
    if ( offset >= ndxrec->gen.length ) return SS$_NOMOREFILES;
    /*
     * Look at record record at current offset, update offset used for
     * next read.
     */
    subrec = (bkrdr_recptr) &ndxrec->reloff[offset];
    if ( subrec->gen.type != BKSBREC_IXTXT ) return SS$_BUGCHECK;
    ctx->offset = offset + subrec->gen.length;
    /*
     * Copy fixed portion of ixtxt record to caller's arguments and initialize
     * variable portion to null values.
     */
    for ( i = 0; i < 7; i++ ) hdr[i] = subrec->ixtxt.ixhdr[i];
    hdr[7] = hdr[8] = 0;
    namlen = 0;
    name[0] = '\0';
    *desc = "";
    *value = 0;
    attr[0] = attr[1] = attr[2] = attr[3] = 0;
    /*
     * Scan text subrecords.
     */
    for ( i = sizeof(subrec->ixtxt); i < subrec->gen.length; i += sublen ) {
	trec = (struct text_rec *) &subrec->reloff[i];
	sublen = trec->reclen;
	switch ( trec->type ) {
	    case 1:
		break;
	    case 2:
	    case 3:
		/*
		 * Contcatenate strings.  Save attributes on first string only.
		 */
		j = sublen - sizeof(struct text_rec) + sizeof(trec->data);
		if ( namlen == 0 ) {
		    hdr[7] = trec->hor;
		    hdr[8] = trec->ver;
		    attr[0] = trec->fontno;
		    attr[1] = trec->x;
		    attr[2] = trec->y;
		}
		if ( (namlen + j) < 254 ) {
		    if ( namlen != 0 ) name[namlen++] = '\0';	/* fake word boundary */
		    memcpy ( &name[namlen], trec->data, j );
		    namlen += j;
		    name[namlen] = '\0';
		}
		attr[3] = namlen;
		break;

	    default:
		/*
		 * Assume this is description following name.
		 */
		*desc = &subrec->reloff[i];
		vptr = (long *) &subrec->reloff[i+strlen(*desc)+1];
		*value = *vptr;
		sublen = 0;
		i = subrec->gen.length;	/* force loop exit */
		break;
	}
    }
    return 1;
}
