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

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-7 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.

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

// (This file gets included by juce_mac_NativeCode.mm, rather than being
// compiled on its own).
#if JUCE_INCLUDED_FILE && JUCE_QUICKTIME && JUCE_USE_CAMERA


//==============================================================================
#define QTCaptureCallbackDelegate MakeObjCClassName(QTCaptureCallbackDelegate)

class QTCameraDeviceInteral;

END_JUCE_NAMESPACE

@interface QTCaptureCallbackDelegate    : NSObject
{
@public
    CameraDevice* owner;
    QTCameraDeviceInteral* internal;
    Time* firstRecordedTime;
}

- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner internalDev: (QTCameraDeviceInteral*) d;
- (void) dealloc;

- (void) captureOutput: (QTCaptureOutput*) captureOutput
         didOutputVideoFrame: (CVImageBufferRef) videoFrame
         withSampleBuffer: (QTSampleBuffer*) sampleBuffer
         fromConnection: (QTCaptureConnection*) connection;

- (void) captureOutput: (QTCaptureFileOutput*) captureOutput
         didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
         fromConnection: (QTCaptureConnection*) connection;

@end

BEGIN_JUCE_NAMESPACE

//==============================================================================
class QTCameraDeviceInteral
{
public:
    QTCameraDeviceInteral (CameraDevice* owner, int index)
    {
        const ScopedAutoReleasePool pool;

        session = [[QTCaptureSession alloc] init];

        NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
        device = (QTCaptureDevice*) [devs objectAtIndex: index];
        input = 0;
        fileOutput = 0;
        imageOutput = 0;
        callbackDelegate = [[QTCaptureCallbackDelegate alloc] initWithOwner: owner
                                                                internalDev: this];

        NSError* err = 0;
        [device retain];
        [device open: &err];

        if (err == 0)
        {
            input = [[QTCaptureDeviceInput alloc] initWithDevice: device];

            [session addInput: input error: &err];

            if (err == 0)
            {
                resetFile();

                imageOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
                [imageOutput setDelegate: callbackDelegate];

                if (err == 0)
                {
                    [session startRunning];
                    return;
                }
            }
        }

        openingError = nsStringToJuce ([err description]);
        DBG (openingError);
    }

    ~QTCameraDeviceInteral()
    {
        [session stopRunning];
        [session removeOutput: imageOutput];

        [session release];
        [input release];
        [device release];
        [fileOutput release];
        [imageOutput release];
        [callbackDelegate release];
    }

    void resetFile()
    {
        [session removeOutput: fileOutput];
        [fileOutput release];
        fileOutput = [[QTCaptureMovieFileOutput alloc] init];
        [fileOutput setDelegate: callbackDelegate];
    }

    void addListener (CameraImageListener* listenerToAdd)
    {
        const ScopedLock sl (listenerLock);

        if (listeners.size() == 0)
            [session addOutput: imageOutput error: nil];

        listeners.addIfNotAlreadyThere (listenerToAdd);
    }

    void removeListener (CameraImageListener* listenerToRemove)
    {
        const ScopedLock sl (listenerLock);
        listeners.removeValue (listenerToRemove);

        if (listeners.size() == 0)
            [session removeOutput: imageOutput];
    }

    static void drawNSBitmapIntoJuceImage (Image& dest, NSBitmapImageRep* source)
    {
        const ScopedAutoReleasePool pool;
        int lineStride, pixelStride;
        uint8* pixels = dest.lockPixelDataReadWrite (0, 0, dest.getWidth(), dest.getHeight(),
                                                     lineStride, pixelStride);

        NSBitmapImageRep* rep = [[NSBitmapImageRep alloc]
            initWithBitmapDataPlanes: &pixels
                          pixelsWide: dest.getWidth()
                          pixelsHigh: dest.getHeight()
                       bitsPerSample: 8
                     samplesPerPixel: pixelStride
                            hasAlpha: dest.hasAlphaChannel()
                            isPlanar: NO
                      colorSpaceName: NSCalibratedRGBColorSpace
                        bitmapFormat: (NSBitmapFormat) 0
                         bytesPerRow: lineStride
                        bitsPerPixel: pixelStride * 8];

        [NSGraphicsContext saveGraphicsState];
        [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithBitmapImageRep: rep]];

        [source drawAtPoint: NSZeroPoint];

        [[NSGraphicsContext currentContext] flushGraphics];
        [NSGraphicsContext restoreGraphicsState];

        uint8* start = pixels;
        for (int h = dest.getHeight(); --h >= 0;)
        {
            uint8* p = start;
            start += lineStride;

            for (int i = dest.getWidth(); --i >= 0;)
            {
#if JUCE_BIG_ENDIAN
                const uint8 oldp3 = p[3];
                const uint8 oldp1 = p[1];
                p[3] = p[0];
                p[0] = oldp1;
                p[1] = p[2];
                p[2] = oldp3;
#else
                const uint8 oldp0 = p[0];
                p[0] = p[2];
                p[2] = oldp0;
#endif

                p += pixelStride;
            }
        }

