
const logger = require('electron-log')

const os = require('os')
const fs = require('fs')
const path = require('path')
const arch = os.arch()
const platform = os.platform()
const isWindows = () => platform === 'win32'
const isMac = () => platform === 'darwin'



/*
 * @Author: baijiayun.com
 * @Date: 2023-08-10 15:01:53
 * @Description: 这是cpp 插件的 bridge 层, 单例
 */
class CppPluginBridge {
    constructor() {
        if (CppPluginBridge.instance) {
            return CppPluginBridge.instance
        }
        this.mainWindow = null
        this.instance = null
        CppPluginBridge.instance = this

    }

    /**
     * 解析原生插件 .node 文件路径（按优先级尝试多个候选位置）
     */
    static resolveNativePluginPath() {
        const candidates = [
            path.resolve(__dirname, '../bjy-electron-plugin/build/Release/bjy_electron_plugin.node'),
            path.resolve(__dirname, 'node_modules/bjy-electron-plugin/build/Release/bjy_electron_plugin.node'),
            path.resolve(__dirname, '../bjy-electron-plugin/build/Debug/bjy_electron_plugin.node'),
            path.resolve(__dirname, 'node_modules/bjy-electron-plugin/build/Debug/bjy_electron_plugin.node'),
        ]

        for (const candidate of candidates) {
            if (fs.existsSync(candidate)) {
                return candidate
            }
        }

        throw new Error(
            `找不到原生插件 bjy_electron_plugin.node，请先编译：cd ../bjy-electron-plugin && npm run rebuild-x64。已尝试：${candidates.join('; ')}`
        )
    }

    bindTargetWindow(mainWindow) {
        this.mainWindow = mainWindow
    }

    /**
     * 
     * @param {string} winArc 
     *  windows 应用架构，只能为 x64 或者 x86 默认是x86
     *  mac arm传递 arm x86传递 x86 默认 x86
     * @returns 
     */
    init(winArc) {
        if (this.instance) {
            return this.instance
        }
        let lib
        let dllPath
        let fileMidPath = `${platform}-${arch}`
        if (isWindows()) {
            if (arch === 'x64') {
                dllPath = path.resolve(process.resourcesPath, './app.asar.unpacked/node_modules/@hysc/boom-cpp-plugin/cppPlugin', fileMidPath);
                process.env.PATH = dllPath + ';' + process.env.PATH;
                lib = require(`./win32-x64/bjy_electron_plugin.node`)
            } else {
                dllPath = path.resolve(process.resourcesPath, './app.asar.unpacked/node_modules/@hysc/boom-cpp-plugin/cppPlugin', fileMidPath);
                process.env.PATH = dllPath + ';' + process.env.PATH;
                lib = require(`./win32-ia32/bjy_electron_plugin.node`)
            }
        } else if (isMac()) {

            dllPath = path.resolve(process.resourcesPath, './app.asar.unpacked/node_modules/@hysc/boom-cpp-plugin/cppPlugin', fileMidPath);
            let exists = fs.existsSync(dllPath);
            logger.log('cpp plugin dllPath:', dllPath, 'exists:', exists)
            if ((os.arch() === 'arm64' && exists)) {
                lib = require(`./darwin-arm64/bjy_electron_plugin.node`)
            } else {
                fileMidPath = 'darwin-x64';
                lib = require(`./darwin-x64/bjy_electron_plugin.node`)
            }
            dllPath = path.resolve(process.resourcesPath, './app.asar.unpacked/node_modules/@hysc/boom-cpp-plugin/cppPlugin', fileMidPath);
            exists = fs.existsSync(dllPath);
            if (!exists) {
                logger.error('cpp plugin not exists, please check the path:', dllPath)
            }
        }

        if (lib) {
            this.instance = new lib.BJYCppPlugin()
            this.instance.createEngineInstance()
            if (isMac()) {
                this.setOnLogEventCallback()
            }
            logger.log('init cpp plugin success, dllPath:', dllPath)
            return
        } else {
            logger.log('init cpp plugin failed', dllPath)
            throw new Error('init cpp plugin failed')
        }
    }
    
    initWithPath(filePath) {
        if (this.instance) {
            return this.instance
        }

        const resolvedPath = filePath = `${platform}-${arch}`
        if (isWindows()) {
            const dllDir = path.dirname(resolvedPath)
            if (!process.env.PATH.includes(dllDir)) {
                process.env.PATH = `${dllDir};${process.env.PATH}`
            }
        }

        let lib
        try {
            lib = require(resolvedPath)
        } catch (error) {
            logger.error('load cpp plugin failed, error:', error)
            throw new Error(`load cpp plugin failed: ${error.message}`)
        }

        if (lib) {
            this.instance = new lib.BJYCppPlugin()
            this.instance.createEngineInstance()
            if (isMac()) {
                this.setOnLogEventCallback()
            }
            logger.log('init cpp plugin success, dllPath:', resolvedPath)
            return this.instance
        }

        throw new Error('init cpp plugin failed')
    }

