
/* video_conf.c: video configuration table utilities */

#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <netdb.h>

#include "video_conf.h"

extern int h_errno;	/* hesiod error number? */
struct hostent *gethostbyname();
struct hostent *hep;

static VCONF_DEVTABLE vtbl;

#include "models.h"
#define VCONF_NDEFS	NUM_MODELS

static char 	err_msg[128];
static char	in_chans[MAXDEVS][MAXDEVS];
static char	out_chans[MAXDEVS][MAXDEVS];
static char	filename[128];
static int	no_swtr = 0;

int
vconf_get_table( userdisplay, file, vdtp )

	char *		userdisplay;
	char *		file;
	VCONF_DEVTABLE *vdtp;	
{
FILE *	fp;
char *	p;
char	line[256];
int	lnum = 0;
int	nitems = 0;
char	ttype[32];
char	tbaud[16];
char	tparity[32];
int	res;
int	i;
int	sw, ch;
int	sw2, ch2;
int	num;

	if( vdtp == NULL )
		return conf_err("given an empty table\n",VCONF_NO_TABLE_ERR);
	if( (fp=fopen(file,"r"))==NULL )
		return conf_err("file not found\n",VCONF_NO_FILE_ERR);

	strcpy( filename, file );
	vconf_mkupper( userdisplay );

	while( !feof(fp) )
	{
		if( fgets( line, 255, fp ) == NULL )
			break;;
		lnum++;
		if( line[strlen(line)]=='\n' )
			line[strlen(line)] = '\0';

		if( line[0] == '#' || isspace(line[0]) )
			continue;  /* skip comments, lines starting with space */

		res = sscanf( line, "%s %s %s %s %s %s %s\n",
			ttype,
			vtbl.dev[nitems].name,
			vtbl.dev[nitems].model,
			vtbl.dev[nitems].tty,
			tbaud,
			tparity,
			vtbl.dev[nitems].channel );

		if( res==EOF )
			break;

		if( res != LINE_ITEMS )
		{
			sprintf(err_msg,"converted %d items, not %d: line %d\n",
				res,LINE_ITEMS,lnum);
			return conf_err( err_msg, VCONF_FILE_FMT_ERR );
		}

		
		if( !strcmp( ttype, "SWTR" ) )
			vtbl.dev[nitems].type = SWTR_DEV;
		else if( !strcmp( ttype, "RPD" ) )
			vtbl.dev[nitems].type = RPD_DEV;
		else if( !strcmp( ttype, "INPUT" ) )
			vtbl.dev[nitems].type = INPUT_DEV;
		else if( !strcmp( ttype, "OUTPUT" ) )
			vtbl.dev[nitems].type = OUTPUT_DEV;
		else
		{
			sprintf(err_msg,"unknown device type, line %d\n",lnum);
			return conf_err( err_msg, VCONF_FILE_FMT_ERR );
		}
		
		if( 	vtbl.dev[nitems].type == RPD_DEV ||
			vtbl.dev[nitems].type == SWTR_DEV )
		{
			vtbl.dev[nitems].baud = atoi( tbaud );
			if( !strcmp( tparity, "none" ) )
				vtbl.dev[nitems].parity = 0;
			else if( !strcmp( tparity, "odd" ) )
				vtbl.dev[nitems].parity = ODDP;
			else if( !strcmp( tparity, "even" ) )
				vtbl.dev[nitems].parity = EVENP;
			else if( !strcmp( tparity, "any" ) )
				vtbl.dev[nitems].parity = ANYP;
			else
			{
				sprintf(err_msg,"unknown parity type, line %d\n",
					lnum);
				return conf_err( err_msg, VCONF_FILE_FMT_ERR );
			}
		}
		else
			vtbl.dev[nitems].parity = 
			vtbl.dev[nitems].baud = 0;

		nitems++;
	}

	fclose(fp);

	if( nitems == 0 )
		conf_err( "must be at least one valid item",
			VCONF_FILE_FMT_ERR );
	vtbl.ndevs = nitems;

	if( check_conf_err(&vtbl) != VCONF_AOK )
		return VCONF_FILE_FMT_ERR;
	
	/* now construct user's private list, based on userdisplay variable */

	if( !userdisplay || !strlen(userdisplay) )
	{	/* no checking; user gets entire table */
		bcopy( &vtbl, vdtp, sizeof(VCONF_DEVTABLE) );
		return VCONF_AOK;	
	}
	else
	{
		/* first, find the OUTPUT device */
		for( i=0; i<vtbl.ndevs; i++ )
		{
			if( !strcmp(vtbl.dev[i].name,userdisplay) )
			{
			vconf_split_chan( vtbl.dev[i].channel, &sw, &ch );
			if( sw==0 )
				no_swtr = 1;
			break;
			}
		}
		/* now, find all the ones we can control and make the table */
		for( num=0, i=0; i<vtbl.ndevs; i++ )
		{
			vconf_split_chan( vtbl.dev[i].channel, &sw2, &ch2 );
			if( no_swtr )
			{
				if( sw2 == sw && ch2 == ch )
					bcopy( &vtbl.dev[i], &(*vdtp).dev[num++], 
						sizeof(VCONF_ENTRY) );
			}
			else if( sw2 == sw )
				bcopy( &vtbl.dev[i], &(*vdtp).dev[num++], 
					sizeof(VCONF_ENTRY) );
		}
		(*vdtp).ndevs = num;
		return VCONF_AOK;
	}
}

