/* azundris. QND wav-player */

static int debug=999;

#define E(lvl,fmt,args...) do { if(debug>=(lvl)) fprintf(stderr,"%s: " fmt,__FUNCTION__,## args); } while(0)

#include "../config.h"
#include "errors.h"

#include <errno.h>
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


#ifdef HAVE_LIBAO
#  include <ao/ao.h>
#  ifdef HAVE_AUDIOFILE
#    include "audiofile.h"
#  else
#    include "wav.h"
#  endif
#  ifdef HAVE_LIBMAD
#    include <mad.h>
#    include <fcntl.h>     /* open() */
#  endif



#  define OBC (32*1024)



#  ifdef HAVE_LIBMAD
/* adapted from madplay */

struct _input {
  int            fd;
  int            eof;
  int            rate;
  unsigned char *data,*out;
  unsigned long  length,olen,ofill;
} input;



static inline signed long audio_linear_round(mad_fixed_t sample) {
#define BITS 16
  sample+=(1L<<(MAD_F_FRACBITS-BITS));
  if(sample>(MAD_F_ONE-1)) sample=MAD_F_ONE-1;
  if(sample<-MAD_F_ONE)    sample=-MAD_F_ONE;
  return sample>>(MAD_F_FRACBITS+1-BITS); }




static enum mad_flow decode_input_read(void *data,struct mad_stream *stream) {
  struct _input *input=data;
  int            len;

  if(input->eof)
    return MAD_FLOW_STOP;

  if(stream->next_frame)
    memmove(input->data,stream->next_frame,
            input->length=&input->data[input->length]-stream->next_frame);

  do {
    len=read(input->fd,input->data+input->length,OBC-input->length);
  } while((len==-1)&&(errno == EINTR));

  if(len==-1)
    return MAD_FLOW_BREAK;
  else if(!len)
    input->eof=1;

  mad_stream_buffer(stream,input->data,input->length+=len);

  return MAD_FLOW_CONTINUE; }

static enum mad_flow decode_output(void *data,struct mad_header const *header,struct mad_pcm *pcm) {
  struct _input       *input=data;
  mad_fixed_t         *ch1,*ch2;
  unsigned char       *obuf;
  size_t               l=pcm->length*4;
  register signed int  sample0,sample1;

  if((input->ofill+l)>input->olen) {
    char *ob;
    E(3,"OUTPUT BUFFER RESIZED %ld  (%d channels)\r",input->olen,pcm->channels);

    input->rate=header->samplerate;
    input->olen=(((input->olen+l)/OBC)+2)*OBC;
    if(!(ob=realloc(input->out,input->olen)))
      return MAD_FLOW_BREAK;
    input->out=ob;
    memset(input->out+input->ofill,0,input->olen-input->ofill); }

  ch1=pcm->samples[0];
  ch2=pcm->samples[1];
  l=pcm->length;
  obuf=&input->out[input->ofill];

  if(ch2) {
    while(l--) {
      sample0=audio_linear_round(*ch1++);
      sample1=audio_linear_round(*ch2++);
      obuf[0]=sample0;
      obuf[1]=sample0>>8;
      obuf[2]=sample1;
      obuf[3]=sample1>>8;
      obuf+=4; }
    input->ofill+=pcm->length*4; }
  else {
    while(l--) {
      sample0=audio_linear_round(*ch1++);
      obuf[0]=sample0;
      obuf[1]=sample0>>8;
      obuf[2]=sample0;
      obuf[3]=sample0>>8;
      obuf+=4; }
    input->ofill+=pcm->length*4; }
  
  return MAD_FLOW_CONTINUE; }