    // ======================= event callback ======================= //
    /**
     * cpp 音频采集事件回调
     * @param {*} data
     * @param {*} sampleRate
     * @param {*} channels
     * @param {*} timestamp
     */
    audioEventCallback(data, sampleRate, channels, timestamp) {
        //logger.log('cpp audio plugin capture audio data', sampleRate, channel, timestamp, 'data length:', data ? data.length : 'null');
        if (this.mainWindow) {
            this.mainWindow.webContents.send('onAudioData', data, sampleRate, channels, timestamp);
        } else {
            logger.log('mainWindow is null, audioEventCallback', sampleRate);
        }
    }

    /**
     * cpp 音频采集错误回调
     * @param {*} code
     * @param {*} msg
     */
    audioCaptureErrorCallback(code, msg) {
        logger.log('cpp audio plugin capture audio error', code, msg)
        logger.log('stop audio capture')
        this.stopCapture()
        logger.log('restart audio capture')
        this.audioStreamSubscribe && this.audioStreamSubscribe.stop()
        this.audioStreamSubscribe = null
        const res = this.startAudioCapture(0, false, (audioData) => {
            this.audioEventCallback(audioData.data, audioData.sampleRate, audioData.channels, audioData.timestamp)
        })
        this.audioStreamSubscribe = res
        logger.log('start audio plugin result', res)
    }


    /**
     * cpp 窗口全屏事件回调
     * @param {*} isFullScreen
     * @param {*} fullScreenWId
     */
    async fullScreenCallBack(isFullScreen, fullScreenWId) {
        logger.log('isFullScreen', isFullScreen, 'fullScreenWId', fullScreenWId)
        const id = `window:${fullScreenWId}:0`
        logger.log('send select source to webContents', id)
        this.mainWindow.webContents.send('onFullScreen', { id }, isFullScreen)
    }

    setOnLogEventCallback() {
        if (this.instance) {
            this.instance.setEventCallback('onLog', this.cppLogCallback.bind(this))
        }
    }

    cppLogCallback(logs) {
        logger.log('cpp plugin logs', logs)
    }

    /**
     * 设置窗口全屏事件回调
     */
    setWinPluginEventCallBack() {
        if (this.instance) {
            logger.log('set win plugin event callback')
            this.instance.setEventCallback('onWindowFullScreenEvent', this.fullScreenCallBack.bind(this))
        }
    }

    setAudioPluginEventCallback() {
                if (this.instance) {
            this.instance.setEventCallback('onCapturedRawAudioFrame', this.audioEventCallback.bind(this))
                    this.instance.setEventCallback('onAudioCaptureError', this.audioCaptureErrorCallback.bind(this))
                }
            }   

    // ======================= event callback end ======================= //

    bringWindowToTop(wid) {
        if (this.instance) {
            const isNumber = !Number.isNaN(Number(wid))
            if (isNumber) {
                logger.log('bring target window to top')
                return this.instance.bringWindowToTop(Number(wid))
            } else {
                logger.log('bring window to top failed, wid is not a number')
            }
        }
    }

    /**
     * 根据窗口ID获取应用的真实名称
     * @param {Number} wid 窗口ID
     * @returns {String} 应用名称，如果获取失败返回空字符串
     */
    getWindowAppName(wid) {
        if (this.instance) {
            const isNumber = !Number.isNaN(Number(wid))
            if (isNumber) {
                logger.log('get window app name for wid:', wid)
                return this.instance.getWindowAppName(Number(wid))
            } else {
                logger.log('get window app name failed, wid is not a number')
                return ''
            }
        }
        return ''
    }

