/*
 * Define routines for traversing bodypart sections within a bookreader
 * file.
 *
 *   int bks_create_section_cursor ( void *bkf_ctx, void **cursor );
 *   int bks_delete_section_cursor ( void *cursor );
 *   int bks_seek_section ( void *cursor, int offset, int direction,
 *	int *type, int *length, long *hdr[9] );
 *   int bks_read_section ( void *cursor, int *type, short *h_v, 
 *	unsigned char attr[4], int *length, char **data, int *is_last );
 *
 *  Author: David Jones
 *  Date:   12-SEP-1995
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ssdef.h>

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

struct section_context {
    void *bkf;			/* bkf_open context for doing I/O */
    bkrdr_recptr root;		/* pointer to root part for file */
    bkrdr_recptr cur;		/* Currently open bodypart page */
    bkrdr_recptr sec;		/* current  subrec within bodypart */
    long sect_id;		/* Currently positions section (-1) for none*/
    int cur_alloc;		/* Allocation size of cur */
    int cur_offset;		/* Offset of sec within cur */
    int offset;			/* Current read offset in sect */
};
typedef struct section_context *bksctx;

int bks_create_section_cursor ( void *bkf, void **cursor )
{
    bksctx ctx;
    int status, page_length, length;
    /*
     * allocate context block.
     */
    ctx = (bksctx) malloc ( sizeof(struct section_context) );
    if ( !ctx ) return SS$_INSFMEM;
    ctx->bkf = bkf;
    ctx->cur_alloc = 0;
    ctx->sect_id = -1;
    ctx->offset = 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 );
    *cursor = (void *) ctx;
    return status;
}

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

    if ( ctx->cur_alloc > 0 ) {
	free ( ctx->cur );
	ctx->cur_alloc = 0;
    }
    free ( ctx );
    return 1;
}
/****************************************************************************/
/* Scan starting at current position for section with matching ID. 
 */
static int scan_for_section ( bksctx ctx, int id )
{
    int i;
    bkrdr_recptr cur, sec;
    cur = ctx->cur;
    for ( i = ctx->cur_offset; i < cur->gen.length; i += sec->gen.length ) {
int kk;
	sec = (bkrdr_recptr) &cur->reloff[i];
#ifdef DEBUG
printf("  [%d/%d] subrec type %d, length: %d  id: ", i, 
		cur->gen.length, sec->gen.type,	sec->gen.length );
for (kk=0; kk < 9; kk++) printf("%d%s",sec->bodytext.bodhdr[kk],
(kk < 8) ? " " : "\n" );
#endif
	if ( sec->gen.length < 0 ) return SS$_BUGCHECK;

	if ( sec->gen.type == BKSBREC_BODYTEXT ) {
	    if ( sec->bodytext.sect_id == id ) break;
	} else if ( sec->gen.type == BKSBREC_FIGHOT ) {
	    if ( sec->hotspot.target == id ) break;
	} else if ( sec->gen.type == BKSBREC_FIGURE ) {
	    if ( sec->figure.sect == id ) break;
	} else if ( sec->gen.type == BKSBREC_EXTENSION ) {
	    if ( sec->extension.sect == id ) break;
	} else {
	    /* skip it unless current is one less */
	    if ( ctx->sect_id+1 == id ) break;
	}
    }
    ctx->cur_offset = i;
    ctx->sec = sec;
    if ( i >= cur->gen.length ) return SS$_BUGCHECK; /* not found */
    ctx->sect_id = id;

    ctx->offset = sizeof(sec->bodytext);	/* prepare for read_section() */
    return 1;
}
/****************************************************************************/
/* Read page and intialize context for reading it's sections.
 */
static int load_bodypart ( bksctx ctx, int part_num )
{
    bkrdr_recptr cur, tmp;
    int status, tmp_len, page_length, sect_id;
   /*
    * Read page and validate type, function return pointer to I/O buffer.
    */
   status = bkf_read_page ( ctx->bkf, part_num, &page_length, &tmp, &tmp_len );
   if ( (status&1) == 0 ) return status;	/* abort on read error */
   if ( tmp->gen.type != BKREC_BODYPART ) return SS$_BUGCHECK;
   /*
    * Make copy of I/O buffer into cursor's private buffer, expanding it
    * if needed.
    */
   if ( page_length > ctx->cur_alloc ) {
	if ( ctx->cur_alloc > 0 ) free ( ctx->cur );
	ctx->cur_alloc = 0;
	cur = ctx->cur = (bkrdr_recptr) malloc ( page_length+15000);
	if ( !cur ) return SS$_INSFMEM;
	ctx->cur_alloc = page_length + 15000;
    }
    memcpy ( ctx->cur, tmp, page_length );
    /*
     * Now position context for read_section calls.
     */
    ctx->cur_offset = sizeof(cur->body);	/* offset of 1st subrec */
    ctx->offset = sizeof(cur->bodytext);	/* offset subrec's 1st textrec*/
    tmp_len = bkf_lookup_part_section ( ctx->bkf, part_num, &sect_id );
    ctx->sect_id = sect_id;

    return status;
}
/****************************************************************************/
/*
 * Seek_section input args corresponde to fseek():
 *   direction = 0 -> offset is absolute.
 *   direction = 1 -> offset is relative to current position.
 *   direction = 2 -> offset is relative to end (unimplemented).
 * Seeking to a section prepares the section for subtext scans, seeking
 * to (cursor,0,1) resets context for the current cursor.
 */
