//
//  Client.m
//  Sample_AVAPIs
//
//  Created by tutk on 3/6/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

#import "Client.h"
#import "IOTCAPIs.h"
#import "AVAPIs.h"
#import "P2PCam/AVIOCTRLDEFs.h"
#import "P2PCam/AVFRAMEINFO.h"
#import "Client_LAN_WiFi_Config.h"
#import <sys/time.h>
#import <pthread.h>

#define ENABLE_DTLS 1 // 0:disable DTLS; 1:enable DTLS
#define ENABLE_RESEND 1 // 0:disable RESEND; 1:enable RESEND
#define AUDIO_BUF_SIZE	1024
#define VIDEO_BUF_SIZE	2000000
#define AV_LIVE_STREAM_CHANNEL 0

#define PLAYBACK_PAUSE 0
#define PLAYBACK_START 1
#define PLAYBACK_STOP  2
#define AV_PLAYBACK_CHANNEL 1

enum BindType {
    ALREADY_BOUND,
    SERVER_BIND,
    LOCAL_BIND
};


/***************** Fill in License Key & Other Required Data Here *****************/

static char gLicenseKey[] = "Input your license key here!";
static char gCredential[512] = {0}; // Set Credential when use ALREADY_BOUND
static char gUDID[MAX_PUBLIC_UDID_LENGTH+1] = "Input your 40 bytes UDID here!"; // Please Set UDID in ViewControll.m
static char gAmToken[64] = "Input token here!";
static char gRealm[64] = "Input realm here!";
static char gPinCode[MAX_PIN_CODE_LENGTH+1] = "Input PIN code here!";
static int gBindType = SERVER_BIND; // ALREADY_BOUND, SERVER_BIND or LOCAL_BIND

// 0:disable 1:enable
static int gEnableSpeaker = 0;
static int gSpeakerTime = 10;
/**********************************************************************************/



@implementation Client

typedef struct CONN_INFO
{
    char udid[MAX_PUBLIC_UDID_LENGTH+1];
    char pin_code[MAX_PIN_CODE_LENGTH+1];
    unsigned int connect_abort;
    unsigned int bind_abort;
    unsigned int send_command_abort;
    bool enable_speaker;
    unsigned int bind_type;
    char rent_am_token[MAX_TOKEN_LENGTH+1];
    char realm[MAX_REALM_LENGTH+1];
    char credential[512];
    int sid;
    int av_index;
    int speaker_time_sec;
}CONN_INFO;

unsigned int _getTickCount() {
    
	struct timeval tv;
    
	if (gettimeofday(&tv, NULL) != 0)
        return 0;
    
	return (tv.tv_sec * 1000 + tv.tv_usec / 1000);
}

static void InitConnectionInfo(CONN_INFO *conn_info)
{
    memset(conn_info, 0, sizeof(CONN_INFO));
    conn_info->sid = -1;
    conn_info->av_index = -1;

    strncpy(conn_info->udid, gUDID, sizeof(conn_info->udid));
    strncpy(conn_info->credential, gCredential, sizeof(conn_info->credential));
    strncpy(conn_info->rent_am_token, gAmToken, sizeof(conn_info->rent_am_token));
    strncpy(conn_info->realm, gRealm, sizeof(conn_info->realm));
    strncpy(conn_info->pin_code, gPinCode, sizeof(conn_info->pin_code));
    conn_info->bind_type = gBindType;
    conn_info->enable_speaker = gEnableSpeaker;
    conn_info->speaker_time_sec = gSpeakerTime;
}


