/*
 * This module provides a utility for processing a template file,
 * generating an output stream of the template contents with conditional
 * and/or iterative substitution from fields of a file.
 *
 * The template file marks the fields to be process by preceding the field
 * with a '%[' and closing with a ']'.
 *
 * int process_template_file ( fname, out, outctx,  callback, cbctx );
 * int process_template_string ( template, out, outctx, callback, cbctx );
 *
 * Example: %[$readfile sys$disk:[dat]test <sub-template>]
 *   %[# comment ]
 *   %[$LB]  %[$RB]		! inserts %[ and ], respectively.
 *   %[$if tag <sub-template>]
 *   %[$elif tag <sub-template>]
 *   %[$else <sub-template>]
 *   %[tag]
 *   %[$escape <sub-template>]	! redirects output to callback
 *
 *   Revised: 14-MAY-1996	! fixed bug in emit_bytes flush logic.
 */
#include <stdio.h>
#include <stdlib.h>
#include <ssdef.h>
#include <string.h>
#include <ctype.h>
#include "subaccess.h"

#include "template.h"		/* verify prototypes */

static int def_subfield_count = 0;		/* default symbol list */
static struct subfield_context def_subfield;

#define SRC_REC_SIZE 8192
#define SRC_REC_MAXFLD 64
struct field_context {
    int line_num;
    int escape_mode;
    int if_state;			/* 0-false, 1-true */
    int out_len;
    template_callback out, escape;
    void *out_arg, *escape_arg;
    struct subfield_context subfield;
    char outbuf[512];			/* scratch space */
};
static int parse_template ( char *template, int length,
	struct field_context *ctx );	/* forward reference */

static void init_context ( struct field_context *src_ctx,
	struct field_context *dst_ctx ) 
{
    dst_ctx->line_num = 0;
    dst_ctx->escape_mode = 0;
    dst_ctx->if_state = 0;
    dst_ctx->out_len = 0;
    dst_ctx->out = src_ctx->out;
    dst_ctx->out_arg = src_ctx->out_arg;
    dst_ctx->escape = src_ctx->escape;
    dst_ctx->escape_arg = src_ctx->escape_arg;
    dst_ctx->subfield.item_count = 0;		/* don't inherits fields */
}

static int emit_bytes ( char *buf, int buflen, struct field_context *ctx )
{
    int status, j;
    status = 1;
#ifdef DEBUG
printf("Emit %d bytes to %x, escape-mode: %d, current len: %d\n", buflen, 
ctx, ctx->escape_mode, ctx->out_len);
if ( buflen > 0 ) printf("   buf: '%s'\n", buf );
#endif
    if ( buflen < 0 ) {
	if ( buf ) buflen = strlen ( buf );
	else {
	    /* flush remaining bytes */
	    if ( ctx->out_len <= 0 ) return 1;
	    if ( ctx->escape_mode ) {
		    status = (*ctx->escape)(ctx->escape_arg,
			ctx->outbuf, ctx->out_len );
	    } else {
		status = (*ctx->out)(ctx->out_arg, ctx->outbuf, 
			ctx->out_len );
	    }
	    ctx->out_len = 0;
	    return status;
	}
    }
    while ( (status&1) == 1 ) {
	if ( buflen + ctx->out_len >= sizeof(ctx->outbuf) ) {
	    /* copy and flush */
	    j = sizeof(ctx->outbuf) - ctx->out_len;
	    memmove (&ctx->outbuf[ctx->out_len], buf, (size_t) j);
	    if ( ctx->escape_mode ) {
		status = (*ctx->escape)(ctx->escape_arg,
			ctx->outbuf, sizeof(ctx->outbuf) );
	    } else {
		status = (*ctx->out)(ctx->out_arg, ctx->outbuf,
			sizeof(ctx->outbuf) );
	    }
	    ctx->out_len = 0;
	    buf = &buf[j];
	    buflen = buflen - j;
	} else {
	    /*
	     * All of bulen fit in outbuf, copy and exit.
	     */
	    memmove (&ctx->outbuf[ctx->out_len], buf, (size_t) buflen);
	    ctx->out_len += buflen;
	    break;
	}
    }
    return status;
}
/*****************************************************************************/
/* Main routine for interpret the contents of a template field.
 */
