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

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

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

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   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.

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

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

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

#if JUCE_QUICKTIME && ! (JUCE_64BIT || JUCE_IOS)

END_JUCE_NAMESPACE

#if ! JUCE_WINDOWS
 #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers)
 #define Component CarbonDummyCompName
 #include <QuickTime/Movies.h>
 #include <QuickTime/QTML.h>
 #include <QuickTime/QuickTimeComponents.h>
 #include <QuickTime/MediaHandlers.h>
 #include <QuickTime/ImageCodec.h>
 #undef Point
 #undef Component
#else
 #if JUCE_MSVC
  #pragma warning (push)
  #pragma warning (disable : 4100)
 #endif

 /* If you've got an include error here, you probably need to install the QuickTime SDK and
    add its header directory to your include path.

    Alternatively, if you don't need any QuickTime services, just set the JUCE_QUICKTIME flag to 0.
 */
 #undef SIZE_MAX
 #include <Movies.h>
 #include <QTML.h>
 #include <QuickTimeComponents.h>
 #include <MediaHandlers.h>
 #include <ImageCodec.h>
 #undef SIZE_MAX

 #if JUCE_MSVC
  #pragma warning (pop)
 #endif
#endif

BEGIN_JUCE_NAMESPACE

bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle);

static const char* const quickTimeFormatName = "QuickTime file";
static const char* const quickTimeExtensions[] = { ".mov", ".mp3", ".mp4", ".m4a", 0 };

//==============================================================================
class QTAudioReader     : public AudioFormatReader
{
public:
    QTAudioReader (InputStream* const input_, const int trackNum_)
        : AudioFormatReader (input_, TRANS (quickTimeFormatName)),
          ok (false),
          movie (0),
          trackNum (trackNum_),
          lastSampleRead (0),
          lastThreadId (0),
          extractor (0),
          dataHandle (0)
    {
        JUCE_AUTORELEASEPOOL
        bufferList.calloc (256, 1);

       #if JUCE_WINDOWS
        if (InitializeQTML (0) != noErr)
            return;
       #endif

        if (EnterMovies() != noErr)
            return;

        bool opened = juce_OpenQuickTimeMovieFromStream (input_, movie, dataHandle);

        if (! opened)
            return;

        {
            const int numTracks = GetMovieTrackCount (movie);
            int trackCount = 0;

            for (int i = 1; i <= numTracks; ++i)
            {
                track = GetMovieIndTrack (movie, i);
                media = GetTrackMedia (track);

                OSType mediaType;
                GetMediaHandlerDescription (media, &mediaType, 0, 0);

                if (mediaType == SoundMediaType
                     && trackCount++ == trackNum_)
                {
                    ok = true;
                    break;
                }
            }
        }

        if (! ok)
            return;

        ok = false;

        lengthInSamples = GetMediaDecodeDuration (media);
        usesFloatingPointData = false;

        samplesPerFrame = (int) (GetMediaDecodeDuration (media) / GetMediaSampleCount (media));

        trackUnitsPerFrame = GetMovieTimeScale (movie) * samplesPerFrame
                                / GetMediaTimeScale (media);

        OSStatus err = MovieAudioExtractionBegin (movie, 0, &extractor);

        unsigned long output_layout_size;
        err = MovieAudioExtractionGetPropertyInfo (extractor,
                                                   kQTPropertyClass_MovieAudioExtraction_Audio,
                                                   kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout,
                                                   0, &output_layout_size, 0);
        if (err != noErr)
            return;

        HeapBlock <AudioChannelLayout> qt_audio_channel_layout;
        qt_audio_channel_layout.calloc (output_layout_size, 1);

        err = MovieAudioExtractionGetProperty (extractor,
                                               kQTPropertyClass_MovieAudioExtraction_Audio,
                                               kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout,
                                               output_layout_size, qt_audio_channel_layout, 0);

        qt_audio_channel_layout[0].mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

        err = MovieAudioExtractionSetProperty (extractor,
                                               kQTPropertyClass_MovieAudioExtraction_Audio,
                                               kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout,
                                               output_layout_size,
                                               qt_audio_channel_layout);

        err = MovieAudioExtractionGetProperty (extractor,
                                               kQTPropertyClass_MovieAudioExtraction_Audio,
                                               kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
                                               sizeof (inputStreamDesc),
                                               &inputStreamDesc, 0);
        if (err != noErr)
            return;

        inputStreamDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger
                                        | kAudioFormatFlagIsPacked
                                        | kAudioFormatFlagsNativeEndian;
        inputStreamDesc.mBitsPerChannel = sizeof (SInt16) * 8;
        inputStreamDesc.mChannelsPerFrame = jmin ((UInt32) 2, inputStreamDesc.mChannelsPerFrame);
        inputStreamDesc.mBytesPerFrame = sizeof (SInt16) * inputStreamDesc.mChannelsPerFrame;
        inputStreamDesc.mBytesPerPacket = inputStreamDesc.mBytesPerFrame;

        err = MovieAudioExtractionSetProperty (extractor,
                                               kQTPropertyClass_MovieAudioExtraction_Audio,
                                               kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
                                               sizeof (inputStreamDesc),
                                               &inputStreamDesc);
        if (err != noErr)
            return;

        Boolean allChannelsDiscrete = false;
        err = MovieAudioExtractionSetProperty (extractor,
                                               kQTPropertyClass_MovieAudioExtraction_Movie,
                                               kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete,
                                               sizeof (allChannelsDiscrete),
                                               &allChannelsDiscrete);

        if (err != noErr)
            return;

        bufferList->mNumberBuffers = 1;
        bufferList->mBuffers[0].mNumberChannels = inputStreamDesc.mChannelsPerFrame;
        bufferList->mBuffers[0].mDataByteSize =  jmax ((UInt32) 4096, (UInt32) (samplesPerFrame * inputStreamDesc.mBytesPerFrame) + 16);

        dataBuffer.malloc (bufferList->mBuffers[0].mDataByteSize);
        bufferList->mBuffers[0].mData = dataBuffer;

        sampleRate = inputStreamDesc.mSampleRate;
        bitsPerSample = 16;
        numChannels = inputStreamDesc.mChannelsPerFrame;

        detachThread();
        ok = true;
    }