static int sound_load_mp3(char *file,ao_sample_format *format,char **bufret,int *bufsize) {
  int                 ret=NOIR_SUCC;
  struct stat         st;

  struct mad_decoder  decoder;
  int                 result;
  struct _input       input;

  memset(&input,0,sizeof(struct _input));

  if(!bufret||!format||!file||!bufsize) {
    E(1,"no parameters!? %p, %p, %p, %p\n",bufret,format,file,bufsize);
    return NOIR_FAIL; }

  *bufsize=0;
  *bufret=NULL;

  if(stat(file,&st))
    return NOIR_NOFILE;
  if(!S_ISREG(st.st_mode))
    return NOIR_NOFILE;

  if((input.fd=open(file,O_RDONLY))<0) {
    E(1,"(\"%s\"): could not open file!?\n",file);
    return NOIR_NOFILE; }

  if(!(input.data=malloc(OBC))) {
    ret=NOIR_FAIL;
    goto close; }

  input.length=0;
  input.eof=0;

  mad_decoder_init(&decoder,&input,
                   decode_input_read,
                   NULL,NULL,
                   decode_output,
                   NULL,0);

  mad_decoder_options(&decoder,0);

  result=mad_decoder_run(&decoder,MAD_DECODER_MODE_SYNC);

  mad_decoder_finish(&decoder);

close:  
  if(input.data) {
    free(input.data);
    input.data=NULL; }

  close(input.fd);

  memset(format,0,sizeof(ao_sample_format));
  if(ret==NOIR_SUCC) {
    format->bits=16;
    format->channels=2;
    format->rate=input.rate;
    format->byte_format=AO_FMT_LITTLE; }
  else if(input.out) {
    free(input.out);
    input.out=NULL; }

  *bufret =input.out;
  *bufsize=input.olen;

  return ret; }
#  endif



#  ifdef HAVE_VORBIS
#    include <vorbis/vorbisfile.h>
#    define BIT_RATE 16
#    if (__BYTE_ORDER == 1234)
#      define ENDIAN 0
#    else
#      define ENDIAN 1
#    endif

static int sound_load_ogg(char *file,ao_sample_format *format,char **bufret,int *bufsize) {
  OggVorbis_File  vf;
  FILE           *fh;
  int             ret=NOIR_SUCC,rate=0,channels=0,section=0,err=0;
  size_t          size=0,read=0,fill=0;
  char           *buffer=NULL,*tmp=NULL;
  vorbis_info    *vi;

  if(!bufret||!format||!file||!bufsize) {
    E(1,"(): no parameters!? %p, %p, %p, %p\n",bufret,format,file,bufsize);
    return NOIR_FAIL; }

  *bufsize=0;
  *bufret=NULL;

  if(!(tmp=malloc(OBC)))
    return NOIR_OOM;

  if(!(fh=fopen(file,"rb"))) {
    E(1,"(\"%s\"): could not open file!?\n",file);
    return NOIR_NOFILE; }
  
  if((err=ov_open(fh,&vf, NULL,0))<0) {
    E(1,"(\"%s\"): not an Ogg-stream!?\n",file);
    fclose(fh);
    return NOIR_FAIL; }

  if(!(vi=ov_info(&vf,-1))) {
    ret=NOIR_FAIL;
    goto close; }

  channels=vi->channels;
  if(channels<=0) {
    ret=NOIR_FAIL;
    goto close; }

  rate=vi->rate;

  do {
    if((read=ov_read(&vf,tmp,OBC,ENDIAN,BIT_RATE/8,1,&section))>0) {
      fill+=read;

      if((fill)>size) {
        char *ob;
        size=(((size+read)/OBC)+2)*OBC;
        if(!(ob=realloc(buffer,size))) {
          ret=NOIR_OOM;
          goto close; }
        buffer=ob;
        memset(buffer+fill,0,size-fill); }
      memcpy(buffer+fill-read,tmp,read); }
  } while(read!=0);

close:  
  ov_clear(&vf); // closes file too

  if(tmp)
    free(tmp);

  memset(format,0,sizeof(ao_sample_format));
  if(ret==NOIR_SUCC) {
    format->bits=16;
    format->channels=channels;
    format->rate=rate;
    format->byte_format=AO_FMT_LITTLE; }
  else if(buffer) {
    free(buffer);
    buffer=NULL; }

  *bufret=buffer;
  *bufsize=size;

  return ret; }
#  endif



#  ifdef HAVE_AUDIOFILE
/* (extension=="wav" || extension=="aiff" || extension=="au" || extension=="snd" || extension=="sf"); */

static const char *errmsg;

static void audiofile_error(long code,const char *msg) {
  errmsg=msg; }

