/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/


#ident	"@(#)lp:lib/filters/insfilter.c	1.15.3.1"
/* EMACS_MODES: !fill, lnumb, !overwrite, !nodelete, !picture */

#include "assert.h"
#include "string.h"
#include "errno.h"
#include "stdlib.h"

#include "lp.h"
#include "filters.h"

#include "regex.h"


#define MATCH(PT,PM) ( \
	STREQU((PT)->pattern, PATT_STAR) \
     || match((PT)->re, *((PM)->pvalue)) \
)


typedef struct PARM {
	char			*keyword;
	unsigned short		flags;
	char			**pvalue;
}			PARM;

#define	X_MUST	0x0800	/* Pipeline MUST use this parm */
#define	X_FIRST	0x1000	/* Use parm only in 1st cmd of pipeline */
#define	X_FIXED	0x2000	/* Get value from elsewhere, not parms */
#define	X_MANY	0x4000	/* Several values allowed for parm */
#define	X_USED	0x8000	/* Used already, don't use again */

static struct S {
	TYPE			input_type;
	TYPE			output_type;
	TYPE			printer_type;
	char *			printer;
	PARM *			parms;
}			S;

#if	defined(__STDC__)

static int		searchlist_t ( TYPE * , TYPE * );
static int		instantiate ( _FILTER ** , TYPE * , TYPE * , int (*)() , void * );
static int		check_pipeline ( _FILTER * , PARM * );
static char		*build_pipe ( _FILTER * , PARM * , unsigned short * );
#else

static int		searchlist_t();
static int		instantiate();
static int		check_pipeline();
static char *		build_pipe();

#endif

/**
 ** Table of recognized keywords, with info. about them.
 **/

#define NFIXED 4

static PARM		parmtable[] = {

/* These must be the first NFIXED, and in this order */
PARM_INPUT,   X_FIXED,				&S.input_type.name,
PARM_OUTPUT,  X_FIXED,				&S.output_type.name,
PARM_TERM,    X_FIXED,				&S.printer_type.name,
PARM_PRINTER, X_FIXED,				&S.printer,

PARM_CPI,     FPARM_CPI,			0,
PARM_LPI,     FPARM_LPI,			0,
PARM_LENGTH,  FPARM_LENGTH,			0,
PARM_WIDTH,   FPARM_WIDTH,			0,
PARM_PAGES,   FPARM_PAGES | X_FIRST | X_MUST,	0,
PARM_CHARSET, FPARM_CHARSET,			0,
PARM_FORM,    FPARM_FORM,			0,
PARM_COPIES,  FPARM_COPIES | X_FIRST,		0,
PARM_MODES,   FPARM_MODES | X_MANY | X_MUST,	0,
0,	      0,				0,
};

/**
 ** insfilter()
 **/

FILTERTYPE
#if	defined(__STDC__)
insfilter (
	char **			pipes,
	char *			input_type,
	char *			output_type,
	char *			printer_type,
	char *			printer,
	char **			parms,
	unsigned short *	flagsp
)
#else
insfilter (pipes,input_type,output_type,printer_type,printer,parms,flagsp)
	char			**pipes,
				*input_type,
				*output_type,
				*printer_type,
				*printer,
				**parms;
	unsigned short		*flagsp;
