#define DEB(stmt)	/* stmt */	/* For debug printing */
#define DEB2(stmt)	stmt 		/* For debug printing */
#define DEB3(stmt)	stmt 		/* For debug printing */
#define DEB_ALWAYS(stmt)	stmt	/* Interesting things */

#define _INCLUDE_POSIX_SOURCE 1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/errno.h>

#define USE_SIMPLE_MACROS
#include <sys/soundcard.h>

#include "mlib.h"

#define _seqbuf eventptr
#define _seqbufptr 4
#define _SEQ_ADVBUF(len) *optr = *optr + 1;

#define STORE(cmd) \
	if (track->events) \
	  { \
	  	unsigned char *eventptr = &(track->events[*optr*12]); \
		*(int *)&eventptr[0] = track->current_time; \
	  	cmd; \
	  } \
	else \
	  { \
	    *optr =  *optr + 1; \
	  }

#define META_EVENT(type, buf, len) \
	{ \
		eventptr[_seqbufptr + 0] = EV_PRIVATE_META; \
		eventptr[_seqbufptr + 2] = (type); \
		*(short*)&eventptr[_seqbufptr + 3] = (short)len; \
		*(char**)&eventptr[_seqbufptr + 4] = malloc(len); \
		memcpy((char**)&eventptr[_seqbufptr + 4], &(buf), len); \
		*optr = *optr + 1; \
	}

#define MAX_EVENT_SIZE	4095

static int midi_msglen[8] = {
		2,	/* 8x = Note off */
		2,	/* 9x = Note on */
		2,	/* Ax = Polyphonic key pressure (After-touch) */
		2,	/* Bx = Control change */
		1,	/* Cx = Program change */
		1,	/* Dx = Channel pressure (After-touch) */
		2,	/* Ex = Pitch wheel change */
		0	/* Fx = system messages */
	};

static char mlib_errstring[256] = "No errors so far\n";

static char *mlib_seterr(char *msg)
{
	strncpy(mlib_errstring, msg, sizeof(mlib_errstring));
	return NULL;
}

static char *mlib_syserr(char *name)
{
	sprintf(mlib_errstring, "%s: %s", name, strerror(errno));
	return NULL;
}

int mlib_chkdesc(mlib_desc *desc)
{
	if (desc == NULL)
	{
		mlib_seterr("NULL mlib descriptor specified");
		return 0;
	}

	if (desc->magic != 0x121234)
	{
		mlib_seterr("Invalid MAGIC in the mlib descriptor");
		return 0;
	}


	if (desc->fd < 0)
	{
		mlib_seterr("Invalid fd in the mlib descriptor");
		return 0;
	}

	return 1;
}

static int
mlib_loadbuf(mlib_desc *desc)
{
	desc->bufp = 0;

	if ((desc->bufcnt=read(desc->fd, desc->buf, sizeof(desc->buf)))==-1)
	{
		mlib_syserr(desc->path);
		return 0;
	}

	if (desc->bufcnt == 0)
	{
		mlib_seterr("End of MIDI file");
		return 0;
	}

	return 1;
}

static int
mlib_seek(mlib_desc *desc, long pos)
{

	if (lseek(desc->fd, pos, 0)==-1)
	{
		mlib_syserr(desc->path);
		return 0;
	}

	desc->bufcnt = 0;
	desc->bufp = 1;

	return 1;
}

static int
mlib_rdstr(mlib_desc *desc, char *target, int nchars)
{
	int i;

	if (!mlib_chkdesc(desc)) return 0;

	for (i=0;i<nchars;i++)
	{
		if (desc->bufp >= desc->bufcnt)
		if (!mlib_loadbuf(desc)) return 0;

		target[i] = desc->buf[desc->bufp];
		desc->bufp++;
	}

	target[nchars] = 0;	/* End of string */

	return nchars;
}

static int
mlib_rdvarlen(mlib_desc *desc, int *target)
{
	int tmp, i;

	*target = 0;

	for (i=0;i<6;i++)	/* Read at most 6 bytes */
	{
		if (desc->bufp >= desc->bufcnt)
		if (!mlib_loadbuf(desc)) return 0;

		tmp = desc->buf[desc->bufp];
		desc->bufp++;

		*target <<= 7;
		*target |= tmp & 0x7f;	/* Extract 7 bits */
		if ((tmp & 0x80) == 0)	return i+1; /* Last byte */
	}

	mlib_seterr("Unterminated variable length value");

	return 0;
}