static int sound_load_wav(char *file,ao_sample_format *format,char **bufret,int *bufsize) {
  AFfilesetup  af_setup=afNewFileSetup();
  AFfilehandle h;
  AFframecount size=0;
  int          ret=NOIR_SUCC,rate=0,channels=0,frames=0;
  char        *buffer=NULL;

  afSetErrorHandler(audiofile_error);

  if(!bufret||!format||!file||!bufsize) {
    E(1,"no parameters!? %p, %p, %p, %p\n",bufret,format,file,bufsize);
    return NOIR_FAIL; }

  *bufsize=0;
  *bufret=NULL;

  if((h=afOpenFile(file,"r",af_setup))==AF_NULL_FILEHANDLE) {
    E(1,"(\"%s\"): could not open file!?\n",file);
    return NOIR_NOFILE; }

  if(afSetVirtualSampleFormat(h,AF_DEFAULT_TRACK,AF_SAMPFMT_TWOSCOMP,16)) {
    E(1,"(\"%s\"): could not set sample-format!?\n",file);
    ret=NOIR_FAIL;
    goto close; }
  if(afSetVirtualByteOrder(h,AF_DEFAULT_TRACK,AF_BYTEORDER_LITTLEENDIAN)) {
    E(1,"(\"%s\"): could not set byte-order!?\n",file);
    ret=NOIR_FAIL;
    goto close; }
  
  channels=afGetChannels(h,AF_DEFAULT_TRACK);
  rate=(unsigned)afGetRate(h,AF_DEFAULT_TRACK);
  
  if((frames=afGetFrameCount(h,AF_DEFAULT_TRACK))<0) {
    ret=NOIR_FAIL;
    goto close; }

  size=afGetVirtualFrameSize(h,AF_DEFAULT_TRACK,1)*frames;
  size=((size/OBC)+2)*OBC;

  E(3,"(\"%s\"): trying to read %d frames...\n",file,frames);

  if(!(buffer=malloc(size)))
    ret=NOIR_OOM;
  else {
    memset(buffer,0,size);
    if(afReadFrames(h,AF_DEFAULT_TRACK,(void *)buffer,frames)!=frames) {
      E(1,"(\"%s\"): could not read sound-data...\n",file);
      ret=NOIR_FAIL; }}

close:
  afCloseFile(h);

  memset(format,0,sizeof(ao_sample_format));
  if(ret==NOIR_SUCC) {
    format->bits=16;
    format->channels=channels;
    format->rate=rate;
    format->byte_format=AO_FMT_LITTLE; }
  else if(buffer) {
    free(buffer);
    buffer=NULL; }

  *bufret=buffer;
  *bufsize=size;

  return ret; }
#  else



static int sound_load_wav(char *file,ao_sample_format *format,char **bufret,int *bufsize) {
  FILE        *fh;
  riff_chunk   chunk;
  int          ret=NOIR_SUCC,have=0;
  riff_header  r;
  riff_common  rc;
  char        *buffer=NULL;

  if(!bufret||!format||!file||!bufsize) {
    E(1,"no parameters!? %p, %p, %p, %p\n",bufret,format,file,bufsize);
    return NOIR_FAIL; }

  *bufsize=0;
  *bufret=NULL;

  if((fh=fopen(file,"rb"))) {
    if(fread(&r,sizeof(riff_header),1,fh)!=1)
      ret=NOIR_NODATA;
    else {
      size_t l=r.len;
      if(strncmp(r.id,"RIFF",4)) {
        E(1,"\"%s\" is not a RIFF-file!\n",file);
        ret=NOIR_FAIL; }
      else if(strncmp(r.wave_id,"WAVE",4)) {
        E(1,"\"%s\" is not a WAVE-file!\n",file);
        ret=NOIR_FAIL; }
      else if(l<=0)
        ret=NOIR_NODATA;
      else {
        char id[5]="    ";
        while((have!=3)&&(l>0)&&(ret==NOIR_SUCC)) {
          if(fread(&chunk,sizeof(riff_chunk),1,fh)!=1) {
            E(1,"(\"%s\"): could not read chunk-header!?\n",file);
            ret=NOIR_FAIL; }
          else {
            l-=sizeof(riff_chunk)+chunk.len;
            strncpy(id,chunk.id,4);
            E(3,"reading chunk-header for \"%4s\", %d bytes, %d left..\n",id,chunk.len,l);
            if(!strncmp(chunk.id,"fmt ",4)) {
              if(fread(&rc,sizeof(riff_common),1,fh)!=1) {
                E(1,"(\"%s\"): could not read format-chunk!?\n",file);
                ret=NOIR_FAIL; }
              else
                have|=1; }
            else if(!strncmp(chunk.id,"data",4)) {
              size_t m=((chunk.len/OBC)+2)*OBC;
              E(3,"(\"%s\"): chunk-length is %d, adjusted to %d.\n",file,chunk.len,m);
              if(!(buffer=malloc(m)))
                ret=NOIR_OOM;
              else {
                memset(buffer,0,m);
                if(fread(buffer,1,chunk.len,fh)!=chunk.len) {
                  E(1,"(\"%s\"): could not read sound-data!?\n",file);
                  ret=NOIR_FAIL; }
                else {
                  *bufret=buffer;
                  *bufsize=m;
                  have|=2; }}}
            else
              fseek(fh,chunk.len,SEEK_CUR); }}}}
    fclose(fh); }
  else
    ret=NOIR_NOFILE;

  if(rc.tag!=FORMAT_PCM) {
    E(0,"(\"%s\"): format is not PCM ($%x), uh-oh...\n",file,rc.tag);
    ret=NOIR_FAIL; }

  if(ret==NOIR_SUCC) {
    if(rc.bits==8) {
      char *m2;
      size_t ol=*bufsize,nl=*bufsize*2;
      if(!(m2=malloc(nl)))
        ret=NOIR_OOM;
      else {
        char *s=buffer,*d=m2;
        unsigned int c;

        E(1,"(\"%s\"): converting to 16 bits...\n",file);
        rc.bits=16;
        *bufsize=nl;
        while(ol-->0) {
          c=*(s++)<<7;
          *(d++)=(c>>8)&0xff;   /* hi-byte */
          *(d++)=c&0xff;        /* lo-byte */
        }
        free(buffer);
        *bufret=buffer=m2; }}

    memset(format,0,sizeof(ao_sample_format));
    format->bits=rc.bits;
    format->channels=rc.channels;
    format->rate=rc.rate;
    format->byte_format=AO_FMT_LITTLE; }

  if((ret!=NOIR_SUCC)&&buffer) {
    free(buffer);
    buffer=NULL; }

  return ret; }