    ~QTAudioReader()
    {
        JUCE_AUTORELEASEPOOL
        checkThreadIsAttached();

        if (dataHandle != nullptr)
            DisposeHandle (dataHandle);

        if (extractor != nullptr)
        {
            MovieAudioExtractionEnd (extractor);
            extractor = nullptr;
        }

        DisposeMovie (movie);

       #if JUCE_MAC
        ExitMoviesOnThread ();
       #endif
    }

    bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
                      int64 startSampleInFile, int numSamples)
    {
        JUCE_AUTORELEASEPOOL
        checkThreadIsAttached();
        bool ok = true;

        while (numSamples > 0)
        {
            if (lastSampleRead != startSampleInFile)
            {
                TimeRecord time;
                time.scale = (TimeScale) inputStreamDesc.mSampleRate;
                time.base = 0;
                time.value.hi = 0;
                time.value.lo = (UInt32) startSampleInFile;

                OSStatus err = MovieAudioExtractionSetProperty (extractor,
                                                                kQTPropertyClass_MovieAudioExtraction_Movie,
                                                                kQTMovieAudioExtractionMoviePropertyID_CurrentTime,
                                                                sizeof (time), &time);

                if (err != noErr)
                {
                    ok = false;
                    break;
                }
            }

            int framesToDo = jmin (numSamples, (int) (bufferList->mBuffers[0].mDataByteSize / inputStreamDesc.mBytesPerFrame));
            bufferList->mBuffers[0].mDataByteSize = inputStreamDesc.mBytesPerFrame * framesToDo;

            UInt32 outFlags = 0;
            UInt32 actualNumFrames = framesToDo;
            OSStatus err = MovieAudioExtractionFillBuffer (extractor, &actualNumFrames, bufferList, &outFlags);
            if (err != noErr)
            {
                ok = false;
                break;
            }

            lastSampleRead = startSampleInFile + actualNumFrames;
            const int samplesReceived = actualNumFrames;

            for (int j = numDestChannels; --j >= 0;)
            {
                if (destSamples[j] != nullptr)
                {
                    const short* src = ((const short*) bufferList->mBuffers[0].mData) + j;

                    for (int i = 0; i < samplesReceived; ++i)
                    {
                        destSamples[j][startOffsetInDestBuffer + i] = (*src << 16);
                        src += numChannels;
                    }
                }
            }

            startOffsetInDestBuffer += samplesReceived;
            startSampleInFile += samplesReceived;
            numSamples -= samplesReceived;

            if (((outFlags & kQTMovieAudioExtractionComplete) != 0 || samplesReceived == 0) && numSamples > 0)
            {
                for (int j = numDestChannels; --j >= 0;)
                    if (destSamples[j] != nullptr)
                        zeromem (destSamples[j] + startOffsetInDestBuffer, sizeof (int) * numSamples);

                break;
            }
        }

        detachThread();
        return ok;
    }

    bool ok;

private:
    Movie movie;
    Media media;
    Track track;
    const int trackNum;
    double trackUnitsPerFrame;
    int samplesPerFrame;
    int64 lastSampleRead;
    Thread::ThreadID lastThreadId;
    MovieAudioExtractionRef extractor;
    AudioStreamBasicDescription inputStreamDesc;
    HeapBlock <AudioBufferList> bufferList;
    HeapBlock <char> dataBuffer;
    Handle dataHandle;

    //==============================================================================
    void checkThreadIsAttached()
    {
       #if JUCE_MAC
        if (Thread::getCurrentThreadId() != lastThreadId)
            EnterMoviesOnThread (0);
        AttachMovieToCurrentThread (movie);
       #endif
    }

    void detachThread()
    {
       #if JUCE_MAC
        DetachMovieFromCurrentThread (movie);
       #endif
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (QTAudioReader);
};


//==============================================================================
QuickTimeAudioFormat::QuickTimeAudioFormat()
    : AudioFormat (TRANS (quickTimeFormatName), StringArray (quickTimeExtensions))
{
}

QuickTimeAudioFormat::~QuickTimeAudioFormat()
{
}

Array<int> QuickTimeAudioFormat::getPossibleSampleRates()    { return Array<int>(); }
Array<int> QuickTimeAudioFormat::getPossibleBitDepths()      { return Array<int>(); }

bool QuickTimeAudioFormat::canDoStereo()    { return true; }
bool QuickTimeAudioFormat::canDoMono()      { return true; }

//==============================================================================
AudioFormatReader* QuickTimeAudioFormat::createReaderFor (InputStream* sourceStream,
                                                          const bool deleteStreamIfOpeningFails)
{
    ScopedPointer <QTAudioReader> r (new QTAudioReader (sourceStream, 0));

    if (r->ok)
        return r.release();

    if (! deleteStreamIfOpeningFails)
        r->input = 0;

    return nullptr;
}

AudioFormatWriter* QuickTimeAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/,
                                                          double /*sampleRateToUse*/,
                                                          unsigned int /*numberOfChannels*/,
                                                          int /*bitsPerSample*/,
                                                          const StringPairArray& /*metadataValues*/,
                                                          int /*qualityOptionIndex*/)
{
    jassertfalse; // not yet implemented!
    return nullptr;
}

#endif
