//
//  BiTTSViewController.m
//  SpeechDemo
//
//  Created by ByteDance on 2025/7/3.
//  Copyright © 2025 fangweiwei. All rights reserved.
//

#import "BiTTSViewController.h"
#include <CoreFoundation/CoreFoundation.h>
#include <objc/objc.h>

#import <AVFoundation/AVFoundation.h>

#import "AppDelegate.h"
#import "FileUtils.h"
#import "SettingsHelper.h"
#import "ViewController.h"
#import "SensitiveDefines.h"

static int TTS_MAX_RETRY_COUNT = 3;

@interface BiTTSViewController () <SpeechEngineDelegate, UITextViewDelegate>

@property (weak, nonatomic) IBOutlet UITextView *referTextView;
@property (weak, nonatomic) IBOutlet UITextView *resultTextView;
@property (weak, nonatomic) IBOutlet UITextField *statusTextView;
@property (weak, nonatomic) IBOutlet UIButton *engineSwitchButton;
@property (weak, nonatomic) IBOutlet UIButton *startEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *stopEngineButton;
@property (weak, nonatomic) IBOutlet UIButton *synthesisButton;
@property (weak, nonatomic) IBOutlet UIButton *pauseResumeButton;

// Device ID: 用于定位线上问题
@property (nonatomic, strong) NSString *deviceID;
// Debug Path: 用于存放一些 SDK 相关的文件，比如模型、日志等
@property (strong, nonatomic) NSString *debugPath;

// SpeechEngine
@property (strong, nonatomic) SpeechEngine *curEngine;

// Engine State
@property (assign, nonatomic) BOOL engineInited;
@property (assign, nonatomic) BOOL engineStarted;
@property (assign, nonatomic) BOOL engineErrorOccurred;
@property (assign, nonatomic) BOOL playerPaused;
@property (assign, nonatomic) BOOL refTextChanged;
// Settings
@property (strong, nonatomic) Settings *settings;

// 一些在线合成的配置
@property (strong, nonatomic) NSString *ttsAppId;
@property (strong, nonatomic) NSString *ttsVoiceOnline;
@property (strong, nonatomic) NSString *ttsVoiceTypeOnline;


// tts合成文本相关
@property (assign, nonatomic) int ttsSynthesisIndex;
@property (strong, nonatomic) NSMutableArray* ttsSynthesisText;
@property (strong, nonatomic) NSMutableDictionary* ttsSynthesisMap;

@property (assign, nonatomic) int ttsRetryCount;


@end

@implementation BiTTSViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.settings = [[SettingsHelper shareInstance]getSettings:VIEW_BITTS];
    
    self.engineSwitchButton.enabled = TRUE;
    [self decorateTextView:self.referTextView];
    [self decorateTextView:self.resultTextView];
    [self.referTextView setDelegate:self];
    self.referTextView.editable = TRUE;
    self.engineInited = FALSE;
    self.engineStarted = FALSE;
    self.engineErrorOccurred = FALSE;
    self.playerPaused = FALSE;
    
    // 初始化和tts有关的字段
    self.ttsSynthesisIndex = 0;
    self.ttsSynthesisText = [[NSMutableArray alloc] init];
    self.ttsSynthesisMap = [[NSMutableDictionary alloc]init];
    
    self.ttsRetryCount = TTS_MAX_RETRY_COUNT;
    
    [self.referTextView setText:@"愿中国青年都摆脱冷气，只是向上走，不必听自暴自弃者流的话。能做事的做事，能发声的发声。有一分热，发一分光。就令萤火一般，也可以在黑暗里发一点光，不必等候炬火。此后如竟没有炬火：我便是唯一的光。"];
    [self.statusTextView setText:@"Waiting for init."];
    [ViewController setAppDelegate:(AppDelegate *)[[UIApplication sharedApplication] delegate]];
    
    self.debugPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSLog(@"当前调试路径 %@", self.debugPath);
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(audioInterruptionHandler:)
                                                 name:AVAudioSessionInterruptionNotification
                                               object:nil];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
}

- (void)decorateTextView:(UITextView *)textView {
    textView.layer.cornerRadius = 5.0f;
    textView.layer.borderWidth = .25f;
    textView.layer.borderColor = [UIColor grayColor].CGColor;
}

