var P2P = require('pipe2pam')
var PamDiff = require('pam-diff')
module.exports = function(jsonData,pamDiffResponder){
    var noiseFilterArray = {};
    const groupKey = jsonData.rawMonitorConfig.ke
    const monitorId = jsonData.rawMonitorConfig.mid
    const triggerTimer = {}
    var pamDiff
    var p2p
    var writeToStderr = function(text){
      try{
        stdioWriters[2].write(Buffer.from(`${text}`, 'utf8' ))
          // stdioWriters[2].write(Buffer.from(`${new Error('writeToStderr').stack}`, 'utf8' ))
      }catch(err){
        fs.appendFileSync('/home/Shinobi/test.log',text + '\n','utf8')
      }
    }
    if(typeof pamDiffResponder === 'function'){
      var sendDetectedData = function(detectorObject){
        pamDiffResponder(detectorObject)
      }
    }else{
      var sendDetectedData = function(detectorObject){
        pamDiffResponder.write(Buffer.from(JSON.stringify(detectorObject)))
      }
    }
    createPamDiffEngine = function(){
        var width,
            height,
            globalSensitivity,
            globalColorThreshold,
            fullFrame = false
        if(jsonData.rawMonitorConfig.details.detector_scale_x===''||jsonData.rawMonitorConfig.details.detector_scale_y===''){
            width = jsonData.rawMonitorConfig.details.detector_scale_x;
            height = jsonData.rawMonitorConfig.details.detector_scale_y;
        }
        else{
            width = jsonData.rawMonitorConfig.width
            height = jsonData.rawMonitorConfig.height
        }
        if(jsonData.rawMonitorConfig.details.detector_sensitivity===''){
            globalSensitivity = 10
        }else{
            globalSensitivity = parseInt(jsonData.rawMonitorConfig.details.detector_sensitivity)
        }
        if(jsonData.rawMonitorConfig.details.detector_color_threshold===''){
            globalColorThreshold = 9
        }else{
            globalColorThreshold = parseInt(jsonData.rawMonitorConfig.details.detector_color_threshold)
        }

        globalThreshold = parseInt(jsonData.rawMonitorConfig.details.detector_threshold) || 0

        var regionJson
        try{
            regionJson = JSON.parse(jsonData.rawMonitorConfig.details.cords)
        }catch(err){
            regionJson = jsonData.rawMonitorConfig.details.cords
        }

        if(Object.keys(regionJson).length === 0 || jsonData.rawMonitorConfig.details.detector_frame === '1'){
            fullFrame = {
                name:'FULL_FRAME',
                sensitivity:globalSensitivity,
                color_threshold:globalColorThreshold,
                points:[
                    [0,0],
                    [0,height],
                    [width,height],
                    [width,0]
                ]
            }
        }


        var regions = createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
        var pamDiffOptions = {
            grayscale: 'luminosity',
            regions : regions.forPam
        }
        if(jsonData.rawMonitorConfig.details.detector_show_matrix==='1'){
            pamDiffOptions.response = 'bounds'
        }
        pamDiff = new PamDiff(pamDiffOptions)
        p2p = new P2P()
        var regionArray = Object.values(regionJson)
        if(jsonData.globalInfo.config.detectorMergePamRegionTriggers === true){
            // merge pam triggers for performance boost
            var buildTriggerEvent = function(trigger){
                var detectorObject = {
                    f:'trigger',
                    id:monitorId,
                    ke:groupKey,
                    name:trigger.name,
                    details:{
                        plug:'built-in',
                        name:trigger.name,
                        reason:'motion',
                        confidence:trigger.percent
                    },
                    plates:[],
                    imgHeight:jsonData.rawMonitorConfig.details.detector_scale_y,
                    imgWidth:jsonData.rawMonitorConfig.details.detector_scale_x
                }
                if(trigger.merged){
                    if(trigger.matrices)detectorObject.details.matrices = trigger.matrices
                    var filteredCount = 0
                    var filteredCountSuccess = 0
                    trigger.merged.forEach(function(triggerPiece){
                        var region = regionArray.find(x => x.name == triggerPiece.name)
                        checkMaximumSensitivity(region, detectorObject, function(err1) {
                            checkTriggerThreshold(region, detectorObject, function(err2) {
                                ++filteredCount
                                if(!err1 && !err2)++filteredCountSuccess
                                if(filteredCount === trigger.merged.length && filteredCountSuccess > 0){
                                    detectorObject.doObjectDetection = (jsonData.globalInfo.isAtleatOneDetectorPluginConnected && jsonData.rawMonitorConfig.details.detector_use_detect_object === '1')
                                    sendDetectedData(detectorObject)
                                }
                            })
                        })
                    })
                }else{
                    if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
                    var region = regionArray.find(x => x.name == detectorObject.name)
                    checkMaximumSensitivity(region, detectorObject, function(err1) {
                        checkTriggerThreshold(region, detectorObject, function(err2) {
                            if(!err1 && !err2){
                                detectorObject.doObjectDetection = (jsonData.globalInfo.isAtleatOneDetectorPluginConnected && jsonData.rawMonitorConfig.details.detector_use_detect_object === '1')
                                sendDetectedData(detectorObject)
                            }
                        })
                    })
                }
            }
            if(jsonData.rawMonitorConfig.details.detector_noise_filter==='1'){
                Object.keys(regions.notForPam).forEach(function(name){
                    if(!noiseFilterArray[name])noiseFilterArray[name]=[];
                })
                pamDiff.on('diff', (data) => {
                    var filteredCount = 0
                    var filteredCountSuccess = 0
                    data.trigger.forEach(function(trigger){
                        filterTheNoise(noiseFilterArray,regions,trigger,function(err){
                            ++filteredCount
                            if(!err)++filteredCountSuccess
                            if(filteredCount === data.trigger.length && filteredCountSuccess > 0){
                                buildTriggerEvent(mergePamTriggers(data))
                            }
                        })
                    })
                })
            }else{
                pamDiff.on('diff', (data) => {
                    buildTriggerEvent(mergePamTriggers(data))
                })
            }
        }else{
            //config.detectorMergePamRegionTriggers NOT true
            //original behaviour, all regions have their own event.
            var buildTriggerEvent = function(trigger){
                var detectorObject = {
                    f:'trigger',
                    id: monitorId,
                    ke: groupKey,
                    name:trigger.name,
                    details:{
                        plug:'built-in',
                        name:trigger.name,
                        reason:'motion',
                        confidence:trigger.percent
                    },
                    plates:[],
                    imgHeight:jsonData.rawMonitorConfig.details.detector_scale_y,
                    imgWidth:jsonData.rawMonitorConfig.details.detector_scale_x
                }
                if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
                var region = Object.values(regionJson).find(x => x.name == detectorObject.name)
                checkMaximumSensitivity(region, detectorObject, function(err1) {
                    checkTriggerThreshold(region, detectorObject, function(err2) {
                        if(!err1 && ! err2){
                            detectorObject.doObjectDetection = (jsonData.globalInfo.isAtleatOneDetectorPluginConnected && jsonData.rawMonitorConfig.details.detector_use_detect_object === '1')
                            sendDetectedData(detectorObject)
                        }
                    })
                })
            }
            if(jsonData.rawMonitorConfig.details.detector_noise_filter==='1'){
                Object.keys(regions.notForPam).forEach(function(name){
                    if(!noiseFilterArray[name])noiseFilterArray[name]=[];
                })
                pamDiff.on('diff', (data) => {
                    data.trigger.forEach(function(trigger){
                        filterTheNoise(noiseFilterArray,regions,trigger,function(){
                            createMatrixFromPamTrigger(trigger)
                            buildTriggerEvent(trigger)
                        })
                    })
                })
            }else{
                pamDiff.on('diff', (data) => {
                    data.trigger.forEach(function(trigger){
                        createMatrixFromPamTrigger(trigger)
                        buildTriggerEvent(trigger)
                    })
                })
            }
        }
    }

    createPamDiffRegionArray = function(regions,globalColorThreshold,globalSensitivity,fullFrame){
        var pamDiffCompliantArray = [],
            arrayForOtherStuff = [],
            json
        try{
            json = JSON.parse(regions)
        }catch(err){
            json = regions
        }
        if(fullFrame){
            json[fullFrame.name]=fullFrame;
        }
        Object.values(json).forEach(function(region){
            if(!region)return false;
            region.polygon = [];
            region.points.forEach(function(points){
                var x = parseFloat(points[0]);
                var y = parseFloat(points[1]);
                if(x < 0)x = 0;
                if(y < 0)y = 0;
                region.polygon.push({
                    x: x,
                    y: y
                })
            })
            if(region.sensitivity===''){
                region.sensitivity = globalSensitivity
            }else{
                region.sensitivity = parseInt(region.sensitivity)
            }
            if(region.color_threshold===''){
                region.color_threshold = globalColorThreshold
            }else{
                region.color_threshold = parseInt(region.color_threshold)
            }
            pamDiffCompliantArray.push({name: region.name, difference: region.color_threshold, percent: region.sensitivity, polygon:region.polygon})
            arrayForOtherStuff[region.name] = region;
        })
        if(pamDiffCompliantArray.length===0){pamDiffCompliantArray = null}
        return {forPam:pamDiffCompliantArray,notForPam:arrayForOtherStuff};
    }

    filterTheNoise = function(noiseFilterArray,regions,trigger,callback){
        if(noiseFilterArray[trigger.name].length > 2){
            var thePreviousTriggerPercent = noiseFilterArray[trigger.name][noiseFilterArray[trigger.name].length - 1];
            var triggerDifference = trigger.percent - thePreviousTriggerPercent;
            var noiseRange = jsonData.rawMonitorConfig.details.detector_noise_filter_range
            if(!noiseRange || noiseRange === ''){
                noiseRange = 6
            }
            noiseRange = parseFloat(noiseRange)
            if(((trigger.percent - thePreviousTriggerPercent) < noiseRange)||(thePreviousTriggerPercent - trigger.percent) > -noiseRange){
                noiseFilterArray[trigger.name].push(trigger.percent);
            }
        }else{
            noiseFilterArray[trigger.name].push(trigger.percent);
        }
        if(noiseFilterArray[trigger.name].length > 10){
            noiseFilterArray[trigger.name] = noiseFilterArray[trigger.name].splice(1,10)
        }
        var theNoise = 0;
        noiseFilterArray[trigger.name].forEach(function(v,n){
            theNoise += v;
        })
        theNoise = theNoise / noiseFilterArray[trigger.name].length;
        var triggerPercentWithoutNoise = trigger.percent - theNoise;
        if(triggerPercentWithoutNoise > regions.notForPam[trigger.name].sensitivity){
            callback(null,trigger)
        }else{
            callback(true)
        }
    }

    checkMaximumSensitivity = function(region, detectorObject, callback) {
        var logName = detectorObject.id + ':' + detectorObject.name
        var globalMaxSensitivity = parseInt(jsonData.rawMonitorConfig.details.detector_max_sensitivity) || undefined
        var maxSensitivity = parseInt(region.max_sensitivity) || globalMaxSensitivity
        if (maxSensitivity === undefined || detectorObject.details.confidence <= maxSensitivity) {
            callback(null)
        } else {
            callback(true)
            if (triggerTimer[detectorObject.name] !== undefined) {
                clearTimeout(triggerTimer[detectorObject.name].timeout)
                triggerTimer[detectorObject.name] = undefined
            }
        }
    }

    checkTriggerThreshold = function(region, detectorObject, callback){
        var threshold = parseInt(region.threshold) || globalThreshold
        if (threshold <= 1) {
            callback(null)
        } else {
            if (triggerTimer[detectorObject.name] === undefined) {
                triggerTimer[detectorObject.name] = {
                    count : threshold,
                    timeout : null
                }
            }
            if (--triggerTimer[detectorObject.name].count == 0) {
                callback(null)
                clearTimeout(triggerTimer[detectorObject.name].timeout)
                triggerTimer[detectorObject.name] = undefined
            } else {
                callback(true)
                var fps = parseFloat(jsonData.rawMonitorConfig.details.detector_fps) || 2
                if (triggerTimer[detectorObject.name].timeout !== null)
                    clearTimeout(triggerTimer[detectorObject.name].timeout)
                triggerTimer[detectorObject.name].timeout = setTimeout(function() {
                    triggerTimer[detectorObject.name] = undefined
                }, ((threshold+0.5) * 1000) / fps)
            }
        }
    }
    mergePamTriggers = function(data){
        if(data.trigger.length > 1){
            var n = 0
            var sum = 0
            var name = []
            var matrices = []
            data.trigger.forEach(function(trigger){
                name.push(trigger.name + ' ('+trigger.percent+'%)')
                ++n
                sum += trigger.percent
                createMatrixFromPamTrigger(trigger)
                if(trigger.matrix)matrices.push(trigger.matrix)
            })
            var average = sum / n
            name = name.join(', ')
            if(matrices.length === 0)matrices = null
            var trigger = {
                name: name,
                percent: parseInt(average),
                matrices: matrices,
                merged: data.trigger
            }
        }else{
            var trigger = data.trigger[0]
            createMatrixFromPamTrigger(trigger)
            trigger.matrices = [trigger.matrix]
        }
        return trigger
    }
    createMatrixFromPamTrigger = function(trigger){
        if(
            trigger.minX &&
            trigger.maxX &&
            trigger.minY &&
            trigger.maxY
        ){
            var coordinates = [
                {"x" : trigger.minX, "y" : trigger.minY},
                {"x" : trigger.maxX, "y" : trigger.minY},
                {"x" : trigger.maxX, "y" : trigger.maxY}
            ]
            var width = Math.sqrt( Math.pow(coordinates[1].x - coordinates[0].x, 2) + Math.pow(coordinates[1].y - coordinates[0].y, 2));
            var height = Math.sqrt( Math.pow(coordinates[2].x - coordinates[1].x, 2) + Math.pow(coordinates[2].y - coordinates[1].y, 2))
            trigger.matrix = {
                x: coordinates[0].x,
                y: coordinates[0].y,
                width: width,
                height: height,
                tag: trigger.name
            }
        }
        return trigger
    }

    return function(cameraProcess,fallback){
      if(jsonData.rawMonitorConfig.details.detector === '1' && jsonData.rawMonitorConfig.coProcessor === false){
          //frames from motion detect
          if(jsonData.rawMonitorConfig.details.detector_pam === '1'){
            createPamDiffEngine()

            cameraProcess.stdio[3].pipe(p2p).pipe(pamDiff)
          }
       }
    };
}
