#include "stdio.h"
#include "sys/file.h"
#ifndef mips
#include "stdlib.h"
#endif
#include "sndheader.h"
#include "xlisp.h"
#include "sound.h"
#include "falloc.h"
#include "sndread.h"
#include "multiread.h"

/* allocate input buffer space for this many bytes/frame,
 * e.g. 8 allows 4 channels of shorts or 8 channels of bytes.
 * If frames are bigger, then multiple reads will be issued.
 */
#define max_bytes_per_frame 8
#define input_buffer_max (max_sample_block_len * max_bytes_per_frame)

/* multiread_fetch - read samples into multiple channels.  */
/*
 * The susp is shared by all channels.  The susp has backpointers
 * to the tail-most snd_list node of each channels, and it is by
 * extending the list at these nodes that sounds are read in.
 * To avoid a circularity, the reference counts on snd_list nodes
 * do not include the backpointers from this susp.  When a snd_list
 * node refcount goes to zero, the multiread susp's free routine
 * is called.  This must scan the backpointers to find the node that
 * has a zero refcount (the free routine is called before the node
 * is deallocated, so this is safe).  The backpointer is then set
 * to NULL.  When all backpointers are NULL, the susp itself is
 * deallocated, because it can only be referenced through the
 * snd_list nodes to which there are backpointers.
 */