#pragma mark - Notifications

-(void)appWillTerminate:(NSNotification*)note {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:AVAudioSessionInterruptionNotification
                                                  object:nil];
}

- (void)audioInterruptionHandler:(NSNotification*)notification {
    AVAudioSessionInterruptionType interruptionType = (AVAudioSessionInterruptionType)[[notification.userInfo objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
    AVAudioSessionInterruptionOptions intertuptionOptions = [[notification.userInfo objectForKey:AVAudioSessionInterruptionOptionKey] unsignedIntValue];
    NSLog(@"Receive audio interruption notification, type: %lu, options: %lu.", (unsigned long)interruptionType, (unsigned long)intertuptionOptions);
    if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
        NSLog(@"Audio session interruption began");
        @synchronized (self) {
            [self pausePlayback];
        }
    } else if (interruptionType == AVAudioSessionInterruptionTypeEnded) {
        @synchronized (self) {
            NSLog(@"Audio session interruption ended");
            if (intertuptionOptions == AVAudioSessionInterruptionOptionShouldResume) {
                AVAudioSession *session = [AVAudioSession sharedInstance];
                AVAudioSessionCategoryOptions cur_options = session.categoryOptions;
                // AudioQueueStart() will return AVAudioSessionErrorCodeCannotInterruptOthers if options didn't contains AVAudioSessionCategoryOptionMixWithOthers
                if (!(cur_options & AVAudioSessionCategoryOptionMixWithOthers)) {
                    AVAudioSessionCategoryOptions readyOptions = AVAudioSessionCategoryOptionMixWithOthers | cur_options;
                    [session setCategory:AVAudioSessionCategoryPlayback withOptions:readyOptions error:nil];
                }
                [self resumePlayback];
                
                cur_options = session.categoryOptions;
                // Remove AVAudioSessionCategoryOptionMixWithOthers, or the playback will not be interrupted any more
                if (cur_options & AVAudioSessionCategoryOptionMixWithOthers) {
                    [session setCategory:AVAudioSessionCategoryPlayback withOptions:((~AVAudioSessionCategoryOptionMixWithOthers) & cur_options) error:nil];
                }
            }
        }
    }
}

#pragma mark - Config & Init & Uninit Methods

-(void)configInitParams {
    //【必需配置】Engine Name
    [self.curEngine setStringParam:SE_BITTS_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING];
    
    
    //【可选配置】Debug & Log
    [self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING];
    [self.curEngine setStringParam:SE_LOG_LEVEL_DEBUG forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING];
    
    //【可选配置】User ID（用以辅助定位线上用户问题）
    [self.curEngine setStringParam:SDEF_UID forKey:SE_PARAMS_KEY_UID_STRING];
    [self.curEngine setStringParam:self.deviceID forKey:SE_PARAMS_KEY_DEVICE_ID_STRING];
    
    //【可选配置】是否将合成出的音频保存到设备上，为 true 时需要正确配置 PARAMS_KEY_TTS_AUDIO_PATH_STRING 才会生效
    [self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_DUMP]
                          forKey:SE_PARAMS_KEY_TTS_ENABLE_DUMP_BOOL];
    // TTS 音频文件保存目录，必须在合成之前创建好且 APP 具有访问权限，保存的音频文件名格式为 tts_{reqid}.wav, {reqid} 是本次合成的请求 id
    // PARAMS_KEY_TTS_ENABLE_DUMP_BOOL 配置为 true 的音频时为【必需配置】，否则为【可选配置】
    [self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_TTS_AUDIO_PATH_STRING];
    //【可选配置】自定义请求Header
    NSString* headerStr = [self.settings getString:SETTING_REQUEST_HEADERS];
    if ([headerStr isEqualToString:@""]) {
        headerStr = @"{}";
    }
    [self.curEngine setStringParam:headerStr forKey:SE_PARAMS_KEY_REQUEST_HEADERS_STRING];
    
    //【可选配置】是否使用 SDK 内置播放器播放合成出的音频，默认为 true
    [self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_PLAYER]
                          forKey:SE_PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL];
    //【可选配置】是否令 SDK 通过回调返回合成的音频数据，默认不返回。
    // 开启后，SDK 会流式返回音频，收到 SETtsAudioData 回调表示当次合成所有的音频已经全部返回
    [self.curEngine setBoolParam:[self.settings getBool:SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK] ? SETtsDataCallbackModeAll : SETtsDataCallbackModeNone forKey:SE_PARAMS_KEY_ENABLE_PLAYER_AUDIO_CALLBACK_BOOL];
    
    // ------------------------ 在线合成相关配置 -----------------------
    
    NSString* appid = [self.settings getString:SETTING_APPID];
    self.ttsAppId = appid.length > 0 ? appid : SDEF_APPID;
    //【必需配置】在线合成鉴权相关：Appid
    [self.curEngine setStringParam:self.ttsAppId forKey:SE_PARAMS_KEY_APP_ID_STRING];
    
    NSString* token = [self.settings getString:SETTING_TOKEN];
    NSString* ttsAppToken = token.length > 0 ? token : SDEF_TOKEN;
    //【必需配置】在线合成鉴权相关：Token
    [self.curEngine setStringParam:ttsAppToken forKey:SE_PARAMS_KEY_APP_TOKEN_STRING];
    
    
    NSString *address = [self.settings getString:SETTING_ADDRESS];
    NSString *ttsAddress = address.length > 0 ? address : SDEF_DEFAULT_ADDRESS;
    //【必需配置】语音合成服务域名
    [self.curEngine setStringParam:ttsAddress forKey:SE_PARAMS_KEY_TTS_ADDRESS_STRING];
    
    NSString *uri = [self.settings getString:SETTING_URI];
    NSString *ttsUri = uri.length > 0 ? uri : SDEF_BITTS_DEFAULT_URI;
    //【必需配置】语音合成服务Uri
    [self.curEngine setStringParam:ttsUri forKey:SE_PARAMS_KEY_TTS_URI_STRING];
    
    NSString *resourceId = [self.settings getString:SETTING_RESOURCE_ID];
    //【必需配置】语音合成服务资源id
    [self.curEngine setStringParam:resourceId forKey: SE_PARAMS_KEY_RESOURCE_ID_STRING];
    //【可选配置】TTS连接超时时间
    [self.curEngine setIntParam:10000 forKey:SE_PARAMS_KEY_TTS_CONN_TIMEOUT_INT];

}

