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

DESCRIPTION
   Netease && Xunfei speech processing main program
   MSC

Copyright (c) 2017 by Netease, Co,LTD.  All Rights Reserved.
============================================================================ */

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

                             REVISION

when                       who why
--------                    ---------
-------------------------------------------
2017/06/28          wangzijiao                                  Created.
============================================================================ */
/* ------------------------------------------------------------------------
** Includes
** ------------------------------------------------------------------------ */
#include "msc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "base.h"
#include "buffer.h"
#include "cae.h"
#include "cae_thread.h"
#include "dbus.h"
#include "error.h"
#include "nduilite.h"
#include <jansson.h>
#include <uvdbus/crypto.h>
#include <uvdbus/libuv_dbus.h>
#include <uvdbus/log.h>

#include "srcwsapi.h"

#ifndef USED_NETEASE_AI
#include <msc/msp_cmn.h>
#include <msc/msp_errors.h>
#include <msc/qisr.h>
#else
#include "msp_errors.h"
#include "msp_types.h"
#endif

/* ------------------------------------------------------------------------
 *
 * Macros
 *
 * ------------------------------------------------------------------------ */

/* ------------------------------------------------------------------------
 *
 * Defines
 *
 * ------------------------------------------------------------------------ */

// 1M
#define BUFFER_SIZE 1024 * 1024 * 1
// 5M
#define BUFFER_CAPACITY 5 * 1024 * 1024
/* ------------------------------------------------------------------------
 *
 * Types
 *
 * ------------------------------------------------------------------------ */

/* ------------------------------------------------------------------------
 *
 * Global Variable Definitions
 *
 * ------------------------------------------------------------------------ */
CAEDATA **cae;
struct IAT_HD *iathd;
static char isBeginSession = 0;
static int audiostate = MSP_AUDIO_SAMPLE_CONTINUE;

/* ------------------------------------------------------------------------
 *
 * Function Definitions
 *
 ** ------------------------------------------------------------------------ */
static void lockIAT(struct IAT_HD *iat);
static void unlockIAT(struct IAT_HD *iat);

static void printIatTime() {
    n_toast("|Session Create|----%d ms----|Session ok|----%d ms----|Recog "
            "Begin|----%d ms----|Recog "
            "Finish| Cur:%llu\n",
            iathd->time_create_session / 1000, iathd->time_audioin_end / 1000,
            iathd->time_recog_end / 1000, Netease_gettime_ms());
}

static void lockIAT(struct IAT_HD *iat) {
    pthread_mutex_lock(&iathd->mutex_lock);
}

static void unlockIAT(struct IAT_HD *iat) {
    pthread_mutex_unlock(&iathd->mutex_lock);
}

#if !USED_NETEASE_AI
int Netease_MSP_Init(CAEDATA **cdata) {
    const char *login_config =
        "appid = 59476683, server_url = http://60.166.12.151/onlytest.htm";
    cae = cdata;
    int ret = MSPLogin(NULL, NULL, login_config);
    if (ret == MSP_SUCCESS) {
        return NETEASE_SUCCESS;
    } else {
        return NETEASE_ERROR_FAIL;
    }
}

int Netease_MSP_Deinit() { return MSPLogout(); }
#endif