int bks_seek_section ( void *cursor, int offset, int direction,
	int *type, int *length, long hdr[9] )
{
    bksctx ctx;
    bkrdr_recptr cur,sec;
    int status, pos, copy_size;

    ctx = (bksctx) cursor;

#ifdef DEBUG
printf("/bks/ seek_section entered, current sect=%d, alloc=%d, offset=%d/%d\n",
	ctx->sect_id, ctx->cur_alloc, ctx->cur_offset, ctx->offset );
#endif
    cur = ctx->cur;
    sec = ctx->sec;
    if ( (offset == 1) && (direction == 1) ) {
	/*
	 * Do sequential seek to start of next subrecord in part.
	 */
	if ( ctx->sect_id < 0 ) return SS$_BADPARAM; /* no current position */
	pos = ctx->cur_offset + sec->gen.length;
	if ( pos >= cur->gen.length ) {
	    /*
	     * Advance to next part.
	     */
	    status = load_bodypart ( ctx, cur->body.nextpart );
	    if ( (status&1) == 0 ) return status;
	    cur = ctx->cur;
	    pos = sizeof(cur->body);
	} else {
	    /* continue in same part */
	    ctx->cur_offset = pos;
	}
	ctx->offset = sizeof(sec->bodytext);
	sec = ctx->sec = (bkrdr_recptr) &cur->reloff[pos];
	offset = ctx->sect_id;
    } else  if ( direction == 1 ) {
	/* Convert relative position to absolute */
	if ( ctx->sect_id < 0 ) return SS$_BADPARAM; /* no current position */
	offset += ctx->sect_id;
	if ( offset <= 0 ) return SS$_BADPARAM;
    } else if ( (direction != 0) || (offset < 0) ) {
	return SS$_BADPARAM;
    } else if ( offset == ctx->sect_id ) {
	/* Redo current subrec */
	ctx->offset = sizeof(sec->bodytext);
    } else if ( (offset-1) == ctx->sect_id && (ctx->sect_id >= 0) &&
	 (cur->gen.length > (ctx->cur_offset + sec->gen.length)) ) {
	/* 
	 * We want next section and it is likely in current part 
	 */
#ifdef DEBUG
printf("/bks/ checking same part for section %d\n", offset );
#endif
	ctx->cur_offset += sec->gen.length;
	scan_for_section ( ctx, offset );
	sec = ctx->sec;
    }
    if ( offset != ctx->sect_id ) {
	int part_num;
	/*
	 * We still haven't found part, use section map to locate
	 * part number containing the section.
	 */
	ctx->sect_id = -1;	/* invalidate current position */
	status = bkf_lookup_section ( ctx->bkf, offset, &part_num );
	if ( (status&1) == 0 ) return status;	/* not in map */
#ifdef DEBUG
printf("/bks/ checking new part (%d) for section %d\n", part_num, offset );
#endif
	/*
	 * Load page and seek to section.
	 */
	status = load_bodypart ( ctx, part_num );
	if ( (status&1) == 0 ) return status;

	status = scan_for_section ( ctx, offset );
	if ( (status&1) == 0 ) return status;
	sec = ctx->sec;
    }

    /*
     * return the section header to the caller.  Call may specify null for
     * length and hdr arguments.
     */
    *type = sec->gen.type;
    if ( length ) *length = sec->gen.length;
    if ( hdr ) {	/* make it optional */
	copy_size = sizeof(long)*9;
	if ( copy_size + sizeof(sec->gen) > sec->gen.length )
        	copy_size = sec->gen.length - sizeof(sec->gen);
        memcpy ( hdr, sec->bodytext.bodhdr, copy_size );
    }
    return 1;
}
/************************************************************************/
/* Return next text record from current section. 
 */