//########################################################
//# Client receive video/audio from device
//########################################################
static void *ThreadReceiveAVData(void *arg)
{
    printf("[%s] Starting....\n", __func__);
    CONN_INFO *conn_info = (CONN_INFO *)arg;
    int av_index = conn_info->av_index;
    int video_buf_size = VIDEO_BUF_SIZE;
    char *buf = (char*)malloc(video_buf_size);
    int ret;

    FRAMEINFO_t frame_info;
    char *video_format[] = {"MPEG4", "H263", "H264", "MJPEG", "UNKNOWN"};
    char *audio_format[] = {"ADPCM", "PCM", "SPEEX", "MP3", "G726", "UNKNOWN"};
    int codec_idx = 0;
    unsigned int frm_no;
    struct timeval tv, tv2;

    int v_fps = 0, a_fps = 0, round = 0, v_lost_cnt = 0, a_lost_cnt = 0;
    int out_buf_size = 0;
    int out_frm_size = 0;
    int out_frm_info_size = 0;
    int v_bps = 0, a_bps = 0;
    int video_not_ready = 1, audio_not_ready = 1;
    gettimeofday(&tv, NULL);

    while(1)
    {
        ret = avRecvFrameData2(av_index, buf, video_buf_size, &out_buf_size, &out_frm_size, (char *)&frame_info, sizeof(frame_info), &out_frm_info_size, &frm_no);
        // show Frame Info at 1st frame
        if(ret > 0 && frm_no == 0) {
            if(frame_info.codec_id == MEDIA_CODEC_VIDEO_MPEG4)
                codec_idx = 0;
            else if(frame_info.codec_id == MEDIA_CODEC_VIDEO_H263)
                codec_idx = 1;
            else if(frame_info.codec_id == MEDIA_CODEC_VIDEO_H264)
                codec_idx = 2;
            else if(frame_info.codec_id == MEDIA_CODEC_VIDEO_MJPEG)
                codec_idx = 3;
            else
                codec_idx = 4;

            printf("--- Recv Video Format: %s ---\n", video_format[codec_idx]);
        }

        if(ret == AV_ER_DATA_NOREADY) {
            //printf("Recv video, AV_ER_DATA_NOREADY[%d]\n", avIndex);
            //usleep(5 * 1000);
            video_not_ready = 1;
        } else if(ret == AV_ER_LOSED_THIS_FRAME) {
            printf("Recv video, Lost video frame NO[%d]\n", frm_no);
            v_lost_cnt++;
        } else if(ret == AV_ER_INCOMPLETE_FRAME) {
            #if 1
            if(out_frm_info_size > 0)
            printf("Incomplete video frame NO[%d] ReadSize[%d] FrmSize[%d] FrmInfoSize[%u] Codec[%d] Flag[%d]\n", frm_no, out_buf_size, out_frm_size, out_frm_info_size, frame_info.codec_id, frame_info.flags);
            else
            printf("Incomplete video frame NO[%d] ReadSize[%d] FrmSize[%d] FrmInfoSize[%u]\n", frm_no, out_buf_size, out_frm_size, out_frm_info_size);
            #endif
            v_lost_cnt++;
        } else if(ret == AV_ER_SESSION_CLOSE_BY_REMOTE) {
            printf("Recv video, AV_ER_SESSION_CLOSE_BY_REMOTE\n");
            break;
        } else if(ret == AV_ER_REMOTE_TIMEOUT_DISCONNECT) {
            printf("Recv video, AV_ER_REMOTE_TIMEOUT_DISCONNECT\n");
            break;
        } else if(ret == IOTC_ER_INVALID_SID) {
            printf("Recv video, Session cant be used anymore\n");
            break;
        } else if(ret == AV_ER_BUFPARA_MAXSIZE_INSUFF) {
            printf("Recv video, video buffer too small\n");
            video_buf_size = video_buf_size+(5*1024);
            buf = (char*)realloc(buf, video_buf_size);
            printf("increase buffer size to %d\n", video_buf_size);
            continue;
        } else {
            v_bps += out_buf_size;
            v_fps++;
            video_not_ready = 0;
            if(frame_info.tags == 1){
                printf("Receive the last video frame\n");
                break;
            }
        }

        ret = avRecvAudioData(av_index, buf, AUDIO_BUF_SIZE, (char *)&frame_info, sizeof(frame_info), &frm_no);
        // show Frame Info at 1st frame
        if(ret > 0 && frm_no == 0)
        {
            int idx = 0;
            if(frame_info.codec_id == MEDIA_CODEC_AUDIO_ADPCM)
                idx = 0;
            else if(frame_info.codec_id == MEDIA_CODEC_AUDIO_PCM)
                idx = 1;
            else if(frame_info.codec_id == MEDIA_CODEC_AUDIO_SPEEX)
                idx = 2;
            else if(frame_info.codec_id == MEDIA_CODEC_AUDIO_MP3)
                idx = 3;
            else if(frame_info.codec_id == MEDIA_CODEC_AUDIO_G726)
                idx = 4;
            else
                idx = 5;
            printf("--- Recv Audio Format: %s ---\n", audio_format[idx]);
        }

        if(ret == AV_ER_SESSION_CLOSE_BY_REMOTE) {
            printf("Recv audio, AV_ER_SESSION_CLOSE_BY_REMOTE\n");
            break;
        } else if(ret == AV_ER_REMOTE_TIMEOUT_DISCONNECT) {
            printf("Recv audio, AV_ER_REMOTE_TIMEOUT_DISCONNECT\n");
            break;
        } else if(ret == IOTC_ER_INVALID_SID) {
            printf("Recv audio, Session cant be used anymore\n");
            break;
        } else if(ret == AV_ER_LOSED_THIS_FRAME) {
            printf("Recv audio, AV_ER_LOSED_THIS_FRAME[%d]\n", frm_no);
            a_lost_cnt++;
        } else if (ret == AV_ER_DATA_NOREADY) {
            //printf("Recv audio, AV_ER_DATA_NOREADY[%d]\n", avIndex);
            //usleep(5 * 1000);
            audio_not_ready = 1;
        } else if(ret < 0) {
            printf("Recv audio, error[%d]!!!\n", ret);
            continue;
        } else {
            a_bps += ret;
            a_fps++;
            audio_not_ready = 0;
            if(frame_info.tags == 1){
                printf("Receive the last audio frame\n");
                break;
            }
        }

        gettimeofday(&tv2, NULL);
        long sec = tv2.tv_sec-tv.tv_sec, usec = tv2.tv_usec-tv.tv_usec;
        if(usec < 0) {
            sec--;
            usec += 1000000;
        }
        usec += (sec*1000000);

        if(usec > 1000000) {
            round++;
            printf("[AVAPIs_Client][avIndex:%d] Video FPS=%d, LostFrmCnt:%d, bps:%d Kbps, Audio FPS=%d, LostFrmCnt:%d, bps:%d Kbps\n",
                   av_index, v_fps, v_lost_cnt, (v_bps/1024)*8, a_fps, a_lost_cnt, (a_bps/1024)*8);
            gettimeofday(&tv, NULL);
            v_bps = 0;
            a_bps = 0;
            v_fps = 0;
            a_fps = 0;
        }
        if(1 == video_not_ready && 1 == audio_not_ready)
            usleep(5*1000);
    }

    if(buf) {
        free(buf);
    }
    printf("[%s] thread exit, avIndex[%d]\n", __func__, av_index);

    return 0;
}