#  endif



int sound_play(char *file) {
  ao_device        *device;
  ao_sample_format  format;
  ao_info          *info;
  int               default_driver;
  char             *buffer=NULL,*ext;
  int               bufsize=0;
  int               ret=NOIR_SUCC;
  size_t            l=strlen(file);
  struct stat       st;

  if(!file||(l<4))
    return NOIR_FAIL;

  if(stat(file,&st))
    return NOIR_NOFILE;
  if(!S_ISREG(st.st_mode))
    return NOIR_NOFILE;

  ext=&file[l-4];

  if(0)
    ;
#  ifdef HAVE_LIBMAD
  else if(!strcasecmp(ext,".mp3"))
    ret=sound_load_mp3(file,&format,&buffer,&bufsize);
#endif
#  ifdef HAVE_VORBIS
  else if(!strcasecmp(ext,".ogg"))
    ret=sound_load_ogg(file,&format,&buffer,&bufsize);
#  endif
  else
    ret=sound_load_wav(file,&format,&buffer,&bufsize);

  if(ret!=NOIR_SUCC) {
    E(0,"(\"%s\"): loading failed (%d)...\n",file,ret);
    return ret; }

  ao_initialize();

  default_driver=ao_default_driver_id();
  if((info=ao_driver_info(default_driver)))
    E(2,"(\"%s\"): %s (%s)\n",file,info->short_name,info->comment);

  E(0,"(\"%s\"): %d bit, %d channel(s), %d samples/s\n",file,format.bits,format.channels,format.rate);

  if(!(device=ao_open_live(default_driver,&format,NULL))) {
    E(0,"could not open sound driver %d: #%d -- %s\n",default_driver,errno,strerror(errno));
    ret=NOIR_FAIL;
    goto fail; }
	
  E(3,"(\"%s\"): playing %d bytes @ %p...  ",file,bufsize,buffer);
  ao_play(device,buffer,bufsize);

  ao_close(device);
  ao_shutdown();

  E(3,"done.\n");

 fail:
  if(buffer)
    free(buffer);

  return NOIR_SUCC; }

#else
int sound_play(char *file) {
  return NOIR_IGNORED; }
#endif



int main(int argc,char **argv) {
  int         c;

  if(argc<2) {
    E(0,"%s <sound-file> ...\n",argv[0]);
    return 1; }

  for(c=1;c<argc;c++) {
    E(0,"%s: playing %s\n",argv[0],argv[c]);
    if(sound_play(argv[c])!=NOIR_SUCC)
      return 1; }

  return 0; }
