//
// Shinobi - Plugin Base
// Copyright (C) 2016-2025 Moe Alam, moeiscool
//
// # Donate
//
// If you like what I am doing here and want me to continue please consider donating :)
// PayPal : paypal@m03.ca
//
const {
    parentPort
} = require('worker_threads');
var fs = require('fs');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var moment = require('moment');
var overAllProcessingCount = 0
module.exports = function(__dirname, config){
    if(!config){
        return console.log(`Configuration file is missing.`)
    }
    var plugLog = (d1) => {
        console.log(new Date(), config.plug, d1)
    }

    process.on('uncaughtException', (err) => {
        console.error('uncaughtException', err)
    })

    if(!config.dirname){config.dirname = '.'}
    if(!config.port){config.port = 8080}
    if(!config.hostPort){config.hostPort = 8082}
    if(config.systemLog === undefined){config.systemLog = true}
    if(config.connectionType === undefined)config.connectionType = 'websocket'
    s = {
        group: {},
        dir: {},
        isWin: (process.platform === 'win32'),
        s: (json) => {
            return JSON.stringify(json,null,3)
        }
    }
    //default stream folder check
    if(!config.streamDir){
        if(s.isWin === false){
            config.streamDir = '/dev/shm'
        }else{
            config.streamDir = config.windowsTempDir
        }
        if(!fs.existsSync(config.streamDir)){
            config.streamDir = config.dirname+'/streams/'
        }else{
            config.streamDir += '/streams/'
        }
    }
    s.dir.streams = config.streamDir
    //streams dir
    if(!fs.existsSync(s.dir.streams)){
        fs.mkdirSync(s.dir.streams)
    }
    s.checkCorrectPathEnding = (x) => {
        var length = x.length
        if(x.charAt(length-1) !== '/'){
            x = x+'/'
        }
        return x.replace('__DIR__',config.dirname)
    }
    s.gid = (x) => {
        if(!x){x = 10};
        var t = "";
        var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        for( var i = 0; i < x; i++ )
            t += p.charAt(Math.floor(Math.random() * p.length));
        return t;
    };
    s.systemLog = (q,w,e) => {
        if(!w){w=''}
        if(!e){e=''}
        if(config.systemLog===true){
           return console.log(moment().format(),q,w,e)
        }
    }
    s.debugLog = () => {
        if(config.debugLog === true){
            console.log(new Date(),arguments)
            if(config.debugLogVerbose === true){
                console.log(new Error())
            }
        }
    }
    s.detectObject = (buffer,d,tx,frameLocation) => {
        console.log('detectObject handler not set')
    }
    const processImage = async (buffer,d,tx,frameLocation) => {
        ++overAllProcessingCount
        sendToParentPort('processCount',overAllProcessingCount)
        s.detectObject(buffer,d,tx,frameLocation,() => {
            --overAllProcessingCount
            sendToParentPort('processCount',overAllProcessingCount)
        })
    }
    const getCpuUsage = (callback) => {
        var k = {}
        switch(s.platform){
            case'win32':
                k.cmd = "@for /f \"skip=1\" %p in ('wmic cpu get loadpercentage') do @echo %p%"
            break;
            case'darwin':
                k.cmd = "ps -A -o %cpu | awk '{s+=$1} END {print s}'";
            break;
            case'linux':
                k.cmd = 'top -b -n 2 | awk \'toupper($0) ~ /^.?CPU/ {gsub("id,","100",$8); gsub("%","",$8); print 100-$8}\' | tail -n 1';
            break;
            case'freebsd':
                k.cmd = 'vmstat 1 2 | awk \'END{print 100-$19}\''
            break;
	        case'openbsd':
                k.cmd = 'vmstat 1 2 | awk \'END{print 100-$18}\''
            break;
        }
        if(config.customCpuCommand){
          exec(config.customCpuCommand,{encoding:'utf8',detached: true},function(err,d){
              if(s.isWin===true) {
                  d = d.replace(/(\r\n|\n|\r)/gm, "").replace(/%/g, "")
              }
              callback(d)
              s.onGetCpuUsageExtensions.forEach(function(extender){
                  extender(d)
              })
          })
        } else if(k.cmd){
             exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
                 if(s.isWin===true){
                     d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"")
                 }
                 callback(d)
                 s.onGetCpuUsageExtensions.forEach(function(extender){
                     extender(d)
                 })
             })
        } else {
            callback(0)
        }
    }
    const parseNvidiaSmi = function(callback){
        var response = {
            ok: true,
        }
        exec('nvidia-smi -x -q',function(err,data){
            var response = xmlParser.toJson(data)
            var newArray = []
            try{
                JSON.parse(response).nvidia_smi_log.gpu.forEach((gpu)=>{
                    newArray.push({
                        id: gpu.minor_number,
                        name: gpu.product_name,
                        brand: gpu.product_brand,
                        fan_speed: gpu.fan_speed,
                        temperature: gpu.temperature,
                        power: gpu.power_readings,
                        utilization: gpu.utilization,
                        maxClocks: gpu.max_clocks,
                    })
                })
            }catch(err){

            }
            if(callback)callback(newArray)
        })
    }
    s.onCameraInitExtensions = []
    s.onCameraInit = (extender) => {
        s.onCameraInitExtensions.push(extender)
    }
    s.onPluginEvent = []
    s.onPluginEventExtender = (extender) => {
        s.onPluginEvent.push(extender)
    }
    s.MainEventController = async (d,cn,tx) => {
        switch(d.f){
            case'init_plugin_as_host':
                if(!cn){
                    console.log('No CN',d)
                    return
                }
                if(d.key!==config.key){
                    console.log(new Date(),'Plugin Key Mismatch',cn.request.connection.remoteAddress,d)
                    cn.emit('init',{ok:false})
                    cn.disconnect()
                }else{
                    console.log(new Date(),'Plugin Connected to Client',cn.request.connection.remoteAddress)
                    cn.emit('init',{ok:true,plug:config.plug,notice:config.notice,type:config.type})
                }
            break;
            case'init_monitor':
                retryConnection = 0
                if(s.group[d.ke] && s.group[d.ke][d.id]){
                    s.group[d.ke][d.id].numberOfTriggers = 0
                    delete(s.group[d.ke][d.id].cords)
                    delete(s.group[d.ke][d.id].buffer)
                    s.onCameraInitExtensions.forEach((extender) => {
                        extender(d,cn,tx)
                    })
                }
            break;
            case'frameFromRam':
                if(!s.group[d.ke]){
                    s.group[d.ke] = {}
                }
                if(!s.group[d.ke][d.id]){
                    s.group[d.ke][d.id] = {}
                }
                processImage(buffer,d,tx,d.frameLocation)
            break;
            case'frame':
                try{
                    if(!s.group[d.ke]){
                        s.group[d.ke]={}
                    }
                    if(!s.group[d.ke][d.id]){
                        s.group[d.ke][d.id] = {}
                        s.onCameraInitExtensions.forEach((extender) => {
                            extender(d,cn,tx)
                        })
                    }
                    if(!s.group[d.ke][d.id].buffer){
                      s.group[d.ke][d.id].buffer = [d.frame];
                    }else{
                      s.group[d.ke][d.id].buffer.push(d.frame)
                    }
                    if(d.frame[d.frame.length-2] === 0xFF && d.frame[d.frame.length-1] === 0xD9){
                        var buffer = Buffer.concat(s.group[d.ke][d.id].buffer);
                        processImage(buffer,d,tx)
                        s.group[d.ke][d.id].buffer = null
                    }
                }catch(err){
                    if(err){
                        s.systemLog(err)
                        delete(s.group[d.ke][d.id].buffer)
                    }
                }
            break;
        }
        s.onPluginEvent.forEach((extender) => {
            extender(d,cn,tx)
        })
    }
    plugLog('Plugin started as Worker')
    const sendToParentPort = (type,data) => {
        parentPort.postMessage([type,data])
    }

    //start plugin as client

    parentPort.on('message',(data) => {
        s.MainEventController(data,null,s.cx)
    })


    s.cx = (x) => {
        var sendData = Object.assign(x,{
            pluginKey : config.key,
            plug : config.plug
        })
        return sendToParentPort('ocv',sendData)
    }

    if(config.clusterMode){
        plugLog('Plugin enabling Cluster Mode...')
        if(config.clusterBasedOnGpu){
            setTimeout(() => {
                parseNvidiaSmi((gpus)=>{
                    sendToParentPort('gpuUsage',gpus)
                })
            },1000 * 10)
        }else{
            setTimeout(() => {
                getCpuUsage((percent) => {
                    sendToParentPort('cpuUsage',percent)
                })
            },1000 * 10)
        }
    }

    s.getImageDimensions = (d) => {
        var height
        var width
        if(
            d.mon.detector_scale_y_object &&
            d.mon.detector_scale_y_object !== '' &&
            d.mon.detector_scale_x_object &&
            d.mon.detector_scale_x_object !== ''
        ){
            height = d.mon.detector_scale_y_object
            width = d.mon.detector_scale_x_object
        }else{
            height = d.mon.detector_scale_y
            width = d.mon.detector_scale_x
        }
        return {
            height : parseFloat(height),
            width : parseFloat(width)
        }
    }
    return s
}