//########################################################
//# Client send audio to device
//########################################################
static int StartSpeaker(int av_index);
static int StopSpeaker(int av_index);
static void *ThreadSpeaker(void *arg)
{
    CONN_INFO *conn_info = (CONN_INFO *)arg;
    char buf[AUDIO_BUF_SIZE];
    int frame_rate = 25;
    int sleep_us = 1000000/frame_rate;
    int ret = 0, av_index = 0;
    unsigned int start_time_sec = 0;
    FILE *fp = fopen("audio/8k_16bit_mono.raw", "rb");
    if(fp == NULL)
    {
        printf("[%s] fopen audio file error!!!\n", __func__);
        pthread_exit(0);
    }
    
    av_index = conn_info->av_index;
    
    ret = StartSpeaker(av_index);
    if(ret != 0){
        printf("[%s] StartSpeaker error[%d]\n", __func__, ret);
        fclose(fp);
        pthread_exit(0);
    }

    FRAMEINFO_t frame_info;
    memset(&frame_info, 0, sizeof(frame_info));

    printf("[%s] Starting avIndex[%d] \n", __func__, av_index);

    frame_info.codec_id = MEDIA_CODEC_AUDIO_PCM;
    frame_info.flags = (AUDIO_SAMPLE_8K << 2) | (AUDIO_DATABITS_16 << 1) | AUDIO_CHANNEL_MONO;
    start_time_sec = _getTickCount()/1000;

    while(1) {
        size_t size = fread(buf, 1, 640, fp);
        if(size == 0) {
            printf("rewind audio\n");
            rewind(fp);
            continue;
        }

        ret = avSendAudioData(av_index, buf, 640, &frame_info, sizeof(frame_info));
        if(ret == AV_ER_SESSION_CLOSE_BY_REMOTE) {
            printf("[%s] AV_ER_SESSION_CLOSE_BY_REMOTE\n", __func__);
            break;
        } else if(ret == AV_ER_REMOTE_TIMEOUT_DISCONNECT) {
            printf("[%s] AV_ER_REMOTE_TIMEOUT_DISCONNECT\n", __func__);
            break;
        } else if(ret == IOTC_ER_INVALID_SID) {
            printf("[%s] Session cant be used anymore\n", __func__);
            break;
        } else if(ret < 0) {
            printf("[%s] avSendAudioData error[%d]\n", __func__, ret);
            break;
        } else {
            //printf("[%s] avSendAudioData[%d]\n", __func__, ret);
        }
        usleep(sleep_us);

        //*** Speaker thread stop condition if necessary ***
        unsigned int diff_time_sec = (_getTickCount()/1000)-start_time_sec;
        if(diff_time_sec > conn_info->speaker_time_sec){
            printf("[%s] elapsed[%u secs]\n", __func__, diff_time_sec);
            break;
        }
    }

    fclose(fp);
    StopSpeaker(av_index);
    printf("[%s] exit, avIndex[%d]\n", __func__, av_index);

    pthread_exit(0);
}