        dest.releasePixelDataReadWrite (pixels);
    }

    void callListeners (NSBitmapImageRep* bitmap)
    {
        Image image (Image::ARGB, [bitmap size].width, [bitmap size].height, false);
        drawNSBitmapIntoJuceImage (image, bitmap);

        const ScopedLock sl (listenerLock);

        for (int i = listeners.size(); --i >= 0;)
        {
            CameraImageListener* l = (CameraImageListener*) listeners[i];

            if (l != 0)
                l->imageReceived (image);
        }
    }

    QTCaptureDevice* device;
    QTCaptureDeviceInput* input;
    QTCaptureSession* session;
    QTCaptureMovieFileOutput* fileOutput;
    QTCaptureDecompressedVideoOutput* imageOutput;
    QTCaptureCallbackDelegate* callbackDelegate;
    String openingError;

    VoidArray listeners;
    CriticalSection listenerLock;
};

END_JUCE_NAMESPACE
@implementation QTCaptureCallbackDelegate

- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner_
                                 internalDev: (QTCameraDeviceInteral*) d
{
    [super init];
    owner = owner_;
    internal = d;
    firstRecordedTime = 0;
    return self;
}

- (void) dealloc
{
    delete firstRecordedTime;
    [super dealloc];
}

- (void) captureOutput: (QTCaptureOutput*) captureOutput
         didOutputVideoFrame: (CVImageBufferRef) videoFrame
         withSampleBuffer: (QTSampleBuffer*) sampleBuffer
         fromConnection: (QTCaptureConnection*) connection
{
    const ScopedAutoReleasePool pool;
    CIImage* image = [CIImage imageWithCVImageBuffer: videoFrame];
    NSBitmapImageRep* bitmap = [[[NSBitmapImageRep alloc] initWithCIImage: image] autorelease];

    internal->callListeners (bitmap);
}

- (void) captureOutput: (QTCaptureFileOutput*) captureOutput
         didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
         fromConnection: (QTCaptureConnection*) connection
{
    if (firstRecordedTime == 0)
        firstRecordedTime = new Time (Time::getCurrentTime());
}

@end
BEGIN_JUCE_NAMESPACE

//==============================================================================
class QTCaptureViewerComp : public NSViewComponent
{
public:
    QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal)
    {
        const ScopedAutoReleasePool pool;
        captureView = [[QTCaptureView alloc] init];
        [captureView setCaptureSession: internal->session];

        setSize (640, 480); //  xxx need to somehow get the movie size - how?
        setView (captureView);
    }

    ~QTCaptureViewerComp()
    {
        setView (0);
        [captureView setCaptureSession: nil];
        [captureView release];
    }

    QTCaptureView* captureView;
};

//==============================================================================
CameraDevice::CameraDevice (const String& name_, int index)
    : name (name_)
{
    isRecording = false;
    QTCameraDeviceInteral* d = new QTCameraDeviceInteral (this, index);
    internal = d;
}

CameraDevice::~CameraDevice()
{
    stopRecording();
    delete (QTCameraDeviceInteral*) internal;
    internal = 0;
}

Component* CameraDevice::createViewerComponent()
{
    return new QTCaptureViewerComp (this, (QTCameraDeviceInteral*) internal);
}

const String CameraDevice::getFileExtension()
{
    return ".mov";
}

void CameraDevice::startRecordingToFile (const File& file)
{
    stopRecording();

    QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
    deleteAndZero (d->callbackDelegate->firstRecordedTime);
    file.deleteFile();
    [d->fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]];
    [d->session addOutput: d->fileOutput error: nil];
    isRecording = true;
}

const Time CameraDevice::getTimeOfFirstRecordedFrame() const
{
    QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
    if (d->callbackDelegate->firstRecordedTime != 0)
        return *d->callbackDelegate->firstRecordedTime;

    return Time();
}

void CameraDevice::stopRecording()
{
    if (isRecording)
    {
        QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
        d->resetFile();
        isRecording = false;
    }
}

void CameraDevice::addListener (CameraImageListener* listenerToAdd)
{
    QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;

    if (listenerToAdd != 0)
        d->addListener (listenerToAdd);
}

void CameraDevice::removeListener (CameraImageListener* listenerToRemove)
{
    QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;

    if (listenerToRemove != 0)
        d->removeListener (listenerToRemove);
}

//==============================================================================
const StringArray CameraDevice::getAvailableDevices()
{
    const ScopedAutoReleasePool pool;

    StringArray results;
    NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];

    for (int i = 0; i < [devs count]; ++i)
    {
        QTCaptureDevice* dev = (QTCaptureDevice*) [devs objectAtIndex: i];
        results.add (nsStringToJuce ([dev localizedDisplayName]));
    }

    return results;
}

CameraDevice* CameraDevice::openDevice (int index,
                                        int minWidth, int minHeight,
                                        int maxWidth, int maxHeight)
{
    CameraDevice* d = new CameraDevice (getAvailableDevices() [index], index);

    if (((QTCameraDeviceInteral*) (d->internal))->openingError.isEmpty())
        return d;

    delete d;
    return 0;
}


#endif