static void *Netease_IAT_thread(void *param) {
    struct IAT_HD *iat = (struct IAT_HD *)param;
    int rec_stat = -1;
    int printCnt = 0;
    int errcode;

    n_toast("IAT thread start \n");
    while (!!iathd->running) {
        native_event_wait(iathd->event, 0x7fffffff);
        while (1) {
            lockIAT(iathd);
            if (IAT_STATUS_GETRESULT != iathd->status) {
                buffer_reset(iat->buffer);
                unlockIAT(iathd);
                n_debug("Current not in getsult status, wait... %d\n",
                        iathd->status);
                break;
            }

            const char *rlst =
                iathd->getResult(iathd->sessionId, &rec_stat, 0, &errcode);

            //            printf("xajhuang+++++++++rlt = %s\n", rlst);

            if (MSP_SUCCESS != errcode) {
                n_error("QISRGetResult fail: %d\n", errcode);
            }
            if (rlst) {
                buffer_add(iat->buffer, (void *)rlst, strlen(rlst));
            }

            if ((printCnt++ % 1000) == 0) {
                n_debug("Iat status: %d, rec_stat:%d .\n", iathd->status,
                        rec_stat);
            }

            if (MSP_SUCCESS != errcode || MSP_REC_STATUS_NO_MATCH == rec_stat ||
                MSP_REC_STATUS_COMPLETE == rec_stat) {
                n_toast("Result:%s\n", rlst);
                // Recognition end
                iathd->time_recog_end = Netease_caltime(&(iathd->time_usec), 1);
                iathd->sessionDestory(iat);
                iat->status = IAT_STATUS_FINISH;

                if (MSP_REC_STATUS_COMPLETE == rec_stat) {
                    Netease_Dbus_Send_CMD(MODULE_VOICEENGINE, MODULE_CONTROLLER,
                                          CMD_MSC_MSG_CONTROLLER_RECOG_SUCCESS,
                                          iat->buffer->data,
                                          iat->buffer->offset);
                    buffer_reset(iat->buffer);
                } else {
                    int reterrcode =
                        (MSP_SUCCESS != errcode) ? errcode : rec_stat;
                    Netease_Dbus_Send_CMD_ERROR(
                        MODULE_VOICEENGINE, MODULE_CONTROLLER,
                        CMD_MSC_MSG_CONTROLLER_RECOG_ERROR, reterrcode);
                }
                printIatTime();

                unlockIAT(iathd);
                break;
            }
            unlockIAT(iathd);
#ifndef USED_NETEASE_AI
            usleep(150 * 1000);
#else
            usleep(10000);
#endif
        }
    }

    n_toast("IAT thread exit\n");
    return NULL;
}

void Netease_IAT_end_session() {
    if (!iathd) {
        return;
    }
    lockIAT(iathd);
    iathd->sessionDestory(iathd);
    iathd->status = IAT_STATUS_INIT;
    unlockIAT(iathd);
}

int Netease_IAT_begin_session(char *recType, char *confirmParam,
                              char *playerStatus) {
    int errcode;
    char buf[200] = {0};
    char buflen = 0;
    int ret = 1;

    if (!iathd) {
        n_error("create session error! iathd error!\n");
        return ret;
    }

    if (1 == isBeginSession) {
        n_toast("Session is now creating, do nothings!\n");
        return ret;
    }
    isBeginSession = 1;

    lockIAT(iathd);

    iathd->sessionDestory(iathd);

    Netease_caltime(&(iathd->time_usec), 1);
    iathd->sessionId = NULL;
    setVss(VoiceSession_Creating);
    iathd->status = IAT_STATUS_AUDIOIN;
    errcode = iathd->sessionCreate(iathd, recType, confirmParam, playerStatus);
    iathd->time_create_session = Netease_caltime(&(iathd->time_usec), 1);
    if (MSP_SUCCESS != errcode) {
        setVss(VoiceSession_Init);
        iathd->status = IAT_STATUS_INIT;
        n_error("IAT Create session error:%d\n", errcode);
        Netease_Dbus_Send_CMD_ERROR(MODULE_VOICEENGINE, MODULE_CONTROLLER,
                                    CMD_MSC_MSG_CONTROLLER_RECOG_ERROR,
                                    errcode);
        ret = 1;
    } else {
        setVss(VoiceSession_Created);
        if (NULL != iathd->sessionId) {
            buflen = snprintf(buf, sizeof(buf), "{\"sessionId\":\"%s\"}",
                              iathd->sessionId);
        }
        Netease_Dbus_Send_CMD(MODULE_VOICEENGINE, MODULE_CONTROLLER,
                              CMD_MSC_MSG_CONTROLLER_SESSION_BEGIN, buf,
                              buflen);
        // iathd->status = IAT_STATUS_AUDIOIN;
        ret = 0;
    }

    unlockIAT(iathd);
    isBeginSession = 0;
    return ret;
}

void Netease_IAT_checkflag(int angle) {
    if (!iathd)
        return;

    setVss(VoiceSession_Init);
    iathd->sessionDestory(iathd);

    lockIAT(iathd);
    iathd->status = IAT_STATUS_FINISH;
    iathd->angle = angle;
    unlockIAT(iathd);
}

IAT_STATUS Netease_IAT_GetStatus(void) { return iathd->status; }