    /**
     * 根据窗口ID获取窗口大小和位置
     * @param {Number} wid 窗口ID
     * @returns {Object} 窗口信息对象，包含width、height、left、top、right、bottom属性，失败时返回空对象
     */
    getWindowSize(wid) {
        if (this.instance) {
            const isNumber = !Number.isNaN(Number(wid))
            if (isNumber) {
                logger.log('get window size for wid:', wid)
                return this.instance.getWindowSize(Number(wid))
            } else {
                logger.log('get window size failed, wid is not a number')
                return {}
            }
        }
        return {}
    }
   /**
     * 采集系统声音
     * @param {Number} sid sourceId 指定窗口ID，设置为0表示采集当前运行进程本身
     * @param {Boolean} onlySourceId 采集模式，true表示只采集指定窗口进程 false表示采集除了本地窗口进程
     */
    startAudioCapture(sid, onlySourceId) {
        logger.log('startCapture', sid, onlySourceId)
        this.stopAudioCapture()
        if (this.instance) {
            const isNumber = !Number.isNaN(Number(sid))
            if (isNumber) {
                const audioStream = this.instance.startAudioCapture(Number(sid), onlySourceId, (audioData)=>{
                   this.mainWindow.webContents.send('onAudioData', audioData.data, audioData.sampleRate, audioData.channels, audioData.timestamp)
                })
                this.audioStreamSubscribe = audioStream
                logger.log('startAudioCapture result', audioStream)
                if (!audioStream) {
                    logger.log('startAudioCapture failed')
                    this.stopCapture()
                    this.mainWindow.webContents.send('onAudioCaptureError', { failed: true, res: -1 })
                }
             
            } else {
                logger.log('startCapture failed, sid is not a number')
                return null;
            }
        }
        return null;
    }

    /**
     *  检测视频窗口变化
     * @param {*} wid
     */
    startWindowDetector(wid, sourceName) {
        logger.log('startWindowDetector', wid, sourceName)
        if (this.instance) {
            const isNumber = !Number.isNaN(Number(wid))
            if (isNumber) {
                const res = this.instance.startWindowDetector(Number(wid))
                logger.log('start window capture res', res)
                if (res < 0) {
                    logger.log('start window capture failed')
                    this.stopWindowDetector()
                    this.mainWindow.webContents.send('onWindowCaptureError', { failed: true, res })
                }
            } else {
                logger.log('start window capture failed, wid is not a number')
            }
        }
    }

    stopWindowDetector() {
        if (this.instance) {
            this.instance.stopWindowDetector()
        }
    }
    stopAudioCapture() {
        if (this.audioStreamSubscribe && typeof this.audioStreamSubscribe.stop === 'function') {
            try {
                this.audioStreamSubscribe.stop()
            } catch (error) {
                logger.error('audioStreamSubscribe.stop failed:', error)
            }
            this.audioStreamSubscribe = null
        }
       
        if (this.instance && isWindows()) {
            this.instance.stopAudioCapture()
        }
    }
    stopCapture() {
        this.stopAudioCapture()
    }

    destroy() {
        if (this.instance) {
            this.stopCapture()
            this.stopWindowDetector()
            this.releaseAudioDeviceMgr()
            this.instance.destroyEngineInstance()
            this.instance = null
            this.mainWindow = null
        }
    }

    // ================ 音频管理 ==================== //

    initAudioDeviceManage() {
        this.instance.audioDeviceMgrInitialize()
    }

    /**
     * 设置当前音频设备
     * 
     * @param {String} speakerName 扬声器名称
     * @param {String} micName 麦克风名称
     */
    setAudioDevice(speakerName, micName) {
        return this.instance.setAudioDevice(speakerName, micName)
    }

    /**
     * 设置扬声器音量
     * @param {Number} volume
     * @returns {Boolean} true 成功 false 失败
     */
    setSpeakerVolume(volume) {
        return this.instance.setSpeakerVolume(volume)
    }

    /**
     * 获取扬声器音量
     * @returns {Number}
     */
    getSpeakerVolume() {
        return this.instance.getSpeakerVolume()
    }

    /**
     * 设置麦克风音量
     * @param {Number} volume 
     * @returns {Boolean} true 成功 false 失败
     */
    setMicVolume(volume) {
        return this.instance.setMicVolume(volume)
    }

    /**
     * 获取麦克风音量
     * @returns {Number}
     */
    getMicVolume() {
        return this.instance.getMicVolume()
    }

    /**
     * 扬声器是否静音
     * @returns {Boolean} true 静音 false 未静音
     */
    isSpeakerMute() {
        return this.instance.isSpeakerMute()
    }

    /**
     * 麦克风是否静音
     * @returns {Boolean} true 静音 false 未静音
     */
    isMicMute() {
        return this.instance.isMicMute()
    }
 
    enableMicrophoneDevice() {
        if (isWindows()) {
            return this.instance.enableMicrophoneDevice()
        } else {
            return true
        }
    }
    

    releaseAudioDeviceMgr() {
        if (isWindows()) {
            return this.instance.releaseAudioDeviceMgr()
        }
    }

   

    // ==================================== //
}

module.exports = CppPluginBridge