-(void)configStartTtsParams {
    // 准备待合成的文本
    if(![self prepareBiTTSText]) {
        char fake_error_info[] = "{err_code:3006, err_msg:\"Invalid input text.\"}";
        [self speechEngineError:[NSData dataWithBytes:fake_error_info length:sizeof(fake_error_info)]];
        return;
    }
}

- (NSString*)getSynthesisText {
    if(self.refTextChanged){ //如果文本有变更重新追加进去
        self.refTextChanged = FALSE;
        
        __block NSString* refText = nil;
        if (![NSThread isMainThread]) {
            dispatch_sync(dispatch_get_main_queue(), ^{
                refText = self.referTextView.text;
            });
        } else {
            refText = self.referTextView.text; // 已经在主线程，直接赋值
        }
        
        if (refText.length > 0) {
            NSError *error = nil;
            NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^;!?。！？；…]+[;!?。！？；…]?" options:0 error:&error];
            
            if (!error) {
                NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:refText options:0 range:NSMakeRange(0, refText.length)];
                
                for (NSTextCheckingResult *match in matches) {
                    NSString *sentence = [refText substringWithRange:match.range];
                    [self addSentence:sentence];
                }
            }
        }
    }
    
    if(self.ttsSynthesisIndex == [self.ttsSynthesisText count]){
        NSLog(@"no more tts conttent to synthesis");
        [self updateTtsResultText:@"No More Text to Synthesis"];
        return nil;
    }
    
    NSString* text = self.ttsSynthesisText[self.ttsSynthesisIndex];
    NSLog(@"Synthesis: %d, text: %@", self.ttsSynthesisIndex, text);
    self.ttsSynthesisIndex = (self.ttsSynthesisIndex + 1);
    return text;
}