static int process_field ( char *field, int fldlen, struct field_context *ctx )
{
    int i, j, k, key_num, status;
    unsigned int reclen;
    struct field_context subctx;
    char fname[256], *rec, *kn_str;
    /*
     * Parse the field.
     */
    if ( *field != '$' && *field != '#' ) {
	/*
	 * Assume field is name of subfield in current context to
	 * sustitute.  Lookup value and output.
	 */
	char *value;
	value = subfield_value ( &ctx->subfield, field );
	if ( value ) {
	    status = emit_bytes ( value, -1, ctx );
	    return status;
	} else {
	    ctx->escape_mode = 0;
	    emit_bytes ( "Unknwon field: ", -1, ctx );
	    emit_bytes ( field, fldlen, ctx );
	    emit_bytes ( "\n", 1, ctx );
	    return 0;
	}
    } else if ( *field == '#' ) {
	/* Comment, ignore */
    } else if ( strncmp ( field, "$readfile ", 10 ) == 0 ) {
	/*
	 * Open file.  Parse out filename.
	 */
	for ( i = 10; isspace(field[i]); i++ );
	for ( j = i+1; !isspace(field[j]); j++ ) if ( !field[j] ) {
	    /* Missing 3rd field */
	    emit_bytes ( "Missing parameter on $readfile\n", -1, ctx );
	    return 0;
	}
	if ( (j - i) > 255 ) {
		/* Filename too long */
	    return 0;
	}

	strncpy ( fname, &field[i], j-i );
	fname[j-i] = '\0';
	key_num = 0;
	kn_str = strchr ( fname, '#' );		/* select alt. key # */
	if ( kn_str ) {
	    *kn_str++ = '\0';
	    sscanf ( kn_str, "%d", &key_num );
	}
	/*
	 * Open file.
	 */
	init_context ( ctx, &subctx );

	status = subfield_open ( fname, "r", 
			SRC_REC_SIZE, &subctx.subfield );
	if ( (status&1) == 0 ) {
		emit_bytes ( "Error openning file '", -1, ctx );
		emit_bytes ( fname, -1, ctx );
		emit_bytes ( "'\n", 2, ctx );
	        return 0;
	}
	/*
	 * If key_num is not primary, read first record by that key to
	 * setup subsequent sequetial reads.
	 */
	if ( key_num > 0 ) {
	    status = subfield_read ( &subctx.subfield, " ", key_num, 1);
	}
	/*
	 * Make context for outputting.
	 */
	emit_bytes ( (char *) 0, -1, ctx );
	/*
	 * Read records of file.
	 */
	while ( (status=subfield_read(&subctx.subfield, "", key_num, 0))&1 ) {
	    /*
	     * Recursively process remainder.
	     */
#ifdef DEBUG
for(i=0;i<subctx.subfield.item_count;i++) 
  printf("fld[%d].name='%s', value='%s'\n", i,subctx.subfield.fld[i].name,
subctx.subfield.fld[i].value );
#endif
	    parse_template ( &field[j+1], fldlen-j-1, &subctx );
 	}
	subfield_close ( &subctx.subfield );
	emit_bytes ( (char *) 0, -1, &subctx );
    } else if ( strncmp ( field, "$escape ", 8 ) == 0 ) {
	/*
	 * Flag that we are in escape mode and process.
	 */
	int save_escape = ctx->escape_mode;
	emit_bytes ( (char *) 0, -1, ctx );
	ctx->escape_mode = 1;
	parse_template ( &field[8], fldlen - 8, ctx );
	emit_bytes ( (char *) 0, -1, ctx );
	ctx->escape_mode = save_escape;

    } else if ( (strncmp ( field, "$if ", 4 ) == 0) ||
	  (strncmp ( field, "$elif ", 6 ) == 0) ) {
	/*
	 * Conditional processing, else and elseif
	 */
	char save_delim, *value;
	for ( i = 3; !isspace(field[i]); i++ );
	if ( (i == 5) && ctx->if_state ) return 1; /* skip */
	for ( ; isspace(field[i]); i++ );	/* skip white */
	if ( !field[i] ) {
	    emit_bytes("Syntax error on $if\n",-1,ctx);
	    return 0;
	}
	for ( j = i+1; !isspace(field[j]); j++ );	/* find end */
	save_delim = field[j]; field[j] = '\0';
	value = subfield_value ( &ctx->subfield, &field[i] );
	field[j] = save_delim;
	if ( save_delim ) j++;
	if ( !value ) {
	    emit_bytes ( "Unknown field in $if: ", -1, ctx );
	    emit_bytes ( &field[i], j-i, ctx );
	    emit_bytes ( "\n", 1, ctx );
	} else if ( *value ) {
	    ctx->if_state = 1;
	    status = parse_template ( &field[j], fldlen - j, ctx );
	} else {
	    ctx->if_state = 0;
	}
    } else if ( strncmp ( field, "$else ", 6 ) == 0 ) {
	/*
	 * Process else, only process if if-state false.
	 */
	if ( ctx->if_state ) return 1;	/* skip */
	status = parse_template ( &field[6], fldlen-6, ctx );
    } else {
	emit_bytes ( "Unknown directive: ", -1, ctx );
	emit_bytes ( field, fldlen, ctx );
	return 0;
    }
    return 1;
}
/****************************************************************************/
/*
 * Scan string for %[field] constructs.
 */