static int
mlib_rdint16(mlib_desc *desc, int *target)
{
	int tmp, i;

	*target = 0;

	for (i=0;i<2;i++)
	{
		if (desc->bufp >= desc->bufcnt)
		if (!mlib_loadbuf(desc)) return 0;

		tmp = desc->buf[desc->bufp];
		desc->bufp++;

		*target <<= 8;
		*target |= tmp & 0xff;
	}

	return 2;
}

static int
mlib_rdbyte(mlib_desc *desc, unsigned char *target)
{
	if (desc->bufp >= desc->bufcnt)
	if (!mlib_loadbuf(desc)) return 0;

	*target = (char)(desc->buf[desc->bufp]);
	desc->bufp++;
	return 1;
}

static int
mlib_rdint32(mlib_desc *desc, int *target)
{
	int tmp, i;

	*target = 0;

	for (i=0;i<4;i++)
	{
		if (desc->bufp >= desc->bufcnt)
		if (!mlib_loadbuf(desc)) return 0;

		tmp = desc->buf[desc->bufp];
		desc->bufp++;

		*target <<= 8;
		*target += tmp & 0xff;
	}

	return 4;
}

static int
mlib_read_mthd(mlib_desc *desc)
{
	char sig[5];
	unsigned char hi, lo;
	int len;

	if (!mlib_chkdesc(desc)) return 0;

	if (!mlib_rdstr(desc, sig, 4)) return 0;

	if (strcmp(sig, "MThd"))
	{
		mlib_seterr("Invalid header signature (!= MThd)");
		return 0;
	}

	if (mlib_rdint32(desc, &len))

	if (mlib_rdint16(desc, &desc->hdr.MThd_fmt))
	DEB2(printf("Header: Format %d\n", desc->hdr.MThd_fmt));

	if (mlib_rdint16(desc, &desc->hdr.MThd_ntrk))
	DEB2(printf("Header: ntrks %d\n", desc->hdr.MThd_ntrk));

	if (mlib_rdbyte(desc, &hi))
	if (mlib_rdbyte(desc, &lo))

	if (hi & 0x80)	/* Negative */
	  {
		DEB2(printf("SMPTE timing: format = %d, resolution = %d\n",
			(signed char)hi, lo));
		desc->hdr.time_mode = TIME_SMPTE;
		desc->hdr.SMPTE_format = (signed char)hi;
		desc->hdr.SMPTE_resolution = lo;
	  }
	else
	  {
		desc->hdr.time_mode = TIME_MIDI;
		desc->hdr.division = (hi << 8) + lo;
		DEB2(printf("Midi timing: timebase = %d ppqn\n",
			desc->hdr.division));
	  }

	desc->curr_trk = -1;
	desc->trk_offs = 0;
	desc->next_trk_offs = 4 + 4 + len;;

	return mlib_seek(desc, desc->trk_offs);	/* Point to the first track */

	return 1;
}

static int
mlib_store_chn_voice_msg(mlib_desc *desc, mlib_track *track, int *optr,
		      unsigned char *evbuf, int len)
{

	unsigned char chn, pgm, note, vel;

	chn = evbuf[0] & 0x0f;	/* Midi channel */

	if (track->init_chn == -1)
	   track->init_chn = chn;
	else
	   if (chn != track->init_chn)
	      track->flags |= TRK_MULTICHN;

	track->chnmask |= (1 << chn);	/* Update channel mask */

	switch (evbuf[0] & 0xf0)
	{
	case 0x90:	/* Note on */
	  vel = evbuf[2];
	  note = evbuf[1];

	  if (vel != 0)
	  {	/* Velocity given -> true note on event */
	        track->flags |= TRK_NOTES;

	        if (note > track->max_note)
	           track->max_note = note;

	        if (note < track->min_note)
	           track->min_note = note;

		if (track->noteon_time == -1)
		   track->noteon_time = track->current_time;

		if (vel != 64)
		   track->flags |= TRK_VEL_NOTEON;

		STORE( SEQ_START_NOTE(0, chn, note, vel) );
	  	break;	/* NOTE! The break is here, not later */
	  }
	  /* Note on with zero G -> note off with vel=64. */
	  /* Fall to the next case */
	  evbuf[2] = 64;	/* Velocity for the note off handler */

	case 0x80:	/* Note off */
	  vel = evbuf[2];
	  note = evbuf[1];

	  if (vel != 64)
	     track->flags |= TRK_VEL_NOTEOFF;
	  STORE( SEQ_STOP_NOTE(0, chn, note, vel) );
	  break;

	case 0xA0:	/* Polyphonic key pressure/Aftertouch */
	  track->flags |= TRK_POLY_AFTERTOUCH | TRK_AFTERTOUCH;
	  STORE( SEQ_KEY_PRESSURE(0, chn, note, vel) );
	  break;

	case 0xB0:	/* Control change */
	  {
/*
 * Introduce a way to combine the LSB with MSB before storing the
 * event.
 */
	    unsigned short value = evbuf[2];	/* Incorrect */
	    unsigned char ctl = evbuf[1];

	    track->flags |= TRK_CONTROLS;
	    STORE( SEQ_CONTROL(0, chn, ctl, value) );
	  }
	  break;

	case 0xC0:	/* Program change */
	  pgm = evbuf[1];
	  if (track->init_pgm == -1)
	     track->init_pgm = pgm;
	  else
	     if (pgm != track->init_pgm)
	        track->flags |= TRK_MULTIPGM;

	   track->pgm_map[pgm] = pgm;
	   STORE( SEQ_SET_PATCH(0, chn, pgm) ); 
	  break;

	case 0xD0:	/* Channel pressure/Aftertouch */
	  track->flags |= TRK_AFTERTOUCH;
	  STORE( SEQ_CHN_PRESSURE(0, chn, evbuf[1]) );
	  break;

	case 0xE0:	/* Pitch bend change */
	  track->flags |= TRK_BENDER;
	  STORE( SEQ_BENDER(0, chn, (evbuf[1] & 0x7f) + ((evbuf[2] & 0x7f)<<7)) );
	  break;

	default:
	  mlib_seterr("Internal error: Unexpected midi event");
	  fprintf(stderr, "Internal error: Unexpected midi event %02x\n", evbuf[0]);

	  return 0;
	}

	return 1;
}