- (void)initEngine {
    NSLog(@"获取设备ID，调试使用");
    AppDelegate *appDelegate = [ViewController getAppDelegate];
    if (appDelegate == nil) {
        appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    }
    [ViewController setAppDelegate:appDelegate];
    self.deviceID = appDelegate.deviceID;
    NSLog(@"获取设备ID成功: %@", self.deviceID);
    
    
    NSLog(@"创建引擎");
    if (self.curEngine == nil) {
        self.curEngine = [[SpeechEngine alloc] init];
        if (![self.curEngine createEngineWithDelegate:self]) {
            NSLog(@"引擎创建失败.");
            return;
        }
    }
    NSLog(@"SDK 版本号: %@", [self.curEngine getVersion]);
    [self initEngineInternal];
}



- (void)initEngineInternal {
    NSLog(@"配置初始化参数");
    [self configInitParams];
    
    NSLog(@"引擎初始化");
    SEEngineErrorCode ret = [self.curEngine initEngine];
    self.engineInited = (ret == SENoError);
    if (self.engineInited) {
        NSLog(@"初始化成功");
        [self speechEngineInitSucceeded];
    } else {
        NSLog(@"初始化失败，返回值: %d", ret);
        [self speechEngineInitFailed:ret];
    }
}

- (void)uninitEngine {
    if (self.curEngine != nil) {
        NSLog(@"引擎析构");
        [self.curEngine destroyEngine];
        self.curEngine = nil;
        NSLog(@"引擎析构完成");
    }
}

#pragma mark - UI Actions

- (IBAction)switchEngine:(id)sender {
    if (self.engineStarted) {
        [self.statusTextView setText:@"Engine is busy, stop it first!"];
        return;
    }
    [self clearResult:nil];
    self.startEngineButton.enabled = FALSE;
    self.synthesisButton.enabled = FALSE;
    self.pauseResumeButton.enabled = FALSE;
    if (self.engineInited) {
        self.referTextView.editable = TRUE;
        [self uninitEngine];
        self.engineInited = FALSE;
        [self.statusTextView setText:@"Waiting for init."];
        self.engineSwitchButton.enabled = TRUE;
        [self.engineSwitchButton setTitle:@"Init Engine" forState:UIControlStateNormal];
        self.stopEngineButton.enabled = FALSE;
    } else {
        self.referTextView.editable = TRUE;
        [self initEngine];
    }
}

- (IBAction)Synthesis:(id)sender {
    [self triggerSynthesis];
}

- (IBAction)startEngineBtnClicked:(id)sender {
    NSLog(@"Start engine, current status: %d", self.engineStarted);
    if (!self.engineStarted) {
        [self clearResult:nil];
        self.engineErrorOccurred = FALSE;
        
        // Directive：启动引擎前调用SYNC_STOP指令，保证前一次请求结束。
        NSLog(@"关闭引擎（同步）");
        NSLog(@"Directive: SEDirectiveSyncStopEngine");
        SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveSyncStopEngine];
        if (ret != SENoError) {
            NSLog(@"Send directive syncstop failed: %d", ret);
        } else {
            [self configStartTtsParams];
            
            NSLog(@"启动引擎.");
            NSLog(@"Directive: SEDirectiveStartEngine");
            NSString* startPayload = @"{\"req_params\":{\"speaker\":\"zh_female_roumeinvyou_emo_v2_mars_bigtts\",\"audio_params\":{\"emotion\":\"excited\",\"loudness_rate\":50}}}";
            SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveStartEngine data:startPayload];
            if (SENoError != ret) {
                NSString* message = [NSString stringWithFormat:@"发送启动引擎指令失败: %d", ret];
                [self sendStartEngineDirectiveFailed:message];
            }
        }
    }
}

- (IBAction)stopEngineBtnClicked:(id)sender {
    NSLog(@"关闭引擎");
    NSLog(@"Directive: SEDirectiveStopEngine");
    [self.curEngine sendDirective:SEDirectiveStopEngine];
}

- (void)pausePlayback {
    NSLog(@"暂停播放");
    NSLog(@"Directive: SEDirectivePausePlayer");
    SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectivePausePlayer];
    if (ret == SENoError) {
        self.playerPaused = TRUE;
        [self.pauseResumeButton setTitle:@"Resume" forState:UIControlStateNormal];
    }
    NSLog(@"Pause playback status: %d", ret);
}