void multiread_fetch(susp, snd_list)
  register read_susp_type susp;
  snd_list_type snd_list;
{
    double scale_factor;
    int i, j;
    int frames_read = 0; /* total frames read in this call to fetch */
    int n;
    boolean more_to_go = true;	/* outer loop until blocks are read */
    sample_block_type out;
    char input_buffer[input_buffer_max];
    signed char *byte_ptr;
    short *int16_ptr;
    long *int32_ptr;
    float *float_ptr;

    /* when we are called, the caller (SND_get_first) will insert a new
     * snd_list node.  We need to do this here for all other channels.
     */
    for (j = 0; j < susp->nchans; j++) {

/*        printf("multiread_fetch: chan[%d] = ", j);
        print_snd_list_type(susp->chan[j]);
        printf("\n");
 */
	if (!susp->chan[j]) {	/* ignore non-existent channels */
/*	    printf("multiread_fetch: ignore channel %d\n", j);*/
	    continue;
	}
	falloc_sample_block(out, "multiread_fetch");
/*	printf("multiread: allocated block %x\n", out); */
	/* Since susp->chan[i] exists, we want to append a block of samples.
	 * The block, out, has been allocated.  Before we insert the block,
	 * we must figure out whether to insert a new snd_list_type node for
	 * the block.  Recall that before SND_get_next is called, the last
	 * snd_list_type in the list will have a null block pointer, and the
	 * snd_list_type's susp field points to the suspension (in this case,
	 * susp).  When SND_get_next (in sound.c) is called, it appends a new
	 * snd_list_type and points the previous one to internal_zero_block 
	 * before calling this fetch routine.  On the other hand, since 
	 * SND_get_next is only going to be called on one of the channels, the
	 * other channels will not have had a snd_list_type appended.
	 * SND_get_next does not tell us directly which channel it wants (it
	 * doesn't know), but we can test by looking for a non-null block in the
	 * snd_list_type pointed to by our back-pointers in susp->chan[].  If
	 * the block is null, the channel was untouched by SND_get_next, and
	 * we should append a snd_list_type.  If it is non-null, then it
	 * points to internal_zero_block (the block inserted by SND_get_next)
	 * and a new snd_list_type has already been appended.
	 */
	if (!susp->chan[j]->block) {
	    snd_list_type snd_list = snd_list_create((snd_susp_type) susp);
	    /* Now we have a snd_list to append to the channel, but a very
	     * interesting thing can happen here.  snd_list_create, which
	     * we just called, MAY have invoked the garbage collector, and
	     * the GC MAY have freed all references to this channel, in which
	     * case multread_free(susp) will have been called, and susp->chan[j]
	     * will now be NULL!
	     */
	    if (!susp->chan[j]) {
		printf("susp %x Channel %d disappeared!\n", susp, j);
		ffree_snd_list(snd_list, "multiread_fetch");
	    } else {
		susp->chan[j]->u.next = snd_list;
	    }
	}
	/* see the note above: we don't know if susp->chan still exists */
	/* Note: We DO know that susp still exists because even if we lost
	 * some channels in a GC, someone is still calling SND_get_next on
	 * some channel.  I suppose that there might be some very pathological
	 * code that could free a global reference to a sound that is in the
	 * midst of being computed, perhaps by doing something bizarre in the
	 * closure that snd_seq activates at the logical stop time of its first
	 * sound, but I haven't thought that one through.
	 */
	if (susp->chan[j]) {
	    susp->chan[j]->block = out;
	    /* check some assertions */
	    if (susp->chan[j]->u.next->u.susp != (snd_susp_type) susp) {
		printf("didn't find susp at end of list for chan %d\n", j);
	    }
	} else { /* we allocated out, but don't need it anymore due to GC */
	    ffree_sample_block(out, "multiread_fetch");
	}
    }

    while (true) {
	int frame_size = susp->nchans * susp->bytes_per_sample;
	/* compute how many sample frames to read */
	long in_count = max_sample_block_len * frame_size;
        long rd_count = in_count;

	if (in_count > input_buffer_max) {
	    in_count = (input_buffer_max / frame_size) * frame_size;
	}

/*	printf("attempting to read %d bytes\n", in_count);*/
	rd_count = read(susp->inf, input_buffer, in_count);

	/* this may truncate a partial frame at eof */
	/* An alternative would be pad the partial frame out with zeros */
	n = rd_count / frame_size;  
/*	printf("got %d bytes = %d frames\n", rd_count, n); */

	/* don't read too many */
	if (n > (susp->cnt - susp->susp.current)) {
	    n = susp->cnt - susp->susp.current;
	}

	/* process one channel at a time, multiple passes through input */
	switch (susp->bytes_per_sample) {
	  case 1:
	    if (susp->mode == MODE_ULAW) {
		scale_factor = 1.0 / SCALE_FACTOR_TO_SHORT;
		for (j = 0; j < susp->nchans; j++) {
		    register sample_block_values_type out_ptr;
		    /* offset by channel number: */
		    byte_ptr = ((signed char *) input_buffer) + j;

		    /* ignore nonexistent channels */
		    if (!susp->chan[j]) continue;

		    /* find pointer to sample buffer */
		    out_ptr = susp->chan[j]->block->samples + frames_read;

		    /* copy samples */
		    for (i = 0; i < n; i++) {
			*out_ptr++ = st_ulaw_to_linear(*byte_ptr) *
					 scale_factor;
			byte_ptr += susp->nchans;
		    }
		    susp->chan[j]->block_len = frames_read + n;
		}
	    } else {
		scale_factor = 1.0 / SCALE_FACTOR_TO_BYTE;
		for (j = 0; j < susp->nchans; j++) {
		    register sample_block_values_type out_ptr;
		    /* offset by channel number: */
		    byte_ptr = ((signed char *) input_buffer) + j;

		    /* ignore nonexistent channels */
		    if (!susp->chan[j]) continue;

		    /* find pointer to sample buffer */
		    out_ptr = susp->chan[j]->block->samples + frames_read;

		    /* copy samples */
		    for (i = 0; i < n; i++) {
			*out_ptr++ = *byte_ptr * scale_factor;
			byte_ptr += susp->nchans;
		    }
		    susp->chan[j]->block_len = frames_read + n;
		}
	    }
	    break;
	  case 2:
	    scale_factor = 1.0 / SCALE_FACTOR_TO_SHORT;
	    for (j = 0; j < susp->nchans; j++) {
		register sample_block_values_type out_ptr;
		/* offset by channel number: */
		int16_ptr = ((short *) input_buffer) + j;

		/* ignore nonexistent channels */
		if (!susp->chan[j]) continue;

		/* find pointer to sample buffer */
		out_ptr = susp->chan[j]->block->samples + frames_read;

		/* copy samples */
		for (i = 0; i < n; i++) {
		    *out_ptr++ = *int16_ptr * scale_factor;
		    int16_ptr += susp->nchans;
		}
		susp->chan[j]->block_len = frames_read + n;
	    }
	    break;
	  case 4:
	    if (susp->mode == MODE_FLOAT) {
		for (j = 0; j < susp->nchans; j++) {
		    register sample_block_values_type out_ptr;
		    /* offset by channel number: */
		    float_ptr = ((float *) input_buffer) + j;

		    /* ignore nonexistent channels */
		    if (!susp->chan[j]) continue;

		    /* find pointer to sample buffer */
		    out_ptr = susp->chan[j]->block->samples + frames_read;

		    /* copy samples */
		    for (i = 0; i < n; i++) {
			*out_ptr++ = *float_ptr;
			float_ptr += susp->nchans;
		    }
		    susp->chan[j]->block_len = frames_read + n;
	        }
	    } else {
		scale_factor = 1.0 / SCALE_FACTOR_TO_LONG;
		for (j = 0; j < susp->nchans; j++) {
		    register sample_block_values_type out_ptr;
		    /* offset by channel number: */
		    int32_ptr = ((long *) input_buffer) + j;

		    /* ignore nonexistent channels */
		    if (!susp->chan[j]) continue;

		    /* find pointer to sample buffer */
		    out_ptr = susp->chan[j]->block->samples + frames_read;

		    /* copy samples */
		    for (i = 0; i < n; i++) {
			*out_ptr++ = *int32_ptr * scale_factor;
			int32_ptr += susp->nchans;
		    }
		    susp->chan[j]->block_len = frames_read + n;
	        }
	    }
	    break;
	}
	frames_read += n;
	susp->susp.current += n;

	if (frames_read == 0) {
	    /* we didn't read anything, but can't return length zero, so
	     * convert snd_list's to pointer to zero block.  This loop
	     * will free the susp via snd_list_unref().
	     */
	    for (j = 0; j < susp->nchans; j++) {
		if (susp->chan[j]) {
		    snd_list_type the_snd_list = susp->chan[j];
		    /* this is done so that multiread_free works right: */
		    susp->chan[j] = susp->chan[j]->u.next;
		    printf("end of file, terminating channel %d\n", j);
		    /* this fixes up the tail of channel j */
		    snd_list_terminate(the_snd_list);
		}
	    }
	    return;
	} else if (susp->cnt == susp->susp.current || rd_count < in_count) {
	    /* we've read the requested number of frames or we
             * reached end of file
	     * last iteration will close file and free susp:
	     */
	    for (j = 0; j < susp->nchans; j++) {
		snd_list_type the_snd_list = susp->chan[j];
		printf("reached susp->cnt, terminating chan %d\n", j);
		if (the_snd_list) {
                    /* assert: */
                    if (the_snd_list->u.next->u.susp != (snd_susp_type) susp) {
                	printf("assertion violation");
                    }
		    /* this is done so that multiread_free works right: */
		    susp->chan[j] = the_snd_list->u.next;
		    snd_list_unref(the_snd_list->u.next);
		    /* terminate by pointing to zero block */
		    the_snd_list->u.next = zero_snd_list;
		}
	    }
	    return;
	} else if (frames_read >= max_sample_block_len) {
	    /* move pointer to next list node */
	    for (j = 0; j < susp->nchans; j++) {
		if (susp->chan[j]) susp->chan[j] = susp->chan[j]->u.next;
	    }
	    return;
	}
    }
} /* multiread__fetch */
  