static int parse_template ( char *template, int length,
	struct field_context *ctx )
{
    int i, state, field_start, bdepth;
    for ( state = i = 0; i < length; i++) switch ( state ) {
	case 0:
	    /* Check for start */
	    if ( template[i] == '%' ) {
		if ( i+1 < length ) if ( template[i+1] == '[' ) {
		    i++;	/* skip the '%' */
		    state = 1;
		    break;
		}
	    }
	    /* Add character to output */
	    if ( ctx->out_len < sizeof(ctx->outbuf) ) {
		ctx->outbuf[ctx->out_len++] = template[i];
	    } else {
		emit_bytes ( &template[i], 1, ctx );
	    }
	    break;
	case 1:
	    /*
	     * Save position and scan for closing bracket.
	     */
	    field_start = i;
	    bdepth = 0;
	    state = 2;

	case 2:
	    if ( template[i] == ']' ) {
		/*
		 * Check for embedded brackets.
		 */
		if ( --bdepth >= 0 ) break;
		/* 
		 * field closed, process it 
		 */
		template[i] = '\0';
		if ( !process_field ( &template[field_start],
			i - field_start, ctx ) ) length = 0;
		template[i] = ']';
		state = 0;	/* reset */
	    } else if ( template[i] == '[' ) bdepth++;
	    break;
    }
    return 1;
}
/****************************************************************************/
/* Make global list of default template fields.
 */
int set_default_template_fields ( int count, char **name, char **value )
{
    int i;
    /*
     * Free current array and allocate new.
     */
    if ( def_subfield_count > 0 ) free ( def_subfield.fld );
    def_subfield.item_count = def_subfield_count = 0;

    if ( count > 0 ) {
        def_subfield.fld = (struct subfield_item *) malloc ( 
		sizeof(struct subfield_item) * count );
        if ( !def_subfield.fld ) return 0;
	def_subfield_count = def_subfield.item_count = count;
    }
    /*
     * Initialize list.  Note we do not allocate separate storage for
     * the strings.
     */
    for ( i = 0; i < count; i++ ) {
	def_subfield.fld[i].size = 0;
	def_subfield.fld[i].name = name[i];
	def_subfield.fld[i].value = value[i];
    }
    return 1;
}
/****************************************************************************/
/* Top-level routine for processing a template file.
 */
int process_template_file ( char *fname, template_callback out,
	void *out_arg, template_callback escape, void *escape_arg )
{
    FILE *tfile;
    char *template, *old_template;
    int length, status, i, t_used, t_alloc;
    /*
     * Read template file into memory.
     */
    t_alloc = 8192;
    template = malloc ( t_alloc );
    if ( ! template ) return SS$_INSFMEM;
    tfile = fopen ( fname, "r" );
    if ( !tfile ) return 20;
    for ( t_used = 0; t_used >= 0; t_used += length ) {
	if ( t_alloc <= t_used ) {
	    /* Expand template region */
	    old_template = template;
	    t_alloc += 8192;
	    template = realloc ( old_template, 8192 );
	    free ( old_template );
	}
	length = fread ( &template[t_used], sizeof(char), t_alloc-t_used,
		tfile );
	if ( length <= 0 ) break;
    }
    fclose ( tfile );
    /*
     * Parse the template.
     */
    status = process_template 
	( template, t_used, out, out_arg, escape, escape_arg );

    free ( template );
    return status;
}

int process_template  ( char *template, int t_size, template_callback out,
	void *out_arg, template_callback escape, void *escape_arg )
{
    int length, status;
    struct field_context field_ctx;
    /*
     * initialize context for parse operation.
     */
    field_ctx.out_len = 0;
    field_ctx.out = out;
    field_ctx.escape_mode = 0;
    field_ctx.out_arg = out_arg;
    field_ctx.escape = escape;
    field_ctx.escape_arg = escape_arg;
    if ( def_subfield_count > 0 ) field_ctx.subfield = def_subfield;
    else field_ctx.subfield.item_count = 0;
    /*
     * Parse the template.
     */
    status = parse_template ( template, t_size, &field_ctx );
    if ( (status&1) == 1 ) status = emit_bytes ( (char *) 0, -1, &field_ctx );
    return status;
}