#endif
{
	_FILTER *		pipeline;

	FILTERTYPE		ret;


	S.input_type.name = input_type;
	S.input_type.info = isterminfo(input_type);
	S.output_type.name = output_type;
	S.output_type.info = isterminfo(output_type);
	S.printer_type.name = printer_type;
	S.printer_type.info = isterminfo(printer_type);
	S.printer = printer;

	/*
	 * If the filters have't been loaded yet, do so now.
	 * We'll load the standard table, but the caller can override
	 * this by first calling "loadfilters()" with the appropriate
	 * filter table name.
	 */
	if (!filters && loadfilters((char *)0) == -1)
		return (fl_none);

	/*
	 * Allocate and initialize space to hold additional
	 * information about each item in "parms".
	 * THIS SPACE MUST BE FREED BEFORE EXITING THIS ROUTINE!
	 */
	{
		register int		n;

		register PARM *		pp;
		register PARM *		ppt;

		register char **	p;



		for (n = 0, p = parms; *p; n++, p++)
			;
		n /= 2;
		n += NFIXED; /* for fixed parms (in/out/printer types) */

		if (!(S.parms = (PARM *)Malloc((n + 1) * sizeof(PARM)))) {
			errno = ENOMEM;
			return (fl_none);
		}

		for (ppt = parmtable; ppt->keyword; ppt++)
			ppt->flags &= ~X_USED;

		/*
		 * Load the parameter list with the fixed ``type''
		 * parameters. Mark them as used (if appropriate)
		 * so we don't pick them up from the callers list.
		 */
		pp = S.parms;
		for (ppt = parmtable; ppt < parmtable + NFIXED; ppt++) {
			pp->keyword = ppt->keyword;
			pp->flags = ppt->flags;
			if (ppt->flags & X_FIXED)
				pp->pvalue = ppt->pvalue;
			else
				pp->pvalue = parms + 1;
			if (!(ppt->flags & X_MANY))
				ppt->flags |= X_USED;
			pp++;
		}

		/*
		 * Copy each parameter from the caller supplied list
		 * to another list, adding information gathered from
		 * the keyword table. Note that some keywords should
		 * be given only once; additional occurrances in the
		 * caller's list will be ignored.
		 */
		for (p = parms; *p; p += 2)
			for (ppt = parmtable; ppt->keyword; ppt++)
				if (
					STREQU(*p, ppt->keyword)
				     && !(ppt->flags & X_USED)
				) {

					pp->keyword = ppt->keyword;
					pp->flags = ppt->flags;
					if (ppt->flags & X_FIXED)
						pp->pvalue = ppt->pvalue;
					else
						pp->pvalue = p + 1;

					if (!(ppt->flags & X_MANY))
						ppt->flags |= X_USED;

					pp++;
					break;

				}
			
		pp->keyword = 0;

	}

	/*
	 * Preview the list of filters, to rule out those that
	 * can't possibly work.
	 */
	{
		register _FILTER *	pf;

		for (pf = filters; pf->name; pf++) {

			pf->mark = FL_CLEAR;

			if (
				printer
			     && !searchlist(printer, pf->printers)
			)
				pf->mark = FL_SKIP;

			else if (
				printer_type
			     && !searchlist_t(&(S.printer_type), pf->printer_types)
			)
				pf->mark = FL_SKIP;

		}
	}

	/*
	 * Find a pipeline that will convert the input-type to the
	 * output-type and map the parameters as well.
	 */
	if (!instantiate(
		&pipeline,
		&S.input_type,
		&S.output_type,
		check_pipeline,
		S.parms
	)) {
		ret = fl_none;
		goto Return;
	}

	if (!pipes) {
		ret = fl_both;
		goto Return;

	} else {
		register _FILTER *	pf;
		register _FILTER *	pfastf; /* first in fast pipe */
		register _FILTER *	pslowf; /* last in slow pipe */

		/*
		 * Found a pipeline, so now build it.
		 */

		/*
		 * Split pipeline after last slow filter.
		 * "pipeline" will point to first filter in slow
		 * pipe, "pfastf" will point to first filter in
		 * fast pipe.
		 */
		for (
			pf = pfastf = pipeline, pslowf = 0;
			pf;
			pf = pf->next
		)
			if (pf->type == fl_slow) {
				pslowf = pf;
				pfastf = pf->next;
			}

		if (pslowf) {
			assert (pslowf != pfastf);
			pslowf->next = 0;
			pipes[0] = build_pipe(pipeline, S.parms, flagsp);
			ret = fl_slow;
		} else
			pipes[0] = 0;

		if (pfastf) {
			pipes[1] = build_pipe(pfastf, S.parms, flagsp);
			ret = fl_fast;
		} else
			pipes[1] = 0;

		if (pslowf && pfastf)
			ret = fl_both;

		/*
		 * Check for the oops case.
		 */
		if (pslowf && !pipes[0] || pfastf && !pipes[1])
			ret = fl_none;

	}

Return:	Free ((char *)S.parms);

	return (ret);
}

/**
 ** searchlist_t() - SEARCH (TYPE *) LIST FOR ITEM
 **/

static int
#if	defined(__STDC__)
typematch (
	TYPE *			type1,
	TYPE *			type2
)
#else
typematch (type1, type2)
	TYPE			*type1, *type2;
#endif
{
	if (
		STREQU(type1->name, NAME_ANY)
	     || STREQU(type2->name, NAME_ANY)
	     || STREQU(type1->name, type2->name)
	     || (
			STREQU(type1->name, NAME_TERMINFO)
		     && type2->info
		)
	     || (
			STREQU(type2->name, NAME_TERMINFO)
		     && type1->info
		)
	)
		return (1);
	else
		return (0);
}

static int
#if	defined(__STDC__)
searchlist_t (
	TYPE *			itemp,
	TYPE *			list
)
#else
searchlist_t (itemp, list)
	TYPE			*itemp;
	register TYPE		*list;