static int
mlib_store_meta_event(mlib_desc *desc, mlib_track *track, int *optr,
		      unsigned char *evbuf, int len)
{
	int type = evbuf[0];
	int i;

/*
 * The event is stored at the end of this function. If there is no 
 * need to store the event, the case entry should return (return 1).
 */

	switch (type)
	{
	case 0x00:	/* Extension events ?????????????? */
	  /* Not supported yet */
	  break;

	case 0x01:	/* Descriptive text */
	case 0x02:	/* Copyright notice */
	case 0x03:	/* Sequence/track name */
	case 0x04:	/* Instrument name */
	case 0x05:	/* Lyric */
	case 0x06:	/* Marker */
	case 0x07:	/* Cue point */
#if 0
	  for (i=0;i<len-1;i++)
	      printf("%c", evbuf[1+i]);
	  printf("\n");
#endif
	  break;

	case 0x21:	/* What is this */
	  break;

/*	Here is a big hole in the known meta event space */

	case 0x2f:	/* End of track */
	  break;

/*	Here is a big hole in the known meta event space */

	case 0x51:	/* Set tempo (usec per MIDI quarter-note) */
	  {
	    int tempo_bpm = 120;
	    unsigned int usecs_per_qn;

	    usecs_per_qn = (evbuf[1] << 16) | (evbuf[2] << 8) | evbuf[3];
	    tempo_bpm = (60000000 + (usecs_per_qn / 2)) / usecs_per_qn;

	    STORE( SEQ_SET_TEMPO(tempo_bpm) );
	    return 1;
	  }
	  break;

	case 0x54:	/* SMPTE offset */
	  break;

	case 0x58:	/* Time signature */
	  {
	  	unsigned sig;

	  	sig = evbuf[1] << 24;
	  	sig |= evbuf[2] << 16;
	  	sig |= evbuf[3] << 8;
	  	sig |= evbuf[4];

	  	if (!desc->timesig)
	  	   desc->timesig = sig;
	  	STORE( SEQ_TIME_SIGNATURE(sig) );
	  }
	  break;

	case 0x59:	/* Key signature */
	  break;

	case 0x7f:	/* Vendor specific meta event */
	  if (evbuf[1] == 0 && evbuf[2] == 0 && evbuf[3] == 0x41)
	    {	/* Microsoft ?? */
		if (len == 4) DEB_ALWAYS(printf("GM file flag \n"));

/* Don't forget to add more code here */
	    }
	  else
	    {
		  DEB_ALWAYS(printf("Private meta event:\n"));
		  for (i=0;i<len;i++)
		    DEB_ALWAYS(printf("%02x ", evbuf[i]));
		  DEB_ALWAYS(printf("\n"));
		  for (i=0;i<len;i++)
		    DEB_ALWAYS(printf("%c", (evbuf[i]>=' ' && evbuf[i] < 127) ?
						evbuf[i] : '.'));
		  DEB_ALWAYS(printf("\n"));
	    }
	  break;

	default:
	  DEB_ALWAYS(printf("Meta event: 0x%02x???\n", type));
	  for (i=0;i<len;i++)
	    DEB_ALWAYS(printf("%02x ", evbuf[i]));
	  DEB_ALWAYS(printf("\n"));
	  for (i=0;i<len;i++)
	    DEB_ALWAYS(printf("%c", (evbuf[i]>=' ' && evbuf[i] < 127) ?
					evbuf[i] : '.'));
	  DEB_ALWAYS(printf("\n"));
	}

	STORE( META_EVENT(type, evbuf, len) );

	return 1;
}