//########################################################
//# Send live stream IOCtrl CMD to device
//########################################################
static int StartIpcamStream(int av_index)
{
    int ret;
    NebulaJsonObject *response = NULL;
    const char *request = "{\"func\":\"startVideo\",\"args\":{\"value\":true}}";
    printf("send json request: %s\n", request);

    ret = avSendJSONCtrlRequest(av_index, request, &response, 10);

    if(ret == AV_ER_NoERROR) {
        if(response)
            printf("get json response: %s\n", Nebula_Json_Obj_To_String(response));
    } else {
        printf("avSendJSONCtrlRequest error (%d)\n", ret);
        return -1;
    }
    // must free response here
    avFreeJSONCtrlResponse(response);


    request = "{\"func\":\"startAudio\",\"args\":{\"value\":true}}";
    printf("send json request: %s\n", request);

    ret = avSendJSONCtrlRequest(av_index, request, &response, 10);

    if(ret == AV_ER_NoERROR) {
        if(response)
            printf("get json response: %s\n", Nebula_Json_Obj_To_String(response));
    } else {
        printf("avSendJSONCtrlRequest error (%d)\n", ret);
        return -1;
    }
    // must free response here
    avFreeJSONCtrlResponse(response);

    return 0;
}


//########################################################
//# Send speaker IOCtrl CMD to device
//########################################################
#define ENABLE_SPEAKER true
#define DISABLE_SPEAKER false
static int SpeakerControl(int av_index, bool enable)
{
    int ret;
    NebulaJsonObject *response = NULL;
    const char func[] = "startSpeaker";
    char request[256] = {0};
    snprintf(request, sizeof(request), "{\"func\":\"%s\",\"args\":{\"value\":%s}}", func, enable?"true":"false");
    printf("send json request: %s\n", request);

    ret = avSendJSONCtrlRequest(av_index, request, &response, 10);
    if(ret == AV_ER_NoERROR) {
        printf("get json response: %s\n\n", Nebula_Json_Obj_To_String(response));
    } else {
        printf("fail to get json response (%d)\n", ret);
        return -1;
    }

    // must free response
    avFreeJSONCtrlResponse(response);

    return 0;
}

static int StartSpeaker(int av_index)
{
    return SpeakerControl(av_index, ENABLE_SPEAKER);
}

static int StopSpeaker(int av_index)
{
    return SpeakerControl(av_index, DISABLE_SPEAKER);
}