int bks_read_section ( void *cursor, int *type, short *h_v, 
	unsigned char attr[4], int *length, char **data, int *is_last )
{
    bksctx ctx;
    bkrdr_recptr cur,sec;
    struct text_rec *txt;
    int status, i, txtlen;

    ctx = (bksctx) cursor;
    sec = ctx->sec;
    i = ctx->offset;
#ifdef DEBUG
printf("/bks/ reading section of type: %d, length: %d, offset: %d\n", 
sec->gen.type, sec->gen.length, i);
#endif
    if ( i < sec->gen.length ) {
	/* return data */
	txt = (struct text_rec *) &sec->reloff[i];
	if ( sec->gen.type != BKSBREC_BODYTEXT 
		|| txt->type < 1 || txt->type > 3 ) {
	    *type = 0;
	    *length = txt->reclen;
	    h_v[0] = h_v[1] = 0;
	    attr[0] = attr[1] = attr[2] = attr[3] = 0;
	    txtlen = sec->gen.length - i;	/* rest of section */
	    *length = txtlen;
	    *data = &sec->reloff[i];
	} else {
	    /* Extract text header */
	    *type = txt->type;
	    txtlen = txt->reclen;
	    h_v[0] = txt->hor; h_v[1] = txt->ver;
	    attr[0] = txt->fontno; attr[1] = txt->x; attr[2] = txt->y;
	    attr[3] = txt->data[0];
	    *length = txtlen - 9;	/* subtact header */
	    *data = txt->data;
	}
	/*
	 * Update offset for next call.
	 */
	ctx->offset = i + txtlen;
	if ( i > ctx->offset ) return SS$_BUGCHECK;
	if ( ctx->offset < sec->gen.length ) *is_last = 0;
	else {
	    cur = ctx->cur;
	    if ( cur->gen.length > (ctx->cur_offset + sec->gen.length)) 
		*is_last = 1;
	    else
		*is_last = 2;
	}
	return 1;

    } else {
	/* No more records */
	cur = ctx->cur;
	if ( cur->gen.length > (ctx->cur_offset + sec->gen.length)) 
		*is_last = 1;
	else
		*is_last = 2;
	return SS$_ENDOFFILE;
    }
}
int bks_seek_part ( void *cursor, int part_offset, int dir, int *sect_cnt )
{
    bksctx ctx;
    bkrdr_recptr cur,sec;
    int status, i;

    ctx = (bksctx) cursor;
    cur = ctx->cur;

#ifdef DEBUG
printf("/bks/ seek_part entered, current sect=%d, alloc=%d, offset=%d/%d\n",
	ctx->sect_id, ctx->cur_alloc, ctx->cur_offset, ctx->offset );
printf("/bks/ seek_part offset: %d dir: %d\n", part_offset, dir );
#endif

    if ( dir == 1 ) {
	/*
	 * Relative seek, traverse links in part headers.
	 */
	if ( ctx->sect_id < 0 ) return SS$_BADPARAM; /* no current position */
	while ( part_offset != 0 ) {
	    if ( part_offset < 0 ) {
	        status = load_bodypart ( ctx, ctx->cur->body.prevpart );
		part_offset++;
	    } else {
		part_offset--;
	        status = load_bodypart ( ctx, ctx->cur->body.nextpart );
	    }

	    if ( (status&1) == 0 ) return status;
	}
    } else if ( dir == 0 ) {
	status = load_bodypart ( ctx, part_offset );
	if ( (status&1) == 0 ) return status;
    }
    if ( dir != 0 ) return SS$_BADPARAM;
    /*
     * scan the sub-records to determine the initial section id
     */
    cur = ctx->cur;
    *sect_cnt = cur->body.sectcnt;
    ctx->sec = (bkrdr_recptr) &cur->reloff[sizeof(cur->body)];

   return 1;
}

int bks_get_cursor_info ( void *cursor, 
	int parts_info[4], 	/* 0-cur_part, 1-prev, 2-next, 3-reserved */
	int sect_info[4] )	/* 0-first_sect, 1-sect_count, 2-cur_sect */
{
    bksctx ctx;
    bkrdr_recptr cur,sec;
    struct text_rec *txt;
    int status, i, txtlen;

    ctx = (bksctx) cursor;
    sec = ctx->sec;

    cur = ctx->cur;
    if ( !cur ) return SS$_ENDOFFILE;
    bkf_lookup_first_section ( ctx->bkf, ctx->sect_id,
		&parts_info[0], &sect_info[0] );
    parts_info[1] = cur->body.prevpart;
    parts_info[2] = cur->body.nextpart;
    parts_info[3] = cur->body.unk1;

    sect_info[1] = cur->body.sectcnt;
    sect_info[2] = ctx->sect_id;
    sect_info[3] = ctx->offset;
    return 1;
}