static int
mlib_input_event(mlib_desc *desc, int *iptr, int *optr, mlib_track *track)
{
	int time, len, event_len, i, p;
	unsigned char b, status, type;

	unsigned char buf[MAX_EVENT_SIZE+1];

	if (!(len=mlib_rdvarlen(desc, &time))) return 0;
	*iptr += len;

	track->current_time += time;
	if (track->end_time < track->current_time)
   	   track->end_time = track->current_time;

	if (!(len=mlib_rdbyte(desc, &type))) return 0;
	*iptr += len;

	DEB(printf("EVENT: time = %d type = 0x%02x: ", time, type));

	p = 0;

	switch (type)
	{
	case 0xff:	/* Meta event */
	  if (!(len=mlib_rdbyte(desc, &type))) return 0;
	  *iptr += len;

	  buf[p++] = type;

	  if (!(len=mlib_rdvarlen(desc, &event_len))) return 0;
	  *iptr += len;

	  DEB(printf("meta(type=%02x, len=%d): ", type, event_len));

	  if ((event_len+1) > MAX_EVENT_SIZE)
	  {
		mlib_seterr("Too long meta event in file");
		*iptr += event_len;
		return 0;	/* Fatal error */
	  }

	  for (i=0;i<event_len;i++)
	  {
		if (!(len=mlib_rdbyte(desc, &b))) return 0;
		*iptr += len;

		buf[p++] = b;
		DEB(printf("%02x ", b));
	  }

/*
 * End of track test
 */

	  if (type == 0x2f)
	  {
	  	*iptr = 0x7fffffff;
	  	return 1;
	  }
	  if (!mlib_store_meta_event(desc, track, optr, buf, p)) return 0;
	  break;

	case 0xf0:	/* Sysex, variation 1 */
	  if (!(len=mlib_rdvarlen(desc, &event_len))) return 0;
	  *iptr += len;
	  DEB(printf("sysex1 (%d): f0 ", event_len));
	  for (i=0;i<event_len;i++)
	  {
		if (!(len=mlib_rdbyte(desc, &b))) return 0;
		*iptr += len;

		DEB(printf("%02x ", b));
	  }
	  break;

	case 0xf7:	/* Sysex, variation 2 */
	  if (!(len=mlib_rdvarlen(desc, &event_len))) return 0;
	  *iptr += len;
	  DEB(printf("sysex2 (%d): ", event_len));
	  for (i=0;i<event_len;i++)
	  {
		if (!(len=mlib_rdbyte(desc, &b))) return 0;
		*iptr += len;

		DEB(printf("%02x ", b));
	  }
	  break;

	default:

	  if ((type & 0xf0) == 0xf0)	/* Sys message */
	    {
		DEB(printf("system message "));
		DEB(printf("\n"));
		mlib_seterr("Unsupported midi file event");
		return 0;
	    }
	  else
	  if (type < 0x80)
	    {
		buf[p++] = status = desc->prev_status;
		DEB(printf("Midi message (RST): %02x %02x ",
			status, type));
		buf[p++] = type;

		event_len = midi_msglen[((status >> 4) & 0xf) -8]-1;
		for (i=0;i<event_len;i++)
		{
			if (!(len=mlib_rdbyte(desc, &b))) return 0;
			*iptr += len;
			buf[p++] = b;
			DEB(printf("%02x ", b));
		}
	  	if (!mlib_store_chn_voice_msg(desc, track, optr, buf, p)) 
			return 0;
	    }
	  else
	    {
		buf[p++] = status = desc->prev_status = type;
		DEB(printf("Midi message: %02x ", status));

		event_len = midi_msglen[((status >> 4) & 0xf) -8];
		for (i=0;i<event_len;i++)
		{
			if (!(len=mlib_rdbyte(desc, &b))) return 0;
			*iptr += len;
			buf[p++] = b;
			DEB(printf("%02x ", b));
		}
	  	if (!mlib_store_chn_voice_msg(desc, track, optr, buf, p)) 
			return 0;
	    }
	}

	DEB(printf("\n"));

	return 1;
}

