var electron = require("electron");
var app = (electron.remote || electron).app;
var ffi = require("ffi-napi");// electron.remote ? electron.remote.require("ffi-napi") : require("ffi-napi");
var ref =require("ref-napi");// electron.remote ? electron.remote.require("ref-napi") : require("ref-napi");
var RefStruct =require("ref-struct-di")(ref); // (electron.remote ? electron.remote.require : require)("ref-struct-di")(ref);
var RefArray =require("ref-array-di")(ref); // (electron.remote ? electron.remote.require : require)("ref-array-di")(ref);
//const uuid = require("uuid");
const crypto = require("crypto");
const { PlayMode, PlayCmds } = require("./TTSEnums");
var CharArray = RefArray(ref.types.char, 4);//这里创建数组类型的时候一定要指定固定长度,否则ref.alloc出该类型的时候会得到错误的结果。
var ByteArray = RefArray(ref.types.uint8);
var path = require("path");
var fs = require("fs");
var currentDir = path.resolve(__dirname);
var audioFilesDir = path.join(process.cwd(), "audioFiles");
var tts = null;
const playQueue = [];
var timerHandle = null;
var resourceRootDir = currentDir;//语音资源根目录。
var resourceDir = "/msc/res/tts";
resourceRootDir = path.join(resourceRootDir, resourceDir);
var targetResourceRootDir = resourceRootDir;
console.log("TTSApi->__dirname="+__dirname);
let delFileMap = {};//延时清除临时文件。
if (app.isPackaged || app.getAppPath().indexOf(".asar") >= 0) {
    if (process.platform == "win32") {
        targetResourceRootDir = path.join(path.dirname(app.getPath("exe")), "msc/res/tts");
    }
    else if (process.platform == "linux") {
        targetResourceRootDir = path.join(app.getPath("appData"), "msc/res/tts");
    }
    if (!fs.existsSync(targetResourceRootDir)) {
        fs.mkdirSync(targetResourceRootDir, {
            recursive: true
        });
    }
    var items = fs.readdirSync(resourceRootDir);
    for (var i = 0; i < items.length; i++) {
        let item = path.join(resourceRootDir, items[i]);
        let targetPath = path.join(targetResourceRootDir, items[i]);
        if (fs.lstatSync(item).isFile()) {
            if (!fs.existsSync(targetPath)) {
                fs.writeFileSync(targetPath, fs.readFileSync(item));
            } else {
                let sourceHash= crypto.createHash("sha256").update(fs.readFileSync(item)).digest("hex");
                let targetHash=crypto.createHash("sha256").update(fs.readFileSync(targetPath)).digest("hex");
                if(targetHash!=sourceHash){
                    fs.writeFileSync(targetPath, fs.readFileSync(item));
                    console.log(`update tts resource ${targetPath}`);
                }
            }
        }
    }
}
function TTSApi(audioApi, options) {
    this.tts = tts;
    this.audioApi = audioApi;
    this.appId = "";//appId 5f055b33
    this.voice_name = "xiaoyan";
    this.speed = 50;//播放速度
    this.volume = 50;//音量大小
    this.rcn = "1";// 1 的中文发音 0：表示发音为yao 1：表示发音为yi 默认值：0
    this.rdn = "0";// 数字发音 合成音频数字发音，支持参数，0 数值优先, 1 完全数值, 2 完全字符串，3 字符串优先，默认值：0
    this.sample_rate = 16000;//合成音频采样率，支持参数，16000，8000，默认为16000
    if (Object.prototype.toString.call(options) == "[object Object]") {
        if (options.hasOwnProperty("appId") && !/^\s*$/.test(options.appId + "")) {
            this.appId = options.appId;
        }
        else {
            throw new Error("用于初始化离线语音合成引擎的appId无效");
        }
        if (options.hasOwnProperty("voice_name") && !/^\s*$/.test(options.voice_name)) {
            this.voice_name = options.voice_name;
        }
        if (options.hasOwnProperty("speed")) {
            options.speed = parseInt(options.speed + "");
            if (options.speed >= 0 && options.speed <= 100) {
                this.speed = options.speed;
            }
        }
        if (options.hasOwnProperty("volume")) {
            options.volume = parseInt(options.volume + "");
            if (options.volume >= 0 && options.volume <= 100) {
                this.volume = options.volume;
            }
        }
        if (options.hasOwnProperty("rcn") && ["0", "1"].indexOf(options.rcn) >= 0) {
            this.rcn = options.rcn;
        }
    }
    if (audioApi) {
        this.playNext();
    }
}
TTSApi.playQueue = playQueue;
TTSApi.isPlay = false;
TTSApi.prototype.textToSpeech = function (text) {
    var returnResult = {
        IsSuccess: false,
        Code: null,
        Data: null,
        Message: ""
    };
    var md5 = crypto.createHash("md5");
    var str = md5.update(text).digest("hex").toLowerCase();
    var filename = path.join(audioFilesDir, "./" + str + ".wav");
    if (fs.existsSync(filename)) {
        returnResult.IsSuccess = true;
        returnResult.Data = filename;
        return returnResult;
    }
    try {
        var errorCode = 0;
        var wave_pcm_hdr = RefStruct({
            riff: CharArray,// = "RIFF"
            size_8: ref.types.int32, // = FileSize - 8
            wave: CharArray, // = "WAVE"
            fmt: CharArray,// = "fmt "
            fmt_size: ref.types.int32,// = 下一个结构体的大小 : 16
            format_tag: ref.types.int16,// = PCM : 1
            channels: ref.types.int16, // = 通道数 : 1
            samples_per_sec: ref.types.int32,// = 采样率 : 8000 | 6000 | 11025 | 16000
            avg_bytes_per_sec: ref.types.int32,// = 每秒字节数 : samples_per_sec * bits_per_sample / 8
            block_align: ref.types.int16,// = 每采样点字节数 : wBitsPerSample / 8
            bits_per_sample: ref.types.int16,// = 量化比特数: 8 | 16
            data: CharArray,// = "data";
            data_size: ref.types.int32 // = 纯数据长度 : FileSize - 44
        });
        var defaultRIFF = new CharArray(["R", "I", "F", "F"], 4); //new CharArray(4);
        var defaultWAVE = new CharArray(["W", "A", "V", "E"], 4);
        var defaultfmt = new CharArray(["f", "m", "t", " "], 4);
        var defaultdata = new CharArray(["d", "a", "t", "a"], 4);
        var default_wav_hdr = new wave_pcm_hdr({
            riff: defaultRIFF,// = "RIFF"
            size_8: 0, // = FileSize - 8
            wave: defaultWAVE, // = "WAVE"
            fmt: defaultfmt,// = "fmt "
            fmt_size: 16,// = 下一个结构体的大小 : 16
            format_tag: 1,// = PCM : 1
            channels: 1, // = 通道数 : 1
            samples_per_sec: 16000,// = 采样率 : 8000 | 6000 | 11025 | 16000
            avg_bytes_per_sec: 32000,// = 每秒字节数 : samples_per_sec * bits_per_sample / 8
            block_align: 2,// = 每采样点字节数 : wBitsPerSample / 8
            bits_per_sample: 16,// = 量化比特数: 8 | 16
            data: defaultdata,// = "data";
            data_size: 0 // = 纯数据长度 : FileSize - 44
        });
        var wav_hdr = default_wav_hdr;
        var wav_hdr_buffer = ref.alloc(wave_pcm_hdr, wav_hdr);

        var commonVoiceResourcePath = null;
        if (process.platform == "linux"){
            commonVoiceResourcePath = path.join(targetResourceRootDir, "commonLinux.jet");
        }
        if (process.platform == "win32") {
            commonVoiceResourcePath = path.join(targetResourceRootDir, "commonWindows.jet");
        }
        console.log("===公共语音库地址："+commonVoiceResourcePath);
        var voiceResourcePath = path.join(targetResourceRootDir, `${this.voice_name}.jet`);

        var login_params = `appid = ${this.appId}, work_dir =.`;//登录参数,appid与msc库绑定,请勿随意改动
        var session_begin_params = `engine_type = local, voice_name = ${this.voice_name}, text_encoding = UTF-8, tts_res_path = fo|${voiceResourcePath};fo|${commonVoiceResourcePath}, sample_rate = ${this.sample_rate}, speed = ${this.speed}, volume = ${this.volume}, pitch = 50, rdn = ${this.rdn},rcn=${this.rcn}`;

        var textBuffer = Buffer.from(text);

        errorCode = this.tts.MSPLogin(null, null, login_params);
        if (errorCode != 0) {
            //登录失败
            returnResult.Code = errorCode;
            returnResult.Message = "LoginError";
            return returnResult;
        }
        var sessionBeginResultBuffer = ref.alloc(ref.types.int, -1);
        var sessionID = this.tts.QTTSSessionBegin(session_begin_params, sessionBeginResultBuffer);
        errorCode = sessionBeginResultBuffer.deref();

        if (errorCode != 0) {
            //会话建立失败
            returnResult.Code = errorCode;
            returnResult.Message = "SessionBeginError";
            return returnResult;
        }

        errorCode = this.tts.QTTSTextPut(sessionID, textBuffer, textBuffer.length, null);
        if (errorCode != 0) {
            //文本传输失败
            returnResult.Code = errorCode;
            returnResult.Message = "TextPutError";
            this.tts.QTTSSessionEnd(sessionID, "TextPutError");
            return returnResult;
        }

        var fd = fs.openSync(filename, 'w');
        fs.writeSync(fd, wav_hdr_buffer, 0, wav_hdr_buffer.length);
        var audio_len = ref.alloc(ref.types.uint32, 0);//音频长度。
        var synth_status = ref.alloc(ref.types.int32, 1);//合成音频文件的状态。1：正在合成 2：合成结束 4：取消
        var ret = ref.alloc(ref.types.int32, -1);//每次请求音频信息API时状态返回，0：表示成功 其他：失败
        while (true) {
            //返回的是音频文件数据。
            var data = this.tts.QTTSAudioGet(sessionID, audio_len, synth_status, ret);
            var audioDataLength = audio_len.deref();
            //音频数据获取失败
            if (ret.deref() != 0) {
                break;
            }
            if (audioDataLength > 0 && !ref.isNull(data)) {
                var audioDataBuffer = ref.reinterpret(data, audioDataLength, 0);
                fs.writeSync(fd, audioDataBuffer, 0, audioDataLength);
                wav_hdr.data_size += audioDataLength;
            }
            //合成完毕
            if (synth_status.deref() == 2) {
                break;
            }
        }
        //判断是否是音频获取失败。
        if (ret.deref() != 0) {
            var sessionEndCode = this.tts.QTTSSessionEnd(sessionID, "AudioGetError");
            errorCode = ret.deref();
            returnResult.Code = errorCode;
            returnResult.Message = "AudioGetError";
            if (fd) {
                fs.closeSync(fd);
                fd = null;
                if (process.platform == "win32") {
                    const child_process = require("child_process");
                    child_process.exec(`del /Q ${filename}`, { windowsHide: true }, function (error, stdOut, stdErr) {
                        if (error) {
                            console.log(`合成失败的语音文件:${filename}删除失败!`);
                        }
                        else {
                            console.log(`合成失败的语音文件:${filename}删除成功!`);
                        }
                    });
                }
                else {
                    fs.unlink(filename, (err) => {
                        if (err) console.error(`合成失败的语音文件:${filename}删除失败!`);
                    });
                }
            }
            return returnResult;
        }
        /* 修正wav文件头数据的大小 */
        wav_hdr.size_8 += wav_hdr.data_size + (wave_pcm_hdr.size - 8);

        var size8Buffer = ref.alloc(ref.types.int32, wav_hdr.size_8);
        fs.writeSync(fd, size8Buffer, 0, size8Buffer.length, 4);

        var dataSizeBuffer = ref.alloc(ref.types.int32, wav_hdr.data_size);
        fs.writeSync(fd, dataSizeBuffer, 0, dataSizeBuffer.length, 40);

        /* 合成完毕 */
        errorCode = this.tts.QTTSSessionEnd(sessionID, "Normal");

        returnResult.IsSuccess = errorCode == 0;
        returnResult.Code = errorCode;
        returnResult.Data = filename;
        return returnResult;
    }
    catch (e) {
        returnResult.IsSuccess = false;
        returnResult.Message = e.message;
        return returnResult;
    }
    finally {
        if (fd) {
            fs.closeSync(fd);
        }
        this.tts.MSPLogout();
    }
}
TTSApi.prototype.playNext = function () {
    var $this = this;
    if (timerHandle) {
        clearInterval(timerHandle);
        timerHandle = null;
    }
    timerHandle = setInterval(function () {
        if (TTSApi.playQueue.length > 0 && $this.audioApi && $this.audioApi.playStatus().indexOf("playing") == -1) {
            var item = TTSApi.playQueue[0];//TTSApi.playQueue.shift();
            $this.play(item, PlayMode.Append, true);
        }
    }, 500);
}
/**
 * 播放
 * @param text  文本
 * @param mode 模式
 * @param isDequeue 
 * @param fileStatus 文件状态  ，0默认保存，1不保存；
 */
