//
//  CSAacEncoder.m
//  CocoaSplit
//
//  Created by Zakk on 11/9/14.
//

#import "CSAacEncoder.h"
#import "CaptureController.h"


@implementation CSAacEncoder



-(instancetype)init
{
    if (self = [super init])
    {
        encoderQueue = dispatch_queue_create("CSAACEncoderQueue", NULL);
        _aSemaphore = dispatch_semaphore_create(0);
        self.skipCompression = NO;
        
    }
    
    return self;
}

-(void)setupEncoderBuffer
{
    TPCircularBufferInit(&_inputBuffer, self.inputASBD->mBytesPerFrame * 8192);
    TPCircularBufferInit(&_scratchBuffer, self.inputASBD->mBytesPerFrame * 8192);

    CMAudioFormatDescriptionCreate(NULL, self.inputASBD, 0, nil, 0, nil, nil, &_pcmFormat);
    
    dispatch_async(encoderQueue, ^{[self encodeAudio];});
}


-(void) enqueuePCM:(AudioBufferList *)pcmBuffer atTime:(const AudioTimeStamp *)atTime
{
    TPCircularBufferCopyAudioBufferList(&_inputBuffer, pcmBuffer, atTime, kTPCircularBufferCopyAll, NULL);
    dispatch_semaphore_signal(_aSemaphore);
}


-(void *)inputBufferPtr
{
    return &_inputBuffer;
}


-(void)encodeAudio
{
    
    
    if (!self.encoderStarted)
    {
        [self setupEncoder];
        self.encoderStarted = YES;
    }
    
    while (1)
    {
        while (TPCircularBufferPeek(&_inputBuffer, NULL, self.inputASBD) >= 1024)
        {

            AudioBufferList *inBuffer = TPCircularBufferPrepareEmptyAudioBufferListWithAudioFormat(&_scratchBuffer, self.inputASBD, 1024, NULL);
            UInt32 inFrameCnt = 1024 ;
            AudioTimeStamp atTime;
            
            TPCircularBufferDequeueBufferListFrames(&_inputBuffer, &inFrameCnt, inBuffer, &atTime, self.inputASBD);
            CMSampleBufferRef pcmSampleBuffer;
            
            uint64_t mach_now = atTime.mHostTime;
            
            double abs_pts = (double)mach_now/NSEC_PER_SEC;
            
            CMTime ptsTime = CMTimeMake(abs_pts*1000, 1000);
            
            CMSampleTimingInfo timeInfo;
            CMTime duration = CMTimeMake(1, self.sampleRate);

            timeInfo.duration = duration;
            timeInfo.presentationTimeStamp = ptsTime;
            timeInfo.decodeTimeStamp = kCMTimeInvalid;
            CMSampleBufferCreate(kCFAllocatorDefault, NULL, NO, NULL, NULL, _pcmFormat, inFrameCnt, 1, &timeInfo, 0, NULL, &pcmSampleBuffer);
            CMSampleBufferSetDataBufferFromAudioBufferList(pcmSampleBuffer, kCFAllocatorDefault, kCFAllocatorDefault, 0, inBuffer);
            if (self.encodedReceiver)
                
            {
                [self.encodedReceiver captureOutputAudio:self.trackName didOutputPCMSampleBuffer:pcmSampleBuffer];
            }
            
            CFRelease(pcmSampleBuffer);
            
            if (self.skipCompression)
            {
                continue;
            }
            
            Float32 *writebuf = malloc(inBuffer->mBuffers[0].mDataByteSize*2);
            AudioBuffer buffer0 = inBuffer->mBuffers[0];
            AudioBuffer buffer1 = inBuffer->mBuffers[1];
            Float32 *data0 = buffer0.mData;
            Float32 *data1 = buffer1.mData;
            int channel_size = buffer0.mDataByteSize/sizeof(Float32);
            int i, u;
            for(i=u=0; i < channel_size; i++,u+=2)
            {
                writebuf[u] = data0[i];
                writebuf[u+1] = data1[i];
            }
            
            @autoreleasepool {
                
                UInt32 num_packets = 1;
                UInt32 outstatus = 0;
                UInt32 bufsize =  inBuffer->mBuffers[0].mDataByteSize*2;//This should be equal to 2x pcmBuffer->mBuffers[0].mDataByteSize
                
                UInt32 buffer_size = maxOutputSize;
                
                void *aacBuffer = malloc(maxOutputSize);
                
                
                OSStatus err;
                
                err = AudioCodecAppendInputData(aacCodec, writebuf, &bufsize, &num_packets, NULL);
                
                free(writebuf);
                AudioStreamPacketDescription packetDesc;
                
                
                
                err = AudioCodecProduceOutputPackets(aacCodec, aacBuffer, &buffer_size, &num_packets, &packetDesc, &outstatus);
                
                if (err != 0)
                {
                    NSLog(@"CODEC PRODUCE OUTPUT ERROR IS %@", [[NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil] description]);
                    
                }
                
                
                if (outstatus == kAudioCodecProduceOutputPacketNeedsMoreInputData)
                {
                    free(aacBuffer);
                    break;
                }
                
                
                if (self.encodedReceiver && buffer_size)
                {
                    CMTime duration = CMTimeMake(1024, self.sampleRate);

                    timeInfo.duration = duration;

                    CMSampleBufferRef newSampleBuf;
                    CMSampleBufferRef timingSampleBuf;
                    CMBlockBufferRef bufferRef;
                    
                    
                    CMBlockBufferCreateWithMemoryBlock(NULL, aacBuffer, buffer_size, kCFAllocatorMalloc, NULL, 0, buffer_size, 0, &bufferRef);
                    
                    
                    CMAudioSampleBufferCreateWithPacketDescriptions(kCFAllocatorDefault, bufferRef, YES, NULL, NULL, cmFormat, 1, ptsTime, &packetDesc, &newSampleBuf);
                    
                    
                    CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, newSampleBuf, 1, &timeInfo, &timingSampleBuf);
                    
                    CFRelease(newSampleBuf);
                    //The sample buffer retains the block buffer when it is handed over to it, we can release ours.
                    CFRelease(bufferRef);
                    
                    [self.encodedReceiver captureOutputAudio:self.trackName didOutputSampleBuffer:timingSampleBuf];
                    
                    //Individual video compressors retain the buffer until they push it to their output, we can release it now.
                    CFRelease(timingSampleBuf);
                    
                } else {
                    free(aacBuffer);
                }
                
                
                
                buffer_size = maxOutputSize;
                num_packets = 1;
                
                outputSampleCount += 1024;
            }
            
        }
        double secs_sleep = (1.0f/self.sampleRate)*1024.0f;
        
        int32_t msecs_sleep = secs_sleep * USEC_PER_SEC;

        
        @synchronized(self)
        {
            if (_stopRequested)
            {
                return;
            }
        }
        usleep(msecs_sleep);
        
        @synchronized(self)
        {
            if (_stopRequested)
            {
                return;
            }
        }
    }
}

