//
//  ExternalAudio.m
//  AgoraAudioIO
//
//  Created by CavanSu on 22/01/2018.
//  Copyright © 2018 CavanSu. All rights reserved.
//

#import "ExternalAudio.h"
#import "AudioController.h"
#import "AudioWriteToFile.h"
#import <AgoraRtcKit/IAgoraMediaEngine.h>
//#if TARGET_OS_IPHONE
//#import <AgoraAudioKit/AgoraRtcEngineKit.h>
//#import <AgoraAudioKit/IAgoraRtcEngine.h>
//#import <AgoraAudioKit/IAgoraMediaEngine.h>
//#else
#import <AgoraRtcKit/AgoraRtcEngineKit.h>
#import <AgoraRtcKit/IAgoraRtcEngine.h>
//#import <AgoraRtcEngineKit/IAgoraMediaEngine.h>
//#endif
#import <iflyMSC/IFlyMSC.h>

@interface ExternalAudio () <AudioControllerDelegate, IFlySpeechRecognizerDelegate>
@property (nonatomic, strong) AudioController *audioController;
@property (nonatomic, assign) AudioCRMode audioCRMode;
@property (nonatomic, assign) int sampleRate;
@property (nonatomic, assign) int channelCount;
@property (nonatomic, weak) AgoraRtcEngineKit *agoraKit;
@property (nonatomic, strong) IFlySpeechRecognizer *iFlySpeechRecognizer;
@end

@implementation ExternalAudio

static NSObject *threadLockCapture;
static NSObject *threadLockPlay;

#pragma mark - C++ AgoraAudioFrameObserver
class AgoraAudioFrameObserver : public agora::media::IAudioFrameObserver
{
private:
    
    // total buffer length of per second
    enum { kBufferLengthBytes = 441 * 2 * 2 * 50 }; //
    
    // capture
    char byteBuffer[kBufferLengthBytes]; // char take up 1 byte, byterBuffer[] take up 88200 bytes
    int readIndex = 0;
    int writeIndex = 0;
    int availableBytes = 0;
    int channels = 1;
    
    // play
    char byteBuffer_play[kBufferLengthBytes];
    int readIndex_play = 0;
    int writeIndex_play = 0;
    int availableBytes_play = 0;
    int channels_play = 1;
    
public:
    int sampleRate = 0;
    int sampleRate_play = 0;
    
    bool isExternalCapture = false;
    bool isExternalRender = false;
    
#pragma mark- <C++ Capture>
    // push audio data to special buffer(Array byteBuffer)
    // bytesLength = date length
    void pushExternalData(void* data, int bytesLength)
    {
        @synchronized(threadLockCapture) {
            
            if (availableBytes + bytesLength > kBufferLengthBytes) {
                
                readIndex = 0;
                writeIndex = 0;
                availableBytes = 0;
            }
            
            if (writeIndex + bytesLength > kBufferLengthBytes) {
                
                int left = kBufferLengthBytes - writeIndex;
                memcpy(byteBuffer + writeIndex, data, left);
                memcpy(byteBuffer, (char *)data + left, bytesLength - left);
                writeIndex = bytesLength - left;
            }
            else {
                
                memcpy(byteBuffer + writeIndex, data, bytesLength);
                writeIndex += bytesLength;
            }
            availableBytes += bytesLength;
        }
        
    }
    
    // copy byteBuffer to audioFrame.buffer
    virtual bool onRecordAudioFrame(AudioFrame& audioFrame) override
    {
        @synchronized(threadLockCapture) {
            if ([[IFlySpeechRecognizer sharedInstance] isListening]) {
                int readBytes = audioFrame.samples * audioFrame.bytesPerSample; // sampleRate / 100 * channels * audioFrame.bytesPerSample;
                //写入音频数据
                NSData *data = [NSData dataWithBytes:audioFrame.buffer length:readBytes];
                BOOL a = [[IFlySpeechRecognizer sharedInstance] writeAudio:data];//写入音频，让SDK识别。建议将音频数据分段写入。
                NSLog(@"音频写入结果------%d",a);
            }
            return true;
        }
        
    }
    
#pragma mark- <C++ Render>
    // read Audio data from byteBuffer_play to audioUnit
    int readAudioData(void* data, int bytesLength)
    {
        @synchronized(threadLockPlay) {
            
            if (NULL == data || bytesLength < 1 || availableBytes_play < bytesLength) {
                return 0;
            }
            
            int readBytes = bytesLength;
            
            unsigned char tmp[4096]; // unsigned char takes up 1 byte
            
            if (readIndex_play + readBytes > kBufferLengthBytes) {
                
                int left = kBufferLengthBytes - readIndex_play;
                memcpy(tmp, byteBuffer_play + readIndex_play, left);
                memcpy(tmp + left, byteBuffer_play, readBytes - left);
                readIndex_play = readBytes - left;
            }
            else {
                
                memcpy(tmp, byteBuffer_play + readIndex_play, readBytes);
                readIndex_play += readBytes;
            }
            
            availableBytes_play -= readBytes;
            
            if (channels_play == 1) {
                memcpy(data, tmp, readBytes);
            }
            
            [AudioWriteToFile writeToFileWithData:data length:readBytes];
            
            return readBytes;
        }
        
    }
    