- (void)resumePlayback {
    NSLog(@"继续播放");
    NSLog(@"Directive: SEDirectiveResumePlayer");
    SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveResumePlayer];
    if (ret == SENoError) {
        self.playerPaused = FALSE;
        [self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
    }
    NSLog(@"Resume playback status: %d", ret);
}

- (IBAction)controlPlayingStatus:(id)sender {
    NSLog(@"Pause or resume player, current player status: %hhd", self.playerPaused);
    if (self.playerPaused) {
        [self resumePlayback];
    } else {
        [self pausePlayback];
    }
}

- (IBAction)clearResult:(id)sender {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.resultTextView setText:@""];
    });
}

#pragma mark - Message Callback



- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data {
    NSLog(@"Message Type: %d.", type);
    switch (type) {
        case SEEngineStart:
            NSLog(@"Callback: 引擎启动成功: data: %@", data);
            [self speechEngineStarted];
            break;
        case SEEngineStop:
            NSLog(@"Callback: 引擎关闭: data: %@", data);
            [self speechEngineStopped];
            break;
        case SEEngineError:
            NSLog(@"Callback: 错误信息: %@", data);
            [self speechEngineError:data];
            break;
        case SEEventConnectionStarted:
            NSLog(@"Callback: SEEventConnectionStarted");
            break;
        case SEEventConnectionFailed:
            NSLog(@"Callback: SEEventConnectionFailed");
            break;
        case SEEventConnectionFinished:
            NSLog(@"Callback: SEEventConnectionFinished");
            break;
        case SEEventSessionStarted:
            NSLog(@"Callback: SEEventSessionStarted");
            break;
        case SEEventSessionCanceled:
            NSLog(@"Callback: SEEventSessionCanceled");
            break;
        case SEEventSessionFinished:
            NSLog(@"Callback: SEEventSessionFinished");
            break;
        case SEEventSessionFailed:
            NSLog(@"Callback: SEEventSessionFailed");
            break;
        case SEEventTTSSentenceStart:
            [self speechStartSynthesis:data];
            NSLog(@"Callback: 合成开始 SentenceStart: %@", data);
            break;
        case SEEventTTSSentenceEnd:
            [self speechFinishSynthesis:data];
            NSLog(@"Callback: 合成结束 SentenceEnd: %@", data);
        case SEEventTTSResponse:
            NSLog(@"Callback: 收到合成音频 TTSResponse: %@", data);
        case SEEventTTSEnded:
            NSLog(@"Callback: TTSEnd: %@", data);
        case SEPlayerAudioData:
//            NSLog(@"Callback: 播放的pcm音频: %@", data);
            break;
        case SEPlayerStartPlayAudio:
            NSLog(@"Callback: 开始播放TTS音频");
            [self speechStartPlaying:nil];
            break;
        case SEPlayerFinishPlayAudio:
            NSLog(@"Callback: 结束播放TTS音频");
            [self speechFinishPlaying:nil];
            break;
        default:
            break;
    }
}

- (void)speechEngineInitSucceeded {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.engineSwitchButton.enabled = TRUE;
        [self.engineSwitchButton setTitle:@"UninitEngine" forState:UIControlStateNormal];
        [self.statusTextView setText:@"Ready"];
        [self.resultTextView setText:[NSString stringWithFormat:@"DeviceID: %@.", self.deviceID]];
        self.referTextView.editable = TRUE;
        self.startEngineButton.enabled = TRUE;
    });
}

- (void)speechEngineInitFailed:(int)initStatus {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self uninitEngine];
        [self.statusTextView setText:[[NSString alloc] initWithFormat:@"Failed to init engine, %d!", initStatus]];
        self.engineSwitchButton.enabled = TRUE;
    });
}

- (void)sendSynthesisDirectiveFailed:(NSString*)tipText {
    NSLog(@"%@", tipText);
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.resultTextView setText:tipText];
        [self.curEngine sendDirective:SEDirectiveStopEngine];
    });
}

- (void)sendStartEngineDirectiveFailed:(NSString*)tipText {
    NSLog(@"%@", tipText);
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.resultTextView setText:tipText];
        self.engineStarted = FALSE;
    });
}

