var express = require('express');
var fs = require('fs');
var bodyParser = require('body-parser');
var os = require('os');
var moment = require('moment');
var request = require('request');
var execSync = require('child_process').execSync;
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var httpProxy = require('http-proxy');
var onvif = require('node-onvif');
var proxy = httpProxy.createProxyServer({})
var ejs = require('ejs');
var CircularJSON = require('circular-json');
module.exports = function(s,config,lang,app,io){
    if(config.productType==='Pro'){
        var LdapAuth = require('ldapauth-fork');
    }
    s.renderPage = function(req,res,paths,passables,callback){
        passables.window = {}
        passables.data = req.params
        passables.originalURL = s.getOriginalUrl(req)
        passables.baseUrl = req.protocol+'://'+req.hostname
        passables.config = s.getConfigWithBranding(req.hostname)
        res.render(paths,passables,callback)
    }
    //child node proxy check
    //params = parameters
    //cb = callback
    //res = response, only needed for express (http server)
    //request = request, only needed for express (http server)
    s.checkChildProxy = function(params,cb,res,req){
        if(s.group[params.ke] && s.group[params.ke].activeMonitors[params.id] && s.group[params.ke].activeMonitors[params.id].childNode){
            var url = 'http://' + s.group[params.ke].activeMonitors[params.id].childNode// + req.originalUrl
            proxy.web(req, res, { target: url })
        }else{
            cb()
        }
    }
    s.closeJsonResponse = function(res,endData){
        res.setHeader('Content-Type', 'application/json')
        res.end(s.prettyPrint(endData))
    }
    //get post data
    s.getPostData = function(req,target,parseJSON){
        if(!target)target = 'data'
        if(!parseJSON)parseJSON = true
        var postData = false
        if(req.query && req.query[target]){
            postData = req.query[target]
        }else{
            postData = req.body[target]
        }
        if(parseJSON === true){
            postData = s.parseJSON(postData)
        }
        return postData
    }
    //get client ip address
    s.getClientIp = function(req){
        return req.headers['cf-connecting-ip']||req.headers["CF-Connecting-IP"]||req.headers["'x-forwarded-for"]||req.connection.remoteAddress;
    }
    ////Pages
    app.enable('trust proxy');
    if(config.webPaths.home !== '/'){
        app.use('/libs',express.static(s.mainDirectory + '/web/libs'))
    }
    app.use(s.checkCorrectPathEnding(config.webPaths.home)+'libs',express.static(s.mainDirectory + '/web/libs'))
    app.use(s.checkCorrectPathEnding(config.webPaths.admin)+'libs',express.static(s.mainDirectory + '/web/libs'))
    app.use(s.checkCorrectPathEnding(config.webPaths.super)+'libs',express.static(s.mainDirectory + '/web/libs'))
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({extended: true}));
    app.use(function (req,res,next){
        res.header("Access-Control-Allow-Origin",req.headers.origin);
        next()
    })
    app.set('views', s.mainDirectory + '/web');
    app.set('view engine','ejs');
    //add template handler
    if(config.renderPaths.handler!==undefined){require(s.mainDirectory+'/web/'+config.renderPaths.handler+'.js').addHandlers(s,app,io,config)}

    /**
    * API : Logout
    */
    app.get(config.webPaths.apiPrefix+':auth/logout/:ke/:id', function (req,res){
        if(s.group[req.params.ke]&&s.group[req.params.ke].users[req.params.auth]){
            delete(s.api[req.params.auth]);
            delete(s.group[req.params.ke].users[req.params.auth]);
            s.sqlQuery("UPDATE Users SET auth=? WHERE auth=? AND ke=? AND uid=?",['',req.params.auth,req.params.ke,req.params.id])
            res.end(s.prettyPrint({ok:true,msg:'You have been logged out, session key is now inactive.'}))
        }else{
            res.end(s.prettyPrint({ok:false,msg:'This group key does not exist or this user is not logged in.'}))
        }
    });
    /**
    * Page : Login Screen
    */
    app.get(config.webPaths.home, function (req,res){
        s.renderPage(req,res,config.renderPaths.index,{lang:lang,config: s.getConfigWithBranding(req.hostname),screen:'dashboard'})
    });
    /**
    * Page : Administrator Login Screen
    */
    app.get(config.webPaths.admin, function (req,res){
        s.renderPage(req,res,config.renderPaths.index,{lang:lang,config: s.getConfigWithBranding(req.hostname),screen:'admin'})
    });
    /**
    * Page : Superuser Login Screen
    */
    app.get(config.webPaths.super, function (req,res){
        s.renderPage(req,res,config.renderPaths.index,{
            lang: lang,
            config: s.getConfigWithBranding(req.hostname),
            screen: 'super'
        })
    });
    /**
    * API : Get User Info
    */
    app.get(config.webPaths.apiPrefix+':auth/userInfo/:ke',function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            req.ret.ok=true
            req.ret.user=user
            res.end(s.prettyPrint(req.ret));
        },res,req);
    })
    //login function
    s.deleteFactorAuth=function(r){
        delete(s.factorAuth[r.ke][r.uid])
        if(Object.keys(s.factorAuth[r.ke]).length===0){
            delete(s.factorAuth[r.ke])
        }
    }
    /**
    * API : Login handler. Dashboard, Streamer, Dashcam Administrator, Superuser
    */
    app.post([
        config.webPaths.home,
        config.webPaths.admin,
        config.webPaths.super,
        s.checkCorrectPathEnding(config.webPaths.home)+':screen',
        s.checkCorrectPathEnding(config.webPaths.admin)+':screen',
        s.checkCorrectPathEnding(config.webPaths.super)+':screen',
    ],function (req,res){
        req.ip = s.getClientIp(req)
        var screenChooser = function(screen){
            var search = function(screen){
                if(req.url.indexOf(screen) > -1){
                    return true
                }
                return false
            }
            switch(true){
                case search(config.webPaths.admin):
                    return 'admin'
                break;
                case search(config.webPaths.super):
                    return 'super'
                break;
                default:
                    return 'dashboard'
                break;
            }
        }
        // brute check
        if(s.failedLoginAttempts[req.body.mail] && s.failedLoginAttempts[req.body.mail].failCount >= 5){
            if(req.query.json=='true'){
                res.end(s.prettyPrint({ok:false}))
            }else{
                s.renderPage(req,res,config.renderPaths.index,{
                    failedLogin: true,
                    message: lang.failedLoginText1,
                    lang: s.copySystemDefaultLanguage(),
                    config: s.getConfigWithBranding(req.hostname),
                    screen: screenChooser(req.params.screen)
                })
            }
            return false
        }
        //
        renderPage = function(focus,data){
            if(s.failedLoginAttempts[req.body.mail]){
                clearTimeout(s.failedLoginAttempts[req.body.mail].timeout)
                delete(s.failedLoginAttempts[req.body.mail])
            }
            if(req.query.json=='true'){
                delete(data.config)
                data.ok=true;
                res.setHeader('Content-Type', 'application/json');
                res.end(s.prettyPrint(data))
            }else{
                data.screen=req.params.screen
                s.renderPage(req,res,focus,data)
            }
        }
        failedAuthentication = function(board){
            // brute protector
            if(!s.failedLoginAttempts[req.body.mail]){
                s.failedLoginAttempts[req.body.mail] = {
                    failCount : 0,
                    ips : {}
                }
            }
            ++s.failedLoginAttempts[req.body.mail].failCount
            if(!s.failedLoginAttempts[req.body.mail].ips[req.ip]){
                s.failedLoginAttempts[req.body.mail].ips[req.ip] = 0
            }
            ++s.failedLoginAttempts[req.body.mail].ips[req.ip]
            clearTimeout(s.failedLoginAttempts[req.body.mail].timeout)
            s.failedLoginAttempts[req.body.mail].timeout = setTimeout(function(){
                delete(s.failedLoginAttempts[req.body.mail])
            },1000 * 60 * 15)
            // check if JSON
            if(req.query.json === 'true'){
                res.setHeader('Content-Type', 'application/json')
                res.end(s.prettyPrint({ok:false}))
            }else{
                s.renderPage(req,res,config.renderPaths.index,{
                    failedLogin: true,
                    message: lang.failedLoginText2,
                    lang: s.copySystemDefaultLanguage(),
                    config: s.getConfigWithBranding(req.hostname),
                    screen: screenChooser(req.params.screen)
                })
            }
            var logTo = {
                ke: '$',
                mid: '$USER'
            }
            var logData = {
                type: lang['Authentication Failed'],
                msg: {
                    for: board,
                    mail: req.body.mail,
                    ip: req.ip
                }
            }
            if(board==='super'){
                s.userLog(logTo,logData)
            }else{
                s.sqlQuery('SELECT ke,uid,details FROM Users WHERE mail=?',[req.body.mail],function(err,r) {
                    if(r&&r[0]){
                        r = r[0]
                        r.details=JSON.parse(r.details);
                        r.lang=s.getLanguageFile(r.details.lang)
                        logData.id=r.uid
                        logData.type=r.lang['Authentication Failed']
                        logTo.ke = r.ke
                    }
                    s.userLog(logTo,logData)
                })
            }
        }
        checkRoute = function(r){
            switch(req.body.function){
                case'cam':
                    s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND type=?',[r.ke,"dashcam"],function(err,rr){
                        req.resp.mons=rr;
                        renderPage(config.renderPaths.dashcam,{
                            // config: s.getConfigWithBranding(req.hostname),
                            $user: req.resp,
                            lang: r.lang,
                            define: s.getDefinitonFile(r.details.lang),
                            customAutoLoad: s.customAutoLoadTree
                        })
                    })
                break;
                case'streamer':
                    s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND type=?',[r.ke,"socket"],function(err,rr){
                        req.resp.mons=rr;
                        renderPage(config.renderPaths.streamer,{
                            // config: s.getConfigWithBranding(req.hostname),
                            $user: req.resp,
                            lang: r.lang,
                            define: s.getDefinitonFile(r.details.lang),
                            customAutoLoad: s.customAutoLoadTree
                        })
                    })
                break;
                case'admin':
                    if(!r.details.sub){
                        s.sqlQuery('SELECT uid,mail,details FROM Users WHERE ke=? AND details LIKE \'%"sub"%\'',[r.ke],function(err,rr) {
                            s.sqlQuery('SELECT * FROM Monitors WHERE ke=?',[r.ke],function(err,rrr) {
                                renderPage(config.renderPaths.admin,{
                                    config: s.getConfigWithBranding(req.hostname),
                                    $user: req.resp,
                                    $subs: rr,
                                    $mons: rrr,
                                    lang: r.lang,
                                    define: s.getDefinitonFile(r.details.lang),
                                    customAutoLoad: s.customAutoLoadTree
                                })
                            })
                        })
                    }else{
                        //not admin user
                        var chosenRender = 'home'
                        if(r.details.landing_page && r.details.landing_page !== '' && config.renderPaths[r.details.landing_page]){
                            chosenRender = r.details.landing_page
                        }
                        renderPage(config.renderPaths[chosenRender],{
                            $user:req.resp,
                            config: s.getConfigWithBranding(req.hostname),
                            lang:r.lang,
                            define:s.getDefinitonFile(r.details.lang),
                            addStorage:s.dir.addStorage,
                            fs:fs,
                            __dirname:s.mainDirectory,
                            customAutoLoad: s.customAutoLoadTree
                        });
                    }
                break;
                default:
                    var chosenRender = 'home'
                    if(r.details.sub && r.details.landing_page && r.details.landing_page !== '' && config.renderPaths[r.details.landing_page]){
                        chosenRender = r.details.landing_page
                    }
                    renderPage(config.renderPaths[chosenRender],{
                        $user:req.resp,
                        config: s.getConfigWithBranding(req.hostname),
                        lang:r.lang,
                        define:s.getDefinitonFile(r.details.lang),
                        addStorage:s.dir.addStorage,
                        fs:fs,
                        __dirname:s.mainDirectory,
                        customAutoLoad: s.customAutoLoadTree
                    })
                break;
            }
            s.userLog({ke:r.ke,mid:'$USER'},{type:r.lang['New Authentication Token'],msg:{for:req.body.function,mail:r.mail,id:r.uid,ip:req.ip}})
        //    res.end();
        }
        if(req.body.mail&&req.body.pass){
            req.default=function(){
                s.sqlQuery('SELECT * FROM Users WHERE mail=? AND pass=?',[req.body.mail,s.createHash(req.body.pass)],function(err,r) {
                    req.resp={ok:false};
                    if(!err&&r&&r[0]){
                        r=r[0];r.auth=s.md5(s.gid());
                        s.sqlQuery("UPDATE Users SET auth=? WHERE ke=? AND uid=?",[r.auth,r.ke,r.uid])
                        req.resp={ok:true,auth_token:r.auth,ke:r.ke,uid:r.uid,mail:r.mail,details:r.details};
                        r.details=JSON.parse(r.details);
                        r.lang=s.getLanguageFile(r.details.lang)
                        req.factorAuth=function(cb){
                            req.params.auth = r.auth
                            req.params.ke = r.ke
                            if(r.details.factorAuth === "1"){
                                if(!r.details.acceptedMachines||!(r.details.acceptedMachines instanceof Object)){
                                    r.details.acceptedMachines={}
                                }
                                if(!r.details.acceptedMachines[req.body.machineID]){
                                    req.complete=function(){
                                        s.factorAuth[r.ke][r.uid].function = req.body.function
                                        s.factorAuth[r.ke][r.uid].info = req.resp
                                        clearTimeout(s.factorAuth[r.ke][r.uid].expireAuth)
                                        s.factorAuth[r.ke][r.uid].expireAuth=setTimeout(function(){
                                            s.deleteFactorAuth(r)
                                        },1000*60*15)
                                        renderPage(config.renderPaths.factorAuth,{$user:req.resp,lang:r.lang})
                                    }
                                    if(!s.factorAuth[r.ke]){s.factorAuth[r.ke]={}}
                                    if(!s.factorAuth[r.ke][r.uid]){
                                        s.factorAuth[r.ke][r.uid]={key:s.nid(),user:r}
                                        s.onTwoFactorAuthCodeNotificationExtensions.forEach(function(extender){
                                            extender(r)
                                        })
                                        req.complete()
                                    }else{
                                        req.complete()
                                    }
                                }else{
                                   checkRoute(r)
                                }
                            }else{
                               checkRoute(r)
                            }
                        }
                        if(r.details.sub){
                            s.sqlQuery('SELECT details FROM Users WHERE ke=? AND details NOT LIKE ?',[r.ke,'%"sub"%'],function(err,rr) {
                                if(rr && rr[0]){
                                    rr=rr[0];
                                    rr.details=JSON.parse(rr.details);
                                    r.details.mon_groups=rr.details.mon_groups;
                                    req.resp.details=JSON.stringify(r.details);
                                    req.factorAuth()
                                }else{
                                    failedAuthentication(req.body.function)
                                }
                            })
                        }else{
                            req.factorAuth()
                        }
                    }else{
                        failedAuthentication(req.body.function)
                    }
                })
            }
            if(LdapAuth&&req.body.function==='ldap'&&req.body.key!==''){
                s.sqlQuery('SELECT * FROM Users WHERE  ke=? AND details NOT LIKE ?',[req.body.key,'%"sub"%'],function(err,r) {
                    if(r&&r[0]){
                        r=r[0]
                        r.details=JSON.parse(r.details)
                        r.lang=s.getLanguageFile(r.details.lang)
                        if(r.details.use_ldap!=='0'&&r.details.ldap_enable==='1'&&r.details.ldap_url&&r.details.ldap_url!==''){
                            req.mailArray={}
                            req.body.mail.split(',').forEach(function(v){
                                v=v.split('=')
                                req.mailArray[v[0]]=v[1]
                            })
                            if(!r.details.ldap_bindDN||r.details.ldap_bindDN===''){
                                r.details.ldap_bindDN=req.body.mail
                            }
                            if(!r.details.ldap_bindCredentials||r.details.ldap_bindCredentials===''){
                                r.details.ldap_bindCredentials=req.body.pass
                            }
                            if(!r.details.ldap_searchFilter||r.details.ldap_searchFilter===''){
                                r.details.ldap_searchFilter=req.body.mail
                                if(req.mailArray.cn){
                                    r.details.ldap_searchFilter='cn='+req.mailArray.cn
                                }
                                if(req.mailArray.uid){
                                    r.details.ldap_searchFilter='uid='+req.mailArray.uid
                                }
                            }else{
                                r.details.ldap_searchFilter=r.details.ldap_searchFilter.replace('{{username}}',req.body.mail)
                            }
                            if(!r.details.ldap_searchBase||r.details.ldap_searchBase===''){
                                r.details.ldap_searchBase='dc=test,dc=com'
                            }
                            req.auth = new LdapAuth({
                                url:r.details.ldap_url,
                                bindDN:r.details.ldap_bindDN,
                                bindCredentials:r.details.ldap_bindCredentials,
                                searchBase:r.details.ldap_searchBase,
                                searchFilter:'('+r.details.ldap_searchFilter+')',
                                reconnect:true
                            });
                            req.auth.on('error', function (err) {
                                console.error('LdapAuth: ', err);
                            });

                            req.auth.authenticate(req.body.mail, req.body.pass, function(err, user) {
                                if(user){
                                    //found user
                                    if(!user.uid){
                                        user.uid=s.gid()
                                    }
                                    req.resp={
                                        ke:req.body.key,
                                        uid:user.uid,
                                        auth:s.createHash(s.gid()),
                                        mail:user.mail,
                                        pass:s.createHash(req.body.pass),
                                        details:JSON.stringify({
                                            sub:'1',
                                            ldap:'1',
                                            allmonitors:'1',
                                            filter: {}
                                        })
                                    }
                                    user.post=[]
                                    Object.keys(req.resp).forEach(function(v){
                                        user.post.push(req.resp[v])
                                    })
                                    s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Success'],msg:{user:user}})
                                    s.sqlQuery('SELECT * FROM Users WHERE  ke=? AND mail=?',[req.body.key,user.cn],function(err,rr){
                                        if(rr&&rr[0]){
                                            //already registered
                                            rr=rr[0]
                                            req.resp=rr;
                                            rr.details=JSON.parse(rr.details)
                                            req.resp.lang=s.getLanguageFile(rr.details.lang)
                                            s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User Authenticated'],msg:{user:user,shinobiUID:rr.uid}})
                                            s.sqlQuery("UPDATE Users SET auth=? WHERE ke=? AND uid=?",[req.resp.auth,req.resp.ke,rr.uid])
                                        }else{
                                            //new ldap login
                                            s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User is New'],msg:{info:r.lang['Creating New Account'],user:user}})
                                            req.resp.lang=r.lang
                                            s.sqlQuery('INSERT INTO Users (ke,uid,auth,mail,pass,details) VALUES (?,?,?,?,?,?)',user.post)
                                        }
                                        req.resp.details = JSON.stringify(req.resp.details)
                                        req.resp.auth_token = req.resp.auth
                                        req.resp.ok=true
                                        checkRoute(req.resp)
                                    })
                                    return
                                }
                                s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Failed'],msg:{err:err}})
                                //no user
                                req.default()
                            });

                            req.auth.close(function(err) {

                            })
                        }else{
                            req.default()
                        }
                    }else{
                        req.default()
                    }
                })
            }else{
                if(req.body.function === 'super'){
                    if(!fs.existsSync(s.location.super)){
                        res.end(lang.superAdminText)
                        return
                    }
                    var ok = s.superAuth({
                        mail: req.body.mail,
                        pass: req.body.pass,
                        users: true,
                        md5: true
                    },function(data){
                        s.sqlQuery('SELECT * FROM Logs WHERE ke=? ORDER BY `time` DESC LIMIT 30',['$'],function(err,r) {
                            if(!r){
                                r=[]
                            }
                            data.Logs = r
                            data.customAutoLoad = s.customAutoLoadTree
                            data.currentVersion = s.currentVersion
                            fs.readFile(s.location.config,'utf8',function(err,file){
                                data.plainConfig = JSON.parse(file)
                                renderPage(config.renderPaths.super,data)
                            })
                        })
                    })
                    if(ok === false){
                        failedAuthentication(req.body.function)
                    }
                }else{
                    req.default()
                }
            }
        }else{
            if(req.body.machineID&&req.body.factorAuthKey){
                if(s.factorAuth[req.body.ke]&&s.factorAuth[req.body.ke][req.body.id]&&s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
                    if(s.factorAuth[req.body.ke][req.body.id].key===req.body.factorAuthKey){
                        if(req.body.remember==="1"){
                            req.details=JSON.parse(s.factorAuth[req.body.ke][req.body.id].info.details)
                            req.lang=s.getLanguageFile(req.details.lang)
                            if(!req.details.acceptedMachines||!(req.details.acceptedMachines instanceof Object)){
                                req.details.acceptedMachines={}
                            }
                            if(!req.details.acceptedMachines[req.body.machineID]){
                                req.details.acceptedMachines[req.body.machineID]={}
                                s.sqlQuery("UPDATE Users SET details=? WHERE ke=? AND uid=?",[s.prettyPrint(req.details),req.body.ke,req.body.id])
                            }
                        }
                        req.body.function = s.factorAuth[req.body.ke][req.body.id].function
                        req.resp = s.factorAuth[req.body.ke][req.body.id].info
                        checkRoute(s.factorAuth[req.body.ke][req.body.id].user)
                    }else{
                        renderPage(config.renderPaths.factorAuth,{$user:s.factorAuth[req.body.ke][req.body.id].info,lang:req.lang});
                        res.end();
                    }
                }else{
                    failedAuthentication(lang['2-Factor Authentication'])
                }
            }else{
                failedAuthentication(lang['2-Factor Authentication'])
            }
        }
    })
    /**
    * API : Brute Protection Lock Reset by API
    */
    app.get([config.webPaths.apiPrefix+':auth/resetBruteProtection/:ke'], function (req,res){
        s.auth(req.params,function(user){
            if(s.failedLoginAttempts[user.mail]){
                clearTimeout(s.failedLoginAttempts[user.mail].timeout)
                delete(s.failedLoginAttempts[user.mail])
            }
            res.end(s.prettyPrint({ok:true}))
        })
    })
    /**
    * Page : Montage - stand alone squished view with gridstackjs
    */
    app.get([
        config.webPaths.apiPrefix+':auth/grid/:ke',
        config.webPaths.apiPrefix+':auth/grid/:ke/:group',
        config.webPaths.apiPrefix+':auth/cycle/:ke',
        config.webPaths.apiPrefix+':auth/cycle/:ke/:group'
    ], function(req,res) {
        s.auth(req.params,function(user){
            if(user.permissions.get_monitors==="0"){
                res.end(user.lang['Not Permitted'])
                return
            }

            req.params.protocol=req.protocol;
            req.sql='SELECT * FROM Monitors WHERE mode!=? AND mode!=? AND ke=?';req.ar=['stop','idle',req.params.ke];
            if(!req.params.id){
                if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                    try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                    req.or=[];
                    user.details.monitors.forEach(function(v,n){
                        req.or.push('mid=?');req.ar.push(v)
                    })
                    req.sql+=' AND ('+req.or.join(' OR ')+')'
                }
            }else{
                if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
                    req.sql+=' and mid=?';req.ar.push(req.params.id)
                }else{
                    res.end(user.lang['There are no monitors that you can view with this account.']);
                    return;
                }
            }
            s.sqlQuery(req.sql,req.ar,function(err,r){
                if(req.params.group){
                    var filteredByGroupCheck = {};
                    var filteredByGroup = [];
                    r.forEach(function(v,n){
                        var details = JSON.parse(r[n].details);
                        try{
                            req.params.group.split('|').forEach(function(group){
                                var groups = JSON.parse(details.groups);
                                if(groups.indexOf(group) > -1 && !filteredByGroupCheck[v.mid]){
                                    filteredByGroupCheck[v.mid] = true;
                                    filteredByGroup.push(v)
                                }
                            })
                        }catch(err){

                        }
                    })
                    r = filteredByGroup;
                }
                r.forEach(function(v,n){
                    if(s.group[v.ke]&&s.group[v.ke].activeMonitors[v.mid]&&s.group[v.ke].activeMonitors[v.mid].watch){
                        r[n].currentlyWatching=Object.keys(s.group[v.ke].activeMonitors[v.mid].watch).length
                    }
                    r[n].subStream={}
                    var details = JSON.parse(r[n].details)
                    if(details.snap==='1'){
                        r[n].subStream.jpeg = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
                    }
                    if(details.stream_channels&&details.stream_channels!==''){
                        try{
                            details.stream_channels=JSON.parse(details.stream_channels)
                            r[n].channels=[]
                            details.stream_channels.forEach(function(b,m){
                                var streamURL
                                switch(b.stream_type){
                                    case'mjpeg':
                                        streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+'/'+m
                                    break;
                                    case'hls':
                                        streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+'/'+m+'/s.m3u8'
                                    break;
                                    case'h264':
                                        streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+'/'+m
                                    break;
                                    case'flv':
                                        streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+'/'+m+'/s.flv'
                                    break;
                                    case'mp4':
                                        streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+'/'+m+'/s.mp4'
                                    break;
                                }
                                r[n].channels.push(streamURL)
                            })
                        }catch(err){
                            s.userLog(req.params,{type:'Broken Monitor Object',msg:'Stream Channels Field is damaged. Skipping.'})
                        }
                    }
                })
                var page = config.renderPaths.grid
                if(req.path.indexOf('/cycle/') > -1){
                    page = config.renderPaths.cycle
                }
                s.renderPage(req,res,page,{
                    data:Object.assign(req.params,req.query),
                    baseUrl:req.protocol+'://'+req.hostname,
                    config: s.getConfigWithBranding(req.hostname),
                    lang:user.lang,
                    $user:user,
                    monitors:r,
                    query:req.query
                });
            })
        },res,req)
    });
    /**
    * API : Get TV Channels (Monitor Streams) json
     */
    app.get([config.webPaths.apiPrefix+':auth/tvChannels/:ke',config.webPaths.apiPrefix+':auth/tvChannels/:ke/:id','/get.php'], function (req,res){
        req.ret={ok:false};
        if(req.query.username&&req.query.password){
            req.params.username = req.query.username
            req.params.password = req.query.password
        }
        var output = ['h264','hls','mp4']
        if(req.query.output&&req.query.output!==''){
            output = req.query.output.split(',')
            output.forEach(function(type,n){
                if(type==='ts'){
                    output[n]='h264'
                    if(output.indexOf('hls')===-1){
                        output.push('hls')
                    }
                }
            })
        }
        var isM3u8 = false;
        if(req.query.type==='m3u8'||req.query.type==='m3u_plus'){
            //is m3u8
            isM3u8 = true;
        }else{
            res.setHeader('Content-Type', 'application/json');
        }
        req.fn=function(user){
            if(user.permissions.get_monitors==="0"){
                res.end(s.prettyPrint([]))
                return
            }
            if(!req.params.ke){
                req.params.ke = user.ke;
            }
            if(req.query.id&&!req.params.id){
                req.params.id = req.query.id;
            }
            req.sql='SELECT * FROM Monitors WHERE mode!=? AND ke=?';req.ar=['stop',req.params.ke];
            if(!req.params.id){
                if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                    try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                    req.or=[];
                    user.details.monitors.forEach(function(v,n){
                        req.or.push('mid=?');req.ar.push(v)
                    })
                    req.sql+=' AND ('+req.or.join(' OR ')+')'
                }
            }else{
                if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
                    req.sql+=' and mid=?';req.ar.push(req.params.id)
                }else{
                    res.end('[]');
                    return;
                }
            }
            s.sqlQuery(req.sql,req.ar,function(err,r){
                var tvChannelMonitors = [];
                r.forEach(function(v,n){
                    var buildStreamURL = function(channelRow,type,channelNumber){
                        var streamURL
                        if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''}
                        switch(type){
                            case'mjpeg':
                                streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+channelNumber
                            break;
                            case'hls':
                                streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+channelNumber+'/s.m3u8'
                            break;
                            case'h264':
                                streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+channelNumber
                            break;
                            case'flv':
                                streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+channelNumber+'/s.flv'
                            break;
                            case'mp4':
                                streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.ts'
                            break;
                        }
                        if(streamURL){
                            if(!channelRow.streamsSortedByType[type]){
                                channelRow.streamsSortedByType[type]=[]
                            }
                            channelRow.streamsSortedByType[type].push(streamURL)
                            channelRow.streams.push(streamURL)
                        }
                        return streamURL
                    }
                    var details = JSON.parse(r[n].details);
                    if(!details.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5)
                    var channelRow = {
                        ke:v.ke,
                        mid:v.mid,
                        type:v.type,
                        groupTitle:details.tv_channel_group_title,
                        channel:details.tv_channel_id,
                    };
                    if(details.snap==='1'){
                        channelRow.snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
                    }
                    channelRow.streams=[]
                    channelRow.streamsSortedByType={}
                    buildStreamURL(channelRow,details.stream_type)
                    if(details.stream_channels&&details.stream_channels!==''){
                        details.stream_channels=JSON.parse(details.stream_channels)
                        details.stream_channels.forEach(function(b,m){
                            buildStreamURL(channelRow,b.stream_type,m.toString())
                        })
                    }
                    if(details.tv_channel==='1'){
                        tvChannelMonitors.push(channelRow)
                    }
                })
                if(isM3u8){
                    var m3u8 = '#EXTM3U'+'\n'
                    tvChannelMonitors.forEach(function(channelRow,n){
                      output.forEach(function(type){
                        if(channelRow.streamsSortedByType[type]){
                            if(req.query.type==='m3u_plus'){
                                m3u8 +='#EXTINF-1 tvg-id="'+channelRow.mid+'" tvg-name="'+channelRow.channel+'" tvg-logo="'+req.protocol+'://'+req.headers.host+channelRow.snapshot+'" group-title="'+channelRow.groupTitle+'",'+channelRow.channel+'\n'
                            }else{
                                m3u8 +='#EXTINF:-1,'+channelRow.channel+' ('+type.toUpperCase()+') \n'
                            }
                            m3u8 += req.protocol+'://'+req.headers.host+channelRow.streamsSortedByType[type][0]+'\n'
                        }
                      })
                    })
                    res.end(m3u8)
                }else{
                    if(tvChannelMonitors.length===1){tvChannelMonitors=tvChannelMonitors[0];}
                    res.end(s.prettyPrint(tvChannelMonitors));
                }
            })
        }
        s.auth(req.params,req.fn,res,req);
    });
    /**
    * API : Get Monitors
     */
    app.get([config.webPaths.apiPrefix+':auth/monitor/:ke',config.webPaths.apiPrefix+':auth/monitor/:ke/:id'], function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        req.fn=function(user){
        if(user.permissions.get_monitors==="0"){
            res.end(s.prettyPrint([]))
            return
        }
            req.sql='SELECT * FROM Monitors WHERE ke=?';req.ar=[req.params.ke];
            if(!req.params.id){
                if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                    try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                    req.or=[];
                    user.details.monitors.forEach(function(v,n){
                        req.or.push('mid=?');req.ar.push(v)
                    })
                    req.sql+=' AND ('+req.or.join(' OR ')+')'
                }
            }else{
                if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
                    req.sql+=' and mid=?';req.ar.push(req.params.id)
                }else{
                    res.end('[]');
                    return;
                }
            }
            s.sqlQuery(req.sql,req.ar,function(err,r){
                r.forEach(function(v,n){
                    if(s.group[v.ke] && s.group[v.ke].activeMonitors[v.mid]){
                        r[n].currentlyWatching = Object.keys(s.group[v.ke].activeMonitors[v.mid].watch).length
                        r[n].currentCpuUsage = s.group[v.ke].activeMonitors[v.mid].currentCpuUsage
                        r[n].status = s.group[v.ke].activeMonitors[v.mid].monitorStatus
                    }
                    var buildStreamURL = function(type,channelNumber){
                        var streamURL
                        if(channelNumber){channelNumber = '/'+channelNumber}else{channelNumber=''}
                        switch(type){
                            case'mjpeg':
                                streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+channelNumber
                            break;
                            case'hls':
                                streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+channelNumber+'/s.m3u8'
                            break;
                            case'h264':
                                streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+channelNumber
                            break;
                            case'flv':
                                streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+channelNumber+'/s.flv'
                            break;
                            case'mp4':
                                streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+channelNumber+'/s.mp4'
                            break;
                        }
                        if(streamURL){
                            if(!r[n].streamsSortedByType[type]){
                                r[n].streamsSortedByType[type]=[]
                            }
                            r[n].streamsSortedByType[type].push(streamURL)
                            r[n].streams.push(streamURL)
                        }
                        return streamURL
                    }
                    var details = JSON.parse(r[n].details);
                    if(!details.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5)
                    if(details.snap==='1'){
                        r[n].snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
                    }
                    r[n].streams=[]
                    r[n].streamsSortedByType={}
                    buildStreamURL(details.stream_type)
                    if(details.stream_channels&&details.stream_channels!==''){
                        details.stream_channels=s.parseJSON(details.stream_channels)
                        details.stream_channels.forEach(function(b,m){
                            buildStreamURL(b.stream_type,m.toString())
                        })
                    }
                })
                if(r.length===1){r=r[0];}
                res.end(s.prettyPrint(r));
            })
        }
        s.auth(req.params,req.fn,res,req);
    });
    /**
    * API : Merge Recorded Videos into one file
     */
     app.get(config.webPaths.apiPrefix+':auth/videosMerge/:ke', function (req,res){
         var failed = function(resp){
             res.setHeader('Content-Type', 'application/json');
             res.end(s.prettyPrint(resp))
         }
         if(req.query.videos && req.query.videos !== ''){
             s.auth(req.params,function(user){
                 var videosSelected = JSON.parse(req.query.videos)
                 var where = []
                 var values = []
                 videosSelected.forEach(function(video){
                     where.push("(ke=? AND mid=? AND `time`=?)")
                     if(!video.ke)video.ke = req.params.ke
                     values.push(video.ke)
                     values.push(video.mid)
                     var time = s.nameToTime(video.filename)
                     if(req.query.isUTC === 'true'){
                         time = s.utcToLocal(time)
                     }
                     time = new Date(time)
                     values.push(time)
                 })
                 s.sqlQuery('SELECT * FROM Videos WHERE '+where.join(' OR '),values,function(err,r){
                     var resp = {ok: false}
                     if(r && r[0]){
                         s.mergeRecordedVideos(r,req.params.ke,function(fullPath,filename){
                             res.setHeader('Content-Disposition', 'attachment; filename="'+filename+'"')
                             var file = fs.createReadStream(fullPath)
                             file.on('close',function(){
                                 setTimeout(function(){
                                     s.file('delete',fullPath)
                                 },1000 * 60 * 3)
                                 res.end()
                             })
                             file.pipe(res)
                         })
                     }else{
                         failed({ok:false,msg:'No Videos Found'})
                     }
                 })
             },res,req);
         }else{
             failed({ok:false,msg:'"videos" query variable is missing from request.'})
         }
    })
    /**
    * API : Get Videos
     */
    app.get([
        config.webPaths.apiPrefix+':auth/videos/:ke',
        config.webPaths.apiPrefix+':auth/videos/:ke/:id',
        config.webPaths.apiPrefix+':auth/cloudVideos/:ke',
        config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id'
    ], function (req,res){
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
            if(
                user.permissions.watch_videos==="0" ||
                hasRestrictions && (!user.details.video_view || user.details.video_view.indexOf(req.params.id)===-1)
            ){
                res.end(s.prettyPrint([]))
                return
            }
            var origURL = req.originalUrl.split('/')
            var videoParam = origURL[origURL.indexOf(req.params.auth) + 1]
            var videoSet = 'Videos'
            switch(videoParam){
                case'cloudVideos':
                    videoSet = 'Cloud Videos'
                break;
            }
            req.sql='SELECT * FROM `'+videoSet+'` WHERE ke=?';req.ar=[req.params.ke];
            req.count_sql='SELECT COUNT(*) FROM `'+videoSet+'` WHERE ke=?';req.count_ar=[req.params.ke];
            if(req.query.archived=='1'){
                req.sql+=' AND details LIKE \'%"archived":"1"\''
                req.count_sql+=' AND details LIKE \'%"archived":"1"\''
            }
            if(!req.params.id){
                if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                    try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                    req.or=[];
                    user.details.monitors.forEach(function(v,n){
                        req.or.push('mid=?');req.ar.push(v)
                    })
                    req.sql+=' AND ('+req.or.join(' OR ')+')'
                    req.count_sql+=' AND ('+req.or.join(' OR ')+')'
                }
            }else{
                if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
                    req.sql+=' and mid=?';req.ar.push(req.params.id)
                    req.count_sql+=' and mid=?';req.count_ar.push(req.params.id)
                }else{
                    res.end('[]');
                    return;
                }
            }
            if(req.query.start||req.query.end){
                if(req.query.start && req.query.start !== ''){
                    req.query.start = s.stringToSqlTime(req.query.start)
                }
                if(req.query.end && req.query.end !== ''){
                    req.query.end = s.stringToSqlTime(req.query.end)
                }
                if(!req.query.startOperator||req.query.startOperator==''){
                    req.query.startOperator='>='
                }
                if(!req.query.endOperator||req.query.endOperator==''){
                    req.query.endOperator='<='
                }
                var endIsStartTo
                var theEndParameter = '`end`'
                if(req.query.endIsStartTo){
                    endIsStartTo = true
                    theEndParameter = '`time`'
                }
                switch(true){
                    case(req.query.start&&req.query.start!==''&&req.query.end&&req.query.end!==''):
                        req.sql+=' AND `time` '+req.query.startOperator+' ? AND '+theEndParameter+' '+req.query.endOperator+' ?';
                        req.count_sql+=' AND `time` '+req.query.startOperator+' ? AND '+theEndParameter+' '+req.query.endOperator+' ?';
                        req.ar.push(req.query.start)
                        req.ar.push(req.query.end)
                        req.count_ar.push(req.query.start)
                        req.count_ar.push(req.query.end)
                    break;
                    case(req.query.start&&req.query.start!==''):
                        req.sql+=' AND `time` '+req.query.startOperator+' ?';
                        req.count_sql+=' AND `time` '+req.query.startOperator+' ?';
                        req.ar.push(req.query.start)
                        req.count_ar.push(req.query.start)
                    break;
                    case(req.query.end&&req.query.end!==''):
                        req.sql+=' AND '+theEndParameter+' '+req.query.endOperator+' ?';
                        req.count_sql+=' AND '+theEndParameter+' '+req.query.endOperator+' ?';
                        req.ar.push(req.query.end)
                        req.count_ar.push(req.query.end)
                    break;
                }
            }
            req.sql+=' ORDER BY `time` DESC';
            if(!req.query.limit||req.query.limit==''){
                req.query.limit='100'
            }
            if(req.query.limit!=='0'){
                req.sql+=' LIMIT '+req.query.limit
            }
            s.sqlQuery(req.sql,req.ar,function(err,r){
                if(!r){
                    res.end(s.prettyPrint({total:0,limit:req.query.limit,skip:0,videos:[]}));
                    return
                }
                s.sqlQuery(req.count_sql,req.count_ar,function(err,count){
                    s.buildVideoLinks(r,{
                        auth : req.params.auth,
                        videoParam : videoParam,
                        hideRemote : config.hideCloudSaveUrls
                    })
                    if(req.query.limit.indexOf(',')>-1){
                        req.skip=parseInt(req.query.limit.split(',')[0])
                        req.query.limit=parseInt(req.query.limit.split(',')[1])
                    }else{
                        req.skip=0
                        req.query.limit=parseInt(req.query.limit)
                    }
                    res.end(s.prettyPrint({isUTC:config.useUTC,total:count[0]['COUNT(*)'],limit:req.query.limit,skip:req.skip,videos:r,endIsStartTo:endIsStartTo}));
                })
            })
        },res,req);
    });
    /**
    * API : Get Events
     */
    app.get([
		config.webPaths.apiPrefix+':auth/events/:ke',
		config.webPaths.apiPrefix+':auth/events/:ke/:id'
	], function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_view.indexOf(req.params.id)===-1){
                res.end(s.prettyPrint([]))
                return
            }
            req.sql='SELECT * FROM Events WHERE ke=?';req.ar=[req.params.ke];
            if(!req.params.id){
                if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                    try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                    req.or=[];
                    user.details.monitors.forEach(function(v,n){
                        req.or.push('mid=?');req.ar.push(v)
                    })
                    req.sql+=' AND ('+req.or.join(' OR ')+')'
                }
            }else{
                if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
                    req.sql+=' and mid=?';req.ar.push(req.params.id)
                }else{
                    res.end('[]');
                    return;
                }
            }
            if(req.query.start||req.query.end){
                if(req.query.start && req.query.start !== ''){
                    req.query.start = s.stringToSqlTime(req.query.start)
                }
                if(req.query.end && req.query.end !== ''){
                    req.query.end = s.stringToSqlTime(req.query.end)
                }
                if(!req.query.startOperator||req.query.startOperator==''){
                    req.query.startOperator='>='
                }
                if(!req.query.endOperator||req.query.endOperator==''){
                    req.query.endOperator='<='
                }
                switch(true){
                    case(req.query.start&&req.query.start!==''&&req.query.end&&req.query.end!==''):
                        req.sql+=' AND `time` '+req.query.startOperator+' ? AND `time` '+req.query.endOperator+' ?';
                        req.ar.push(req.query.start)
                        req.ar.push(req.query.end)
                    break;
                    case(req.query.start&&req.query.start!==''):
                        req.sql+=' AND `time` '+req.query.startOperator+' ?';
                        req.ar.push(req.query.start)
                    break;
                    case(req.query.end&&req.query.end!==''):
                        req.sql+=' AND `time` '+req.query.endOperator+' ?';
                        req.ar.push(req.query.end)
                    break;
                }
            }
            req.sql+=' ORDER BY `time` DESC';
            if(!req.query.limit||req.query.limit==''){
                req.query.limit='100'
            }
            if(req.query.limit!=='0'){
                req.sql+=' LIMIT '+req.query.limit
            }
            s.sqlQuery(req.sql,req.ar,function(err,r){
                if(err){
                    err.sql=req.sql;
                    res.end(s.prettyPrint(err));
                    return
                }
                if(!r){r=[]}
                r.forEach(function(v,n){
                    r[n].details=JSON.parse(v.details);
                })
                res.end(s.prettyPrint(r));
            })
        },res,req);
    });
    /**
    * API : Get Logs
     */
    app.get([config.webPaths.apiPrefix+':auth/logs/:ke',config.webPaths.apiPrefix+':auth/logs/:ke/:id'], function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            if(user.permissions.get_logs==="0" || user.details.sub && user.details.view_logs !== '1'){
                res.end(s.prettyPrint([]))
                return
            }
            req.sql='SELECT * FROM Logs WHERE ke=?';req.ar=[req.params.ke];
            if(!req.params.id){
                if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                    try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                    req.or=[];
                    user.details.monitors.forEach(function(v,n){
                        req.or.push('mid=?');req.ar.push(v)
                    })
                    req.sql+=' AND ('+req.or.join(' OR ')+')'
                }
            }else{
                if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1||req.params.id.indexOf('$')>-1){
                    req.sql+=' and mid=?';req.ar.push(req.params.id)
                }else{
                    res.end('[]');
                    return;
                }
            }
            if(req.query.start||req.query.end){
                if(!req.query.startOperator||req.query.startOperator==''){
                    req.query.startOperator='>='
                }
                if(!req.query.endOperator||req.query.endOperator==''){
                    req.query.endOperator='<='
                }
                if(req.query.start && req.query.start !== '' && req.query.end && req.query.end !== ''){
                    req.query.start = s.stringToSqlTime(req.query.start)
                    req.query.end = s.stringToSqlTime(req.query.end)
                    req.sql+=' AND `time` '+req.query.startOperator+' ? AND `time` '+req.query.endOperator+' ?';
                    req.ar.push(req.query.start)
                    req.ar.push(req.query.end)
                }else if(req.query.start && req.query.start !== ''){
                    req.query.start = s.stringToSqlTime(req.query.start)
                    req.sql+=' AND `time` '+req.query.startOperator+' ?';
                    req.ar.push(req.query.start)
                }
            }
            if(!req.query.limit||req.query.limit==''){req.query.limit=50}
            req.sql+=' ORDER BY `time` DESC LIMIT '+req.query.limit+'';
            s.sqlQuery(req.sql,req.ar,function(err,r){
                if(err){
                    err.sql=req.sql;
                    res.end(s.prettyPrint(err));
                    return
                }
                if(!r){r=[]}
                r.forEach(function(v,n){
                    r[n].info=JSON.parse(v.info)
                })
                res.end(s.prettyPrint(r));
            })
        },res,req);
    })
    /**
    * API : Get Monitors Online
     */
    app.get(config.webPaths.apiPrefix+':auth/smonitor/:ke', function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        req.fn=function(user){
            if(user.permissions.get_monitors==="0"){
                res.end(s.prettyPrint([]))
                return
            }
            req.sql='SELECT * FROM Monitors WHERE ke=?';req.ar=[req.params.ke];
            if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                req.or=[];
                user.details.monitors.forEach(function(v,n){
                    req.or.push('mid=?');req.ar.push(v)
                })
                req.sql+=' AND ('+req.or.join(' OR ')+')'
            }
            s.sqlQuery(req.sql,req.ar,function(err,r){
                if(r&&r[0]){
                    req.ar=[];
                    r.forEach(function(v){
                        if(s.group[req.params.ke]&&s.group[req.params.ke].activeMonitors[v.mid]&&s.group[req.params.ke].activeMonitors[v.mid].isStarted === true){
                            req.ar.push(v)
                        }
                    })
                }else{
                    req.ar=[];
                }
                res.end(s.prettyPrint(req.ar));
            })
        }
        s.auth(req.params,req.fn,res,req);
    });
    /**
    * API : Monitor Mode Controller
     */
    app.get([config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff/:fff'], function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            if(user.permissions.control_monitors==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitor_edit.indexOf(req.params.id)===-1){
                res.end(user.lang['Not Permitted'])
                return
            }
            if(req.params.f===''){req.ret.msg=user.lang.monitorGetText1;res.end(s.prettyPrint(req.ret));return}
            if(req.params.f!=='stop'&&req.params.f!=='start'&&req.params.f!=='record'){
                req.ret.msg='Mode not recognized.';
                res.end(s.prettyPrint(req.ret));
                return;
            }
            s.sqlQuery('SELECT * FROM Monitors WHERE ke=? AND mid=?',[req.params.ke,req.params.id],function(err,r){
                if(r&&r[0]){
                    r=r[0];
                    if(req.query.reset==='1'||(s.group[r.ke]&&s.group[r.ke].rawMonitorConfigurations[r.mid].mode!==req.params.f)||req.query.fps&&(!s.group[r.ke].activeMonitors[r.mid].currentState||!s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on)){
                        if(req.query.reset!=='1'||!s.group[r.ke].activeMonitors[r.mid].trigger_timer){
                            if(!s.group[r.ke].activeMonitors[r.mid].currentState)s.group[r.ke].activeMonitors[r.mid].currentState={}
                            s.group[r.ke].activeMonitors[r.mid].currentState.mode=r.mode.toString()
                            s.group[r.ke].activeMonitors[r.mid].currentState.fps=r.fps.toString()
                            if(!s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on){
                               s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on=true
                            }else{
                                s.group[r.ke].activeMonitors[r.mid].currentState.trigger_on=false
                            }
                            r.mode=req.params.f;
                            try{r.details=JSON.parse(r.details);}catch(er){}
                            if(req.query.fps){
                                r.fps=parseFloat(r.details.detector_trigger_record_fps)
                                s.group[r.ke].activeMonitors[r.mid].currentState.detector_trigger_record_fps=r.fps
                            }
                            r.id=r.mid;
                            s.sqlQuery('UPDATE Monitors SET mode=? WHERE ke=? AND mid=?',[r.mode,r.ke,r.mid]);
                            s.group[r.ke].rawMonitorConfigurations[r.mid]=r;
                            s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke);
                            s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke);
                            s.camera('stop',s.cleanMonitorObject(r));
                            if(req.params.f!=='stop'){
                                s.camera(req.params.f,s.cleanMonitorObject(r));
                            }
                            req.ret.msg=user.lang['Monitor mode changed']+' : '+req.params.f;
                        }else{
                            req.ret.msg=user.lang['Reset Timer'];
                        }
                        req.ret.cmd_at=s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss');
                        req.ret.ok=true;
                        if(req.params.ff&&req.params.f!=='stop'){
                            req.params.ff=parseFloat(req.params.ff);
                            clearTimeout(s.group[r.ke].activeMonitors[r.mid].trigger_timer)
                            switch(req.params.fff){
                                case'day':case'days':
                                    req.timeout=req.params.ff*1000*60*60*24
                                break;
                                case'hr':case'hour':case'hours':
                                    req.timeout=req.params.ff*1000*60*60
                                break;
                                case'min':case'minute':case'minutes':
                                    req.timeout=req.params.ff*1000*60
                                break;
                                default://seconds
                                    req.timeout=req.params.ff*1000
                                break;
                            }
                            s.group[r.ke].activeMonitors[r.mid].trigger_timer=setTimeout(function(){
                                delete(s.group[r.ke].activeMonitors[r.mid].trigger_timer)
                                s.sqlQuery('UPDATE Monitors SET mode=? WHERE ke=? AND mid=?',[s.group[r.ke].activeMonitors[r.mid].currentState.mode,r.ke,r.mid]);
                                r.neglectTriggerTimer=1;
                                r.mode=s.group[r.ke].activeMonitors[r.mid].currentState.mode;
                                r.fps=s.group[r.ke].activeMonitors[r.mid].currentState.fps;
                                s.camera('stop',s.cleanMonitorObject(r),function(){
                                    if(s.group[r.ke].activeMonitors[r.mid].currentState.mode!=='stop'){
                                        s.camera(s.group[r.ke].activeMonitors[r.mid].currentState.mode,s.cleanMonitorObject(r));
                                    }
                                    s.group[r.ke].rawMonitorConfigurations[r.mid]=r;
                                });
                                s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'GRP_'+r.ke);
                                s.tx({f:'monitor_edit',mid:r.mid,ke:r.ke,mon:r},'STR_'+r.ke);
                            },req.timeout);
    //                        req.ret.end_at=s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss').add(req.timeout,'milliseconds');
                        }
                     }else{
                        req.ret.msg=user.lang['Monitor mode is already']+' : '+req.params.f;
                    }
                }else{
                    req.ret.msg=user.lang['Monitor or Key does not exist.'];
                }
                res.end(s.prettyPrint(req.ret));
            })
        },res,req);
    })
    /**
    * API : Get fileBin files
     */
    app.get([config.webPaths.apiPrefix+':auth/fileBin/:ke',config.webPaths.apiPrefix+':auth/fileBin/:ke/:id'],function (req,res){
        res.setHeader('Content-Type', 'application/json');
        req.fn=function(user){
            req.sql='SELECT * FROM Files WHERE ke=?';req.ar=[req.params.ke];
            if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
                try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
                req.or=[];
                user.details.monitors.forEach(function(v,n){
                    req.or.push('mid=?');req.ar.push(v)
                })
                req.sql+=' AND ('+req.or.join(' OR ')+')'
            }else{
                if(req.params.id&&(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1)){
                    req.sql+=' and mid=?';req.ar.push(req.params.id)
                }
            }
            s.sqlQuery(req.sql,req.ar,function(err,r){
                if(!r){
                    r=[]
                }else{
                    r.forEach(function(v){
                        v.details=JSON.parse(v.details)
                        v.href='/'+req.params.auth+'/fileBin/'+req.params.ke+'/'+req.params.id+'/'+v.details.year+'/'+v.details.month+'/'+v.details.day+'/'+v.name;
                    })
                }
                res.end(s.prettyPrint(r));
            })
        }
        s.auth(req.params,req.fn,res,req);
    });
    /**
    * API : Get fileBin file
     */
    app.get(config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:year/:month/:day/:file', function (req,res){
        req.fn=function(user){
            req.failed=function(){
                res.end(user.lang['File Not Found'])
            }
            if (!s.group[req.params.ke].fileBin[req.params.id+'/'+req.params.file]){
                s.sqlQuery('SELECT * FROM Files WHERE ke=? AND mid=? AND name=?',[req.params.ke,req.params.id,req.params.file],function(err,r){
                    if(r&&r[0]){
                        r=r[0]
                        r.details=JSON.parse(r.details)
                        req.dir=s.dir.fileBin+req.params.ke+'/'+req.params.id+'/'+r.details.year+'/'+r.details.month+'/'+r.details.day+'/'+req.params.file;
                        if(fs.existsSync(req.dir)){
                            res.on('finish',function(){res.end();});
                            fs.createReadStream(req.dir).pipe(res);
                        }else{
                            req.failed()
                        }
                    }else{
                        req.failed()
                    }
                })
            }else{
                res.end(user.lang['Please Wait for Completion'])
            }
        }
        s.auth(req.params,req.fn,res,req);
    });
    /**
    * API : Zip Videos and Get Link from fileBin
     */
    app.get(config.webPaths.apiPrefix+':auth/zipVideos/:ke', function (req,res){
        var failed = function(resp){
            res.setHeader('Content-Type', 'application/json');
            res.end(s.prettyPrint(resp))
        }
        if(req.query.videos && req.query.videos !== ''){
            s.auth(req.params,function(user){
                var videosSelected = JSON.parse(req.query.videos)
                var where = []
                var values = []
                videosSelected.forEach(function(video){
                    where.push("(ke=? AND mid=? AND `time`=?)")
                    if(!video.ke)video.ke = req.params.ke
                    values.push(video.ke)
                    values.push(video.mid)
                    var time = s.nameToTime(video.filename)
                    if(req.query.isUTC === 'true'){
                        time = s.utcToLocal(time)
                    }
                    time = new Date(time)
                    values.push(time)
                })
                s.sqlQuery('SELECT * FROM Videos WHERE '+where.join(' OR '),values,function(err,r){
                    var resp = {ok: false}
                    if(r && r[0]){
                        resp.ok = true
                        var zipDownload = null
                        var tempFiles = []
                        var fileId = s.gid()
                        var fileBinDir = s.dir.fileBin+req.params.ke+'/'
                        var tempScript = s.dir.streams+req.params.ke+'/'+fileId+'.sh'
                        var zippedFilename = s.formattedTime()+'-'+fileId+'-Shinobi_Recordings.zip'
                        var zippedFile = fileBinDir+zippedFilename
                        var script = 'cd '+fileBinDir+' && zip -9 -r '+zippedFile
                        res.on('close', () => {
                            if(zipDownload && zipDownload.destroy){
                                zipDownload.destroy()
                            }
                            fs.unlink(zippedFile);
                        })
                        if(!fs.existsSync(fileBinDir)){
                            fs.mkdirSync(fileBinDir);
                        }
                        r.forEach(function(video){
                            var timeFormatted = s.formattedTime(video.time)
                            video.filename = timeFormatted+'.'+video.ext
                            var dir = s.getVideoDirectory(video)+video.filename
                            var tempVideoFile = timeFormatted+' - '+video.mid+'.'+video.ext
                            fs.writeFileSync(fileBinDir+tempVideoFile, fs.readFileSync(dir))
                            tempFiles.push(fileBinDir+tempVideoFile)
                            script += ' "'+tempVideoFile+'"'
                        })
                        fs.writeFileSync(tempScript,script,'utf8')
                        var zipCreate = spawn('sh',(tempScript).split(' '),{detached: true})
                        zipCreate.stderr.on('data',function(data){
                            s.userLog({ke:req.params.ke,mid:'$USER'},{title:'Zip Create Error',msg:data.toString()})
                        })
                        zipCreate.on('exit',function(data){
                            fs.unlinkSync(tempScript)
                            tempFiles.forEach(function(file){
                                fs.unlink(file,function(){})
                            })
                            res.setHeader('Content-Disposition', 'attachment; filename="'+zippedFilename+'"')
                            var zipDownload = fs.createReadStream(zippedFile)
                            zipDownload.pipe(res)
                            zipDownload.on('error', function (error) {
                                var errorString = error.toString()
                                s.userLog({
                                    ke: req.params.ke,
                                    mid: '$USER'
                                },{
                                    title: 'Zip Download Error',
                                    msg: errorString
                                })
                                if(zipDownload && zipDownload.destroy){
                                    zipDownload.destroy()
                                }
                                res.end(s.prettyPrint({
                                    ok: false,
                                    msg: errorString
                                }))
                            })
                            zipDownload.on('close', function () {
                                res.end()
                                zipDownload.destroy()
                                fs.unlinkSync(zippedFile)
                            })
                        })
                    }else{
                        failed({ok:false,msg:'No Videos Found'})
                    }
                })
            },res,req);
        }else{
            failed({ok:false,msg:'"videos" query variable is missing from request.'})
        }
    })
    /**
    * API : Zip Cloud Videos and Get Link from fileBin
     */
    app.get(config.webPaths.apiPrefix+':auth/zipCloudVideos/:ke', function (req,res){
        var failed = function(resp){
            res.setHeader('Content-Type', 'application/json');
            res.end(s.prettyPrint(resp))
        }
        if(req.query.videos && req.query.videos !== ''){
            s.auth(req.params,function(user){
                var videosSelected = JSON.parse(req.query.videos)
                var where = []
                var values = []
                videosSelected.forEach(function(video){
                    where.push("(ke=? AND mid=? AND `time`=?)")
                    if(!video.ke)video.ke = req.params.ke
                    values.push(video.ke)
                    values.push(video.mid)
                    var time = s.nameToTime(video.filename)
                    if(req.query.isUTC === 'true'){
                        time = s.utcToLocal(time)
                    }
                    time = new Date(time)
                    values.push(time)
                })
                s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE '+where.join(' OR '),values,function(err,r){
                    var resp = {ok: false}
                    if(r && r[0]){
                        resp.ok = true
                        var zipDownload = null
                        var tempFiles = []
                        var fileId = s.gid()
                        var fileBinDir = s.dir.fileBin+req.params.ke+'/'
                        var tempScript = s.dir.streams+req.params.ke+'/'+fileId+'.sh'
                        var zippedFilename = s.formattedTime()+'-'+fileId+'-Shinobi_Cloud_Backed_Recordings.zip'
                        var zippedFile = fileBinDir+zippedFilename
                        var script = 'cd '+fileBinDir+' && zip -9 -r '+zippedFile
                        res.on('close', () => {
                            if(zipDownload && zipDownload.destroy){
                                zipDownload.destroy()
                            }
                            fs.unlink(zippedFile);
                        })
                        if(!fs.existsSync(fileBinDir)){
                            fs.mkdirSync(fileBinDir);
                        }
                        var cloudDownloadCount = 0
                        var getFile = function(video,completed){
                            if(!video)completed();
                            s.checkDetails(video)
                            var filename = video.href.split('/')
                            filename = filename[filename.length - 1]
                            var timeFormatted = s.formattedTime(video.time)
                            var tempVideoFile = video.details.type + '-' + video.mid + '-' + filename
                            var tempFileWriteStream = fs.createWriteStream(fileBinDir+tempVideoFile)
                            tempFileWriteStream.on('finish', function() {
                                ++cloudDownloadCount
                                getFile(r[cloudDownloadCount],completed)
                            })
                            var cloudVideoDownload = request(video.href)
                            cloudVideoDownload.on('response',  function (res) {
                                res.pipe(tempFileWriteStream)
                            })
                            tempFiles.push(fileBinDir+tempVideoFile)
                            script += ' "'+tempVideoFile+'"'
                        }
                        getFile(r[cloudDownloadCount],function(){
                            fs.writeFileSync(tempScript,script,'utf8')
                            var zipCreate = spawn('sh',(tempScript).split(' '),{detached: true})
                            zipCreate.stderr.on('data',function(data){
                                s.userLog({ke:req.params.ke,mid:'$USER'},{title:'Zip Create Error',msg:data.toString()})
                            })
                            zipCreate.on('exit',function(data){
                                fs.unlinkSync(tempScript)
                                tempFiles.forEach(function(file){
                                    fs.unlink(file,function(){})
                                })
                                res.setHeader('Content-Disposition', 'attachment; filename="' + zippedFilename + '"')
                                var zipDownload = fs.createReadStream(zippedFile)
                                zipDownload.pipe(res)
                                zipDownload.on('error', function (error) {
                                    var errorString = error.toString()
                                    s.userLog({
                                        ke: req.params.ke,
                                        mid: '$USER'
                                    },{
                                        title: 'Zip Download Error',
                                        msg: errorString
                                    })
                                    if(zipDownload && zipDownload.destroy){
                                        zipDownload.destroy()
                                    }
                                    res.end(s.prettyPrint({
                                        ok: false,
                                        msg: errorString
                                    }))
                                })
                                zipDownload.on('close', function () {
                                    res.end()
                                    zipDownload.destroy()
                                    fs.unlinkSync(zippedFile)
                                })
                            })
                        })
                    }else{
                        failed({ok:false,msg:'No Videos Found'})
                    }
                })
            },res,req);
        }else{
            failed({ok:false,msg:'"videos" query variable is missing from request.'})
        }
    })
    /**
    * API : Get Cloud Video File (proxy)
     */
    app.get(config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file', function (req,res){
        s.auth(req.params,function(user){
            if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
                res.end(user.lang['Not Permitted'])
                return
            }
            var time = s.nameToTime(req.params.file)
            if(req.query.isUTC === 'true'){
                time = s.utcToLocal(time)
            }
            time = new Date(time)
            s.sqlQuery('SELECT * FROM `Cloud Videos` WHERE ke=? AND mid=? AND `time`=? LIMIT 1',[req.params.ke,req.params.id,time],function(err,r){
                if(r&&r[0]){
                    r = r[0]
                    req.pipe(request(r.href)).pipe(res)
                }else{
                    res.end(user.lang['File Not Found in Database'])
                }
            })
        },res,req);
    });
    /**
    * API : Get Video File
     */
    app.get(config.webPaths.apiPrefix+':auth/videos/:ke/:id/:file', function (req,res){
        s.auth(req.params,function(user){
            if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
                res.end(user.lang['Not Permitted'])
                return
            }
            var time = s.nameToTime(req.params.file)
            if(req.query.isUTC === 'true'){
                time = s.utcToLocal(time)
            }
            time = new Date(time)
            s.sqlQuery('SELECT * FROM Videos WHERE ke=? AND mid=? AND `time`=? LIMIT 1',[req.params.ke,req.params.id,time],function(err,r){
                if(r&&r[0]){
                    req.dir=s.getVideoDirectory(r[0])+req.params.file
                    if (fs.existsSync(req.dir)){
                        s.streamMp4FileOverHttp(req.dir,req,res)
                    }else{
                        res.end(user.lang['File Not Found in Filesystem'])
                    }
                }else{
                    res.end(user.lang['File Not Found in Database'])
                }
            })
        },res,req);
    });
    /**
    * API : Motion Trigger via GET request
     */
     app.get(config.webPaths.apiPrefix+':auth/motion/:ke/:id', function (req,res){
         s.auth(req.params,function(user){
             if(req.query.data){
                 try{
                     var d = {
                         id: req.params.id,
                         ke: req.params.ke,
                         details: s.parseJSON(req.query.data)
                     }
                 }catch(err){
                     s.closeJsonResponse(res,{
                         ok: false,
                         msg: user.lang['Data Broken']
                     })
                     return
                 }
             }else{
                 s.closeJsonResponse(res,{
                     ok: false,
                     msg: user.lang['No Data']
                 })
                 return
             }
             if(!d.ke||!d.id||!s.group[d.ke]){
                 s.closeJsonResponse(res,{
                     ok: false,
                     msg: user.lang['No Group with this key exists']
                 })
                 return
             }
             if(!s.group[d.ke].rawMonitorConfigurations[d.id]){
                 s.closeJsonResponse(res,{
                     ok: false,
                     msg: user.lang['Monitor or Key does not exist.']
                 })
                 return
             }
             var details = s.group[d.ke].rawMonitorConfigurations[d.id].details
             var detectorHttpApi = details.detector_http_api
             var detectorOn = (details.detector === '1')
             switch(detectorHttpApi){
                 case'0':
                     s.closeJsonResponse(res,{
                         ok: false,
                         msg: user.lang['Trigger Blocked']
                     })
                    return
                 break;
                 case'2':
                    if(!detectorOn){
                        s.closeJsonResponse(res,{
                            ok: false,
                            msg: user.lang['Trigger Blocked']
                        })
                        return
                    }
                 break;
                 case'2':
                    if(detectorOn){
                        s.closeJsonResponse(res,{
                            ok: false,
                            msg: user.lang['Trigger Blocked']
                        })
                        return
                    }
                 break;
             }
             s.triggerEvent(d)
             s.closeJsonResponse(res,{
                 ok: true,
                 msg: user.lang['Trigger Successful']
             })
         },res,req)
     })
    /**
    * API : WebHook Tester
     */
    app.get(config.webPaths.apiPrefix+':auth/hookTester/:ke/:id', function (req,res){
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            s.userLog(req.params,{type:'Test',msg:'Hook Test'})
            res.end(s.prettyPrint({ok:true}))
        },res,req);
    })
    /**
    * API : Camera PTZ Controller
     */
    app.get(config.webPaths.apiPrefix+':auth/control/:ke/:id/:direction', function (req,res){
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            s.cameraControl(req.params,function(resp){
                res.end(s.prettyPrint(resp))
            });
        },res,req);
    })
    /**
    * API : Modify Video File
     */
    app.get([
        config.webPaths.apiPrefix+':auth/videos/:ke/:id/:file/:mode',
        config.webPaths.apiPrefix+':auth/videos/:ke/:id/:file/:mode/:f',
        config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode',
        config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode/:f'
    ], function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_delete.indexOf(req.params.id)===-1){
                res.end(user.lang['Not Permitted'])
                return
            }
            var time = s.nameToTime(req.params.file)
            if(req.query.isUTC === 'true'){
                time = s.utcToLocal(time)
            }
            time = new Date(time)
            var origURL = req.originalUrl.split('/')
            var videoParam = origURL[origURL.indexOf(req.params.auth) + 1]
            var videoSet = 'Videos'
            switch(videoParam){
                case'cloudVideos':
                    videoSet = 'Cloud Videos'
                break;
            }
            req.sql='SELECT * FROM `'+videoSet+'` WHERE ke=? AND mid=? AND `time`=?';
            req.ar=[req.params.ke,req.params.id,time];
            s.sqlQuery(req.sql,req.ar,function(err,r){
                if(r&&r[0]){
                    r=r[0];r.filename=s.formattedTime(r.time)+'.'+r.ext;
                    switch(req.params.mode){
                        case'fix':
                            req.ret.ok=true;
                            s.video('fix',r)
                        break;
                        case'status':
                            r.f = 'video_edit'
                            switch(videoParam){
                                case'cloudVideos':
                                    r.f += '_cloud'
                                break;
                            }
                            r.status = parseInt(req.params.f)
                            if(isNaN(req.params.f)||req.params.f===0){
                                req.ret.msg='Not a valid value.';
                            }else{
                                req.ret.ok=true;
                                s.sqlQuery('UPDATE `'+videoSet+'` SET status=? WHERE ke=? AND mid=? AND `time`=?',[req.params.f,req.params.ke,req.params.id,time])
                                s.tx(r,'GRP_'+r.ke);
                            }
                        break;
                        case'delete':
                            req.ret.ok=true;
                            switch(videoParam){
                                case'cloudVideos':
                                    s.deleteVideoFromCloud(r)
                                break;
                                default:
                                    s.deleteVideo(r)
                                break;
                            }
                        break;
                        default:
                            req.ret.msg=user.lang.modifyVideoText1;
                        break;
                    }
                }else{
                    req.ret.msg=user.lang['No such file'];
                }
                res.end(s.prettyPrint(req.ret));
            })
        },res,req);
    })
    /**
    * API : Stream In to push data to ffmpeg by HTTP
     */
    app.all(['/streamIn/:ke/:id','/streamIn/:ke/:id/:feed'], function (req, res) {
        var checkOrigin = function(search){return req.headers.host.indexOf(search)>-1}
        if(checkOrigin('127.0.0.1')){
            if(!req.params.feed){req.params.feed='1'}
            if(!s.group[req.params.ke].activeMonitors[req.params.id].streamIn[req.params.feed]){
                s.group[req.params.ke].activeMonitors[req.params.id].streamIn[req.params.feed] = new events.EventEmitter().setMaxListeners(0)
            }
            //req.params.feed = Feed Number
            res.connection.setTimeout(0);
            req.on('data', function(buffer){
                s.group[req.params.ke].activeMonitors[req.params.id].streamIn[req.params.feed].emit('data',buffer)
            });
            req.on('end',function(){
    //            console.log('streamIn closed',req.params);
            });
        }else{
            res.end('Local connection is only allowed.')
        }
    })
    /**
    * API : FFprobe
     */
    app.get(config.webPaths.apiPrefix+':auth/probe/:ke',function (req,res){
        req.ret={ok:false};
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            switch(req.query.action){
    //            case'stop':
    //                exec('kill -9 '+user.ffprobe.pid,{detatched: true})
    //            break;
                default:
                    if(!req.query.url){
                        req.ret.error = 'Missing URL'
                        res.end(s.prettyPrint(req.ret));
                        return
                    }
                    if(user.ffprobe){
                        req.ret.error = 'Account is already probing'
                        res.end(s.prettyPrint(req.ret));
                        return
                    }
                    user.ffprobe=1;
                    if(req.query.flags==='default'){
                        req.query.flags = '-v quiet -print_format json -show_format -show_streams'
                    }else{
                        if(!req.query.flags){
                            req.query.flags = ''
                        }
                    }
                    req.probeCommand = s.splitForFFPMEG(req.query.flags+' -i '+req.query.url).join(' ')
                    exec('ffprobe '+req.probeCommand+' | echo ',function(err,stdout,stderr){
                        delete(user.ffprobe)
                        if(err){
                           req.ret.error=(err)
                        }else{
                            req.ret.ok=true
                            req.ret.result = stdout+stderr
                        }
                        req.ret.probe = req.probeCommand
                        res.end(s.prettyPrint(req.ret));
                    })
                break;
            }
        },res,req);
    })
    /**
    * API : ONVIF Method Controller
     */
    app.all([config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:action',config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:service/:action'],function (req,res){
        var response = {ok:false};
        res.setHeader('Content-Type', 'application/json');
        s.auth(req.params,function(user){
            var errorMessage = function(msg,error){
                response.ok = false
                response.msg = msg
                response.error = error
                res.end(s.prettyPrint(response))
            }
            var actionCallback = function(onvifActionResponse){
                response.ok = true
                if(onvifActionResponse.data){
                    response.responseFromDevice = onvifActionResponse.data
                }else{
                    response.responseFromDevice = onvifActionResponse
                }
                if(onvifActionResponse.soap)response.soap = onvifActionResponse.soap
                res.end(s.prettyPrint(response))
            }
            var isEmpty = function(obj) {
                for(var key in obj) {
                    if(obj.hasOwnProperty(key))
                        return false;
                }
                return true;
            }
            var doAction = function(Camera){
                var completeAction = function(command){
                    if(command.then){
                        command.then(actionCallback).catch(function(error){
                            errorMessage('Device responded with an error',error)
                        })
                    }else if(command){
                        response.ok = true
                        response.repsonseFromDevice = command
                        res.end(s.prettyPrint(response))
                    }else{
                        response.error = 'Big Errors, Please report it to Shinobi Development'
                        res.end(s.prettyPrint(response))
                    }
                }
                var action
                if(req.params.service){
                    if(Camera.services[req.params.service] === undefined){
                        return errorMessage('This is not an available service. Please use one of the following : '+Object.keys(Camera.services).join(', '))
                    }
                    if(Camera.services[req.params.service] === null){
                        return errorMessage('This service is not activated. Maybe you are not connected through ONVIF. You can test by attempting to use the "Control" feature with ONVIF in Shinobi.')
                    }
                    action = Camera.services[req.params.service][req.params.action]
                }else{
                    action = Camera[req.params.action]
                }
                if(!action || typeof action !== 'function'){
                    errorMessage(req.params.action+' is not an available ONVIF function. See https://github.com/futomi/node-onvif for functions.')
                }else{
                    var argNames = s.getFunctionParamNames(action)
                    var options
                    var command
                    if(argNames[0] === 'options' || argNames[0] === 'params'){
                        options = {}
                        if(req.query.options){
                            var jsonRevokedText = 'JSON not formated correctly'
                            try{
                                options = JSON.parse(req.query.options)
                            }catch(err){
                                return errorMessage(jsonRevokedText,err)
                            }
                        }else if(req.body.options){
                            try{
                                options = JSON.parse(req.body.options)
                            }catch(err){
                                return errorMessage(jsonRevokedText,err)
                            }
                        }else if(req.query.params){
                            try{
                                options = JSON.parse(req.query.params)
                            }catch(err){
                                return errorMessage(jsonRevokedText,err)
                            }
                        }else if(req.body.params){
                            try{
                                options = JSON.parse(req.body.params)
                            }catch(err){
                                return errorMessage(jsonRevokedText,err)
                            }
                        }
                    }
                    if(req.params.service){
                        command = Camera.services[req.params.service][req.params.action](options)
                    }else{
                        command = Camera[req.params.action](options)
                    }
                    completeAction(command)
                }
            }
            if(!s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection){
                //prepeare onvif connection
                var controlURL
                var monitorConfig = s.group[req.params.ke].rawMonitorConfigurations[req.params.id]
                if(!monitorConfig.details.control_base_url||monitorConfig.details.control_base_url===''){
                    controlURL = s.buildMonitorUrl(monitorConfig, true)
                }else{
                    controlURL = monitorConfig.details.control_base_url
                }
                var controlURLOptions = s.cameraControlOptionsFromUrl(controlURL,monitorConfig)
                //create onvif connection
                s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection = new onvif.OnvifDevice({
                    xaddr : 'http://' + controlURLOptions.host + ':' + controlURLOptions.port + '/onvif/device_service',
                    user : controlURLOptions.username,
                    pass : controlURLOptions.password
                })
                var device = s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection
                device.init().then((info) => {
                    if(info)doAction(device)
                }).catch(function(error){
                    return errorMessage('Device responded with an error',error)
                })
            }else{
                doAction(s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection)
            }
        },res,req);
    })
    /**
    * API : Account Edit from Dashboard
     */
    app.all(config.webPaths.apiPrefix+':auth/accounts/:ke/edit',function (req,res){
        s.auth(req.params,function(user){
            var endData = {
                ok : false
            }
            var form = s.getPostData(req)
            if(form){
                endData.ok = true
                s.accountSettingsEdit({
                    ke: req.params.ke,
                    uid: user.uid,
                    form: form,
                    cnid: user.cnid
                })
            }else{
                endData.msg = lang.postDataBroken
            }
            s.closeJsonResponse(res,endData)
        },res,req)
    })
    /**
    * API : Get Definitions JSON
     */
    app.get(config.webPaths.apiPrefix+':auth/definitions/:ke',function (req,res){
        s.auth(req.params,function(user){
            var endData = {
                ok: true,
                definitions: s.getDefinitonFile(user.details.lang)
            }
            s.closeJsonResponse(res,endData)
        },res,req)
    })
    /**
    * API : Get Language JSON
     */
    app.get(config.webPaths.apiPrefix+':auth/language/:ke',function (req,res){
        s.auth(req.params,function(user){
            var endData = {
                ok: true,
                definitions: s.getLanguageFile(user.details.lang)
            }
            s.closeJsonResponse(res,endData)
        },res,req)
    })
    /**
    * Robots.txt
    */
    app.get('/robots.txt', function (req,res){
        res.on('finish',function(){
            res.end()
        })
        fs.createReadStream(s.mainDirectory + '/web/pages/robots.txt').pipe(res)
    })
}
