/*
 * DATlib   Copyright (c) 1995-1996 Marcus Meissner &
 *          Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *          All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Marcus Meissner
 *    at the Friedrich-Alexander Universitaet Erlangen-Nuernberg (FAU), IMMD4
 *
 * 4. The name of the University or the author may not be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND THE AUTHOR ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR THE AUTHOR BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* $Id: if_userlevel.c,v 1.1 1997/03/16 20:29:34 msmeissn Exp msmeissn $
 *
 * $Log: if_userlevel.c,v $
 * Revision 1.1  1997/03/16 20:29:34  msmeissn
 * Initial revision
 *
 *
 * if_userlevel.c: Interface routines to the underlying OS (unix for now)
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
extern	int errno;
#include <sys/sysmacros.h>	/* minor, used in mtio.h */
#if !defined(_KERNEL) && !defined(getminor)
#define getminor(x) minor(x)
#endif
#include <sys/stat.h>
#include <sys/mtio.h>		/* used for MT_UNIT */

#include "dataudiolib.h"
#include "if_userlevel.h"
#include "mt_ext.h"

#ifdef sun
int open(char *fn,int mode,...);
#endif

/* just as the name says */
#define GENERATE_NEWID_AND_RETURN \
	n=0;\
	/* ugly */\
	while (1) {\
		for (i=0;i<da_entries;i++)\
			if (da_states[i].id==n)\
				break;\
		if (i==da_entries)\
			break;\
		n++;\
	}\
	return n;

/* generate unique ID */
da_devid
da_uniqueid(int id) {
	struct	stat		stbuf;
	int			i,n;
	extern struct da_state	*da_states;
	extern int		da_entries;

	if (-1==stat((char*)id,&stbuf)) {
		GENERATE_NEWID_AND_RETURN;
	}
	if (!S_ISCHR(stbuf.st_mode)) {
		GENERATE_NEWID_AND_RETURN;
	}
	/* dynamically allocated major numbers ... 
	 * sigh. we should check for them too 
	 */
	return major(stbuf.st_rdev)<<16;
}

/* add callback ... */
int
da_add_callback(	
	int	devid,
	enum da_cb_type	type,
	enum da_cb_granularity	granularity,
	void (*cbfun)(DA_CB_PROTO)
) {
	struct	da_user_cb	*cb;
	struct	da_state	*st;
	struct	da_user_private	*priv;
	st=da_lookup(devid);
	if (st==NULL) {
		errno=ENOENT;
		return -1;
	}
	priv=(struct da_user_private*)st->ifdata;
	if (priv==NULL) {
		errno=EBADF;
		return -1;
	}
	cb=(struct da_user_cb*)malloc(sizeof(struct da_user_cb));
	cb->next	= priv->cb;
	priv->cb	= cb;
	cb->id		= ++priv->cbid;
	cb->type	= type;
	cb->granularity	= granularity;
	cb->cbfun	= cbfun;
	memset(&(cb->data),'\0',sizeof(cb->data));
	return da_up_add_callback(devid,cb->id,type,granularity);
}

/* down callback function */
int
da_down_callback(int devid,int cbid,union da_callback_data *data) {
	struct	da_state	*st;
	struct	da_user_private	*priv;
	struct	da_user_cb	*pcb;

	st=da_lookup(devid);
	if (st==NULL) {
		errno=ENOENT;
		return -1;
	}
	priv=(struct da_user_private*)(st->ifdata);
	pcb=priv->cb;
	while (pcb) {
		if (cbid==pcb->id) {
			pcb->cbfun(st->id,pcb->type,data);
			return 0;
		}
		pcb=pcb->next;
	}
	return -1;
}