- (void)speechEngineStarted {
    self.ttsRetryCount = TTS_MAX_RETRY_COUNT;
    dispatch_async(dispatch_get_main_queue(), ^{
        self.referTextView.editable = TRUE;
        self.engineStarted = true;
        [self.statusTextView setText:@"Engine Started!"];
        self.startEngineButton.enabled = FALSE;
        self.synthesisButton.enabled = TRUE;
        self.stopEngineButton.enabled = TRUE;
    });
}

- (void)speechEngineStopped {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.referTextView.editable = TRUE;
        self.engineStarted = FALSE;
        [self.statusTextView setText:@"Engine Stopped!"];
        self.startEngineButton.enabled = TRUE;
        self.synthesisButton.enabled = FALSE;
        self.stopEngineButton.enabled = FALSE;
        [self.pauseResumeButton setTitle:@"Pause" forState:UIControlStateNormal];
        self.pauseResumeButton.enabled = FALSE;
        self.playerPaused = FALSE;
    });
}

- (void)speechEngineError:(NSData *)data {
    dispatch_async(dispatch_get_main_queue(), ^{
        
        BOOL needStop = NO;
        id json_obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        if ([json_obj isKindOfClass:[NSDictionary class]]) {
            NSDictionary *error_info = json_obj;
            NSInteger code = [[error_info objectForKey:@"err_code"] intValue];
            switch (code) {
                case SETTSLimitQps:
                case SETTSLimitCount:
                case SETTSServerBusy:
                case SETTSLongText:
                case SETTSInvalidText:
                case SETTSSynthesisTimeout:
                case SETTSSynthesisError:
                case SETTSSynthesisWaitingTimeout:
                case SETTSErrorUnknown:
                    NSLog(@"When meeting this kind of error, continue to synthesize.");
                    [self triggerSynthesis];
                    break;
                case SEConnectTimeout:
                case SEReceiveTimeout:
                case SENetLibError:
                    // 遇到网络错误时建议重试，重试次数不超过 3 次
                    needStop = ![self retrySynthesis];
                    if (needStop) {
                        self.engineErrorOccurred = TRUE;
                    }
                    break;
                default:
                    needStop = YES;
                    self.engineErrorOccurred = TRUE;
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self.resultTextView
                         setText:[[NSString alloc]
                                  initWithData:data
                                  encoding:NSUTF8StringEncoding]];
                    });
                    break;
            }
        } else {
            needStop = YES;
        }
        if (needStop) {
            [self.curEngine sendDirective:SEDirectiveStopEngine];
        }
    });
}

- (void)scrollTextViewToBottom {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.resultTextView.text.length > 0) {
            NSRange bottomRange = NSMakeRange(self.resultTextView.text.length - 1, 1);
            [self.resultTextView scrollRangeToVisible:bottomRange];
            [self.resultTextView layoutIfNeeded];
        }
    });
}

-(void)updateTtsResultText:(NSString*) playingId {
    if (self.engineErrorOccurred) {
        NSLog(@"When a fatal error occurs, prevent the playback text from being displayed.");
        return;
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSString *currentText = self.resultTextView.text ?: @"";
        NSString *newText = [NSString stringWithFormat:@"%@\n%@", currentText, playingId];
        [self.resultTextView setText:newText];
    });
    
    [self scrollTextViewToBottom];
}

- (void)speechStartSynthesis:(NSData *)data {
    if (self.ttsSynthesisIndex < [self.ttsSynthesisText count]) {
        NSString* req_id = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        [self.ttsSynthesisMap setValue:[NSNumber numberWithInt:self.ttsSynthesisIndex] forKey:req_id];
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.synthesisButton.enabled = FALSE;
    });
}

- (void)speechFinishSynthesis:(NSData *)data {
    if (self.ttsRetryCount < TTS_MAX_RETRY_COUNT) {
        self.ttsRetryCount = TTS_MAX_RETRY_COUNT;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        self.synthesisButton.enabled = TRUE;
    });
}

- (void)speechStartPlaying:(NSData *)data {
    NSLog(@"TTS start playing");
    dispatch_async(dispatch_get_main_queue(), ^{
        self.pauseResumeButton.enabled = TRUE;
    });
}


- (void)speechFinishPlaying :(NSData *)data {
    NSLog(@"TTS finish playing");
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateTtsResultText:@"playing finished"];
    });
    [self triggerSynthesis];
 
}

