//
// Shinobi - Python DLIB Plugin
// 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
//
process.on('uncaughtException', function (err) {
    console.error('uncaughtException',err);
});
//main vars
var fs=require('fs');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var moment = require('moment');
var http = require('http');
var express = require('express');
var socketIoClient = require('socket.io-client');
var config = require('./conf.json');
var http = require('http'),
    app = express(),
    server = http.createServer(app);

exec("kill $(ps aux | grep '[p]ython3 pumpkin.py' | awk '{print $2}')")

s={
    group:{},
    dir:{},
    isWin:(process.platform==='win32'),
    s:function(json){return JSON.stringify(json,null,3)}
}
s.checkCorrectPathEnding=function(x){
    var length=x.length
    if(x.charAt(length-1)!=='/'){
        x=x+'/'
    }
    return x.replace('__DIR__',__dirname)
}
s.debugLog = function(){
    if(config.debugLog === true){
        console.log(new Date(),arguments)
        if(config.debugLogVerbose === true){
            console.log(new Error())
        }
    }
}
if(!config.port){config.port=8080}
if(!config.pythonScript){config.pythonScript=__dirname+'/pumpkin.py'}
if(!config.pythonPort){config.pythonPort=7990}
if(!config.hostPort){config.hostPort=8082}
if(config.systemLog===undefined){config.systemLog=true}
//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=__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.gid=function(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.getRequest = function(url,callback){
    return http.get(url, function(res){
        var body = '';
        res.on('data', function(chunk){
            body += chunk;
        });
        res.on('end',function(){
            try{body = JSON.parse(body)}catch(err){}
            callback(body)
        });
    }).on('error', function(e){
//                              s.systemLog("Get Snapshot Error", e);
    });
}
s.multiplerHeight = 1
s.multiplerWidth = 1
s.detectObject=function(buffer,d,tx){
  d.tmpFile=s.gid(5)+'.jpg'
  if(!fs.existsSync(s.dir.streams)){
      fs.mkdirSync(s.dir.streams);
  }
  d.dir=s.dir.streams+d.ke+'/'
  if(!fs.existsSync(d.dir)){
      fs.mkdirSync(d.dir);
  }
  d.dir=s.dir.streams+d.ke+'/'+d.id+'/'
  if(!fs.existsSync(d.dir)){
      fs.mkdirSync(d.dir);
  }
  fs.writeFile(d.dir+d.tmpFile,buffer,function(err){
      if(err) return s.systemLog(err);
      if(s.isPythonRunning === false){
          return console.log('Python Script is not Running.')
      }
      var callbackId = s.gid(10)
      s.group[d.ke][d.id].sendToPython({path:d.dir+d.tmpFile,id:callbackId,trackerId:d.ke+d.id},function(data){
          if(data.length > 0){
              var mats=[]
              data.forEach(function(v){
                mats.push({
                  x:v.x,
                  y:v.y,
                  width: v.w,
                  height: v.h,
                  confidence:v.confidence,
                  tag:v.tag
                })
              })
              tx({
                  f:'trigger',
                  id:d.id,
                  ke:d.ke,
                  details:{
                      plug:config.plug,
                      name:'dlib',
                      reason:'object',
                      matrices:mats,
                      imgHeight:parseFloat(d.mon.detector_scale_y),
                      imgWidth:parseFloat(d.mon.detector_scale_x)
                  }
              })
          }
          delete(s.callbacks[callbackId])
          exec('rm -rf '+d.dir+d.tmpFile,{encoding:'utf8'})
      })
  })
}
s.systemLog=function(q,w,e){
    if(w===undefined){return}
    if(!w){w=''}
    if(!e){e=''}
    if(config.systemLog===true){
       return console.log(moment().format(),q,w,e)
    }
}
s.MainEventController=function(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':
            if(s.group[d.ke]&&s.group[d.ke][d.id]){
                delete(s.group[d.ke][d.id].buffer)
                s.group[d.ke][d.id].refreshTracker(d.ke+d.id)
            }
        break;
        case'frame':
            try{
                if(!s.group[d.ke]){
                    s.group[d.ke]={}
                }
                if(!s.group[d.ke][d.id]){
                  var engine = s.createCameraBridgeToPython(d.ke+d.id)
                    s.group[d.ke][d.id]={
                        sendToPython : engine.sendToPython,
                        refreshTracker : engine.refreshTracker
                    }
                }
                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){
                    s.detectObject(Buffer.concat(s.group[d.ke][d.id].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;
    }
}
server.listen(config.hostPort);
//web pages and plugin api
app.get('/', function (req, res) {
  res.end('<b>'+config.plug+'</b> for Shinobi is running')
});
//Conector to Shinobi
if(config.mode==='host'){
    //start plugin as host
    var io = require('socket.io')(server);
    io.attach(server);
    s.connectedClients={};
    io.on('connection', function (cn) {
        s.connectedClients[cn.id]={id:cn.id}
        s.connectedClients[cn.id].tx = function(data){
            data.pluginKey=config.key;data.plug=config.plug;
            return io.to(cn.id).emit('ocv',data);
        }
        cn.on('f',function(d){
            s.MainEventController(d,cn,s.connectedClients[cn.id].tx)
        });
        cn.on('disconnect',function(d){
            delete(s.connectedClients[cn.id])
        })
    });
}else{
    //start plugin as client
    if(!config.host){config.host='localhost'}
    var io = socketIoClient('ws://'+config.host+':'+config.port);//connect to master
    s.cx=function(x){x.pluginKey=config.key;x.plug=config.plug;return io.emit('ocv',x)}
    io.on('connect',function(d){
        s.cx({f:'init',plug:config.plug,notice:config.notice,type:config.type});
    })
    io.on('disconnect',function(d){
        io.connect();
    })
    io.on('f',function(d){
        s.MainEventController(d,null,s.cx)
    })
}

//Start Python Controller
s.callbacks = {}
s.createCameraBridgeToPython = function(uniqueId){
    var pythonIo = socketIoClient('ws://localhost:'+config.pythonPort,{transports : ['websocket']});
    var sendToPython = function(data,callback){
        s.callbacks[data.id] = callback
        pythonIo.emit('f',data)
    }
    var refreshTracker = function(data){
        pythonIo.emit('refreshTracker',{trackerId : data})
    }
    pythonIo.on('connect',function(d){
        s.debugLog(uniqueId+' is Connected from Python')
    })
    pythonIo.on('disconnect',function(d){
        s.debugLog(uniqueId+' is Disconnected from Python')
        setTimeout(function(){
            pythonIo.connect();
            s.debugLog(uniqueId+' is Attempting to Reconect to Python')
        },3000)
    })
    pythonIo.on('f',function(d){
        if(s.callbacks[d.id]){
            s.callbacks[d.id](d.data)
            delete(s.callbacks[d.id])
        }
    })
    return {refreshTracker : refreshTracker, sendToPython : sendToPython}
}


//Start Python Daemon
process.env.PYTHONUNBUFFERED = 1;
s.createPythonProcess = function(){
    s.isPythonRunning = false
    s.pythonScript = spawn('sh',[__dirname+'/bootPy.sh',config.pythonScript,__dirname]);
    var onStdErr = function(data){
        s.debugLog(data.toString())
    }
    var onStdOut = function(data){
        s.debugLog(data.toString())
    }
    setTimeout(function(){
      s.isPythonRunning = true
    },5000)
    s.pythonScript.stderr.on('data',onStdErr);

    s.pythonScript.stdout.on('data',onStdOut);

    s.pythonScript.on('close', function () {
        s.debugLog('Python CLOSED')
    });
}
s.createPythonProcess()
