/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified 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.

   JUCE 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 JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "../../../juce_core/basics/juce_StandardHeader.h"

#if JUCE_USE_FLAC

#define FLAC__NO_DLL 1

#include "flac/all.h"

#ifdef JUCE_WIN32
 #ifndef FLAC_LIB_FILE
  #define FLAC_LIB_FILE     "libFLAC_static.lib"
 #endif

 #pragma comment(lib, FLAC_LIB_FILE)
 #pragma message("auto-linking to " FLAC_LIB_FILE)
#endif

BEGIN_JUCE_NAMESPACE

#include "juce_FlacAudioFormat.h"
#include "../../../juce_core/text/juce_LocalisedStrings.h"

//==============================================================================
#define formatName                          TRANS("FLAC file")
static const tchar* const extensions[] =    { T(".flac"), 0 };


//==============================================================================
class FlacReader  : public AudioFormatReader
{
    FLAC__SeekableStreamDecoder* decoder;
    AudioSampleBuffer reservoir;
    int reservoirStart, samplesInReservoir;
    bool ok;

public:
    //==============================================================================
    FlacReader (InputStream* const in)
        : AudioFormatReader (in, formatName),
          reservoir (2, 0),
          reservoirStart (0),
          samplesInReservoir (0)
    {
        lengthInSamples = 0;

        decoder = FLAC__seekable_stream_decoder_new();

        FLAC__seekable_stream_decoder_set_read_callback (decoder, readCallback_);
        FLAC__seekable_stream_decoder_set_seek_callback (decoder, seekCallback_);
        FLAC__seekable_stream_decoder_set_tell_callback (decoder, tellCallback_);
        FLAC__seekable_stream_decoder_set_length_callback (decoder, lengthCallback_);
        FLAC__seekable_stream_decoder_set_eof_callback (decoder, eofCallback_);
        FLAC__seekable_stream_decoder_set_write_callback (decoder, writeCallback_);
        FLAC__seekable_stream_decoder_set_metadata_callback (decoder, metadataCallback_);
        FLAC__seekable_stream_decoder_set_client_data (decoder, (void*) this);
        FLAC__seekable_stream_decoder_set_error_callback (decoder, errorCallback_);

        FLAC__SeekableStreamDecoderState error = FLAC__seekable_stream_decoder_init (decoder);

        ok = (error == FLAC__SEEKABLE_STREAM_DECODER_OK);

        if (ok)
        {
            FLAC__seekable_stream_decoder_process_until_end_of_metadata (decoder);

            if (lengthInSamples == 0)
            {
                jassertfalse

                //xxx should work out length somehow if not in metadata..
            }
        }
    }

    ~FlacReader()
    {
        FLAC__seekable_stream_decoder_delete (decoder);
    }

    void useMetadata (const FLAC__StreamMetadata_StreamInfo& info)
    {
        sampleRate = info.sample_rate;
        bitsPerSample = info.bits_per_sample;
        lengthInSamples = (unsigned int)info.total_samples;
        numChannels = info.channels;

        reservoir.setSize (numChannels, 2 * info.max_blocksize, false, false, true);
    }

    // returns the number of samples read
    bool read (int** destSamples,
               int64 startSampleInFile,
               int numSamples)
    {
        if (! ok)
            return false;

        int offset = 0;

        if (startSampleInFile < 0)
        {
            const int num = (int) jmin ((int64) numSamples, -startSampleInFile);

            int n = 0;
            while (destSamples[n] != 0)
            {
                zeromem (destSamples[n], sizeof (int) * num);
                ++n;
            }

            offset += num;
            startSampleInFile += num;
            numSamples -= num;
        }

        while (numSamples > 0)
        {
            if (startSampleInFile >= reservoirStart
                 && startSampleInFile < reservoirStart + samplesInReservoir)
            {
                const int num = (int) jmin ((int64) numSamples,
                                            reservoirStart + samplesInReservoir - startSampleInFile);

                jassert (num > 0);

                int n = 0;
                while (destSamples[n] != 0)
                {
                    memcpy (destSamples[n] + offset,
                            reservoir.getSampleData (n, (int) (startSampleInFile - reservoirStart)),
                            sizeof (int) * num);
                    ++n;
                }

                offset += num;
                startSampleInFile += num;
                numSamples -= num;
            }
            else
            {
                if (startSampleInFile < reservoirStart
                     || startSampleInFile > reservoirStart + jmax (samplesInReservoir, 511))
                {
                    if (startSampleInFile >= (int) lengthInSamples)
                    {
                        samplesInReservoir = 0;
                        break;
                    }

                    // had some problems with flac crashing if the read pos is aligned more
                    // accurately than this. Probably fixed in newer versions of the library, though.
                    reservoirStart = (int) (startSampleInFile & ~511);
                    FLAC__seekable_stream_decoder_seek_absolute (decoder, (FLAC__uint64) reservoirStart);
                }
                else
                {
                    reservoirStart += samplesInReservoir;
                }

                samplesInReservoir = 0;

                FLAC__seekable_stream_decoder_process_single (decoder);

                if (samplesInReservoir == 0)
                    break;
            }
        }

        if (numSamples > 0)
        {
            int n = 0;
            while (destSamples[n] != 0)
            {
                zeromem (destSamples[n] + offset, sizeof (int) * numSamples);
                ++n;
            }
        }

        return true;
    }

