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

   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_USE_CDBURNER

//==============================================================================
END_JUCE_NAMESPACE

#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice)

@interface OpenDiskDevice   : NSObject
{
    DRDevice* device;

    NSMutableArray* tracks;
}

- (OpenDiskDevice*) initWithDevice: (DRDevice*) device;
- (void) dealloc;
- (bool) isDiskPresent;
- (int) getNumAvailableAudioBlocks;
- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_;
- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener  errorString: (JUCE_NAMESPACE::String*) error
         ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting;
@end

//==============================================================================
#define AudioTrackProducer MakeObjCClassName(AudioTrackProducer)

@interface AudioTrackProducer   : NSObject
{
    JUCE_NAMESPACE::AudioSource* source;
    int readPosition, lengthInFrames;
}

- (AudioTrackProducer*) init: (int) lengthInFrames;
- (AudioTrackProducer*) initWithAudioSource: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) lengthInSamples;
- (void) dealloc;
- (void) setupTrackProperties: (DRTrack*) track;

- (void) cleanupTrackAfterBurn: (DRTrack*) track;
- (BOOL) cleanupTrackAfterVerification:(DRTrack*)track;
- (uint64_t) estimateLengthOfTrack:(DRTrack*)track;
- (BOOL) prepareTrack:(DRTrack*)track forBurn:(DRBurn*)burn
         toMedia:(NSDictionary*)mediaInfo;
- (BOOL) prepareTrackForVerification:(DRTrack*)track;
- (uint32_t) produceDataForTrack:(DRTrack*)track intoBuffer:(char*)buffer
        length:(uint32_t)bufferLength atAddress:(uint64_t)address
        blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags;
- (uint32_t) producePreGapForTrack:(DRTrack*)track
             intoBuffer:(char*)buffer length:(uint32_t)bufferLength
             atAddress:(uint64_t)address blockSize:(uint32_t)blockSize
             ioFlags:(uint32_t*)flags;
- (BOOL) verifyDataForTrack:(DRTrack*)track inBuffer:(const char*)buffer
         length:(uint32_t)bufferLength atAddress:(uint64_t)address
         blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags;
- (uint32_t) producePreGapForTrack:(DRTrack*)track
        intoBuffer:(char*)buffer length:(uint32_t)bufferLength
        atAddress:(uint64_t)address blockSize:(uint32_t)blockSize
        ioFlags:(uint32_t*)flags;
@end

//==============================================================================
@implementation OpenDiskDevice

- (OpenDiskDevice*) initWithDevice: (DRDevice*) device_
{
    [super init];

    device = device_;
    tracks = [[NSMutableArray alloc] init];
    return self;
}

- (void) dealloc
{
    [tracks release];
    [super dealloc];
}

- (bool) isDiskPresent
{
    return [device isValid]
             && [[[device status] objectForKey: DRDeviceMediaStateKey]
                    isEqualTo: DRDeviceMediaStateMediaPresent];
}

- (int) getNumAvailableAudioBlocks
{
    return [[[[device status] objectForKey: DRDeviceMediaInfoKey]
                              objectForKey: DRDeviceMediaBlocksFreeKey] intValue];
}

- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) numSamples_
{
    AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_];
    DRTrack* t = [[DRTrack alloc] initWithProducer: p];
    [p setupTrackProperties: t];

    [tracks addObject: t];

    [t release];
    [p release];
}

- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error
         ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting
{
    DRBurn* burn = [DRBurn burnForDevice: device];

    if (! [device acquireExclusiveAccess])
    {
        *error = "Couldn't open or write to the CD device";
        return;
    }

    [device acquireMediaReservation];

    NSMutableDictionary* d = [[burn properties] mutableCopy];
    [d autorelease];
    [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey];
    [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey];
    [d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount)
          forKey: DRBurnCompletionActionKey];
    [burn setProperties: d];

    [burn writeLayout: tracks];

    for (;;)
    {
        JUCE_NAMESPACE::Thread::sleep (300);
        float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue];

        if (listener != 0 && listener->audioCDBurnProgress (progress))
        {
            [burn abort];
            *error = "User cancelled the write operation";
            break;
        }

        if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed])
        {
            *error = "Write operation failed";
            break;
        }
        else if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone])
        {
            break;
        }

        NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey]
                                                    objectForKey: DRErrorStatusErrorStringKey];

        if ([err length] > 0)
        {
            *error = JUCE_NAMESPACE::String::fromUTF8 ((JUCE_NAMESPACE::uint8*) [err UTF8String]);
            break;
        }
    }

    [device releaseMediaReservation];
    [device releaseExclusiveAccess];
}
@end

//==============================================================================
@implementation AudioTrackProducer

- (AudioTrackProducer*) init: (int) lengthInFrames_
{
    lengthInFrames = lengthInFrames_;
    readPosition = 0;
    return self;
}

- (void) setupTrackProperties: (DRTrack*) track
{
    NSMutableDictionary*  p = [[track properties] mutableCopy];
    [p setObject:[DRMSF msfWithFrames: lengthInFrames] forKey: DRTrackLengthKey];
    [p setObject:[NSNumber numberWithUnsignedShort:2352] forKey: DRBlockSizeKey];
    [p setObject:[NSNumber numberWithInt:0] forKey: DRDataFormKey];
    [p setObject:[NSNumber numberWithInt:0] forKey: DRBlockTypeKey];
    [p setObject:[NSNumber numberWithInt:0] forKey: DRTrackModeKey];
    [p setObject:[NSNumber numberWithInt:0] forKey: DRSessionFormatKey];


    [track setProperties: p];
    [p release];
}

