//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2006 by PDSoft (Attila Padar)                *
//*                    http://mpxplay.cjb.net                              *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  This program is distributed in the hope that it will be useful,       *
//*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
//*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function:WAV file reading/demuxing

//#define MPXPLAY_USE_DEBUGF 1
//#define MPXPLAY_DEBUG_OUTPUT stdout

#include "in_file.h"

#ifdef MPXPLAY_LINK_INFILE_WAV

#include "newfunc\newfunc.h"

typedef struct wav_demuxer_data_s{
 unsigned long filedatabegin;
 unsigned long filedatalen;
 mpxp_uint8_t *extradata;
 unsigned long extradata_size;
}wav_demuxer_data_s;

static unsigned int wav_chunk_search(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,unsigned long);

static int WAV_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 struct wav_demuxer_data_s *wavi;
 unsigned long tagname,taglen,indatalen,maxdatalen;

 if(!fbfs->fopen_read(fbds,filename,0))
  return MPXPLAY_ERROR_INFILE_CANTOPEN;

 if(fbfs->get_le32(fbds)!=PDS_GET4C_LE32('R','I','F','F'))
  goto err_out_opn;
 fbfs->get_le32(fbds); // riff size

 wavi=calloc(1,sizeof(struct wav_demuxer_data_s));
 if(!wavi)
  goto err_out_opn;
 miis->private_data=wavi;

 miis->filesize=fbfs->filelength(fbds);

 tagname=fbfs->get_le32(fbds);

 if(tagname==PDS_GET4C_LE32('R','M','P','3')){
  spi->streamtype  = MPXPLAY_SPI_STREAMTYPE_AUDIO;
  spi->wave_id     = MPXPLAY_WAVEID_MP3;
 }else{
  if(tagname!=PDS_GET4C_LE32('W','A','V','E'))
   goto err_out_opn;

  if(!wav_chunk_search(fbfs,fbds,PDS_GET4C_LE32('f','m','t',' ')))  // search for 'fmt' chunk
   goto err_out_opn;

  taglen=fbfs->get_le32(fbds);
  if((taglen<14) || (taglen>miis->filesize))  // invalid fmt chunk size
   goto err_out_opn;

  spi->streamtype   = MPXPLAY_SPI_STREAMTYPE_AUDIO;
  spi->wave_id      = fbfs->get_le16(fbds);
  adi->filechannels = fbfs->get_le16(fbds);
  adi->outchannels  = adi->filechannels;
  adi->freq         = fbfs->get_le32(fbds);
  adi->bitrate      = (fbfs->get_le32(fbds) * 8 + 500)/1000;
  spi->block_align  = fbfs->get_le16(fbds);
  if(taglen == 14)  // plain vanilla WAVEFORMAT
   adi->bits = 8;
  else
   adi->bits = fbfs->get_le16(fbds);

  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"wi:%4.4X c:%d f:%d a:%d b:%d",spi->wave_id,adi->filechannels,adi->freq,spi->block_align,adi->bits);

  if(!spi->wave_id || !adi->filechannels || (adi->filechannels>PCM_MAX_CHANNELS))
   goto err_out_opn;
  if(!adi->freq || !spi->block_align)
   goto err_out_opn;

  if(taglen >= 18){  // WAVEFORMATEX
   wavi->extradata_size = fbfs->get_le16(fbds);
   if(wavi->extradata_size){
    if(wavi->extradata_size > (taglen - 18))
     wavi->extradata_size = taglen - 18;
    wavi->extradata = malloc(wavi->extradata_size + MPXPLAY_SPI_EXTRADATA_PADDING);
    if(!wavi->extradata)
     goto err_out_opn;
    if(fbfs->fread(fbds, wavi->extradata, wavi->extradata_size)!=wavi->extradata_size)
     goto err_out_opn;
    pds_memset(wavi->extradata+wavi->extradata_size,0,MPXPLAY_SPI_EXTRADATA_PADDING);
    spi->extradata=wavi->extradata;
    spi->extradata_size=wavi->extradata_size;
   }
   if((taglen - wavi->extradata_size - 18) > 0) // skip garbage at the end
    fbfs->fseek(fbds, (taglen - wavi->extradata_size - 18), SEEK_CUR);
  }else
   if(taglen>16)
    fbfs->fseek(fbds, (taglen-16), SEEK_CUR);
 }

 if(!wav_chunk_search(fbfs,fbds,PDS_GET4C_LE32('d','a','t','a'))){
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"data chunk not found!");
  goto err_out_opn;
 }

 indatalen=fbfs->get_le32(fbds);
 wavi->filedatabegin=fbfs->ftell(fbds);
 maxdatalen=miis->filesize-wavi->filedatabegin; // maxdatalen=filesize-headersize
 if(!indatalen || (indatalen>maxdatalen))       // indatalen have to be less than maxdatalen
  indatalen=maxdatalen;
 wavi->filedatalen=indatalen;

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"databegin:%d datalen:%d",wavi->filedatabegin,wavi->filedatalen);

 funcbit_enable(spi->flags,(MPXPLAY_SPI_FLAG_NEED_DECODER|MPXPLAY_SPI_FLAG_CONTAINER));

 if((spi->wave_id==MPXPLAY_WAVEID_PCM_SLE) || (spi->wave_id==MPXPLAY_WAVEID_PCM_FLOAT)){
  if(spi->wave_id==MPXPLAY_WAVEID_PCM_FLOAT)
   adi->bits=1;
  else
   if(!adi->bits || (adi->bits>PCM_MAX_BITS))
    goto err_out_opn;
  if(indatalen<spi->block_align) // we need 1 sample at least
   goto err_out_opn;
  miis->timemsec=(float)indatalen*1000.0/(float)spi->block_align/(float)adi->freq;
  spi->bs_framesize=mpxplay_infile_get_samplenum_per_frame(adi->freq)*spi->block_align;
  adi->bitrate=0;
 }else
  funcbit_enable(spi->flags,MPXPLAY_SPI_FLAG_NEED_PARSING);

 return MPXPLAY_ERROR_INFILE_OK;