    void useSamples (const FLAC__int32* const buffer[], int numSamples)
    {
        if (numSamples > reservoir.getNumSamples())
            reservoir.setSize (numChannels, numSamples, false, false, true);

        const int bitsToShift = 32 - bitsPerSample;

        for (int i = 0; i < (int) numChannels; ++i)
        {
            const FLAC__int32* src = buffer[i];

            int n = i;
            while (src == 0 && n > 0)
                src = buffer [--n];

            if (src != 0)
            {
                int* dest = (int*) reservoir.getSampleData(i);

                for (int j = 0; j < numSamples; ++j)
                    dest[j] = src[j] << bitsToShift;
            }
        }

        samplesInReservoir = numSamples;
    }

    //==============================================================================
    static FLAC__SeekableStreamDecoderReadStatus readCallback_ (const FLAC__SeekableStreamDecoder*, FLAC__byte buffer[], unsigned int* bytes, void* client_data)
    {
        *bytes = (unsigned int) ((const FlacReader*) client_data)->input->read (buffer, (int) *bytes);
        return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
    }

    static FLAC__SeekableStreamDecoderSeekStatus seekCallback_ (const FLAC__SeekableStreamDecoder*, FLAC__uint64 absolute_byte_offset, void* client_data)
    {
        ((const FlacReader*) client_data)->input->setPosition ((int) absolute_byte_offset);
        return FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
    }

    static FLAC__SeekableStreamDecoderTellStatus tellCallback_ (const FLAC__SeekableStreamDecoder*, FLAC__uint64* absolute_byte_offset, void* client_data)
    {
        *absolute_byte_offset = ((const FlacReader*) client_data)->input->getPosition();
        return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
    }

    static FLAC__SeekableStreamDecoderLengthStatus lengthCallback_ (const FLAC__SeekableStreamDecoder*, FLAC__uint64* stream_length, void* client_data)
    {
        *stream_length = ((const FlacReader*) client_data)->input->getTotalLength();
        return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
    }

    static FLAC__bool eofCallback_ (const FLAC__SeekableStreamDecoder*, void *client_data)
    {
        return ((const FlacReader*) client_data)->input->isExhausted();
    }

    static FLAC__StreamDecoderWriteStatus writeCallback_ (const FLAC__SeekableStreamDecoder*,
                                                          const FLAC__Frame* frame,
                                                          const FLAC__int32* const buffer[],
                                                          void* client_data)
    {
        ((FlacReader*) client_data)->useSamples (buffer, frame->header.blocksize);
        return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
    }

    static void metadataCallback_ (const FLAC__SeekableStreamDecoder*,
                                   const FLAC__StreamMetadata* metadata,
                                   void* client_data)
    {
        ((FlacReader*) client_data)->useMetadata (metadata->data.stream_info);
    }

    static void errorCallback_ (const FLAC__SeekableStreamDecoder*, FLAC__StreamDecoderErrorStatus, void*)
    {
    }

    juce_UseDebuggingNewOperator
};


//==============================================================================
class FlacWriter  : public AudioFormatWriter
{
    FLAC__StreamEncoder* encoder;
    MemoryBlock temp;

public:
    bool ok;

    //==============================================================================
    FlacWriter (FileOutputStream* const out,
                const double sampleRate,
                const int numChannels,
                const int bitsPerSample_)
        : AudioFormatWriter (out, formatName,
                             sampleRate,
                             numChannels,
                             bitsPerSample_)
    {
        encoder = FLAC__stream_encoder_new();

        FLAC__stream_encoder_set_do_mid_side_stereo (encoder, numChannels == 2);
        FLAC__stream_encoder_set_loose_mid_side_stereo (encoder, numChannels == 2);
        FLAC__stream_encoder_set_channels (encoder, numChannels);
        FLAC__stream_encoder_set_bits_per_sample (encoder, jmin (24, bitsPerSample));
        FLAC__stream_encoder_set_sample_rate (encoder, (unsigned int) sampleRate);
        FLAC__stream_encoder_set_blocksize (encoder, 2048);
        FLAC__stream_encoder_set_do_escape_coding (encoder, true);
        FLAC__stream_encoder_set_write_callback (encoder, &encodeWriteCallback);
        FLAC__stream_encoder_set_metadata_callback (encoder, &encodeMetadataCallback);
        FLAC__stream_encoder_set_client_data (encoder, (void*) this);

        ok = (FLAC__stream_encoder_init (encoder) == FLAC__STREAM_ENCODER_OK);
    }

    ~FlacWriter()
    {
        if (ok)
        {
            FLAC__stream_encoder_finish (encoder);
            output->flush();
        }
        else
        {
            output = 0; // to stop the base class deleting this, as it needs to be returned
                        // to the caller of createWriter()
        }

        FLAC__stream_encoder_delete (encoder);
    }