/* saves state */
void
da_down_save_state(FILE *F,struct da_state *st) {
	int	i;
	fprintf(F,"values=%ld\n",st->values);
	fprintf(F,"bnr=%ld\n",st->bnr);
	/* mute */
	fprintf(F,"cnt_jump=%ld\n",st->cnt_jump);
	fprintf(F,"cnt_start=%ld\n",st->cnt_start);
	fprintf(F,"cnt_toc=%ld\n",st->cnt_toc);
	fprintf(F,"cur_toc=%ld\n",st->cur_toc);
	fprintf(F,"prognr=%ld\n",st->prognr);
	fprintf(F,"abstime.indexnr=%d\n",st->abstime.indexnr);
	fprintf(F,"abstime.hour=%d\n",st->abstime.hour);
	fprintf(F,"abstime.minute=%d\n",st->abstime.minute);
	fprintf(F,"abstime.second=%d\n",st->abstime.second);
	fprintf(F,"abstime.frame=%d\n",st->abstime.frame);
	if (st->values&DA_V_HAVE_CATALOG) {
		fprintf(F,"catalog=");
		for (i=0;i<13;i++) fprintf(F,"%d ",st->catalog.nr[i]);
		fprintf(F,"\n");
	}
	if (st->values&DA_V_HAVE_ISRC) {
		fprintf(F,"isrc.country=%ld\n",st->isrc.country);
		fprintf(F,"isrc.producer=%ld\n",st->isrc.producer);
		fprintf(F,"isrc.year=%d\n",st->isrc.year);
		fprintf(F,"isrc.serialnr=%ld\n",st->isrc.serialnr);
	}
	fprintf(F,"runtime.indexnr=%d\n",st->runtime.indexnr);
	fprintf(F,"runtime.hour=%d\n",st->runtime.hour);
	fprintf(F,"runtime.minute=%d\n",st->runtime.minute);
	fprintf(F,"runtime.second=%d\n",st->runtime.second);
	fprintf(F,"runtime.frame=%d\n",st->runtime.frame);
	fprintf(F,"recorddate.weekday=%d\n",st->recorddate.weekday);
	fprintf(F,"recorddate.year=%d\n",st->recorddate.year);
	fprintf(F,"recorddate.month=%d\n",st->recorddate.month);
	fprintf(F,"recorddate.day=%d\n",st->recorddate.day);
	fprintf(F,"recorddate.hour=%d\n",st->recorddate.hour);
	fprintf(F,"recorddate.minute=%d\n",st->recorddate.minute);
	fprintf(F,"recorddate.second=%d\n",st->recorddate.second);
	fprintf(F,"scms=%d\n",st->subid.scms);
	fprintf(F,"progtime.indexnr=%d\n",st->progtime.indexnr);
	fprintf(F,"progtime.hour=%d\n",st->progtime.hour);
	fprintf(F,"progtime.minute=%d\n",st->progtime.minute);
	fprintf(F,"progtime.second=%d\n",st->progtime.second);
	fprintf(F,"progtime.frame=%d\n",st->progtime.frame);
	fprintf(F,"samplerate=%d\n",st->subid.samplerate);
	fprintf(F,"channels=%d\n",st->subid.channels);
	fprintf(F,"encoding=%d\n",st->subid.encoding);
	fprintf(F,"tocentries=%ld\n",st->tocentries);
	for (i=0;i<st->tocentries;i++) {
		fprintf(F,"toc%d.value=%d\n",i,st->toc[i].value);
		fprintf(F,"toc%d.prognr=%ld\n",i,st->toc[i].prognr);
		fprintf(F,"toc%d.hour=%d\n",i,st->toc[i].hour);
		fprintf(F,"toc%d.minute=%d\n",i,st->toc[i].minute);
		fprintf(F,"toc%d.second=%d\n",i,st->toc[i].second);
		fprintf(F,"toc%d.frame=%d\n",i,st->toc[i].frame);
	}
}