- (void)speechTtsAudioData:(NSData *)data {
}

- (BOOL)retrySynthesis {
    BOOL ret = FALSE;
    if (self.engineStarted && self.ttsRetryCount > 0) {
        NSLog(@"Retry synthesis for text: %@", self.ttsSynthesisText[self.ttsSynthesisIndex]);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            [self triggerSynthesis];
        });
        self.ttsRetryCount -= 1;
        ret = TRUE;
    }
    return ret;
}

-(void)triggerSynthesis {
    NSString *text = [self getSynthesisText];
    if(text == nil){
        return;
    }
    
    SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveEventStartSession data:@""];
    if (ret != SENoError) {
        NSLog(@"Start Session faile: %d", ret);
    }
    
    NSString* taskRequestJson = [NSString stringWithFormat: @"{\"req_params\":{\"text\":\"%@\"}}", text];
    // SEDirectiveTaskRequest 是可以连续发送的，服务端会缓存每一句文本，然后按顺序通过sentence_start 、 task respnse...  sentence_end返回当前处理句的结果
    ret = [self.curEngine sendDirective:SEDirectiveEventTaskRequest data:taskRequestJson];
    [self updateTtsResultText:text];
    NSLog(@"触发合成");
    NSLog(@"Directive: SEDirectiveTaskRequest");
    if (ret != SENoError) {
        NSLog(@"Synthesis faile: %d", ret);
    }
    
    ret = [self.curEngine sendDirective:SEDirectiveEventFinishSession data:@""];
    if (ret != SENoError) {
        NSLog(@"Finish Session faile: %d", ret);
    }
}

-(void)addSentence:(NSString*) text {
    NSCharacterSet* blankChar = [NSCharacterSet characterSetWithCharactersInString:@" "];
    NSString* tmp = [text stringByTrimmingCharactersInSet:blankChar];
    if (tmp.length > 0) {
        [self.ttsSynthesisText addObject:tmp];
    }
}

-(void)resetTtsContext {
    self.ttsSynthesisIndex = 0;
    [self.ttsSynthesisText removeAllObjects];
    [self.ttsSynthesisMap removeAllObjects];
}

-(BOOL)prepareBiTTSText {
    [self resetTtsContext];
    __block NSString* text = nil;
    if (![NSThread isMainThread]) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            text = self.referTextView.text;
        });
    } else {
        text = self.referTextView.text; // 已经在主线程，直接赋值
    }
    if (text.length <= 0) {
        text = @"愿中国青年都摆脱冷气，只是向上走，不必听自暴自弃者流的话。能做事的做事，能发声的发声。有一分热，发一分光。就令萤火一般，也可以在黑暗里发一点光，不必等候炬火。此后如竟没有炬火：我便是唯一的光。";
    }
    if (self.ttsSynthesisText == nil || [self.ttsSynthesisText count] <= 0) {
        // 使用下面几个标点符号来分句，会让通过 MESSAGE_TYPE_TTS_PLAYBACK_PROGRESS 返回的播放进度更加准确
        NSArray* temp = [text componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@";!?。！？；…"]];
        if (text.length > 0) {
            NSError *error = nil;
            NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^;!?。！？；…]+[;!?。！？；…]?" options:0 error:&error];
            
            if (!error) {
                NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, text.length)];
                
                for (NSTextCheckingResult *match in matches) {
                    NSString *sentence = [text substringWithRange:match.range];
                    [self addSentence:sentence];
                }
            }
        }
    }
    NSLog(@"Synthesis text item num: %ld.", [self.ttsSynthesisText count]);
    return [self.ttsSynthesisText count] > 0;
}

#pragma mark - Helper

- (long)timeDelayFrom:(long)pastTimestamp {
    return [[NSDate date] timeIntervalSince1970] * 1000 - pastTimestamp;
}


#pragma mark - UITextViewDelegate

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    if([text isEqualToString:@"\n"]) {
        [textView resignFirstResponder];
        return NO;
    }
    return YES;
}
- (void)textViewDidChange:(UITextView *)textView {
    if(textView == _referTextView){
        self.refTextChanged = TRUE;
    }
}
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
    id nextPage = [segue destinationViewController];
    [nextPage setValue:VIEW_BITTS forKey:@"viewId"];
}

@end