//########################################################
//# Thread - Create AV Connection and Create Video Receive thread
//########################################################
static int StartAvClient(int sid, unsigned char chid, unsigned int timeout_sec) {
    AVClientStartInConfig av_client_in_config;
    AVClientStartOutConfig av_client_out_config;
    memset(&av_client_in_config, 0, sizeof(av_client_in_config));
    memset(&av_client_out_config, 0, sizeof(av_client_out_config));

    av_client_in_config.cb = sizeof(av_client_in_config);
    av_client_in_config.iotc_session_id = sid;
    av_client_in_config.iotc_channel_id = chid;
    av_client_in_config.timeout_sec = timeout_sec;
    av_client_in_config.resend = ENABLE_RESEND;

    if(ENABLE_DTLS) {
        av_client_in_config.security_mode = AV_SECURITY_DTLS;
    } else {
        av_client_in_config.security_mode = AV_SECURITY_SIMPLE;
    }

    av_client_in_config.account_or_identity = "";
    av_client_in_config.password_or_token = "";
    av_client_in_config.auth_type = AV_AUTH_NEBULA;
    av_client_out_config.cb = sizeof(av_client_out_config);

    int av_index = avClientStartEx(&av_client_in_config, &av_client_out_config);

    printf("avClientStartEx: sid=[%d] chid=[%d] avIndex[%d] resend[%d] two_way_streaming[%d]\n", sid, chid,
            av_index, av_client_out_config.resend, av_client_out_config.two_way_streaming);

    return av_index;
}

//########################################################
//# Bind
//########################################################
static int ClientSeverBind(CONN_INFO *conn_info, NebulaClientCtx **client_ctx, unsigned int timeout_ms) {

    int ret = 0;
    NebulaJsonObject *json_args = NULL;
    ret = Nebula_Client_New(conn_info->udid, client_ctx);
    printf("Nebula_Client_New ret[%d]\n", ret);
    if (ret != NEBULA_ER_NoERROR) {
        return -1;
    }

    ret = Nebula_Client_Bind(*client_ctx, conn_info->pin_code, &json_args, timeout_ms, &conn_info->bind_abort);
    printf("Nebula_Client_Bind ret[%d]\n", ret);
    if (ret != NEBULA_ER_NoERROR) {
        Nebula_Client_Delete(*client_ctx);
        *client_ctx = NULL;
        return -1;
    }

    ret = Nebula_Client_Free_Bind_Response(json_args);
    if (ret != NEBULA_ER_NoERROR) {
        Nebula_Client_Delete(*client_ctx);
        *client_ctx = NULL;
        printf("Nebula_Client_Free_Bind_Response failed!\n");
        return -1;
    }

    return 0;
}

void EnableSDKLog(){
    LogAttr log_attr;
    memset(&log_attr, 0, sizeof(log_attr));
    NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [directoryPaths objectAtIndex:0];
    NSString *iotcFilePath = [documentDirectory stringByAppendingPathComponent:@"log_iotc.txt"];
    NSString *avapiFilePath = [documentDirectory stringByAppendingPathComponent:@"log_avapi.txt"];
    log_attr.path = (char *)[iotcFilePath UTF8String];
    IOTC_Set_Log_Attr(log_attr);
    log_attr.path = (char *)[avapiFilePath UTF8String];
    AV_Set_Log_Attr(log_attr);
}

int InitializeSDKModules(){
    int ret = Nebula_Initialize();
    printf("Nebula_Initialize ret[%d]\n", ret);
    if (ret != NEBULA_ER_NoERROR) {
        return -1;
    }

    ret = IOTC_Initialize2(0);
    printf("IOTC_Initialize2 ret[%d]\n", ret);
    if (ret != IOTC_ER_NoERROR) {
        return -1;
    }

    ret = avInitialize(32);
    printf("avInitialize ret[%d]\n", ret);
    if(ret < 0){
        return -1;
    }
    
    return 0;
}

