const fs = require('fs');
const spawn = require('child_process').spawn;
const exec = require('child_process').exec;
const events = require('events');
const URL = require('url')
const {
  Worker
} = require('worker_threads');
const { queryStringToObject, createQueryStringFromObject } = require('./common.js')
module.exports = function(s,config,lang){
    const {
        asyncSetTimeout,
    } = require('./basic/utils.js')(process.cwd(),config)
    const {
        splitForFFMPEG,
        applyPartialToConfiguration,
        getWarningChangesForMonitor,
    } = require('./ffmpeg/utils.js')(s,config,lang)
    const {
        processKill,
        monitorStop,
        monitorIdle,
        monitorStart,
        monitorRestart,
        monitorAddViewer,
        monitorRemoveViewer,
        getMonitorConfiguration,
        copyMonitorConfiguration,
        checkObjectsInMonitorDetails,
        isGroupBelowMaxMonitorCount,
    } = require('./monitor/utils.js')(s,config,lang)
    s.initiateMonitorObject = function(e){
        if(!s.group[e.ke]){s.group[e.ke]={}};
        if(!s.group[e.ke].activeMonitors){s.group[e.ke].activeMonitors={}}
        if(!s.group[e.ke].activeMonitors[e.mid]){s.group[e.ke].activeMonitors[e.mid]={}}
        const activeMonitor = s.group[e.ke].activeMonitors[e.mid]
        activeMonitor.ke = e.ke
        activeMonitor.mid = e.mid
        if(!activeMonitor.streamIn){activeMonitor.streamIn={}};
        if(!activeMonitor.emitterChannel){activeMonitor.emitterChannel={}};
        if(!activeMonitor.mp4frag){activeMonitor.mp4frag={}};
        if(!activeMonitor.firstStreamChunk){activeMonitor.firstStreamChunk={}};
        if(!activeMonitor.contentWriter){activeMonitor.contentWriter={}};
        if(!activeMonitor.childNodeStreamWriters){activeMonitor.childNodeStreamWriters={}};
        if(!activeMonitor.eventBasedRecording){activeMonitor.eventBasedRecording={}};
        if(!activeMonitor.watch){activeMonitor.watch = []};
        if(!activeMonitor.fixingVideos){activeMonitor.fixingVideos={}};
        // if(!activeMonitor.viewerConnection){activeMonitor.viewerConnection={}};
        // if(!activeMonitor.viewerConnectionCount){activeMonitor.viewerConnectionCount=0};
        if(!activeMonitor.parsedObjects){activeMonitor.parsedObjects={}};
        if(!activeMonitor.detector_motion_count){activeMonitor.detector_motion_count=[]};
        if(!activeMonitor.eventsCounted){activeMonitor.eventsCounted = {}};
        if(!activeMonitor.isStarted){activeMonitor.isStarted = false};
        if(!activeMonitor.pipe4BufferPieces){activeMonitor.pipe4BufferPieces = []};
        if(!activeMonitor.secondaryDetectorOutput){activeMonitor.secondaryDetectorOutput = new events.EventEmitter()};
        if(activeMonitor.delete){clearTimeout(activeMonitor.delete)}
        if(!s.group[e.ke].rawMonitorConfigurations){s.group[e.ke].rawMonitorConfigurations={}}
        if(!activeMonitor.criticalErrors)activeMonitor.criticalErrors = {
            "404": false,
            "453": false,
            "500": false,
        }
        s.onMonitorInitExtensions.forEach(function(extender){
            extender(e)
        })
    }
    s.sendMonitorStatus = function(e){
        if(!e.status || !e.code)console.error(JSON.stringify(e),new Error());
        const groupKey = e.ke
        const monitorId = e.mid || e.id
        const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
        activeMonitor.monitorStatus = `${e.status}`
        activeMonitor.monitorStatusCode = `${e.code}`
        s.tx(Object.assign(e,{f:'monitor_status'}),'GRP_'+e.ke)
    }
    s.getMonitorCpuUsage = function(e,callback){
        if(s.group[e.ke].activeMonitors[e.mid] && s.group[e.ke].activeMonitors[e.mid].spawn){
            const getUsage = function(callback2){
                fs.promises.readFile("/proc/" + s.group[e.ke].activeMonitors[e.mid].spawn.pid + "/stat").then((data) => {
                    const elems = data.toString().split(' ');
                    const utime = parseInt(elems[13]);
                    const stime = parseInt(elems[14]);
                    callback2(utime + stime);
                }).catch((err) => {
                    s.debugLog(err)
                    clearInterval(0)
                })
            }
            getUsage(function(startTime){
                setTimeout(function(){
                    getUsage(function(endTime){
                        const delta = endTime - startTime;
                        const percentage = 100 * (delta / 10000);
                        callback(percentage)
                    })
                }, 1000)
            })
        }else{
            callback(0)
        }
    }
    s.buildMonitorUrl = function(e,noPath){
        var authd = ''
        var url
        if(e.details.muser&&e.details.muser!==''&&e.host.indexOf('@')===-1) {
            e.username = e.details.muser
            e.password = e.details.mpass
            authd = e.details.muser+':'+e.details.mpass+'@'
        }
        if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port}
        url = e.protocol+'://'+authd+e.host+e.porty
        if(noPath !== true)url += e.path
        return url
    }
    s.cleanMonitorObjectForDatabase = function(dirtyMonitor){
        var cleanMonitor = {}
        var acceptedFields = [
            'mid',
            'ke',
            'name',
            'details',
            'type',
            'ext',
            'protocol',
            'host',
            'path',
            'port',
            'fps',
            'mode',
            'saveDir',
            'tags',
            'width',
            'height'
        ];
        Object.keys(dirtyMonitor).forEach(function(key){
            if(acceptedFields.indexOf(key) > -1){
                cleanMonitor[key] = dirtyMonitor[key]
            }
        })
        return cleanMonitor
    }
    s.cleanMonitorObject = function(e){
        var x = {keys:Object.keys(e),ar:{}};
        x.keys.forEach(function(v){
            if(v!=='last_frame'&&v!=='record'&&v!=='spawn'&&v!=='running'&&(v!=='time'&&typeof e[v]!=='function')){x.ar[v]=e[v];}
        });
        return x.ar;
    }
    s.getStreamsDirectory = (monitor) => {
        return s.dir.streams + monitor.ke + '/' + (monitor.mid || monitor.id) + '/'
    }
    s.getRawSnapshotFromMonitor = function(monitor,options){
        return new Promise((resolve,reject) => {
            options = options instanceof Object ? options : {flags: ''}
            s.checkDetails(monitor)
            let isDetectorStream = false
            var inputOptions = []
            var outputOptions = []
            var streamDir = s.dir.streams + monitor.ke + '/' + monitor.mid + '/'
            var url = options.url
            var secondsInward = options.secondsInward || '5'
            if(secondsInward.length === 1 && !isNaN(secondsInward))secondsInward = '0' + secondsInward;
            var dynamicTimeout = (secondsInward * 1000) + 5000;
            if(options.flags)outputOptions.push(options.flags)
            const checkExists = function(streamDir,callback){
                s.fileStats(streamDir,function(err){
                    var response = false
                    if(err){
                        // s.debugLog(err)
                    }else{
                        response = true
                    }
                    callback(response)
                })
            }
            const noIconChecks = function(){
                const runExtraction = async function(){
                    var sendTempImage = function(){
                      fs.readFile(temporaryImageFile,function(err,buffer){
                         if(!err){
                             resolve({
                                 screenShot: buffer,
                                 isStaticFile: false
                             })
                             fs.rm(temporaryImageFile,function(){})
                         }else{
                             resolve({
                                 screenShot: null,
                                 isStaticFile: false
                             })
                         }
                      })
                    }
                    try{
                        var temporaryImageFile = streamDir + s.gid(5) + '.jpg'
                        var iconImageFile = streamDir + 'icon.jpg'
                        const snapRawFilters = monitor.details.cust_snap_raw
                        if(snapRawFilters)outputOptions.push(snapRawFilters);
                        var ffmpegCmd = splitForFFMPEG(`-y -loglevel warning ${isDetectorStream ? '-live_start_index 2' : ''} -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
                        try{
                            await fs.promises.mkdir(streamDir, {recursive: true}, (err) => {s.debugLog(err)})
                        }catch(err){
                            console.error(err)
                        }
                        const snapProcess = new Worker(__dirname + '/cameraThread/snapshot.js', {
                            workerData: {
                                jsonData: {
                                  cmd: ffmpegCmd,
                                  temporaryImageFile: temporaryImageFile,
                                  iconImageFile: iconImageFile,
                                  useIcon: options.useIcon,
                                  rawMonitorConfig: s.group[monitor.ke].rawMonitorConfigurations[monitor.mid],
                              },
                              ffmpegAbsolutePath: config.ffmpegDir,
                            }
                        });
                        snapProcess.on('message', function(data){
                            s.debugLog(data)
                        })
                        snapProcess.on('error', (data) => {
                            console.log(data)
                            processKill(snapProcess)
                        })
                        snapProcess.on('exit', (code) => {
                            clearTimeout(snapProcessTimeout)
                            sendTempImage()
                        })
                        var snapProcessTimeout = setTimeout(function(){
                            processKill(snapProcess)
                        },dynamicTimeout)
                    }catch(err){
                        console.log(err)
                    }
                }
                if(url){
                    runExtraction()
                }else{
                    checkExists(streamDir + 's.jpg',function(success){
                        if(success === false){
                            checkExists(streamDir + 'detectorStream.m3u8',function(success){
                                if(success === false){
                                    checkExists(streamDir + 's.m3u8',function(success){
                                        if(success === false){
                                            switch(monitor.type){
                                                case'h264':
                                                    switch(monitor.protocol){
                                                        case'rtsp':
                                                            if(
                                                                monitor.details.rtsp_transport
                                                                && monitor.details.rtsp_transport !== ''
                                                                && monitor.details.rtsp_transport !== 'no'
                                                            ){
                                                                inputOptions.push('-rtsp_transport ' + monitor.details.rtsp_transport)
                                                            }
                                                        break;
                                                    }
                                                break;
                                            }
                                            url = s.buildMonitorUrl(monitor)
                                        }else{
                                            outputOptions.push(`-ss 00:00:${secondsInward}`)
                                            url = streamDir + 's.m3u8'
                                        }
                                        runExtraction()
                                    })
                                }else{
                                    isDetectorStream = true
                                    outputOptions.push(`-ss 00:00:${secondsInward}`)
                                    url = streamDir + 'detectorStream.m3u8'
                                    runExtraction()
                                }
                            })
                        }else{
                            fs.promises.readFile(streamDir + 's.jpg').then(function(snapBuffer){
                                resolve({
                                    screenShot: snapBuffer,
                                    isStaticFile: true
                                })
                            }).catch(() => {
                                sendTempImage()
                            })
                        }
                    })
                }
            }
            if(options.useIcon === true){
                checkExists(streamDir + 'icon.jpg',function(success){
                    if(success === false){
                        noIconChecks()
                    }else{
                        var snapBuffer = fs.readFileSync(streamDir + 'icon.jpg')
                        resolve({
                            screenShot: snapBuffer,
                            isStaticFile: false
                        })
                    }
                })
            }else{
                noIconChecks()
            }
        })
    }
    s.mergeDetectorBufferChunks = function(monitor,callback){
        return new Promise((resolve,reject) => {
            var pathDir = s.dir.streams+monitor.ke+'/'+monitor.id+'/'
            var mergedFile = s.formattedTime()+'.mp4'
            var mergedFilepath = pathDir+mergedFile
            fs.readdir(pathDir,function(err,streamDirItems){
                var items = []
                var copiedItems = []
                var videoLength = s.group[monitor.ke].rawMonitorConfigurations[monitor.id].details.detector_send_video_length
                if(!videoLength || videoLength === '')videoLength = '10'
                if(videoLength.length === 1)videoLength = '0' + videoLength
                var createMerged = function(copiedItems){
                    var allts = pathDir+items.join('_')
                    s.fileStats(allts,function(err,stats){
                        if(err){
                            //not exist
                            var cat = 'cat '+copiedItems.join(' ')+' > '+allts
                            exec(cat,function(){
                                var merger = spawn(config.ffmpegDir,splitForFFMPEG(('-re -i '+allts+' -acodec copy -vcodec copy -t 00:00:' + videoLength + ' '+pathDir+mergedFile)))
                                merger.stderr.on('data',function(data){
                                    s.userLog(monitor,{type:"Buffer Merge",msg:data.toString()})
                                })
                                merger.on('close',function(){
                                    s.file('delete',allts)
                                    copiedItems.forEach(function(copiedItem){
                                        s.file('delete',copiedItem)
                                    })
                                    setTimeout(function(){
                                        s.file('delete',mergedFilepath)
                                    },1000 * 60 * 3)
                                    delete(merger)
                                    if(callback)callback(mergedFilepath,mergedFile)
                                    resolve({
                                        filePath: mergedFilepath,
                                        filename: mergedFile,
                                    })
                                })
                            })
                        }else{
                            //file exist
                            if(callback)callback(mergedFilepath,mergedFile)
                            resolve({
                                filePath: mergedFilepath,
                                filename: mergedFile,
                            })
                        }
                    })
                }
                streamDirItems.forEach(function(filename){
                    if(filename.indexOf('detectorStream') > -1 && filename.indexOf('.m3u8') === -1){
                        items.push(filename)
                    }
                })
                items.sort()
                // items = items.slice(items.length - 5,items.length)
                items.forEach(function(filename){
                    try{
                        var tempFilename = filename.split('.')
                        tempFilename[0] = tempFilename[0] + 'm'
                        tempFilename = tempFilename.join('.')
                        var tempWriteStream = fs.createWriteStream(pathDir+tempFilename)
                        tempWriteStream.on('finish', function(){
                            copiedItems.push(pathDir+tempFilename)
                            if(copiedItems.length === items.length){
                                createMerged(copiedItems.sort())
                            }
                        })
                        fs.createReadStream(pathDir+filename).pipe(tempWriteStream)
                    }catch(err){

                    }
                })
            })
        })
    }
    s.mergeRecordedVideos = function(videoRows,groupKey,callback){
        var tempDir = s.dir.streams + groupKey + '/'
        var pathDir = s.dir.fileBin + groupKey + '/'
        var streamDirItems = fs.readdirSync(pathDir)
        var items = []
        var mergedFile = []
        videoRows.forEach(function(video){
            var filepath = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
            if(
                filepath.indexOf('.mp4') > -1
                // || filename.indexOf('.webm') > -1
            ){
                mergedFile.push(s.formattedTime(video.time))
                items.push(filepath)
            }
        })
        mergedFile.sort()
        mergedFile = mergedFile.join('_') + '.mp4'
        var mergedFilepath = pathDir + mergedFile
        var mergedRawFilepath = pathDir + 'raw_' + mergedFile
        items.sort()
        s.fileStats(mergedFilepath,function(err,stats){
            if(err){
                //not exist
                var tempScriptPath = tempDir + s.gid(5) + '.sh'
                var cat = 'cat '+items.join(' ')+' > '+mergedRawFilepath
                fs.writeFileSync(tempScriptPath,cat,'utf8')
                exec('sh ' + tempScriptPath,function(){
                    s.userLog({
                        ke: groupKey,
                        mid: '$USER'
                    },{type:lang['Videos Merge'],msg:mergedFile})
                    var merger = spawn(config.ffmpegDir,splitForFFMPEG(('-re -loglevel warning -i ' + mergedRawFilepath + ' -acodec copy -vcodec copy ' + mergedFilepath)))
                    merger.stderr.on('data',function(data){
                        s.userLog({
                            ke: groupKey,
                            mid: '$USER'
                        },{type:lang['Videos Merge'],msg:data.toString()})
                    })
                    merger.on('close',function(){
                        s.file('delete',mergedRawFilepath)
                        s.file('delete',tempScriptPath)
                        setTimeout(function(){
                            s.fileStats(mergedFilepath,function(err,stats){
                                if(!err)s.file('delete',mergedFilepath)
                            })
                        },1000 * 60 * 60 * 24)
                        delete(merger)
                        callback(mergedFilepath,mergedFile)
                    })
                })
            }else{
                //file exist
                callback(mergedFilepath,mergedFile)
            }
        })
        return items
    }
    s.cameraControlOptionsFromUrl = function(e,monitorConfig){
        URLobject = URL.parse(e)
        if(monitorConfig.details.control_url_method === 'ONVIF' && monitorConfig.details.control_base_url === ''){
            if(monitorConfig.details.onvif_port === ''){
                monitorConfig.details.onvif_port = 8000
            }
            URLobject.port = monitorConfig.details.onvif_port
        }else if(!URLobject.port){
            URLobject.port = 80
        }
        const options = {
            host: URLobject.hostname,
            port: URLobject.port,
            method: monitorConfig.details.control_url_method
        }
        const queryStringObjects = queryStringToObject(URLobject.query || "")
        if (queryStringObjects && queryStringObjects.postData) {
            options.postData = decodeURIComponent(queryStringObjects.postData)
            options.path = URLobject.pathname + '?' + decodeURIComponent(createQueryStringFromObject(Object.assign(queryStringObjects,{postData: null})))
        } else if(URLobject.query){
            options.path = URLobject.pathname + '?' + URLobject.query
        } else {
            options.path = URLobject.pathname
        }
        if(URLobject.username&&URLobject.password){
            options.username = URLobject.username
            options.password = URLobject.password
            options.auth=URLobject.username+':'+URLobject.password
        }else if(URLobject.auth){
            var auth = URLobject.auth.split(':')
            options.auth=URLobject.auth
            options.username = auth[0]
            options.password = auth[1]
        }
        return options
    }
    s.cameraSendSnapshot = async (e,options) => {
        options = Object.assign({
            flags: '-s 500x500'
        },options || {})
        s.checkDetails(e)
        if(e.ke && config.doSnapshot === true){
            if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
                async function getRaw(){
                    var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
                    const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
                    if(screenShot){
                        s.tx({
                            f: 'monitor_snapshot',
                            snapshot: screenShot.toString('base64'),
                            snapshot_format: 'b64',
                            mid: e.mid,
                            ke: e.ke
                        },'GRP_'+e.ke)
                    }else{
                        s.debugLog('Damaged Snapshot Data')
                        s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
                    }
                }
                if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
                    try{
                        const screenShot = await s.getSnapshotFromOnvif({
                            ke: e.ke,
                            mid: e.mid,
                        });
                        s.tx({
                            f: 'monitor_snapshot',
                            snapshot: screenShot.toString('base64'),
                            snapshot_format: 'b64',
                            mid: e.mid,
                            ke: e.ke
                        },'GRP_'+e.ke)
                    }catch(err){
                        s.debugLog(err)
                        await getRaw()
                    }
                }else{
                    await getRaw()
                }
            }else{
                s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
            }
        }else{
            s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
        }
    }
    s.getCameraSnapshot = async (e,options) => {
        const getDefaultImage = async () => {
            return await fs.promises.readFile(config.defaultMjpeg)
        }
        options = Object.assign({
            flags: '-s 500x500'
        },options || {})
        if(e.ke && config.doSnapshot === true){
            if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
                var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
                const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
                if(screenShot){
                    return screenShot
                }else{
                    return await getDefaultImage()
               }
            }else{
                return await getDefaultImage()
            }
        }else{
            return await getDefaultImage()
        }
    }
    s.addOrEditMonitor = async function(form,callback,user){
        var endData = {
            ok: false
        }
        if(!form.mid){
            endData.msg = lang['No Monitor ID Present in Form']
            if(callback)callback(endData);
            resolve(endData)
            return
        }
        form.mid = form.mid.replace(/[^\w\s]/gi,'').replace(/ /g,'')
        const selectResponse = await s.knexQueryPromise({
            action: "select",
            columns: "*",
            table: "Monitors",
            where: [
                ['ke','=',form.ke],
                ['mid','=',form.mid],
            ]
        });
        const monitorExists = selectResponse.rows && selectResponse.rows[0];
        var affectMonitor = false
        var monitorQuery = {}
        var txData = {
            f: 'monitor_edit',
            mid: form.mid,
            ke: form.ke,
            mon: form
        }
        // auto correct
        const {
            configPartial,
            warnings,
            probeResponse,
            probeStreams,
        } = await getWarningChangesForMonitor(form)
        applyPartialToConfiguration(form,configPartial)
        form = s.cleanMonitorObjectForDatabase(form)
        //
        if(monitorExists){
            txData.new = false
            Object.keys(form).forEach(function(v){
                if(
                    form[v] !== undefined &&
                    form[v] !== `undefined` &&
                    form[v] !== null &&
                    form[v] !== `null` &&
                    form[v] !== false &&
                    form[v] !== `false`
                ){
                    if(form[v] instanceof Object){
                        form[v] = s.s(form[v])
                    }
                    monitorQuery[v] = form[v]
                }
            })
            s.userLog(form,{type:'Monitor Updated',msg:'by user : '+user.uid})
            endData.msg = user.lang['Monitor Updated by user']+' : '+user.uid
            s.knexQuery({
                action: "update",
                table: "Monitors",
                update: monitorQuery,
                where: [
                    ['ke','=',form.ke],
                    ['mid','=',form.mid],
                ]
            })
            affectMonitor = true
        }else if(isGroupBelowMaxMonitorCount(form.ke)){
            txData.new = true
            Object.keys(form).forEach(function(v){
                if(form[v] && form[v] !== ''){
                    if(form[v] instanceof Object){
                        form[v] = s.s(form[v])
                    }
                    monitorQuery[v] = form[v]
                }
            })
            s.userLog(form,{type:'Monitor Added',msg:'by user : '+user.uid})
            endData.msg = user.lang['Monitor Added by user']+' : '+user.uid
            s.knexQuery({
                action: "insert",
                table: "Monitors",
                insert: monitorQuery
            })
            affectMonitor = true
        }else{
            txData.f = 'monitor_edit_failed'
            txData.ff = 'max_reached'
            endData.msg = user.lang.monitorEditFailedMaxReached
        }
        if(affectMonitor === true){
            form.details = JSON.parse(form.details)
            endData.ok = true
            s.initiateMonitorObject({mid:form.mid,ke:form.ke})
            s.group[form.ke].rawMonitorConfigurations[form.mid] = s.cleanMonitorObject(form)
            if(form.mode === 'stop'){
                await s.camera('stop',form)
            }else{
                let monitorConfig = copyMonitorConfiguration(form.ke,form.mid)
                await s.camera('stop',monitorConfig);
                await asyncSetTimeout(2500)
                await s.camera(form.mode,monitorConfig);
            }
            s.tx(txData,'STR_'+form.ke)
        }
        s.tx(txData,'GRP_'+form.ke)
        if(callback)callback(!endData.ok,endData);
        let monitorConfig = copyMonitorConfiguration(form.ke,form.mid)
        s.onMonitorSaveExtensions.forEach(function(extender){
            extender(monitorConfig,form,endData)
        })
        return endData
    }
    s.camera = async (selectedMode,e,cn) => {
        // e = monitor object
        // cn = socket connection or callback or options (depends on function chosen)
        if(cn && cn.ke && !e.ke){e.ke = cn.ke}
        const groupKey = e.ke
        const monitorId = e.mid || e.id
        e.functionMode = selectedMode
        if(!e.mode){e.mode = selectedMode}
        s.checkDetails(e)
        s.initiateMonitorObject({ke:e.ke,mid:monitorId})
        checkObjectsInMonitorDetails(e)
        switch(e.functionMode){
            case'watch_on':
                monitorAddViewer(e,cn)
            break;
            case'watch_off':
                monitorRemoveViewer(e,cn)
            break;
            case'restart':
                await monitorRestart(e)
            break;
            case'idle':case'stop':
                await monitorStop(e)
                if(e.functionMode === 'idle'){
                    monitorIdle(e)
                }
            break;
            case'start':case'record':
                await monitorStart(e)
            break;
            default:
                console.log('No s.camera execute : ',selectedMode)
            break;
        }
        if(typeof cn === 'function'){cn()}
    }
    //
    s.activateMonitorStates = function(groupKey,stateName,user,callback){
        var endData = {
            ok: false
        }
        s.findPreset([groupKey,'monitorStates',stateName],function(notFound,preset){
            if(notFound === false){
                var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND '
                var monitorQuery = []
                var monitorPresets = {}
                preset.details.monitors.forEach(function(monitor){
                    const whereConditions = {}
                    if(monitorQuery.length === 0){
                        whereConditions.ke = groupKey
                        monitorQuery.push(['ke','=',groupKey])
                    }else{
                        monitorQuery.push(['or','ke','=',groupKey])
                    }
                    monitorQuery.push(['mid','=',monitor.mid])
                    monitorPresets[monitor.mid] = monitor
                })
                s.knexQuery({
                    action: "select",
                    columns: "*",
                    table: "Monitors",
                    where: monitorQuery
                },function(err,monitors){
                    if(monitors && monitors[0]){
                        monitors.forEach(function(monitor){
                            s.checkDetails(monitor)
                            s.checkDetails(monitorPresets[monitor.mid])
                            var monitorPreset = monitorPresets[monitor.mid]
                            monitorPreset.details = Object.assign(monitor.details,monitorPreset.details)
                            monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset))
                            monitor.details = JSON.stringify(monitor.details)
                            s.addOrEditMonitor(Object.assign({},monitor),null,user)
                        })
                        endData.ok = true
                        s.tx({f:'change_group_state',ke:groupKey,name:stateName},'GRP_'+groupKey)
                        callback(endData)
                    }else{
                        endData.msg = user.lang['State Configuration has no monitors associated']
                        callback(endData)
                    }
                })
            }else{
                endData.msg = user.lang['State Configuration Not Found']
                callback(endData)
            }
        })
    }
    s.getMonitorRestrictions = (permissions,monitorId) => {
        const monitorRestrictions = []
        if(
            !monitorId &&
            permissions.sub &&
            permissions.monitors &&
            permissions.allmonitors !== '1'
        ){
            try{
                permissions.monitors = s.parseJSON(permissions.monitors)
                permissions.monitors.forEach(function(v,n){
                    if(n === 0){
                        monitorRestrictions.push(['mid','=',v])
                    }else{
                        monitorRestrictions.push(['or','mid','=',v])
                    }
                })
                console.log(monitorRestrictions)
            }catch(er){
            }
        }else if(
            monitorId && (
                !permissions.sub ||
                permissions.allmonitors !== '0' ||
                permissions.monitors.indexOf(monitorId) >- 1
            )
        ){
            monitorRestrictions.push(['mid','=',monitorId])
        }else if(
            !monitorId &&
            permissions.sub &&
            permissions.allmonitors !== '0'
        ){}
        return monitorRestrictions
    }
    s.checkPermission = (user) => {
        // provide "user" object given from "s.auth"
        const isSubAccount = !!user.details.sub
        const isApiKey = !user.login_type;
        const isSessionKey = user.isSessionKey;
        const response = {
            isSubAccount,
            hasAllPermissions: isSubAccount && user.details.allmonitors === '1',
            isRestricted: isSubAccount && user.details.allmonitors !== '1',
            isRestrictedApiKey: false,
            apiKeyPermissions: {},
            userPermissions: {},
        }
        const permissions = user.permissions
        const details = user.details;
        [
            'auth_socket',
            'get_monitors',
            'control_monitors',
            'get_logs',
            'watch_stream',
            'watch_snapshot',
            'watch_videos',
            'delete_videos',
        ].forEach((key) => {
            const permissionOff = !isSessionKey && isApiKey && permissions[key] !== '1';
            response.apiKeyPermissions[key] = isSessionKey || permissions[key] === '1';
            response.apiKeyPermissions[`${key}_disallowed`] = permissionOff;
            response.isRestrictedApiKey = response.isRestrictedApiKey || permissionOff;
        });
        // Base Level Permissions
            // allmonitors : All Monitors and Privileges
            // monitor_create : Can Create and Delete Monitors
            // user_change : Can Change User Settings
            // view_logs : Can View Logs
        [
            'allmonitors',
            'monitor_create',
            'user_change',
            'view_logs',
        ].forEach((key) => {
            response.userPermissions[key] = details[key] === '1' || !details[key];
            response.userPermissions[`${key}_disallowed`] = details[key] === '0';
        });
        return response
    }
    s.getMonitorsPermitted = (userDetails,monitorId,permissionTarget) => {
        const monitorRestrictions = []
        const monitors = {}
        permissionTarget = permissionTarget || 'monitors'
        const permissionSet = s.parseJSON(userDetails[permissionTarget]) || []
        // const viewOnlyCheck = permissionTarget === 'monitors'
        function setMonitorPermissions(mid){
            // monitors : Can View Monitor
            // monitor_edit : Can Edit Monitor (Delete as well)
            // video_view : Can View Videos and Events
            // video_delete : Can Delete Videos and Events
            [
                'monitors',
                'monitor_edit',
                'video_view',
                'video_delete',
            ].forEach((key) => {
                monitors[`${mid}_${key}`] = userDetails[key] && userDetails[key].indexOf(mid) > -1 || false;
            });
            return true
        }
        function addToQuery(mid,n){
            if(n === 0){
                monitorRestrictions.push(['mid','=',mid])
            }else{
                monitorRestrictions.push(['or','mid','=',mid])
            }
        };
        if(
            !monitorId &&
            userDetails.sub &&
            permissionSet &&
            userDetails.allmonitors !== '1'
        ){
            try{
                permissionSet.forEach(function(v,n){
                    setMonitorPermissions(v)
                    addToQuery(v,n)
                })
            }catch(err){
                s.debugLog(err)
            }
        }else if(
            monitorId && (
                !userDetails.sub ||
                userDetails.allmonitors !== '0' ||
                permissionSet.indexOf(monitorId) >- 1
            )
        ){
            setMonitorPermissions(monitorId)
            addToQuery(monitorId,0)
        }
        return {
            monitorPermissions: monitors,
            // queryConditions
            monitorRestrictions: monitorRestrictions,
        }
    }
}