TTSApi.prototype.play = function (text, mode, isDequeue, fileStatus) {
    this.tiemOutDelFile(text)
    switch (mode) {
        case PlayMode.Append:
            if ((TTSApi.playQueue.length == 0 || isDequeue) && this.audioApi.playStatus().indexOf("playing") == -1) {
                var result = this.textToSpeech(text);
                if (result.IsSuccess) {
                    let playResult = this.audioApi.playAudio(result.Data, PlayCmds.Play, PlayMode.Append);
                    let index = TTSApi.playQueue.indexOf(text);
                    //播放成功后清空队列中的项。
                    if (playResult == 0 && index != -1) {
                        TTSApi.playQueue.splice(index, 1);
                        this.tiemOutDelFile(text,result.Data,fileStatus==1?true:false)
                    }
                    //未能播放则加入队列。
                    else if (playResult == -1 && index == -1) {
                        TTSApi.playQueue.unshift(text);
                    }
                }
                else {
                    console.log("播放失败,语音合成失败:" + JSON.stringify(result));
                }
            }
            else {
                if (TTSApi.playQueue.indexOf(text) == -1) {
                    TTSApi.playQueue.push(text);
                }
            }
            break;
        case PlayMode.Override:
            var result = this.textToSpeech(text);
            if (result.IsSuccess) {
                this.audioApi.playAudio(result.Data, PlayCmds.Stop);
                this.audioApi.playAudio(result.Data, PlayCmds.Play, PlayMode.Override);
                this.tiemOutDelFile(text,result.Data,fileStatus==1?true:false)
            }
            else {
                console.log("播放失败,语音合成失败:" + JSON.stringify(result));
            }
            break;
        case PlayMode.Clear:
            var result = this.textToSpeech(text);
            if (result.IsSuccess) {
                TTSApi.playQueue.splice(0, TTSApi.playQueue.length);
                this.audioApi.playAudio(result.Data, PlayCmds.Stop);
                this.audioApi.playAudio(result.Data, PlayCmds.Play, PlayMode.Clear);
                this.tiemOutDelFile(text,result.Data,fileStatus==1?true:false)
            }
            else {
                console.log("播放失败,语音合成失败:" + JSON.stringify(result));
            }
            break;
    }
}