#endif
{
	if (!list || !list->name)
		return (0);

	/*
	 * This is a linear search--we believe that the lists
	 * will be short.
	 */
	while (list->name) {
		if (typematch(itemp, list))
			return (1);
		list++;
	}
	return (0);
}

/**
 ** instantiate() - CREATE FILTER-PIPELINE KNOWING INPUT/OUTPUT TYPES
 **/

/*
 *	The "instantiate()" routine is the meat of the "insfilter()"
 *	algorithm. It is given an input-type and output-type and finds a
 *	filter-pipline that will convert the input-type into the
 *	output-type. Since the filter-pipeline must meet other criteria,
 *	a function "verify" is also given, along with the set of criteria;
 *	these are used by "instantiate()" to verify a filter-pipeline. 
 *
 *	The filter-pipeline is built up and returned in "pipeline".
 *	Conceptually this is just a list of filters, with the pipeline to
 *	be constructed by simply concatenating the filter simple-commmands
 *	(after filling in option templates) in the order found in the
 *	list. What is used in the routine, though, is a pair of linked
 *	lists, one list forming the ``right-half'' of the pipeline, the
 *	other forming the ``left-half''. The pipeline is then the two
 *	lists taken together.
 *
 *	The "instantiate()" routine looks for a single filter that matches
 *	the input-type and output-type and satisfies the criteria. If one
 *	is found, it is added to the end of the ``left-half'' list (it
 *	could be added to the beginning of the ``right-half'' list with no
 *	problem). The two lists are linked together to form one linked
 *	list, which is passed, along with the set of criteria, to the
 *	"verify" routine to check the filter-pipeline. If it passes the
 *	check, the work is done. 
 *
 *	If a single filter is not found, "instantiate()" examines all
 *	pairs of filters where one in the pair can accept the input-type
 *	and the other can produce the output-type. For each of these, it
 *	calls itself again to find a filter that can join the pair
 *	together--one that accepts as input the output-type of the first
 *	in the pair, and produces as output the input-type of the second
 *	in the pair.  This joining filter may be a single filter or may
 *	be a filter-pipeline. "instantiate()" checks for the trivial case
 *	where the input-type is the output-type; with trivial cases it
 *	links the two lists without adding a filter and checks it with
 *	"verify".
 */

/**
 ** instantiate()
 **/

/*
 * A PIPELIST is what is passed to each recursive call to "instantiate()".
 * It contains a pointer to the end of the ``left-list'', a pointer to the
 * head of the ``right-list'', and a pointer to the head of the left-list.
 * The latter is passed to "verify". The end of the right-list (and thus
 * the end of the entire list when left and right are joined) is the
 * filter with a null ``next'' pointer.
 */
typedef struct PIPELIST {
	_FILTER *		lhead;
	_FILTER *		ltail;
	_FILTER *		rhead;
}			PIPELIST;

#if	defined(__STDC__)
static int		_instantiate ( PIPELIST * , TYPE * , TYPE * , int (*)( _FILTER * , void * ) , void * );
#else
static int		_instantiate();
#endif

static int		peg;

static int
#if	defined(__STDC)
instantiate (
	_FILTER **		pline,
	TYPE *			input,
	TYPE *			output,
	int			(*verify)( _FILTER * , void * ),
	void *			criteria
)
#else
instantiate (pline, input, output, verify, criteria)
	_FILTER			**pline;
	TYPE			*input,
				*output;
	int			(*verify)();
	char			*criteria;
#endif
{
	PIPELIST		p;
	int			ret;

	peg = 0;
	p.lhead = p.ltail = p.rhead = 0;
	ret = _instantiate(&p, input, output, verify, criteria);
	*pline = p.lhead;
	return (ret);
}

#define	ENTER()		int our_tag = ++peg;

#define	LEAVE(Y)	if (!Y) { \
				register _FILTER *f; \
				for (f = filters; f->name; f++) \
					CLEAR(f); \
				return(0); \
			} else return(1)

#define MARK(F,M)	(((F)->mark |= M), (F)->level = our_tag)

#define CLEAR(F)	if ((F)->level == our_tag) \
				(F)->level = 0, (F)->mark = FL_CLEAR

#define CHECK(F,M)	(((F)->mark & M) && (F)->level == our_tag)

#define	USED(F)		((F)->mark)

static int
#if	defined(__STDC__)
_instantiate (
	PIPELIST *		pp,
	TYPE *			inputp,
	TYPE *			outputp,
	int			(*verify)( _FILTER * , void * ),
	void *			criteria
)
#else
_instantiate (pp, inputp, outputp, verify, criteria)
	PIPELIST		*pp;
	TYPE			*inputp,
				*outputp;
	int			(*verify)();
	char			*criteria;