void Netease_IAT_write(const void *audiobuf, int audiolen) {
    int ret;
    int epstatus;
    int recogstatus;
    int errcode;
    char buf[200] = {0};
    int buflen = 0;

    if (!iathd || IAT_STATUS_AUDIOIN != iathd->status) {
        return;
    }

    // lockIAT(iathd);
    if (!iathd || IAT_STATUS_GETRESULT == iathd->status ||
        IAT_STATUS_FINISH == iathd->status || IAT_STATUS_INIT == iathd->status)
        goto unlock_exit;

    // if (IAT_STATUS_INIT == iathd->status) {
    //     Netease_caltime(&(iathd->time_usec), 1);
    //     audiostate = MSP_AUDIO_SAMPLE_FIRST;
    //     errcode = iathd->sessionCreate(iathd, NULL, NULL, NULL);
    //     if (MSP_SUCCESS != errcode) {
    //         n_error("IAT Create session error:%d\n", errcode);
    //         ret = errcode;
    //         goto error_exit;
    //     } else {
    //         iathd->status = IAT_STATUS_AUDIOIN;
    //     }
    // } else {
    //     audiostate = MSP_AUDIO_SAMPLE_CONTINUE;
    // }

    // write_audio:
    ret = iathd->audioWrite(iathd->sessionId, audiobuf, audiolen, audiostate,
                            &epstatus, &recogstatus);

    audiostate = MSP_AUDIO_SAMPLE_CONTINUE;

    if (MSP_SUCCESS != ret) {
        n_error("ISR Write audio fail:%d\n", ret);
        goto error_exit;
    }

    //    printf("ret = %d, epstatus = %d, recogstatus = %d\n", ret, epstatus,
    //    recogstatus);

    if (MSP_EP_AFTER_SPEECH == epstatus ||
        MSP_REC_STATUS_SUCCESS == recogstatus) {
        n_debug("Begin to recog!\n");
        if (NULL != iathd->sessionId) {
            buflen = snprintf(buf, sizeof(buf), "{\"sessionId\":\"%s\"}",
                              iathd->sessionId);
        }
        Netease_Dbus_Send_CMD(MODULE_VOICEENGINE, MODULE_CONTROLLER,
                              CMD_MSC_MSG_CONTROLLER_RECOGING, buf, buflen);

        iathd->audioWrite(iathd->sessionId, NULL, 0, MSP_AUDIO_SAMPLE_LAST,
                          NULL, NULL);
        iathd->time_audioin_end = Netease_caltime(&(iathd->time_usec), 1);
        usleep(5000);
        iathd->status = IAT_STATUS_GETRESULT;
        native_event_set(iathd->event);
#if USED_NETEASE_DUILITE && USED_NETEASE_DUILITE_VAD
        Netease_duilite_vad_stop(1);
#endif

    } else if (MSP_EP_IN_SPEECH == epstatus ||
               MSP_REC_STATUS_INCOMPLETE == recogstatus) {
        // do nothing in speech
    } else if (MSP_EP_TIMEOUT == epstatus || MSP_EP_ERROR == epstatus ||
               MSP_EP_MAX_SPEECH == epstatus) {
        ret = epstatus;
        n_error("IAT ERROR: epstatus: %d\n", epstatus);
        goto error_exit;
    } else {
        n_error("Unknow epstatus: %d, recogstatus: %d\n", epstatus,
                recogstatus);
    }

    goto unlock_exit;

error_exit:
    lockIAT(iathd);
    iathd->sessionDestory(iathd);
    iathd->status = IAT_STATUS_FINISH;
    Netease_Dbus_Send_CMD_ERROR(MODULE_VOICEENGINE, MODULE_CONTROLLER,
                                CMD_MSC_MSG_CONTROLLER_RECOG_ERROR, ret);
    unlockIAT(iathd);
unlock_exit:
    // unlockIAT(iathd);
    return;
}

#define JSON_VBOXSTATE(state, songid, playlistid, radioid)                     \
    json_pack("{s:i, s:s, s:s, s:s}", "state", state, "songid", songid,        \
              "playlist", playlistid, "radioid", radioid)

const char *session_begin_params =
    "udata_uuid=%s, udata_aes = %d,  udata_state = %s, udata_devtime=%llu,"
    "udata_os=linux, sch = 1, sub = iat, domain = iat, "
    "language = zh_cn, accent = mandarin, sample_rate = 16000, "
    "result_type = "
    "plain, result_encoding = UTF-8, vad_enable=1, vad_bos=2000, vad_eos=1500";

#define IAT_SERVER_PARAMS(buffer, uuid, isAes, params)                         \
    sprintf(buffer, session_begin_params, uuid, isAes, params,                 \
            Netease_gettime_ms())

