/*----------------------------------------------------------------------------

   libtunepimp -- The MusicBrainz tagging library.  
                  Let a thousand taggers bloom!
   
   Copyright (C) Robert Kaye 2003
   
   This file is part of libtunepimp.

   libtunepimp is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   libtunepimp 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.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with libtunepimp; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   $Id: flacdecode.cpp 7230 2006-04-16 11:25:38Z luks $

----------------------------------------------------------------------------*/
#include <stdlib.h>
#include <string.h>
#include "flacdecode.h"

void FLAC_errorcb(const FLAC__StreamDecoder * decoder,
                  FLAC__StreamDecoderErrorStatus status, void *client_data)
{
}

void FLAC_metadatacb(const FLAC__StreamDecoder * decoder,
                     const FLAC__StreamMetadata * metadata, void *client_data)
{
   if (metadata->type != FLAC__METADATA_TYPE_STREAMINFO)
      return;

   clientdata_t *cd = reinterpret_cast < clientdata_t * >(client_data);

   const FLAC__StreamMetadata_StreamInfo *si = &metadata->data.stream_info;

   cd->samplesPerSecond = si->sample_rate;
   cd->bitsPerSample = si->bits_per_sample;
   cd->channels = si->channels;
   cd->duration = (si->total_samples * 1000) / si->sample_rate;
}

FLAC__StreamDecoderWriteStatus FLAC_writecb(const FLAC__StreamDecoder * decoder,
                                            const FLAC__Frame * frame,
                                            const FLAC__int32 * const buffer[], void *client_data)
{
   
   // No idea how PUID signatures are generated for more than two 
   // channels, so not going to try
   if (frame->header.channels > 2)
      return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;

   // PUID code doesn't support anything other than 8 and 16 bit samples
   if (frame->header.bits_per_sample != 8 &&
       frame->header.bits_per_sample != 16 &&
       frame->header.bits_per_sample != 24)
      return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;

   int   samplesize = frame->header.bits_per_sample / 8;
   int   bufsize = samplesize * frame->header.channels * frame->header.blocksize;

   char *newdata = new char[bufsize];
   char *outputptr = newdata;

   for (unsigned samplenum = 0; samplenum < frame->header.blocksize; samplenum++)
        for (unsigned channum = 0; channum < frame->header.channels; channum++)
        {
           memcpy(outputptr, &buffer[channum][samplenum], samplesize);
           outputptr += samplesize;
        }

   clientdata_t *cd = reinterpret_cast < clientdata_t * >(client_data);
   if (cd->dataBytes == 0)
   {
       cd->data = newdata;
       cd->dataBytes = bufsize;
   }
   else
   {
       char *temp = new char[bufsize + cd->dataBytes];
       memcpy(temp, cd->data, cd->dataBytes);
       memcpy(temp + cd->dataBytes, newdata, bufsize);
       delete [] newdata;
       cd->data = temp;
       cd->dataBytes += bufsize;
   }

   return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

FLAC__StreamDecoderReadStatus FLAC_readcb(const FLAC__StreamDecoder *decoder, 
                                          FLAC__byte buffer[], 
                                          unsigned *bytes, 
                                          void *client_data)
{
   clientdata_t *cd = reinterpret_cast < clientdata_t * >(client_data);

   unsigned numRead = tread(buffer, sizeof(char), *bytes, cd->in);
   if (numRead < *bytes)
   {
       *bytes = numRead;
       return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
   }

   return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}

FlacDecode::FlacDecode(const string & fileName, const string &encoding) : DecodePlugin()
{
   FLAC__StreamDecoderState state;

   cd.in = NULL;
   this->encoding = encoding;
   ready = false;
   decoder = FLAC__stream_decoder_new();
   
   if (!decoder)
      return;

   memset(&cd, 0, sizeof(clientdata_t));
   cd.in = topen(fileName.c_str(), "rb", encoding.c_str());
   if (!cd.in)
      return;

   if (!FLAC__stream_decoder_set_read_callback(decoder, &FLAC_readcb))
      return;

   if (!FLAC__stream_decoder_set_write_callback(decoder, &FLAC_writecb))
      return;

   if (!FLAC__stream_decoder_set_metadata_callback(decoder, &FLAC_metadatacb))
      return;

   if (!FLAC__stream_decoder_set_error_callback(decoder, &FLAC_errorcb))
      return;

   if (!FLAC__stream_decoder_set_client_data(decoder, &cd))
      return;

   state = FLAC__stream_decoder_init(decoder);
   if (state != FLAC__STREAM_DECODER_SEARCH_FOR_METADATA)
      return;

   FLAC__stream_decoder_process_until_end_of_metadata(decoder);
   state = FLAC__stream_decoder_get_state(decoder);
   if (state != FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC )
       return;

   duration = cd.duration;
   samplesPerSecond = cd.samplesPerSecond;
   channels = cd.channels;
   bitsPerSample = cd.bitsPerSample;
   ready = true;
}

FlacDecode::~FlacDecode(void)
{
   FLAC__stream_decoder_finish(decoder);
   FLAC__stream_decoder_delete(decoder);
   if (cd.in)
       tclose(cd.in);
}

int FlacDecode::getInfo(unsigned long &duration,
                        unsigned int &samplesPerSecond,
                        unsigned int &bitsPerSample, unsigned int &channels)
{
   if (!ready)
      return 0;

   duration = this->duration;
   samplesPerSecond = this->samplesPerSecond;
   bitsPerSample = this->bitsPerSample;
   channels = this->channels;

   return 1;
}

int FlacDecode::read(char *data, int maxBytes)
{
   FLAC__StreamDecoderState state;

   if (!ready)
       return -1;

   // If we have any leftover bytes, use those first
   if (cd.dataBytes > 0)
   {
       int toCopy = (maxBytes < cd.dataBytes) ? maxBytes : cd.dataBytes;

       memcpy(data, cd.data, toCopy);
       if (toCopy < cd.dataBytes)
       {
           memmove(cd.data, cd.data + toCopy, cd.dataBytes - toCopy);
           cd.dataBytes -= toCopy;
       }
       else
       {
           delete [] cd.data;
           cd.data = NULL;
           cd.dataBytes = 0;
       }

       return toCopy;
   }

   // No leftover bytes, so generate new ones
   while(1)
   {
       FLAC__stream_decoder_process_single(decoder);
       if (cd.data)
          break;
       
       state = FLAC__stream_decoder_get_state(decoder);
       if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
           break;
       if (state != FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC ||
           state != FLAC__STREAM_DECODER_READ_FRAME)
       {
          return -1;
       }
   }

   int ret;
   if (cd.dataBytes > maxBytes)
   {
       memcpy(data, cd.data, maxBytes);
       memmove(cd.data, cd.data + maxBytes, cd.dataBytes - maxBytes);
       cd.dataBytes -= maxBytes;
       ret = maxBytes;
   }
   else
   {
       ret = cd.dataBytes;
       memcpy(data, cd.data, cd.dataBytes);
       delete  [] cd.data;
       cd.data = NULL;
       cd.dataBytes = 0;
   }

   return ret;
}