#endif
{
	register _FILTER *	prev_lhead;
	register _FILTER *	prev_ltail;


	/*
	 * Must be first ``statement'' after declarations.
	 */
	ENTER ();

	/*
	 * We're done when we've added filters on the left and right
	 * that let us connect the left and right directly; i.e. when
	 * the output of the left is the same type as the input of the
	 * right. HOWEVER, there must be at least one filter involved,
	 * to allow the filter feature to be used for handling modes,
	 * pages, copies, etc. not just FILTERING data.
	 */
	if (typematch(inputp, outputp) && pp->lhead) {

		/*
		 * Getting here means that we must have a left and right
		 * pipeline. Why? For "pp->lhead" to be non-zero it
		 * must have been set below. The first place below
		 * doesn't set the right pipeline, but it also doesn't
		 * get us here (at least not directly). The only
		 * place we can get to here again is the second place
		 * "pp->phead" is set, and THAT sets the right pipeline.
		 */
		pp->ltail->next = pp->rhead;
		if ((*verify)(pp->lhead, criteria))
			LEAVE (1);
		else
			LEAVE (0);

	}

	/*
	 * Each time we search the list of filters, we examine
	 * them in the order given and stop searching when a filter
	 * that meets the needs is found. If the list is ordered with
	 * fast filters before slow filters, then fast filters will
	 * be chosen over otherwise-equal filters.
	 */

	/*
	 * See if there's a single filter that will work.
	 * Just in case we can't find one, mark those that
	 * will work as left- or right-filters, to save time
	 * later.
	 *
	 * Also, record exactly *which* input/output
	 * type would be needed if the filter was used.
	 * This record will be complete (both input and output
	 * recorded) IF the single filter works. Otherwise,
	 * only the input, for the left possible filters,
	 * and the output, for the right possible filters,
	 * will be recorded. Thus, we'll have to record the
	 * missing types later.
	 */
	{
		register _FILTER *		pf;


		for (pf = filters; pf->name; pf++) {

			if (USED(pf))
				continue;

			if (searchlist_t(inputp, pf->input_types)) {
				MARK (pf, FL_LEFT);
				pf->inputp = inputp;
			}
			if (searchlist_t(outputp, pf->output_types)) {
				MARK (pf, FL_RIGHT);
				pf->outputp = outputp;
			}

			if (CHECK(pf, FL_LEFT) && CHECK(pf, FL_RIGHT)) {
				prev_lhead = pp->lhead;
				prev_ltail = pp->ltail;

				if (!pp->lhead)
					pp->lhead = pf;
				else
					pp->ltail->next = pf;
				(pp->ltail = pf)->next = pp->rhead;

				if ((*verify)(pp->lhead, criteria))
					LEAVE (1);

				if ((pp->ltail = prev_ltail))
					pp->ltail->next = 0;
				pp->lhead = prev_lhead;

			}

		}
	}

	/*
	 * Try all DISJOINT pairs of left- and right-filters; recursively
	 * call this function to find a filter that will connect
	 * them (it might be a ``null'' filter).
	 */
	{
		register _FILTER *	pfl;
		register _FILTER *	pfr;

		register TYPE *		llist;
		register TYPE *		rlist;


		for (pfl = filters; pfl->name; pfl++) {

			if (!CHECK(pfl, FL_LEFT))
				continue;

			for (pfr = filters; pfr->name; pfr++) {

				if (pfr == pfl || !CHECK(pfr, FL_RIGHT))
					continue;

				prev_lhead = pp->lhead;
				prev_ltail = pp->ltail;

				if (!pp->lhead)
					pp->lhead = pfl;
				else
					pp->ltail->next = pfl;
				(pp->ltail = pfl)->next = 0;

				pfr->next = pp->rhead;
				pp->rhead = pfr;

				/*
				 * Try all the possible output types of
				 * the left filter with all the possible
				 * input types of the right filter. If
				 * we find a combo. that works, record
				 * the output and input types for the
				 * respective filters.
				 */
				for (
					llist = pfl->output_types;
					llist->name;
					llist++
				)
					for (
						rlist = pfr->input_types;
						rlist->name;
						rlist++
					)
						if (_instantiate(
							pp,
							llist,
							rlist,
							verify,
							criteria
						)) {
							pfl->outputp = llist;
							pfr->inputp = rlist;
							LEAVE (1);
						}
				pp->rhead = pfr->next;
				if ((pp->ltail = prev_ltail))
					pp->ltail->next = 0;
				pp->lhead = prev_lhead;

			}

		}
	}

	LEAVE (0);
}