- (AudioTrackProducer*) initWithAudioSource: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) lengthInSamples
{
    AudioTrackProducer* s = [self init: (lengthInSamples + 587) / 588];

    if (s != nil)
        s->source = source_;

    return s;
}

- (void) dealloc
{
    if (source != 0)
    {
        source->releaseResources();
        delete source;
    }

    [super dealloc];
}

- (void) cleanupTrackAfterBurn: (DRTrack*) track
{
}

- (BOOL) cleanupTrackAfterVerification: (DRTrack*) track
{
    return true;
}

- (uint64_t) estimateLengthOfTrack: (DRTrack*) track
{
    return lengthInFrames;
}

- (BOOL) prepareTrack: (DRTrack*) track forBurn: (DRBurn*) burn
         toMedia: (NSDictionary*) mediaInfo
{
    if (source != 0)
        source->prepareToPlay (44100 / 75, 44100);

    readPosition = 0;
    return true;
}

- (BOOL) prepareTrackForVerification: (DRTrack*) track
{
    if (source != 0)
        source->prepareToPlay (44100 / 75, 44100);

    return true;
}

- (uint32_t) produceDataForTrack: (DRTrack*) track intoBuffer: (char*) buffer
        length: (uint32_t) bufferLength atAddress: (uint64_t) address
        blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags
{
    if (source != 0)
    {
        const int numSamples = JUCE_NAMESPACE::jmin (bufferLength / 4, (lengthInFrames * (44100 / 75)) - readPosition);

        if (numSamples > 0)
        {
            JUCE_NAMESPACE::AudioSampleBuffer tempBuffer (2, numSamples);

            JUCE_NAMESPACE::AudioSourceChannelInfo info;
            info.buffer = &tempBuffer;
            info.startSample = 0;
            info.numSamples = numSamples;

            source->getNextAudioBlock (info);

            JUCE_NAMESPACE::AudioDataConverters::convertFloatToInt16LE (tempBuffer.getSampleData (0),
                                                                        buffer, numSamples, 4);
            JUCE_NAMESPACE::AudioDataConverters::convertFloatToInt16LE (tempBuffer.getSampleData (1),
                                                                        buffer + 2, numSamples, 4);

            readPosition += numSamples;
        }

        return numSamples * 4;
    }

    return 0;
}

- (uint32_t) producePreGapForTrack: (DRTrack*) track
        intoBuffer: (char*) buffer length: (uint32_t) bufferLength
        atAddress: (uint64_t) address blockSize: (uint32_t) blockSize
        ioFlags: (uint32_t*) flags
{
    zeromem (buffer, bufferLength);
    return bufferLength;
}

- (BOOL) verifyDataForTrack: (DRTrack*) track inBuffer: (const char*) buffer
         length: (uint32_t) bufferLength atAddress: (uint64_t) address
         blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags
{
    return true;
}

@end


BEGIN_JUCE_NAMESPACE

//==============================================================================
AudioCDBurner::AudioCDBurner (const int deviceIndex)
    : internal (0)
{
    OpenDiskDevice* dev = [[OpenDiskDevice alloc] initWithDevice: [[DRDevice devices] objectAtIndex: deviceIndex]];

    internal = (void*) dev;
}

AudioCDBurner::~AudioCDBurner()
{
    OpenDiskDevice* dev = (OpenDiskDevice*) internal;

    if (dev != 0)
        [dev release];
}

AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
{
    AudioCDBurner* b = new AudioCDBurner (deviceIndex);

    if (b->internal == 0)
        deleteAndZero (b);

    return b;
}

static NSArray* findDiskBurnerDevices()
{
    NSMutableArray* results = [NSMutableArray array];
    NSArray* devs = [DRDevice devices];

    if (devs != 0)
    {
        int num = [devs count];
        int i;
        for (i = 0; i < num; ++i)
        {
            NSDictionary* dic = [[devs objectAtIndex: i] info];
            NSString* name = [dic valueForKey: DRDeviceProductNameKey];
            if (name != nil)
                [results addObject: name];
        }
    }

    return results;
}

const StringArray AudioCDBurner::findAvailableDevices()
{
    NSArray* names = findDiskBurnerDevices();
    StringArray s;

    for (unsigned int i = 0; i < [names count]; ++i)
        s.add (String::fromUTF8 ((JUCE_NAMESPACE::uint8*) [[names objectAtIndex: i] UTF8String]));

    return s;
}

bool AudioCDBurner::isDiskPresent() const
{
    OpenDiskDevice* dev = (OpenDiskDevice*) internal;

    return dev != 0 && [dev isDiskPresent];
}

int AudioCDBurner::getNumAvailableAudioBlocks() const
{
    OpenDiskDevice* dev = (OpenDiskDevice*) internal;

    return [dev getNumAvailableAudioBlocks];
}

bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps)
{
    OpenDiskDevice* dev = (OpenDiskDevice*) internal;

    if (dev != 0)
    {
        [dev addSourceTrack: source numSamples: numSamps];
        return true;
    }

    return false;
}

const String AudioCDBurner::burn (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener* listener,
                                  const bool ejectDiscAfterwards,
                                  const bool peformFakeBurnForTesting)
{
    String error ("Couldn't open or write to the CD device");

    OpenDiskDevice* dev = (OpenDiskDevice*) internal;

    if (dev != 0)
    {
        error = String::empty;
        [dev burn: listener
             errorString: &error
             ejectAfterwards: ejectDiscAfterwards
             isFake: peformFakeBurnForTesting];
    }

    return error;
}


//==============================================================================
void AudioCDReader::ejectDisk()
{
    const ScopedAutoReleasePool p;
    [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())];
}


#endif
