#include        "cs.h"                            /*                WAVE.C   */

/* Modified 1998 June 5 by Damien Miller & Robin Whittle        */
/* to compile under glibc, rather than the earlier C library.   */
/* One change only - search for "glibc"                         */
/* diskin.c and wave.c both used tell()                         */

#include        "soundio.h"
#include        "wave.h"
#include        "sfheader.h"
#include        <math.h>

#ifndef TRUE
#define TRUE    1
#define FALSE   0
#endif

static struct wav_head formhdr;
static long   framesize;
/* RWD.2.98 to avoid recalculating  variable  headersizes */
static long	this_hdrsize	= 0;
static short	this_format	= 0;
static long	datasize_offset	= 0;
static long	factchunk_offset= 0;
static long	nchans		= 0;	/* because not enough info passed to wavReWriteHdr() ! */

static short lenshort(short sval) /* coerce a natural short into a littlendian short */
{
    char  benchar[2];
    char *p = benchar;

    *p++ = 0xFF & sval;
    *p   = 0xFF & (sval >> 8);
    return(*(short *)benchar);
}

static long lenlong(long lval)     /* coerce a natural long into a littlendian long */
{
    char  benchar[4];
    char *p = benchar;

    *p++ = (char)(0xFF & lval);
    *p++ = (char)(0xFF & (lval >> 8));
    *p++ = (char)(0xFF & (lval >> 16));
    *p   = (char)(0xFF & (lval >> 24));
    return(*(long *)benchar);
}

static short natlshort(short sval) /* coerce a littlendian short into a natural short */
{
    unsigned char benchar[2];
    short natshort;

    *(short *)benchar = sval;
    natshort = benchar[1];
    natshort <<= 8;
    natshort |= benchar[0];
    return(natshort);
}

static long natllong(long lval)     /* coerce a littlendian long into a natural long */
{
    unsigned char benchar[4];
    unsigned char *p = benchar + 3;
    long natlong;

    *(long *)benchar = lval;
    natlong = *p--;
    natlong <<= 8;
    natlong |= *p--;
    natlong <<= 8;
    natlong |= *p--;
    natlong <<= 8;
    natlong |= *p;
    return(natlong);
}

/* RWD.2.98 many changes to set correct variable-header length etc */
void wavWriteHdr(               /* Write WAV header at start of file.  */
    int fd,                     /* Called after open, before data writes*/
    int sampsize,               /* sample size in bytes */
    int nchls,
    double sr)                  /* sampling rate */
{
        long databytes;
#ifdef DEBUG
        printf("wavWriteHdr: fd %d sampsize %d nchls %d sr %lf\n",
                fd,sampsize,nchls,sr);
#endif
        framesize = sampsize * nchls;
        databytes = 0;          /* reset later by wavReWriteHdr */

        formhdr.magic = *((long *)RIFF_ID);
        formhdr.len0 = lenlong(databytes + 36/*WAVHDRSIZ*/); 
        formhdr.magic1 = *((long *)WAVE_ID);
        formhdr.magic2 = *((long *)FMT_ID);
        /*formhdr.len = lenlong((long)16);        /* length of format chunk */
/* RWD.2.98 */
        this_format = (short)(sampsize==4 ? 3 : 1);
        formhdr.format = lenshort(this_format); /* PCM */		
        formhdr.len = this_format == 3 ? lenlong(18L) : lenlong(16L);
        nchans	= nchnls;       /* RWD for wavReWriteHdr() for fact chunk */
        formhdr.nchns = lenshort((short)nchls);
        formhdr.rate = lenlong((long)sr);
        formhdr.aver = lenlong((long)(sr * framesize));
        formhdr.nBlockAlign = lenshort((short)framesize); /* bytes per frame */
        formhdr.size = lenshort((short)(8 * sampsize));	/* bits per sample */
	/* RWD.2.98 have to do these later */
#if 0
        formhdr.magic3 = *((long *)DATA_ID);
        formhdr.datasize = lenlong(databytes);
#endif
        if (write(fd, (char *)&formhdr, sizeof(formhdr)) != sizeof(formhdr))
            die("error writing WAV header");
        /* RWD add cbSize if format = 3 */
        if (this_format==3){
          short cbSize = 0;
          long factid = *((long *)FACT_ID);
          long chunksize = lenlong( sizeof(long));
          long factdata = 0;    /* filled in later... */
          if ((write(fd,(char *) &cbSize,sizeof(short)) != sizeof(short))
              || (write(fd,(char *)&factid,sizeof(long)) <0)
              || (write(fd,(char *)&chunksize,sizeof(long)) <0)
              || ((factchunk_offset = lseek(fd,0,SEEK_CUR)) < 0)
              || (write(fd,(char *)&factdata,sizeof(long)) < 0))
            die("error writing WAV header");
          /* add statutory fact chunk for non-PCM formats */
        }
	/* RWD.2.98 then write data chunk info */
        {
          long magic3 = *((long *)DATA_ID);
          long datasize = lenlong(databytes);
          if ((write(fd,(char *)&magic3,  sizeof(long)) != sizeof(long))
              || ((datasize_offset = lseek(fd,0,SEEK_CUR)) < 0)
              || (write(fd,(char *)&datasize,sizeof(long)) != sizeof(long)))
            die("error writing WAV header");
        }
        this_hdrsize = datasize_offset+sizeof(long);
}