NebulaClientCtx *BindAndCreateClientContext(CONN_INFO *conn_info){
    int ret = -1;
    NebulaClientCtx *client_ctx = NULL;
    switch (conn_info->bind_type) {
    case SERVER_BIND:
        printf("Bind type : Server Bind\n");
        ret = ClientSeverBind(conn_info, &client_ctx, 30000);
        printf("ClientSeverBind ret[%d]\n", ret);
        if (ret == NEBULA_ER_NoERROR) {
            //fetch credential from client context
            const char *credential = Nebula_Client_To_String(client_ctx);
            
            //please save credential for future use
            strcpy(conn_info->credential, credential);
        }
        else
            printf("server bind failed!!\n");
        break;
    case LOCAL_BIND:
        printf("Bind type : Local Bind\n");
        ret = Client_LAN_WIFI_Config(conn_info->udid, 0);
        printf("Client_LAN_WIFI_Config ret[%d]\n", ret);
        if (ret != 0) {
            printf("local bind failed!!\n");
            break;
        }
        Client_LAN_Get_Bind_Credential(conn_info->credential);
        if (strlen(conn_info->credential) == 0) {
            printf("get CTX string fail, exit...!!\n");
            break;
        }
        Nebula_Client_New_From_String(conn_info->udid, conn_info->credential, &client_ctx);

        //please save credential for future use
        break;
    case ALREADY_BOUND:
        printf("Bind type : Already Bound\n");

        //please use the credential saved after LOCAL_BIND or SERVER_BIND
        Nebula_Client_New_From_String(conn_info->udid, conn_info->credential, &client_ctx);
        break;
    default:
        printf("Unknown Bind type[%d]\n", conn_info->bind_type);
    }
    
    return client_ctx;
}

int DemoSendTwoNebulaCommand(NebulaClientCtx *client_ctx, unsigned int *abort){
    NebulaJsonObject *json_response = NULL;
    const char *profile_str = NULL;
    
    char request1[] = "{\"func\":\"getProfile\"}";
    printf("Request: %s\n", request1);
    int ret = Nebula_Client_Send_Command(client_ctx, request1, &json_response, 10000, abort);
    printf("Nebula_Client_Send_Command [%d] \n", ret);
    if (ret != NEBULA_ER_NoERROR) {
        return -1;
    }

    profile_str = Nebula_Json_Obj_To_String(json_response);
    printf("Response: %s\n", profile_str);

    ret = Nebula_Client_Free_Send_Command_Response(json_response);
    printf("Nebula_Client_Free_Send_Command_Response [%d]\n", ret);
    if (ret != NEBULA_ER_NoERROR)
        return -1;

    char request2[] = "{\"func\":\"getNightMode\"}";
    printf("Request : %s\n", request2);
    ret = Nebula_Client_Send_Command(client_ctx, request2, &json_response, 10000, abort);
    printf("Nebula_Client_Send_Command [%d]\n\n", ret);
    if (ret != NEBULA_ER_NoERROR)
        return -1;

    profile_str = Nebula_Json_Obj_To_String(json_response);
    printf("[getNightMode]\n%s\n", profile_str);

    ret = Nebula_Client_Free_Send_Command_Response(json_response);
    printf("Nebula_Client_Free_Send_Command_Response [%d]\n", ret);
    if (ret != NEBULA_ER_NoERROR)
        return -1;
    
    return 0;
}

int DumpIOTCSessionInfo(int sid){
    struct st_SInfoEx session_info;
    session_info.size = sizeof(struct st_SInfoEx);
    int ret = IOTC_Session_Check_Ex(sid, &session_info);
    printf("IOTC_Session_Check_Ex ret[%d]\n", ret);
    if(ret != IOTC_ER_NoERROR)
        return -1;
    
    char *mode[3] = {"P2P", "RLY", "LAN"};
    if( isdigit( session_info.RemoteIP[0] )){
        printf("device is from[IP:%s, Port:%d] Mode[%s] VPG[%d:%d:%d] VER[%X] NAT[%d] AES[%d]\n", \
        session_info.RemoteIP, session_info.RemotePort, mode[(int)session_info.Mode], session_info.VID, session_info.PID, session_info.GID, session_info.IOTCVersion, session_info.RemoteNatType, session_info.isSecure);
    }
    
    return 0;
}