/* loads state */
void
da_down_load_state(FILE *F,struct da_state *st) {
	int	l,i;
	char	buf[200];
	struct	da_tocentry	te;
	static	int	lastent=-1;

	st->toc=NULL;
	te.name=NULL;
#define READINT(s,x) if (sscanf(buf,s"=%d",&l)) {x=l;continue;}
	while (fgets(buf,200,F)!=NULL) {
		READINT("values",st->values);
		READINT("bnr",st->bnr);
		READINT("cnt_jump",st->cnt_jump);
		READINT("cnt_start",st->cnt_start);
		READINT("cnt_toc",st->cnt_toc);
		READINT("cur_toc",st->cur_toc);

		READINT("samplerate",st->subid.samplerate);
		READINT("channels",st->subid.channels);
		READINT("encoding",st->subid.encoding);
		READINT("scms",st->subid.scms);
		READINT("prognr",st->prognr);
		READINT("abstime.indexnr",st->abstime.indexnr);
		READINT("abstime.hour",st->abstime.hour);
		READINT("abstime.minute",st->abstime.minute);
		READINT("abstime.second",st->abstime.second);
		READINT("abstime.frame",st->abstime.frame);
		READINT("runtime.indexnr",st->runtime.indexnr);
		READINT("runtime.hour",st->runtime.hour);
		READINT("runtime.minute",st->runtime.minute);
		READINT("runtime.second",st->runtime.second);
		READINT("runtime.frame",st->runtime.frame);
		READINT("recorddate.weekday",st->recorddate.weekday);
		READINT("recorddate.year",st->recorddate.year);
		READINT("recorddate.month",st->recorddate.month);
		READINT("recorddate.day",st->recorddate.day);
		READINT("recorddate.hour",st->recorddate.hour);
		READINT("recorddate.minute",st->recorddate.minute);
		READINT("recorddate.second",st->recorddate.second);
		READINT("progtime.indexnr",st->progtime.indexnr);
		READINT("progtime.hour",st->progtime.hour);
		READINT("progtime.minute",st->progtime.minute);
		READINT("progtime.second",st->progtime.second);
		READINT("progtime.frame",st->progtime.frame);
		READINT("tocentries",st->tocentries);
		if (sscanf(buf,"toc%d.",&i)) {
			char	buf2[100];

			if (lastent!=i) {
				if (lastent!=-1)
					da_toc_add(st,&te,1);
				lastent=i;
			}
			sprintf(buf2,"toc%d.value=%%d",i);
			if (sscanf(buf,buf2,&l)) {te.value=l;continue;}
			sprintf(buf2,"toc%d.prognr=%%d",i);
			if (sscanf(buf,buf2,&l)) {te.prognr=l;continue;}
			sprintf(buf2,"toc%d.hour=%%d",i);
			if (sscanf(buf,buf2,&l)) {te.hour=l;continue;}
			sprintf(buf2,"toc%d.minute=%%d",i);
			if (sscanf(buf,buf2,&l)) {te.minute=l;continue;}
			sprintf(buf2,"toc%d.second=%%d",i);
			if (sscanf(buf,buf2,&l)) {te.second=l;continue;}
			sprintf(buf2,"toc%d.frame=%%d",i);
			if (sscanf(buf,buf2,&l)) {te.frame=l;continue;}
		}
		if (st->values&DA_V_HAVE_CATALOG) {
			int	x[13],i;
			if (sscanf(buf,"catalog=%d %d %d %d %d %d %d %d %d %d %d %d %d\n",x,x+1,x+2,x+3,x+4,x+5,x+6,x+7,x+8,x+9,x+10,x+11,x+12)==13) {
				for (i=13;i--;)
					st->catalog.nr[i]=x[i];
			}
		}
		if (st->values&DA_V_HAVE_ISRC) {
			READINT("isrc.country",st->isrc.country);
			READINT("isrc.producer",st->isrc.producer);
			READINT("isrc.year",st->isrc.year);
			READINT("isrc.serialnr",st->isrc.serialnr);
		}
	}
	if (lastent!=-1)
		da_toc_add(st,&te,0);
	da_toc_update(st);
	st->values|=DA_V_STATE_LOADED;
}

/* the UNIX open interface */
int
da_downopen(char *fn,int opflags) {
	int	fd;

	fd=open(fn,opflags);
	if (	(fd==-1) &&
		(errno==ENOENT) &&
		(	(opflags&O_ACCMODE==O_RDWR)||
			(opflags&O_ACCMODE==O_WRONLY)
		)
	)
		fd=open(fn,opflags|O_CREAT,0666);
	if (fd==-1)
		perror("downopen:open:");
	return fd;
}

/* the init interface */
int
da_downinit(int fd,int opflags,char *fn,struct da_state *st) {
	struct	stat stbuf;
	struct	da_user_private *private;

	st->ifdata=(caddr_t)(private=(struct da_user_private*)malloc(sizeof(struct da_user_private)));
	if (-1==fstat(fd,&stbuf)) {
		perror("da_downopen:fstat");
		return -1;
	}
	private->fn=strdup(fn);
	private->mode = opflags;
	if (S_ISCHR(stbuf.st_mode)) {
		int	stateid;
		char	fnbuf[100];
		FILE	*F;

		private->type=DATAUDIO_DEV;
		private->major=major(stbuf.st_rdev);
		private->minor=minor(stbuf.st_rdev);
		stateid=(private->major<<16)|private->minor;
		sprintf(fnbuf,"/tmp/.dataudio_%d",stateid);
		if (NULL!=(F=fopen(fnbuf,"r")))
			da_down_load_state(F,st);
		fclose(F);
	} else
		private->type=DATAUDIO_FILE;
	return fd;
}

/* the UNIX close interface */
int
da_downclose(int fd) {
	char	fnbuf[100];
	FILE	*F;
	struct	da_state	*st;
	struct	da_user_private *private;
	int	stateid;
	extern	struct da_state *da_lookup(int id);
	
	st=da_lookup(fd);
	if (st==NULL) {fprintf(stderr,"da_lookup didn't find %d?\n",fd);exit(1);}
	private=(struct da_user_private*)st->ifdata;
	if (private->type==DATAUDIO_DEV) {
		stateid=(private->major<<16)|private->minor;
		sprintf(fnbuf,"/tmp/.dataudio_%d",stateid);
		if (NULL!=(F=fopen(fnbuf,"w")))
			da_down_save_state(F,st);
		fclose(F);
	}
	return close(fd);
}