/* RWD: WHY aren't the same params passed here as to wavWriteHdr() ???? */
void wavReWriteHdr(int fd,        /* Write proper sizes into WAV header  */
                   long datasize) /*        called before closing file   */
{                                 /*        & optionally under -R        */

 					/* The line below changed for 
					 * compatibility with glibc.  
					 * Previously "tell(fd)"
				 	 */ 

    long  endpos = lseek(fd, 0, SEEK_CUR);

    /* RWD.2.98: all changed to deal with variable headersize */
    long length = lenlong(endpos-8);
    long size = lenlong(datasize);
    long size_in_samps = lenlong((datasize / sizeof(float)) / nchans);/* for fact chunk */
    if (endpos != datasize + this_hdrsize)
      die("inconsistent WAV size"); /* RWD.2.98: should be able to fudge something! */
    if (lseek(fd, 4L, 0)<0
        || write(fd, (char *)&length, sizeof(long)) != sizeof(long)
        || (lseek(fd,datasize_offset,0) < 0)
        || write(fd,(char *)&size,sizeof(long)) != sizeof(long))
      die("error rewriting WAV header");
    /* update fact chunk if non-PCM format */
    if (this_format==(short)3)
      if ((lseek(fd,factchunk_offset,0) <0)
          || (write(fd,(char *)&size_in_samps,sizeof(long)) != sizeof(long)))
        die("error rewriting WAV header");		
    lseek(fd, endpos, 0);
}

int is_wav_form(long firstlong) /* test a long for wav form ID                 */
                                /* called by readheader prior to wavReadHeader */
{
        return (firstlong == *(long *)RIFF_ID);
}

void wavReadHeader(             /* Read WAV header, fill hdr, & */
    int fd,                     /* postn rd ptr to start of samps*/
    char *fname,
    HEADATA *hdr,               /* datablock for passing data back */
    long firstlong,
    SOUNDIN *p)
{
        struct wav_head form;

        IGNORE(fname);
        p->filetyp = 0;                 /* ensure no bytrev on sreadin here */
        if (!is_wav_form(firstlong))    /* double check it's a form header */
            die("bad form for wavReadHeader");         /* & read remainder */
        sreadin(fd,(char *)&form + sizeof(long),sizeof(form) - sizeof(long),p);
        if (form.magic1 != *(long *) WAVE_ID) {
            err_printf( "Got form.magic = %lx\n", form.magic);
            die("form header not type wav");
        }
        hdr->sr = natllong(form.rate);
        hdr->nchnls = natlshort(form.nchns);
        hdr->sampsize = (natlshort(form.size)) / 8;
        hdr->format = (hdr->sampsize == 2 ? AE_SHORT :
                       hdr->sampsize == 1 ? AE_UNCH :
                       form.format == 3 ? AE_FLOAT :
                       AE_LONG);
        hdr->hdrsize = sizeof(form);
        hdr->filetyp = TYP_WAV;
        hdr->aiffdata = NULL;
        /* hdr->audsize = natllong(form.datasize);  RWD.2.98 see below... */
        hdr->readlong = FALSE;
        hdr->firstlong = 0;
        /* RWD.2.98 check for cbSize field */
        if (form.len==18){
          short cbSize;
          if (read(fd,(void*)&cbSize,sizeof(short)) <0)
            die("error skipping unknown chunk in WAV file");
          if (cbSize>0)
            die("error reading format data: is this a compressed file?");
        }
/* RWD.2.98 now step through all following chunks, until we find data chunk */
        {
          long chunk_id, chunksize;
          if ((read(fd,(void *)&chunk_id,sizeof(long)) < 0)
              || (read(fd,(void *) &chunksize,sizeof(long)) < 0)
              || natllong(chunksize) < 0)  /*TODO: can we have zero-sized chunks???? */
            die("error reading unknown chunk in WAV file");
          while (chunk_id != *(long*)DATA_ID) {
            if (lseek(fd,natllong(chunksize),SEEK_CUR) < 0
                || read(fd,(void*)&chunk_id,sizeof(long)) < 0
                || read(fd,(void*)&chunksize,sizeof(long)) < 0
                || natllong(chunksize) < 0) {
              die("error skipping unknown chunk in WAV file");
            }
          }
          hdr->audsize = natllong(chunksize);
          hdr->hdrsize = lseek(fd,0,SEEK_CUR);    /* RWD Apr 97 */
	}	
}