-(void) setupEncoder
{
    //create the input format.
    
     AudioStreamBasicDescription inputFormat = {0};
    
    
    inputFormat.mSampleRate = self.sampleRate;
    inputFormat.mFormatID = kAudioFormatLinearPCM;
    inputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat;
    inputFormat.mChannelsPerFrame = 2;
    inputFormat.mBitsPerChannel = 32;
    inputFormat.mBytesPerFrame = 8;
    inputFormat.mBytesPerPacket = 8;
    inputFormat.mFramesPerPacket = 1;
    
    AudioStreamBasicDescription outputFormat = {0};
    
    outputFormat.mSampleRate = self.sampleRate;
    outputFormat.mFormatID = kAudioFormatMPEG4AAC;
    outputFormat.mChannelsPerFrame = 2;
    outputFormat.mBytesPerPacket = 0;
    outputFormat.mBytesPerFrame = 0;
    outputFormat.mFramesPerPacket = 1024;
    outputFormat.mBitsPerChannel = 0;
    
    
    OSStatus err;
    AudioComponentDescription acDesc = {0};
    
    acDesc.componentType = kAudioEncoderComponentType;
    acDesc.componentSubType = kAudioFormatMPEG4AAC;
    
   
    
    AudioComponent aComp = NULL;
    
    aComp = AudioComponentFindNext(aComp, &acDesc);
    AudioComponentInstanceNew(aComp, &aacCodec);
    
    
    
    UInt32 control_mode = kAudioCodecBitRateControlMode_LongTermAverage;
    
    
    UInt32 getoutputsize = sizeof(UInt32);
    Boolean writeable;
    UInt32 cookiestructsize;
    AudioStreamBasicDescription outasbd;
    UInt32 outasbd_size = sizeof(outasbd);
    
    
    err = AudioCodecSetProperty(aacCodec, kAudioCodecPropertyBitRateControlMode, sizeof(control_mode), &control_mode);
    if (err)
    {
        NSLog(@"SET TARGET BITRATE CONTROL %d", err);
    }
    
    err = AudioCodecSetProperty(aacCodec, kAudioCodecPropertyCurrentTargetBitRate, sizeof(_bitRate), &_bitRate);
    if (err)
    {
        NSLog(@"SET TARGET BITRATE %d", err);
    }


    err = AudioCodecInitialize(aacCodec, &inputFormat, &outputFormat, NULL, 0);
    if (err)
    {
        NSLog(@"CODEC INITIALIZE %@", [[NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil] description]);
    }


    err = AudioCodecGetProperty(aacCodec, kAudioCodecPropertyCurrentOutputFormat, &outasbd_size, &outasbd);
    
    
    err = AudioCodecGetPropertyInfo(aacCodec, kAudioCodecPropertyMagicCookie, &cookiestructsize, &writeable);

    err = AudioCodecGetProperty(aacCodec, kAudioCodecPropertyMaximumPacketByteSize, &getoutputsize, &maxOutputSize);
    magicCookie = malloc(cookiestructsize);

    
    err = AudioCodecGetProperty(aacCodec, kAudioCodecPropertyMagicCookie, &cookiestructsize, magicCookie);


    
    
    outputSampleCount = 0;
    
    
    CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &outasbd, 0, NULL, cookiestructsize ,magicCookie, NULL, &cmFormat);
    
    
    CFDictionaryRef encoderState;
    
    cookiestructsize = sizeof(CFDictionaryRef);
    
    AudioCodecGetProperty(aacCodec, kAudioCodecPropertySettings, &cookiestructsize, &encoderState);
    return;
    
}

-(void)stopEncoder
{
    if (self.encoderStarted == YES)
    {
        self.encoderStarted = NO;
        AudioCodecUninitialize(aacCodec);
        free(magicCookie);
        @synchronized(self)
        {
            _stopRequested = YES;
        }
    }
}
-(void)dealloc
{
    [self stopEncoder];
    AudioComponentInstanceDispose(aacCodec);
    if (cmFormat)
    {
        CFRelease(cmFormat);
    }
    
    if (_pcmFormat)
    {
        CFRelease(_pcmFormat);
    }
    TPCircularBufferCleanup(&_inputBuffer);
    TPCircularBufferCleanup(&_scratchBuffer);
}


@end