    //==============================================================================
    bool write (const int** samplesToWrite, int numSamples)
    {
        if (! ok)
            return false;

        const int bitsToShift = 32 - bitsPerSample;

        if (bitsPerSample == 32)
        {
            return FLAC__stream_encoder_process (encoder,
                                                 (const FLAC__int32**) samplesToWrite,
                                                 numSamples) != 0;
        }
        else
        {
            const int numChannels = (samplesToWrite[1] == 0) ? 1 : 2;
            temp.setSize (sizeof (int) * numSamples * numChannels);

            int* buf[2];
            buf[0] = (int*) temp.getData();
            buf[1] = ((int*) temp.getData()) + numSamples;

            for (int i = numChannels; --i >= 0;)
            {
                if (samplesToWrite[i] != 0)
                {
                    for (int j = 0; j < numSamples; ++j)
                        buf [i][j] = (samplesToWrite [i][j] >> bitsToShift);
                }
            }

            return FLAC__stream_encoder_process (encoder, (const FLAC__int32**) buf, numSamples) != 0;
        }
    }

    bool writeData (const void* const data, int size) const
    {
        return output->write (data, size);
    }

    static void packUint32 (FLAC__uint32 val, FLAC__byte* b, const int bytes)
    {
        b += bytes;

        for (int i = 0; i < bytes; ++i)
        {
            *(--b) = (FLAC__byte) (val & 0xff);
            val >>= 8;
        }
    }

    void writeMetaData (const FLAC__StreamMetadata* metadata)
    {
        const FLAC__StreamMetadata_StreamInfo& info = metadata->data.stream_info;

        unsigned char buffer [FLAC__STREAM_METADATA_STREAMINFO_LENGTH];
        const unsigned int channelsMinus1 = info.channels - 1;
        const unsigned int bitsMinus1 = info.bits_per_sample - 1;

        packUint32 (info.min_blocksize, buffer, 2);
        packUint32 (info.max_blocksize, buffer + 2, 2);
        packUint32 (info.min_framesize, buffer + 4, 3);
        packUint32 (info.max_framesize, buffer + 7, 3);
        buffer[10] = (uint8) ((info.sample_rate >> 12) & 0xff);
        buffer[11] = (uint8) ((info.sample_rate >> 4) & 0xff);
        buffer[12] = (uint8) (((info.sample_rate & 0x0f) << 4) | (channelsMinus1 << 1) | (bitsMinus1 >> 4));
        buffer[13] = (FLAC__byte) (((bitsMinus1 & 0x0f) << 4) | ((info.total_samples >> 32) & 0x0f));
        packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4);
        memcpy (buffer + 18, info.md5sum, 16);

        output->setPosition (4);
        output->writeIntBigEndian (FLAC__STREAM_METADATA_STREAMINFO_LENGTH);
        output->write (buffer, FLAC__STREAM_METADATA_STREAMINFO_LENGTH);
    }

    //==============================================================================
    static FLAC__StreamEncoderWriteStatus encodeWriteCallback (const FLAC__StreamEncoder*,
                                                               const FLAC__byte buffer[],
                                                               unsigned int bytes,
                                                               unsigned int /*samples*/,
                                                               unsigned int /*current_frame*/,
                                                               void* client_data)
    {
        return ((FlacWriter*) client_data)->writeData (buffer, bytes)
                ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK
                : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
    }

    static void encodeMetadataCallback (const FLAC__StreamEncoder*,
                                        const FLAC__StreamMetadata* metadata,
                                        void* client_data)
    {
        ((FlacWriter*) client_data)->writeMetaData (metadata);
    }

    juce_UseDebuggingNewOperator
};


//==============================================================================
FlacAudioFormat::FlacAudioFormat()
    : AudioFormat (formatName, (const tchar**) extensions)
{
}

FlacAudioFormat::~FlacAudioFormat()
{
}

const Array <int> FlacAudioFormat::getPossibleSampleRates()
{
    const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 0 };
    return Array <int> (rates);
}

const Array <int> FlacAudioFormat::getPossibleBitDepths()
{
    const int depths[] = { 16, 24, 0 };
    return Array <int> (depths);
}

bool FlacAudioFormat::canDoStereo()
{
    return true;
}

bool FlacAudioFormat::canDoMono()
{
    return true;
}

bool FlacAudioFormat::isCompressed()
{
    return true;
}

AudioFormatReader* FlacAudioFormat::createReaderFor (InputStream* in)
{
    FlacReader* r = new FlacReader (in);

    if (r->sampleRate == 0)
        deleteAndZero (r);

    return r;
}

AudioFormatWriter* FlacAudioFormat::createWriterFor (FileOutputStream* out,
                                                     double sampleRate,
                                                     unsigned int numberOfChannels,
                                                     int bitsPerSample,
                                                     const StringPairArray& /*metadataValues*/,
                                                     int /*qualityOptionIndex*/)
{
    if (getPossibleBitDepths().contains (bitsPerSample))
    {
        FlacWriter* w = new FlacWriter (out,
                                        sampleRate,
                                        numberOfChannels,
                                        bitsPerSample);

        if (! w->ok)
            deleteAndZero (w);

        return w;
    }

    return 0;
}

END_JUCE_NAMESPACE

#endif