static int IAT_session_create(struct IAT_HD *hd) {
    char tmp[1024];
    int errcode;
    char *paramsbuf = NULL;
    char *Uuid = getUuid();
    if (Uuid[0] == '\0') {
        Netease_dbus_initargs();
        Uuid = "this is default uuid";
    }
#if 0
    char *vboxstatebuf = NULL;
    json_t *vboxstate =
        JSON_VBOXSTATE(1, "songid1234", "playlistid1234", "radioid1234");
    if (vboxstate) {
        vboxstatebuf = json_dumps(vboxstate, 0);
        if (vboxstatebuf) {
            paramsbuf = EvpBase64EncodeNoAlign(vboxstatebuf);
        }
    }
#endif
    BaseLock();
    if (strlen(getSongInfo()) > 0) {
        paramsbuf = (char *)EvpBase64EncodeNoAlign(getSongInfo());
        // paramsbuf = getSongInfo();
    }
#if 0
    n_toast("The paramsbuf= %s\n len:%d, songinfo:%s\n", paramsbuf,
            strlen(getSongInfo()), getSongInfo());
#endif
    IAT_SERVER_PARAMS(tmp, Uuid, 0, paramsbuf ? paramsbuf : "e30=");

    BaseUnlock();
    // n_debug("json: %s\n base64:%s\ntotal:%s\ntotal2:%s\n", vboxstatebuf,
    //       paramsbuf, tmp, session_begin_params);
    n_toast("Song: %s\n", getSongInfo());
    n_debug("to server: %s\n", tmp);

    if (!hd)
        return -1;

    hd->sessionId = (char *)QISRSessionBegin((const char *)NULL, tmp, &errcode);
    audiostate = MSP_AUDIO_SAMPLE_FIRST;
    n_toast("Create new session:%s\n", hd->sessionId);

    if (MSP_SUCCESS != errcode) {
        hd->sessionId = NULL;
    }

    if (!!paramsbuf) {
        // n_toast("begin free buf: 0x%p\n", paramsbuf);
        free(paramsbuf);
        // n_toast("finish free buf: 0x%p\n", paramsbuf);
    }

#if 0
    if (vboxstatebuf)
        free(vboxstatebuf);
    if (vboxstate)
        json_decref(vboxstate);
#endif
    return errcode;
}

static int IAT_session_destroy(struct IAT_HD *hd) {
    int ret = -1;
    if (!hd)
        return ret;

    if (hd->sessionId) {
        n_debug("Del session:%s,%d\n", hd->sessionId, ret);
        ret = QISRSessionEnd(hd->sessionId, NULL);
        hd->sessionId = NULL;
    }
    if (ret == MSP_SUCCESS)
        hd->sessionId = NULL;

    return ret;
}

int Netease_IAT_Init() {
    int ret;
    iathd = (struct IAT_HD *)malloc(sizeof(struct IAT_HD));
    if (!iathd) {
        ret = NETEASE_ERROR_FAIL;
    }
#ifndef USED_NETEASE_AI
    iathd->sessionCreate = IAT_session_create;
    iathd->sessionDestory = IAT_session_destroy;
    iathd->audioWrite = QISRAudioWrite;
    iathd->getResult = QISRGetResult;
#else
    iathd->sessionCreate = netease_session_create;
    iathd->sessionDestory = netease_session_destroy;
    iathd->audioWrite = netease_audio_write;
    iathd->getResult = netease_get_result;
#endif
    iathd->sessionId = NULL;
    iathd->event = native_event_create(NULL, NULL);
    iathd->status = IAT_STATUS_INIT;
    iathd->running = 1;
    pthread_create(&iathd->tid_result, NULL, Netease_IAT_thread, iathd);
    iathd->buffer = new_buffer(BUFFER_SIZE, BUFFER_CAPACITY);
    pthread_mutex_init(&iathd->mutex_lock, NULL);

    ret = NETEASE_SUCCESS;
    //_exit:
    return ret;
}

void Netease_IAT_DeInit() {
    if (!iathd)
        return;
    iathd->running = 0;
    native_event_set(iathd->event);
    pthread_join(iathd->tid_result, NULL);

    if (iathd->event) {
        native_event_destroy(iathd->event);
    }

    iathd->sessionCreate = NULL;
    iathd->sessionDestory = NULL;
    iathd->audioWrite = NULL;
    iathd->getResult = NULL;

    free_buffer(iathd->buffer);
    free(iathd);
    iathd = NULL;
}
