#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/soundcard.h>

static int seqfd = -1;
static int initialized = 0;

unsigned char *_seqbuf;
int _seqbuflen = 0;
int _seqbufptr = 0;

int synth_types[64] = {0};
#define ST_NONE		0
#define ST_GUS		1
#define ST_OPL		2

int nrsynths = 0;

extern int gusload (int seqfd, int type, int dev, int pgm);
extern int gusinit (int seqfd, int dev);

void
seqbuf_dump ()
{
  if (_seqbufptr)
    if (write (seqfd, _seqbuf, _seqbufptr) == -1)
      {
	perror ("write /dev/sequencer");
	exit (-1);
      }
  _seqbufptr = 0;
}

static int
oss_init_lib(void)
{
	int i, synth;
	struct synth_info info;

	if (_seqbuflen < 32 || _seqbuflen > 2048)
	   _seqbuflen = 2048;

	if ((_seqbuf=malloc(_seqbuflen))==NULL)
		return 3;

	nrsynths = 0;
	if (ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &nrsynths)==-1)
	{
		perror("/dev/sequencer (NRSYNTHS");
		exit(-1);
	}

	for (synth=0;synth < nrsynths;synth++)
	{
		info.device = synth;

		if (ioctl(seqfd, SNDCTL_SYNTH_ID, &info)==-1)
		{
			perror("/dev/sequencer (SYNTH_ID");
			fprintf(stderr, "OSS version 3.8 or later is required with OSSlib\n");
			exit(-1);
		}

		if (strcmp(info.name, "GUS")==0 ||
		    strcmp(info.name, "IWAVE")==0 ||
		    strcmp(info.name, "SoftOSS")==0)
		  {
			synth_types[synth] = ST_GUS;
			gusinit(seqfd, synth);
		  }
		else
		if (strcmp(info.name, "OPL")==0)
		  {
			synth_types[synth] = ST_OPL;
			opl3init(seqfd, synth);
		  }
	}

	initialized = 1;
	return 0; /* OK */
}

int
OSS_init(int userfd, int buflen)
{
	if (_seqbuflen || _seqbuflen || seqfd != -1 || initialized)
	{
		fprintf(stderr, "libOSS: OSS_init called too late\n");
		return 1;
	}

	seqfd = userfd;

	if (buflen < 32 || buflen > 2048)
	{
		fprintf(stderr, "libOSS: OSS_init called with invalid buflen\n");
		return 2;
	}

	_seqbuflen = buflen;

	return oss_init_lib();
}

static void
sanity_check(int fd, unsigned char *buf, int buflen)
{
	if (seqfd != fd)
	if (seqfd == -1)
	   seqfd = fd;
	else
	  {
	     fprintf(stderr, "OSSlib: seqfd is inconsistent\n");
	  }

	if (buf != _seqbuf)
	{
	   fprintf(stderr, "OSSlib: _seqbuf is inconsistent\n");
	}

	if (buflen != _seqbuflen)
	{
	   fprintf(stderr, "OSSlib: _seqbuf is inconsistent\n");
	}

	if (!initialized)
	   if (oss_init_lib() != 0)
	   {
		fprintf(stderr, "OSSlib: Library initialization failed\n");
		exit(-1);
	   }
}

void
OSS_seqbuf_dump(int fd, unsigned char *buf, int buflen)
{
	sanity_check(fd, buf, buflen);

	write(seqfd, _seqbuf, _seqbufptr);
	_seqbufptr = 0;
}

void
OSS_seq_advbuf(int len, int fd, unsigned char *buf, int buflen)
{
	sanity_check(fd, buf, buflen);
	_seqbufptr += len;
}

void
OSS_seq_needbuf(int len, int fd, unsigned char *buf, int buflen)
{
	sanity_check(fd, buf, buflen);

	if ((_seqbufptr+len) > _seqbuflen)
	{
		write(seqfd, _seqbuf, _seqbufptr);
		_seqbufptr = 0;
	}	
}

void
OSS_patch_caching(int dev, int chn, int patch, int fd, unsigned char *buf, int buflen)
{
	sanity_check(fd, buf, buflen);

	switch (synth_types[dev])
	{
	case ST_GUS:
		gusload(seqfd, 0, dev, patch);
		break;
	case ST_OPL:
		opl3load(seqfd, 0, dev, patch);
		break;
	}
}

void
OSS_drum_caching(int dev, int chn, int patch, int fd, unsigned char *buf, int buflen)
{
	sanity_check(fd, buf, buflen);

	switch (synth_types[dev])
	{
	case ST_GUS:
		gusload(seqfd, 0, dev, patch+128);
		break;
	case ST_OPL:
		opl3load(seqfd, 0, dev, patch+128);
		break;
	}
}

void
OSS_write_patch(int fd, unsigned char *buf, int len)
{
	sanity_check(fd, _seqbuf, _seqbuflen);

	if (write(fd, buf, len)==-1)
	{
	   perror("OSS_write_patch");
	   exit(-1);
	}
}

int
OSS_write_patch2(int fd, unsigned char *buf, int len)
{
	sanity_check(fd, _seqbuf, _seqbuflen);

	return write(fd, buf, len);
}