/**
 *  延时删除语音合成文件
 * @param {*} text 语音文本
 * @param {*} filePath  语音合成文件地址
 * @param {*} ifAdd 是否添加延时删除，
 */
TTSApi.prototype.tiemOutDelFile=function (text,filePath="", ifAdd = false) {
    if (ifAdd) {
        if (delFileMap[text]) {
            clearTimeout(delFileMap[text]) 
            delFileMap[text] = null
        }
        if (filePath) {
            delFileMap[text] = setTimeout(() => { 
                try {
                    this.audioApi.playAudio(filePath, PlayCmds.Stop);
                    fs.unlinkSync(filePath);
                    delFileMap[text] = null
                    delete delFileMap[text]
                } catch (e) {
                    console.log("延时删除语音合成文件错误",e);
                }
            },text.length*500)
        }
    } else {
        if (delFileMap[text]) {
            clearTimeout(delFileMap[text]) 
            delFileMap[text] = null
            delete delFileMap[text]
        }
    }
    
}
/**
 * 停止播放
 */
TTSApi.prototype.stopPlay = function () {
    this.audioApi.stopPlay();
}
/**
 * 释放资源
 */
TTSApi.prototype.dispose = function () {
    if (this.audioApi.dispose) {
        this.audioApi.dispose();
    }
}
module.exports = function (sdkPath) {
    console.log("TTSApi module.exports executed");
    try {
        if (!fs.existsSync(sdkPath)) {
            console.error("SDK file does not exist:" + sdkPath);
        }
        tts = ffi.Library(sdkPath, {
            "MSPLogin": ["int", ["string", "string", "string"]],
            "MSPLogout": ["int", []],
            "QTTSSessionBegin": ["string", ["string", ref.refType(ref.types.int)]],
            "QTTSSessionEnd": ["int", ["string", "string"]],
            "QTTSTextPut": ["int", ["string", "string", ref.types.uint32, "string"]],
            "QTTSAudioGet": [ref.refType(ref.types.void), ["string", ref.refType(ref.types.uint32), ref.refType(ref.types.int32), ref.refType(ref.types.int32)]]
        });
        if (!fs.existsSync(audioFilesDir)) {
            fs.mkdirSync(audioFilesDir);
        }
        return TTSApi;
    } catch (e) {
        console.error("TTSApi module.exports error:" + e.message);
        return TTSApi;
    }
};