err_out_opn:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static unsigned int wav_chunk_search(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,unsigned long search_id)
{
 unsigned int retry=20;

 do{
  unsigned long tagname,taglen;
  tagname=fbfs->get_le32(fbds);
  if(fbfs->eof(fbds))
   break;
  if(tagname==search_id)
   return 1;
  taglen=fbfs->get_le32(fbds);
  if(taglen!=PDS_GET4C_LE32('f','a','c','t')) // ??? old Wavelab bug?
   if(fbfs->fseek(fbds,taglen,SEEK_CUR)<0)
    break;
 }while(--retry);
 return 0;
}

static void WAV_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct wav_demuxer_data_s *wavi=miis->private_data;
 if(wavi){
  if(wavi->extradata)
   free(wavi->extradata);
  free(wavi);
 }
 fbfs->fclose(fbds);
}

static int WAV_infile_demux(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct wav_demuxer_data_s *wavi=miis->private_data;
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 long filepos,fileend,readsize;

 filepos=fbfs->ftell(fbds);
 fileend=wavi->filedatabegin+wavi->filedatalen;
 readsize=spi->bs_readsize;

 if((filepos+readsize)>fileend)
  readsize=fileend-filepos;

 if(readsize<=0)
  return MPXPLAY_ERROR_INFILE_EOF;

 spi->bs_leftbytes=fbfs->fread(fbds,spi->bitstreambuf,readsize);
 if(!spi->bs_leftbytes)
  return MPXPLAY_ERROR_INFILE_NODATA;

 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"demux fp:%8d fpe:%8d db:%d dl:%d fe:%d brs:%d rs:%d",
 // filepos,filepos+readsize,wavi->filedatabegin,wavi->filedatalen,fileend,spi->bs_readsize,readsize);

 return MPXPLAY_ERROR_INFILE_OK;
}

static long WAV_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newmpxframenum)
{
 struct wav_demuxer_data_s *wavi=miis->private_data;
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 long newfilepos;

 newfilepos=newmpxframenum*spi->bs_readsize; // ??? if not pcm, decoder should set bs_framesize/bs_readsize

 if(newfilepos>=wavi->filedatalen)
  return MPXPLAY_ERROR_INFILE_EOF;

 newfilepos+=wavi->filedatabegin;

 if(fbfs->fseek(fbds,newfilepos,SEEK_SET)<0)
  return MPXPLAY_ERROR_INFILE_EOF;

 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"fseek fp:%8d",newfilepos);

 return newmpxframenum;
}

struct mpxplay_infile_func_s IN_WAV_funcs={
 0,
 NULL,
 NULL,
 &WAV_infile_open,
 &WAV_infile_open,
 &WAV_infile_open,
 &WAV_infile_close,
 &WAV_infile_demux,
 &WAV_infile_fseek,
 NULL,
 NULL,
 NULL,
 NULL,
 {"WAV",NULL}
};

#endif //MPXPLAY_LINK_INFILE_WAV