/**
 ** check_pipeline() - CHECK THAT PIPELINE HANDLES MODES, PAGE-LIST
 **/

static int
#if	defined(__STDC__)
check_pipeline (
	_FILTER *		pipeline,
	PARM *			parms
)
#else
check_pipeline (pipeline, parms)
	_FILTER			*pipeline;
	PARM			*parms;
#endif
{
	register PARM *		pm;

	register _FILTER *	pf;

	register TEMPLATE *	pt;

	register int		fail;


	for (fail = 0, pm = parms; !fail && pm->keyword; pm++) {

		if ( !(pm->flags & X_MUST) )
			continue;

		for (pf = pipeline; pf; pf = pf->next) {

			if (!(pt = pf->templates))
				continue;

			for (; pt->keyword; pt++)
				if (
					STREQU(pt->keyword, pm->keyword)
				     && pt->result
				     && MATCH(pt, pm)
				)
					goto Okay;

		}
		fail = 1;
		continue;

Okay:		;

	}

	return (fail? 0 : 1);
}
	
/**
 ** build_filter() - CONSTRUCT PIPELINE FROM LINKED LIST OF FILTERS
 **/

#if	defined(__STDC__)
static size_t		build_simple_cmd ( char ** , _FILTER * , PARM * , unsigned short * );
#else
static size_t		build_simple_cmd();
#endif

static char *
#if	defined(__STDC__)
build_pipe (
	_FILTER *		pipeline,
	PARM *			parms,
	unsigned short *	fp
)
#else
build_pipe (pipeline, parms, fp)
	_FILTER			*pipeline;
	PARM			*parms;
	unsigned short		*fp;
#endif
{
	register _FILTER *	pf;

	register size_t		nchars;
	register size_t		n;

	char *			p;	/* NOT register */
	char *			ret;


	/*
	 * This is a two-pass routine. In the first pass we add
	 * up how much space is needed for the pipeline, in the second
	 * pass we allocate the space and construct the pipeline.
	 */

	for (nchars = 0, pf = pipeline; pf; pf = pf->next)
		if ((n = build_simple_cmd((char **)0, pf, parms, fp)) > 0)
			nchars += n + 1;   /* +1 for '|' or ending null */

	if (!(ret = p = Malloc(nchars))) {
		errno = ENOMEM;
		return (0);
	}

	for (pf = pipeline; pf; pf = pf->next, *p++ = (pf? '|' : 0))
		(void)build_simple_cmd (&p, pf, parms, fp);

	return (ret);
}

/**
 ** build_simple_cmd()
 **/

static size_t
#if	defined(__STDC__)
build_simple_cmd (
	char **			pp,
	_FILTER *		pf,
	PARM *			parms,
	unsigned short *	flagsp
)
#else
build_simple_cmd (pp, pf, parms, flagsp)
	char			**pp;
	_FILTER			*pf;
	PARM			*parms;
	unsigned short		*flagsp;
#endif
{
	register size_t		ncount;

	register TEMPLATE *	pt;

	register PARM *		pm;


	if (pf->command) {
		ncount = strlen(pf->command);
		if (pp) {
			strcpy (*pp, pf->command);
			*pp += ncount;
		}
	} else
		ncount = 0;

	if (!pf->templates)
		return (ncount);

	for (pm = parms; pm->keyword; pm++) {

		if ((pm->flags & X_USED) || !*(pm->pvalue))
			continue;

		for (pt = pf->templates; pt->keyword; pt++) {

			if (
				!STREQU(pt->keyword, pm->keyword)
			     || !pt->result
			)
				continue;

			/*
			 * INPUT and OUTPUT are those for *this* filter,
			 * not for the entire pipeline.
			 */
			if (STREQU(pt->keyword, PARM_INPUT))
				pm->pvalue = &(pf->inputp->name);
			else if (STREQU(pt->keyword, PARM_OUTPUT))
				pm->pvalue = &(pf->outputp->name);

			if (MATCH(pt, pm)) {
				if (pp)
					*(*pp)++ = ' ';
				ncount++;

				ncount += replace(pp, pt->result, *(pm->pvalue), pt->nbra);

				/*
				 * Difficulty here due to the two pass
				 * nature of this code. The first pass
				 * just counts the number of bytes; if
				 * we mark the once-only parms as being
				 * used, then we don't pick them up the
				 * second time through. We could get
				 * difficult and mark them temporarily,
				 * but that's hard. So on the first pass
				 * we don't mark the flags. The only
				 * problem is an estimate too high.
				 */
				if (pp && pm->flags & X_FIRST)
					pm->flags |= X_USED;

				*flagsp |= pm->flags;

			}
		}
	}

	return (ncount);
}