void multiread_free(read_susp_type susp)
{
    int j;
    boolean active = false;
/*    printf("multiread_free: "); */
    for (j = 0; j < susp->nchans; j++) {
	if (susp->chan[j]) {
	    if (susp->chan[j]->refcnt) active = true;
	    else {
		susp->chan[j] = NULL;
		printf("deactivating channel %d\n", j);
	    }
	}
    }
    if (!active) {
/*	printf("all channels freed, freeing susp now\n"); */
	read_free(susp);
    }
}


LVAL multiread_create(susp)
  read_susp_type susp;
{
    LVAL result;
    int j;

    xlsave1(result);

    result = newvector(susp->nchans);	/* create array for sounds */
    falloc_generic_n(susp->chan, snd_list_type, susp->nchans, 
		     "multiread_create");
    /* create sounds to return */
    for (j = 0; j < susp->nchans; j++) {
	sound_type snd = sound_create((snd_susp_type)susp, 
				      susp->susp.t0, susp->susp.sr, 1.0);
	LVAL snd_lval = cvsound(snd);
/*	printf("multiread_create: sound %d is %x, LVAL %x\n", j, snd, snd_lval); */
	setelement(result, j, snd_lval);
	susp->chan[j] = snd->list;
    }
    xlpop();
    return result;
}