/* the UNIX write interface */
int
da_downwrite(int fd,unsigned char *buf,int size) {
	int	res,retries;

	res=-1;retries=20;
	while (retries-- && res==-1) {
		res=write(fd,buf,size);
	}
	return res;
}

/* the UNIX read interface */
int
da_downread(int fd,unsigned char *buf,int size) {
	int	ret,retries;

	retries=20;
	while (retries--) {
		ret=read(fd,buf,size);
		if (ret!=-1)
			break;
	}
	if (ret==-1)
		perror("read");
	return ret;
}

/* the UNIX ioctl interface, passes all necessary calls further down */
int 
da_downcontrol(int fd,int code,long arg) {
	union	mt_locate	mtloc;
	struct	da_locate	*daloc=(struct da_locate*)arg;
	struct	da_position	*dapos;
	unsigned	char	buf[20];
	int			res;
	struct da_state		*st;
	struct da_user_private	*priv;	

	if (code==DA_LOCATE) {
		st=da_lookup(fd);
		priv=(struct da_user_private*)st->ifdata;
		if (priv->mode & O_NDELAY)
			mtloc.locate_pno.immediate=1;
		else
			mtloc.locate_pno.immediate=0;
		switch (daloc->type) {
		case DA_LOC_PROGNR:
			mtloc.locate_pno.type=MT_LTYPE_PNO;
			mtloc.locate_pno.pno=l2bcd(daloc->u.prognr);
			break;
		case DA_LOC_PROGTIME:
			mtloc.locate_progtime.type=MT_LTYPE_PTIME;
			mtloc.locate_progtime.hour=l2bcd(daloc->u.time.hour);
			mtloc.locate_progtime.minute=l2bcd(daloc->u.time.minute);
			mtloc.locate_progtime.second=l2bcd(daloc->u.time.second);
			mtloc.locate_progtime.frame=l2bcd(daloc->u.time.frame);
			break;
		case DA_LOC_ABSTIME:
			mtloc.locate_abstime.type=MT_LTYPE_ATIME;
			mtloc.locate_abstime.hour=l2bcd(daloc->u.time.hour);
			mtloc.locate_abstime.minute=l2bcd(daloc->u.time.minute);
			mtloc.locate_abstime.second=l2bcd(daloc->u.time.second);
			mtloc.locate_abstime.frame=l2bcd(daloc->u.time.frame);
			break;
		case DA_LOC_RECTIME:
			mtloc.locate_runtime.type=MT_LTYPE_RTIME;
			mtloc.locate_runtime.hour=l2bcd(daloc->u.time.hour);
			mtloc.locate_runtime.minute=l2bcd(daloc->u.time.minute);
			mtloc.locate_runtime.second=l2bcd(daloc->u.time.second);
			mtloc.locate_runtime.frame=l2bcd(daloc->u.time.frame);
			break;
		}
		res=ioctl(fd,MTIOLOCATE,&mtloc);
		if (res==-1) {
			int	saveerrno=errno;
			perror("ioctl MTIOLOCATE");
			errno=saveerrno;
		}
		return res;
	}
	if (code==DA_READ_POSITION) {
		if (-1==ioctl(fd,MTIOREADPOS,buf)) {
			perror("ioctl MTIOREADPOS");
			return -1;
		}
		dapos=(struct da_position*)arg;
		dapos->prognr=buf[4]*256+buf[5]*16+buf[6];
		dapos->indexnr=buf[7];
		dapos->progtime.indexnr=dapos->indexnr;
		dapos->abstime.indexnr=dapos->indexnr;
		dapos->runtime.indexnr=dapos->indexnr;
		dapos->progtime.hour=buf[8];
		dapos->progtime.minute=buf[9];
		dapos->progtime.second=buf[10];
		dapos->progtime.frame=buf[11];
		dapos->abstime.hour=buf[12];
		dapos->abstime.minute=buf[13];
		dapos->abstime.second=buf[14];
		dapos->abstime.frame=buf[15];
		dapos->runtime.hour=buf[16];
		dapos->runtime.minute=buf[17];
		dapos->runtime.second=buf[18];
		dapos->runtime.frame=buf[19];
		return 0;
	}
	if (code==DA_REWIND) {
		struct	mtop	mtop;

		mtop.mt_op=MTREW;
		return ioctl(fd,MTIOCTOP,&mtop);
	}
#ifdef sun
	if (code==DA_ABORT)
		return ioctl(fd,MTIOABORT);
#endif
	if (code==DA_EJECT) {
		struct	mtop	mtop;

		mtop.mt_op=MTOFFL;
		return ioctl(fd,MTIOCTOP,&mtop);
	}
	return ioctl(fd,code,arg);
}