typedef struct
{
	int	ins;
	int	outs;
} CHAN_CAP;

static int
check_conf_err( vdtp )

	VCONF_DEVTABLE *vdtp;
{
int	res;
int	swtr[MAXDEVS];
int	ch, sw;
int	i, j;
int	unit;
char 	root[32];
CHAN_CAP	cc[MAXDEVS];
char	host[32];
int	num;

	/* do all the error checking: 
		no two devnames the same;
		no two ports the same;
		no two things connected to same channel;
		check baud is okay;
		check i/o channels are not like devs (imp?);
		check switcher unit #'s against channel;
		switcher name must be SWTR;
		no two unit #'s the same
		*/

	for( i=0; i<MAXDEVS; i++ )	/* initialize swtr table */
		swtr[i] = -1;

	for( i=0; i<(*vdtp).ndevs; i++ )
	{
		for( j=0; j<i; j++ )	/* check no match for name,ttyport,chan */
		{
			if( !strcmp( (*vdtp).dev[j].name,(*vdtp).dev[i].name ) )
			{
				sprintf(err_msg,"duplicate name: %s\n",
					(*vdtp).dev[i].name );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( 	((*vdtp).dev[i].type == SWTR_DEV ||
				(*vdtp).dev[i].type == RPD_DEV) &&
				!strcmp((*vdtp).dev[j].tty,(*vdtp).dev[i].tty) )
			{
				sprintf(err_msg,"duplicate ttyport used: %s\n",
					(*vdtp).dev[i].tty );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
		}

		if( (*vdtp).dev[i].type == SWTR_DEV )
		{
			res = vconf_split_chan( (*vdtp).dev[i].channel, 
				&sw, &ch );
			if( res )
			{
				sprintf(err_msg,"bad channel format, device %s: '%s'\n",
					(*vdtp).dev[i].name,
					(*vdtp).dev[i].channel );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			res = vconf_split_name( (*vdtp).dev[i].name, root, &unit );
			if( res )	/* must have a unit number */
			{
				sprintf(err_msg,"no unit number for swtr: '%s'\n",
					(*vdtp).dev[i].name );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( strcmp(root,"SWTR") )	/* must be called SWTR */
			{
				sprintf(err_msg,
					"switcher name must be 'SWTR', not '%s'\n",
					(*vdtp).dev[i].name );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( unit != sw )	/* switcher field matches unit */
			{
				sprintf(err_msg,
				"switcher id and unit number don't match for '%s'\n",
					(*vdtp).dev[i].name );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( ch != 0 )		/* must be no channel */
			{
				sprintf(err_msg,
				"bad channel id; should be '0', not %d\n",ch);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			swtr[unit] = i;		/* set devtable index */
			if( ttyutil_makebaud( (*vdtp).dev[i].baud ) < 0 )
			{
				sprintf(err_msg,
				"invalid baud rate %d for device %s\n",
				(*vdtp).dev[i].baud,(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			res = vconf_get_swtr_chans( (*vdtp).dev[i].model,
				&cc[unit].ins,&cc[unit].outs );
			if( res )
			{
				sprintf(err_msg,
			"can't get channels (i.e., '4x1') from model '%s'\n",
				(*vdtp).dev[i].model);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( cc[unit].ins >= MAXDEVS )
			{
				sprintf(err_msg,
					"input channels > %d, swtr '%s'\n",
					MAXDEVS-1,(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( cc[unit].outs >= MAXDEVS )
			{
				sprintf(err_msg,
					"output channels > %d, swtr '%s'\n",
					MAXDEVS-1,(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			res=vconf_find_model((*vdtp).dev[i].model);
			if( res<0 )
			{
				sprintf(err_msg,
					"unsupported model '%s', device '%s'\n",
					(*vdtp).dev[i].model,(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			else
				strcpy( (*vdtp).dev[i].mon_proc,
					model_tbl[res].gdb_mon );
		}
		
		else if( (*vdtp).dev[i].type == RPD_DEV )
		{
			if( ttyutil_makebaud( (*vdtp).dev[i].baud ) < 0 )
			{
				sprintf(err_msg,
				"invalid baud rate %d for device %s\n",
				(*vdtp).dev[i].baud,(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			res = vconf_split_chan( (*vdtp).dev[i].channel, 
				&sw, &ch );
			if( res )
			{
				sprintf(err_msg,"bad channel format, device %s: '%s'\n",
					(*vdtp).dev[i].name,
					(*vdtp).dev[i].channel );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}

			res=vconf_find_model((*vdtp).dev[i].model);
			if( res<0 )
			{
				sprintf(err_msg,
					"unsupported model '%s', device '%s'\n",
					(*vdtp).dev[i].model,(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			else
				strcpy( (*vdtp).dev[i].mon_proc,
					model_tbl[res].gdb_mon );

			if( sw==0 )	/* don't worry about channels; no swtr */
				continue;
			if( sw < 1 || sw >= MAXDEVS || swtr[sw] < 0 )
			{
				sprintf(err_msg,
				"non-existent switcher designation, device '%s'\n",
				(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( ch > cc[sw].ins || ch<1 )
			{
				sprintf(err_msg,
			"bad channel in swtr/channel pair, device '%s'\n",
				(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( in_chans[sw][ch] )	/* check for no match */
			{
				sprintf(err_msg,
				"input channel used twice: %s, device %s\n",
					(*vdtp).dev[i].channel,
					(*vdtp).dev[i].name );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			else
				in_chans[sw][ch] = 1;
		}
	
		else if( (*vdtp).dev[i].type == INPUT_DEV ||
			(*vdtp).dev[i].type == OUTPUT_DEV )
		{
			res = vconf_split_chan( (*vdtp).dev[i].channel, 
				&sw, &ch );
			if( res )
			{
				sprintf(err_msg,
				"bad format for channel: '%s', device %s\n",
				(*vdtp).dev[i].channel,(*vdtp).dev[i].name );
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( sw==0 )	/* don't worry about channels; no swtr */
			{
				if( chk_output( vdtp, i ) )
					return VCONF_FILE_FMT_ERR;
				continue;
			}
			if( sw < 1 || sw >= MAXDEVS || swtr[sw] < 0 )
			{
				sprintf(err_msg,
				"bad switcher designation, device '%s'\n",
				(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
			if( (*vdtp).dev[i].type == INPUT_DEV )
			{
				if( ch > cc[sw].ins || ch<1 )
				{
					sprintf(err_msg,
				"bad channel in swtr/channel pair, device '%s'\n",
					(*vdtp).dev[i].name);
					return conf_err(err_msg,VCONF_FILE_FMT_ERR);
				}
				if( in_chans[sw][ch] )
				{
					sprintf(err_msg,
				"input channel used twice: %s, device %s\n",
					(*vdtp).dev[i].channel,
					(*vdtp).dev[i].name );
					return conf_err(err_msg,
						VCONF_FILE_FMT_ERR);
				}
				else
					in_chans[sw][ch] = 1;
			}
			else if( (*vdtp).dev[i].type == OUTPUT_DEV )
			{
				if( ch > cc[sw].outs || ch<1 )
				{
					sprintf(err_msg,
				"bad channel in swtr/channel pair, device '%s'\n",
					(*vdtp).dev[i].name);
					return conf_err(err_msg,VCONF_FILE_FMT_ERR);
				}
				if( out_chans[sw][ch] )
				{
					sprintf(err_msg,
				"output channel used twice: %s, device %s\n",
					(*vdtp).dev[i].channel,
					(*vdtp).dev[i].name );
					return conf_err(err_msg,
						VCONF_FILE_FMT_ERR);
				}
				else
					out_chans[sw][ch] = 1;
				/* check output name */
				if( chk_output( vdtp, i ) )
					return VCONF_FILE_FMT_ERR;
			}

			/* check for unnecessary input */
			if( 	strcmp((*vdtp).dev[i].model,"?") ||
				strcmp((*vdtp).dev[i].tty,"?") ||
				((*vdtp).dev[i].baud != 0) ||
				((*vdtp).dev[i].parity != 0) )
			{
				sprintf(err_msg,
				"invalid data, device '%s'\n",
				(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
		}
	
		else	/* undefined type of dev */
		{
				sprintf(err_msg,"unknown type of device: %s\n",
					(*vdtp).dev[i].type);	
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
		}
	}
	
	return VCONF_AOK;
}

static int
chk_output( vdtp, i )

	VCONF_DEVTABLE *vdtp;
	int	i;
{
int	res;
char	host[64];
int	num;
int	j;
char *	index();

	if( index( (*vdtp).dev[i].name,':' )!=NULL ) /* see if display name */
	{
		res = sscanf( (*vdtp).dev[i].name,
			"%[^:]:%d",host,&num);
		if( res!=2 )
		{
			sprintf(err_msg,
			"output device '%s' is bad host name\n",
			(*vdtp).dev[i].name);
			return conf_err(err_msg,
				VCONF_FILE_FMT_ERR);
		}
		if( num<0 || num>1 )
		{
			sprintf(err_msg,
			"output device '%s' has bad display number\n",
			(*vdtp).dev[i].name);
			return conf_err(err_msg,
				VCONF_FILE_FMT_ERR);
		}
		if( !strcmp(host,"unix") )	/* substitute full name */
		{
			gethostname(host,64);
			vconf_mkupper(host);
			sprintf((*vdtp).dev[i].name,
				"%s:%d",host,num);
		}
		else	/* validate the host name */
		{
			hep = gethostbyname( host );
			if( hep==NULL )	/* failed */
			{
				if( h_errno==HOST_NOT_FOUND )
				{
					sprintf(err_msg,
					"invalid host name, device '%s'\n",
					(*vdtp).dev[i].name);
					return conf_err(err_msg,
						VCONF_FILE_FMT_ERR);
				}
				else
					fprintf(stderr,
			"error validating host, device '%s'; ignored!\n",
					(*vdtp).dev[i].name);
			}		
		}
		return 0;
	}

 	/* see if it's a recorder input */
	if( (*vdtp).dev[i].name[0] == '>' )
	{
	strcpy(host,(*vdtp).dev[i].name+1);
	for( j=0; j<i; j++ )
		if( !strcmp(host,(*vdtp).dev[j].name) )
		{
			if( (*vdtp).dev[j].type == RPD_DEV )
				return 0;
			else
			{
				sprintf(err_msg,
				"output '%s' is not to a valid RPD\n",
				(*vdtp).dev[i].name);
				return conf_err(err_msg,VCONF_FILE_FMT_ERR);
			}
		}
	}

	/* see if a cable channel */
	res = sscanf( (*vdtp).dev[i].name, "%[^-]-%d",
		host,&num);
	if( res!=2 || strcmp(host,"CH") )
	{
		sprintf(err_msg,"output device '%s' not a cable channel\n",
		(*vdtp).dev[i].name);
		return conf_err(err_msg,VCONF_FILE_FMT_ERR);
	}

	return 0;
}

int
vconf_find_default_rpd( vdtp )	/* return index of 1st rpd in user's table */

	VCONF_DEVTABLE *vdtp;
{
int	i;

	for( i=0; i<(*vdtp).ndevs; i++ )
		if( (*vdtp).dev[i].type == RPD_DEV )
			return i;
	return -1;
}

int
vconf_find_switcher( vdtp )	/* return index of user's switcher */

	VCONF_DEVTABLE *vdtp;
{
int	i;

	for( i=0; i<(*vdtp).ndevs; i++ )
		if( (*vdtp).dev[i].type == SWTR_DEV )
			return i;
	return -1;
}

int
vconf_find_name( vdtp, name )	/* returns index of matching sysname */

	VCONF_DEVTABLE *vdtp;
	char *name;
{
int	i;

	if( !name || !vdtp )
		return -1;
	for( i=0; i<(*vdtp).ndevs; i++ )
		if( !strcmp( name, (*vdtp).dev[i].name ) )
			return i;
	return -1;
}

int
vconf_find_model( name )	/* returns index, or -1 */

	char *name;
{
int	i;

	if( !name )
		return -1;
	for( i=0; i<VCONF_NDEFS; i++ )
		if( !strcmp(name,model_tbl[i].the_name) )
			return i;
	return -1;
}
	
int
vconf_split_chan( str, swtr, chan )

	char *	str;
	int *	swtr;
	int *	chan;
{
int	res;

	if( !str || !swtr || !chan )
		return -1;
	*swtr = 0;
	*chan = 0;
	res = sscanf( str, "%d-%d", swtr, chan );
	if( res==2 )
		return 0;
	return -1;
}

int
vconf_split_name( str, root, num )

	char *	str;
	char *	root;
	int *	num;
{
int	res;

	if( !str || !num || !root )
		return -1;
	*num = 0;
	*root = 0;
	res = sscanf( str, "%[^-]-%d", root, num );
	if( res==2 )
		return 0;
	return -1;
}

int
vconf_get_swtr_chans( str, ins, outs )

	char *	str;
	int *	ins;
	int *	outs;
{
int	res;
char 	root[32];

	if( !str || !ins || !outs )
		return -1;
	*ins = *outs = 0;
	res = sscanf( str, "%[^_]_%dx%d", root,ins,outs);
	if( res==3 )
		return 0;
	return -1;
}

char *
vconf_nameoftype( t )

	int	t;
{
	switch(t)
	{
		case SWTR_DEV:
			return "switcher";
			break;
		case RPD_DEV:
			return "record/playback device";
			break;
		case INPUT_DEV:
			return "channel source";
			break;
		case OUTPUT_DEV:
			return "channel sink";
			break;
		default:
			return "UNDEFINED";
			break;
	}
}

char *
vconf_nameofparity( t )

	int	t;
{
	switch(t)
	{
		case EVENP:
			return "even parity";
			break;
		case ODDP:
			return "odd parity";
			break;
		case 0:
			return "no parity";
			break;
		case ANYP:
			return "any parity";
			break;
		default:
			return "UNDEFINED";
			break;
	}
}

static int
conf_err( explain, retcode )

	char *	explain;
	int	retcode;
{
	fprintf( stderr, "VCONF err: %s: %s\n", filename,explain );
	return retcode;
}

int
vconf_mkupper( name )

	char *name;
{
char *p;

	for( p=name; p && *p; p++ )
		if( islower(*p) )
			*p = toupper(*p);
	return 0;
}