void *StartClientThread () {
    NebulaClientCtx  *client_ctx = NULL;
    CONN_INFO *conn_info = (CONN_INFO *)malloc(sizeof(CONN_INFO));
    int timeout_sec = 30;
    
    //License key Must be set first, otherwise SDK would not function
    int ret = TUTK_SDK_Set_License_Key(gLicenseKey);
    printf("TUTK_SDK_Set_License_Key: %d\n", ret);
    if (ret < 0) {
        return -1;
    }
    
    InitConnectionInfo(conn_info);
    
    // Please link IOTCAPIs_ALLT.a, otherwise no log will be produced
    EnableSDKLog();

    // Initlize modules for IOTC, AV, Nebula
    ret = InitializeSDKModules();
    if(ret < 0){
        printf("Initialize SDK module failed!\n");
        goto THREAD_CONNECT_EXIT;
    }
    
    // Use local bind or server bind to fetch credential from device
    // Use local bind to configure device's wifi
    client_ctx = BindAndCreateClientContext(conn_info);
    if(client_ctx == NULL){
        printf("fail to create client context\n");
        goto THREAD_CONNECT_EXIT;
    }
    
    // Demo how to send nebula command here
    ret = DemoSendTwoNebulaCommand(client_ctx, &conn_info->send_command_abort);
    if(ret < 0){
        printf("Send nebula command failed!\n");
        goto THREAD_CONNECT_EXIT;
    }

    // Establish IOTC connection in order to receive audio & video frame through P2P
    conn_info->sid = IOTC_Client_Connect_By_Nebula(client_ctx, conn_info->rent_am_token, conn_info->realm, 60000, &conn_info->connect_abort);
    printf("IOTC_Client_Connect_By_Nebula ret[%d]\n", ret);
    if (conn_info->sid < 0)
        goto THREAD_CONNECT_EXIT;

    // Dump IOTC session information
    ret = DumpIOTCSessionInfo(conn_info->sid);
    if(ret < 0)
        goto THREAD_CONNECT_EXIT;

    // Establish AV connection
    conn_info->av_index = StartAvClient(conn_info->sid, AV_LIVE_STREAM_CHANNEL, timeout_sec);
    if(conn_info->av_index < 0) {
        printf("fail to establish av connection!\n");
        goto THREAD_CONNECT_EXIT;
    }

    // Send AV json control (similar to nebula command) to inform device to send audio and video frame
    if (StartIpcamStream(conn_info->av_index) != 0) {
        goto THREAD_CONNECT_EXIT;
    }

    // Start a thread to send audio data to device
    if(conn_info->enable_speaker){
        pthread_t thread_speaker_id = 0;
        ret = pthread_create(&thread_speaker_id, NULL, &ThreadSpeaker, (void *)conn_info);
        if (ret != 0) {
            printf("  [%s:%d] pthread_create ThreadSpeaker failed\n", __func__, __LINE__);
            goto THREAD_CONNECT_EXIT;
        }
        pthread_detach(thread_speaker_id);
    }

    // Start a thread to receive audio and video frame
    pthread_t thread_avdata_id = 0;
    ret = pthread_create(&thread_avdata_id, NULL, &ThreadReceiveAVData, (void *)conn_info);
    if (ret != 0) {
        printf("  [%s:%d] pthread_create ThreadReceiveAVData failed\n", __func__, __LINE__);
        goto THREAD_CONNECT_EXIT;
    }
    
    pthread_join(thread_avdata_id, NULL);

THREAD_CONNECT_EXIT:
	
    if(conn_info->av_index >= 0){
        avClientStop(conn_info->av_index);
        printf("avClientStop avIndex[%d]\n", conn_info->av_index);
    }

    if(conn_info->sid >= 0){
        IOTC_Session_Close(conn_info->sid);
        printf("IOTC_Session_Close SID[%d]\n", conn_info->sid);    }
    
    if(client_ctx){
        Nebula_Client_Delete(client_ctx);
        printf("Delete Nebula Client content...\n");
    }
    
    if(conn_info)
        free(conn_info);

    avDeInitialize();
    printf("avDeInitialize\n");
    IOTC_DeInitialize();
    printf("IOTC_DeInitialize\n");
    Nebula_DeInitialize();
    printf("Nebula_DeInitialize\n");
    
    return 0;
}

- (void)startClient {
    // Many SDK APIs are block APIs, please do not use them in UI/Main thread
    pthread_t client_thread;
    pthread_create(&client_thread, NULL, &StartClientThread, NULL);
    pthread_detach(client_thread);

}

@end