static int
mlib_load_pass(mlib_desc *desc, mlib_track *track, int *iptr, int *optr, int len)
{
	int i;

	for (i=0;i<128;i++)
	    track->pgm_map[i] = -1;

	track->len = 0;
	track->flags = 0;
	track->init_chn = -1;
	track->init_pgm = -1;
	track->port = -1;
	track->chn = -1;
	track->chnmask = 0;
	track->pgm = -1;
	track->current_time = 0;
	track->noteon_time = -1;
	track->end_time = 0;
	track->min_note = 300;
	track->max_note = -1;

	*iptr = *optr = 0;
	desc->prev_status = 0;

	while (*iptr<len)
	  if (!mlib_input_event(desc, iptr, optr, track))
	{
		return 0;
	}

	return 1;
}

mlib_track *mlib_loadtrack(mlib_desc *desc, int *end_detected)
{
	mlib_track *track;
	char sig[5];
	int dummy, len, iptr, optr, pass1events;

	*end_detected = 0;

	if (!mlib_chkdesc(desc)) return NULL;

	if ((track = (mlib_track *)malloc(sizeof(*track)))==NULL)
	   return (mlib_track*)mlib_seterr("Can't malloc track descriptor");

	desc->curr_trk++;

	if (desc->curr_trk >= desc->hdr.MThd_ntrk)
	{
		*end_detected = 1;
		return (mlib_track*)mlib_seterr(
			"No more tracks in the midi file");
	}

	desc->trk_offs = desc->next_trk_offs;

	if (!mlib_seek(desc, desc->trk_offs))
	{
		free((char *)track);
		return NULL;
	}

	if (!mlib_rdstr(desc, sig, 4))
	{
		free((char *)track);
		return NULL;
	}

	if (strcmp(sig, "MTrk"))
	{
		free((char *)track);
		return (mlib_track *)mlib_seterr(
			"Invalid track signature (!= MTrk)");
	}

	if (!mlib_rdint32(desc, &len))
	{
		free((char *)track);
		return 0;
	}

	desc->next_trk_offs = desc->trk_offs + 4 + 4 + len;

	track->events = NULL;

	if (!mlib_load_pass(desc, track, &iptr, &optr, len))
	{
		free((char *)track);
		return NULL;
	}

	pass1events = optr;

/*
 *	Pass 2 actually store the messages
 */

	track->events = malloc(12 * pass1events);
	if (pass1events == 0) return track;	/* Empty track */

	if (!mlib_seek(desc, desc->trk_offs+4))	/* Past the MTrk */
	{
		free(track->events);
		free((char *)track);
		return NULL;
	}

	if (!mlib_rdint32(desc, &dummy))
	{
		free(track->events);
		free((char *)track);
		return 0;
	}

	if (!mlib_load_pass(desc, track, &iptr, &optr, len))
	{
		free(track->events);
		free((char *)track);
		return NULL;
	}

	track->len = optr;

	if (optr != pass1events)
	   fprintf(stderr, 
	     "Warning: Events counts of pass 1 and 2 differ (%d/%d)\n",
	        	pass1events, optr);

	return track;
}

void
mlib_deltrack(mlib_track *track)
{
	if (track == NULL) return;

	if (track->events != NULL)
	   free(track->events);

	free((char *)track);
}

mlib_desc *mlib_open(char *path)
{
	mlib_desc *desc;
	int i;

	desc = (mlib_desc *)malloc(sizeof(*desc));
	if (desc == NULL)
	   return (mlib_desc *)mlib_seterr("Malloc failed");

	if ((desc->fd=open(path, O_RDONLY, 0))== -1)
	{
		free((char *)desc);
		return (mlib_desc*)mlib_syserr(path);
	}

	strncpy(desc->path, path, sizeof(desc->path));

	desc->bufcnt = 0;
	desc->bufp = 1;
	desc->curr_trk = 0;
	desc->trk_offs = 0;
	desc->next_trk_offs = 0;
	desc->magic = 0x121234;
	desc->control_track = NULL;
	desc->timesig = 0;
	for (i=0;i<MAX_TRACK;i++)
	    desc->tracks[i] = NULL;

	if (!mlib_read_mthd(desc))
	{
		close(desc->fd);
		free((char *)desc);
		return NULL;
	}

	return desc;
}

void
mlib_close(mlib_desc *desc)
{
	if (mlib_chkdesc(desc))
	{
		close(desc->fd);
		desc->fd = -1;
		free(desc);
	}
}

char *mlib_errmsg(void)
{
	return &mlib_errstring[0];
}