    // recive remote audio stream, push audio data to byteBuffer_play
    virtual bool onPlaybackAudioFrame(AudioFrame& audioFrame) override
    {
        @synchronized(threadLockPlay) {
            
            if (isExternalRender == false) return true;
            
            int bytesLength = audioFrame.samples * audioFrame.channels * audioFrame.bytesPerSample;
            char *data = (char *)audioFrame.buffer;
            
            sampleRate_play = audioFrame.samplesPerSec;
            channels_play = audioFrame.channels;
            
            if (availableBytes_play + bytesLength > kBufferLengthBytes) {
                
                readIndex_play = 0;
                writeIndex_play = 0;
                availableBytes_play = 0;
            }
            
            if (writeIndex_play + bytesLength > kBufferLengthBytes) {
                
                int left = kBufferLengthBytes - writeIndex_play;
                memcpy(byteBuffer_play + writeIndex_play, data, left);
                memcpy(byteBuffer_play, (char *)data + left, bytesLength - left);
                writeIndex_play = bytesLength - left;
            }
            else {
                
                memcpy(byteBuffer_play + writeIndex_play, data, bytesLength);
                writeIndex_play += bytesLength;
            }
            
            availableBytes_play += bytesLength;
            
            return true;
        }
        
    }
    
    virtual bool onPlaybackAudioFrameBeforeMixing(unsigned int uid, AudioFrame& audioFrame) override { return true; }
    
    virtual bool onMixedAudioFrame(AudioFrame& audioFrame) override { return true; }
};

static AgoraAudioFrameObserver* s_audioFrameObserver;


+ (instancetype)sharedExternalAudio {
    ExternalAudio *audio = [[ExternalAudio alloc] init];
    return audio;
}

- (void)setupExternalAudioWithAgoraKit:(AgoraRtcEngineKit *)agoraKit sampleRate:(int)sampleRate channels:(int)channels audioCRMode:(AudioCRMode)audioCRMode IOType:(IOUnitType)ioType {
    
    if(_iFlySpeechRecognizer == nil) {
        //创建语音识别对象
        _iFlySpeechRecognizer = [IFlySpeechRecognizer sharedInstance];
    }
    
    //    //设置音频源为音频流（-1）
    //    [_iFlySpeechRecognizer setParameter:@"-1" forKey:@"audio_source"];
    //
    //
    //    //启动识别服务
    //    [self.iFlySpeechRecognizer startListening];
    //    [_iFlySpeechRecognizer setDelegate:self];
    
    threadLockCapture = [[NSObject alloc] init];
    threadLockPlay = [[NSObject alloc] init];
    
    // AudioController
    self.audioController = [AudioController audioController];
    self.audioController.delegate = self;
    [self.audioController setUpAudioSessionWithSampleRate:sampleRate channelCount:channels audioCRMode:audioCRMode IOType:ioType];
    
    // Agora Engine of C++
    agora::rtc::IRtcEngine* rtc_engine = (agora::rtc::IRtcEngine*)agoraKit.getNativeHandle;
    agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
    mediaEngine.queryInterface(rtc_engine, agora::AGORA_IID_MEDIA_ENGINE);
    
    if (mediaEngine) {
        s_audioFrameObserver = new AgoraAudioFrameObserver();
        s_audioFrameObserver -> sampleRate = sampleRate;
        s_audioFrameObserver -> sampleRate_play = channels;
        mediaEngine->registerAudioFrameObserver(s_audioFrameObserver);
    }
    
    if (audioCRMode == AudioCRModeExterCaptureExterRender || audioCRMode == AudioCRModeSDKCaptureExterRender) {
        s_audioFrameObserver -> isExternalRender = true;
    }
    if (audioCRMode == AudioCRModeExterCaptureExterRender || audioCRMode == AudioCRModeExterCaptureSDKRender) {
        s_audioFrameObserver -> isExternalCapture = true;
    }
    
    self.agoraKit = agoraKit;
    self.audioCRMode = audioCRMode;
}

- (void)startWork {
    [self.audioController startWork];
}

- (void)stopWork {
    [self.audioController stopWork];
    [self cancelRegiset];
}

- (void)cancelRegiset {
    agora::rtc::IRtcEngine* rtc_engine = (agora::rtc::IRtcEngine*)self.agoraKit.getNativeHandle;
    agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
    mediaEngine.queryInterface(rtc_engine, agora::AGORA_IID_MEDIA_ENGINE);
    mediaEngine->registerAudioFrameObserver(NULL);
}

- (void)audioController:(AudioController *)controller didCaptureData:(unsigned char *)data bytesLength:(int)bytesLength {
    
    if (self.audioCRMode != AudioCRModeExterCaptureSDKRender) {
        if (s_audioFrameObserver) {
            s_audioFrameObserver -> pushExternalData(data, bytesLength);
        }
    }
    else {
        [self.agoraKit pushExternalAudioFrameRawData:data samples:bytesLength / 2 timestamp:0];
    }
    
}

- (int)audioController:(AudioController *)controller didRenderData:(unsigned char *)data bytesLength:(int)bytesLength {
    int result = 0;
    
    if (s_audioFrameObserver) {
        result = s_audioFrameObserver -> readAudioData(data, bytesLength);
    }
    
    return result;
}

- (void)audioController:(AudioController *)controller error:(OSStatus)error info:(NSString *)info {
    if ([self.delegate respondsToSelector:@selector(externalAudio:errorInfo:)]) {
        NSString *errorInfo = [NSString stringWithFormat:@"<EA Error> error:%d, info:%@", error, info];
        [self.delegate externalAudio:self errorInfo:errorInfo];
    }
}

- (void)dealloc {
    NSLog(@"<ExternalAudio Log>ExAudio dealloc");
}

@end
