var os = require('os');
var exec = require('child_process').exec;
module.exports = function(s,config,lang){
    const { fetchWithAuthentication, asyncSetTimeout } = require('../basic/utils.js')(process.cwd(),config)
    const moveLock = {}
    const ptzTimeoutsUntilResetToHome = {}
    const sliceUrlAuth = (url) => {
        return /^(.+?\/\/)(?:.+?:.+?@)?(.+)$/.exec(url).slice(1).join('')
    }
    function getGenericControlParameters(options,doStart){
        const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
        const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
        const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
        let theURL;
        if(doStart){
            theURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}`]
        }else{
            theURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}_stop`]
        }
        let controlOptions = s.cameraControlOptionsFromUrl(theURL,monitorConfig);
        const hasDigestAuthEnabled = monitorConfig.details.control_digest_auth === '1'
        const requestUrl = controlBaseUrl + controlOptions.path
        return {
            monitorConfig,
            controlUrlMethod,
            controlBaseUrl,
            theURL,
            controlOptions,
            hasDigestAuthEnabled,
            requestUrl,
        }
    }
    function moveGeneric(options,doStart){
        if(!s.group[options.ke] || !s.group[options.ke].activeMonitors[options.id]){return}
        return new Promise((resolve,reject) => {
            if(doStart)moveLock[options.ke + options.id] = true;
            const {
                monitorConfig,
                controlUrlMethod,
                controlBaseUrl,
                theURL,
                controlOptions,
                hasDigestAuthEnabled,
                requestUrl,
            } = getGenericControlParameters(options,doStart)
            const response =  {
                ok: true,
                type: lang[doStart ? 'Control Triggered' : 'Control Trigger Ended']
            }
            var fetchWithAuthData;
            if (controlOptions.postData){
                fetchWithAuthData = {
                    method: controlUrlMethod || controlOptions.method,
                    digestAuth: hasDigestAuthEnabled,
                    postData: controlOptions.postData
                }
            }
            else{
                 fetchWithAuthData = {
                    method: controlUrlMethod || controlOptions.method,
                    digestAuth: hasDigestAuthEnabled
                }
            }
            const theRequest = fetchWithAuthentication(requestUrl,fetchWithAuthData);
            theRequest.then(res => res.text())
            .then((data) => {
                response.msg = data;
                if(doStart){
                    const stopCommandEnabled = monitorConfig.details.control_stop === '1' || monitorConfig.details.control_stop === '2';
                    if(stopCommandEnabled && options.direction !== 'center'){
                        s.userLog(monitorConfig,{type: lang['Control Trigger Started']});
                    }else{
                        moveLock[options.ke + options.id] = false
                        s.userLog(monitorConfig,response);
                    }
                }else{
                    moveLock[options.ke + options.id] = false
                    s.userLog(monitorConfig,response);
                }
                resolve(response)
            });
            theRequest.catch((err) => {
                response.ok = false
                response.type = lang['Control Error']
                response.msg = err
                resolve(response)
            })
        })
    }
    function getOnvifControlOptions(options){
        const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
        const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
        const turnSpeed = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
        const controlOptions = {
            Velocity : {}
        }
        if(options.axis){
            options.axis.forEach((axis) => {
                controlOptions.Velocity[axis.direction] = axis.amount < 0 ? -turnSpeed : axis.amount > 0 ? turnSpeed : 0
            })
        }else{
            const onvifDirections = {
                "left": [-turnSpeed,'x'],
                "right": [turnSpeed,'x'],
                "down": [invertedVerticalAxis ? turnSpeed : -turnSpeed,'y'],
                "up": [invertedVerticalAxis ? -turnSpeed : turnSpeed,'y'],
                "zoom_in": [turnSpeed,'z'],
                "zoom_out": [-turnSpeed,'z']
            }
            const direction = onvifDirections[options.direction]
            controlOptions.Velocity[direction[1]] = direction[0]
        }
        (['x','y','z']).forEach(function(axis){
            if(!controlOptions.Velocity[axis])
                controlOptions.Velocity[axis] = 0
        })
        return controlOptions
    }
    async function startMoveOnvif(options,callback){
        const controlOptions = getOnvifControlOptions(options);
        let activeMonitor = s.group[options.ke].activeMonitors[options.id]
        let device = activeMonitor.onvifConnection
        if(
            !device ||
            !device.current_profile ||
            !device.current_profile.token
        ){
            const response = await s.createOnvifDevice({
                ke: options.ke,
                id: options.id,
            })
            device = activeMonitor.onvifConnection
        }
        function returnResponse(){
            return new Promise((resolve,reject) => {
                if(
                    !device ||
                    !device.current_profile ||
                    !device.current_profile.token
                ){
                    resolve({ok: false, msg: lang.ONVIFEventsNotAvailableText1})
                    return
                }
                controlOptions.ProfileToken = device.current_profile.token
                s.runOnvifMethod({
                    auth: {
                        ke: options.ke,
                        id: options.id,
                        action: 'continuousMove',
                        service: 'ptz',
                    },
                    options: controlOptions,
                },resolve)
            })
        }
        return await returnResponse();
    }
    function stopMoveOnvif(options){
        return new Promise((resolve,reject) => {
            const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
            try{
                s.runOnvifMethod({
                    auth: {
                        ke: options.ke,
                        id: options.id,
                        action: 'stop',
                        service: 'ptz',
                    },
                    options: {
                        'PanTilt': true,
                        'Zoom': true,
                        ProfileToken: device.current_profile.token
                    },
                },resolve)
            }catch(err){
                resolve({ok: false})
            }
        })
    }
    function relativeMoveOnvif(options){
        return new Promise((resolve,reject) => {
            const controlOptions = getOnvifControlOptions(options);
            controlOptions.Speed = {'x': 1, 'y': 1, 'z': 1}
            controlOptions.Translation = Object.assign(controlOptions.Velocity,{})
            delete(controlOptions.Velocity)
            moveLock[options.ke + options.id] = true
            s.runOnvifMethod({
                auth: {
                    ke: options.ke,
                    id: options.id,
                    action: 'relativeMove',
                    service: 'ptz',
                },
                options: controlOptions,
            },(response) => {
                if(response.ok){
                    resolve({type: 'Control Triggered'})
                }else{
                    resolve({type: 'Control Triggered', error: response.error})
                }
                moveLock[options.ke + options.id] = false
            })
        })
    }
    function moveOnvifCamera(options,doMove){
        return new Promise((resolve,reject) => {
            const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
            const controlUrlStopTimeout = parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
            options.direction = doMove ? options.direction : 'stopMove';
            switch(options.direction){
                case'center':
                    const onvifHomeControlMethod = monitorConfig.details.onvif_non_standard
                    const actionFunction = onvifHomeControlMethod === '2' ? moveToHomePosition : moveToPresetPosition
                    moveLock[options.ke + options.id] = true
                    actionFunction({
                        ke: options.ke,
                        id: options.id,
                    },(endData) => {
                        moveLock[options.ke + options.id] = false
                        resolve({ type: lang['Moving to Home Preset'], msg: endData })
                    })
                break;
                case'stopMove':
                    stopMoveOnvif({
                        ke: options.ke,
                        id: options.id,
                    }).then((response) => {
                        moveLock[options.ke + options.id] = false
                        resolve({ type: lang['Control Trigger Ended'], msg: response })
                    })
                break;
                default:
                    try{
                        moveLock[options.ke + options.id] = true
                        startMoveOnvif(options).then((moveResponse) => {
                            if(!moveResponse.ok){
                                s.debugLog('ONVIF Move Error',moveResponse)
                            }
                            resolve({ type: lang['Control Triggered'], msg: moveResponse })
                        })
                    }catch(err){
                        console.log(err)
                        console.log(new Error())
                    }
                break;
            }
        })
    }
    async function startMove(options){
        const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
        const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
        if(controlUrlMethod === 'ONVIF'){
            return await moveOnvifCamera(options,true);
        }else{
            return await moveGeneric(options,true);
        }
    }
    async function stopMove(options){
        const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
        const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
        if(controlUrlMethod === 'ONVIF'){
            return await moveOnvifCamera(options,false);
        }else{
            return await moveGeneric(options,false);
        }
    }
    async function ptzControl(options,callback){
        if(!s.group[options.ke] || !s.group[options.ke].activeMonitors[options.id]){return}
        const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
        const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
        const controlUrlStopTimeout = options.moveTimeout || parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
        const stopCommandEnabled = monitorConfig.details.control_stop === '1' || monitorConfig.details.control_stop === '2';
        const axisLock = monitorConfig.details.control_axis_lock
        const direction = options.direction
        if(monitorConfig.details.control !== "1"){
            s.userLog(monitorConfig,{
                type: lang['Control Error'],
                msg: lang.ControlErrorText1
            });
            return {
                ok: false,
                msg: lang.ControlErrorText1
            }
        }
        let response = {
            direction,
        }
        if(axisLock){
            const isHorizontalOnly = axisLock === '1'
            const isVerticalOnly = axisLock === '2'
            const moveIsHorizontal = direction === 'left' || direction === 'right'
            const moveIsVertical = direction === 'up' || direction === 'down'
            if(
                isHorizontalOnly && moveIsVertical ||
                isVerticalOnly && moveIsHorizontal
            ){
                response.ok = false
                response.msg = isHorizontalOnly ? lang['Pan Only'] : lang['Tilt Only']
                return response
            }
        }
        if(controlUrlMethod === 'ONVIF'){
            if(direction === 'center'){
                response.moveResponse = await moveOnvifCamera(options,true)
            }else if(stopCommandEnabled){
                response.moveResponse = await moveOnvifCamera(options,true)
                if(direction !== 'stopMove' && direction !== 'center'){
                    await asyncSetTimeout(controlUrlStopTimeout)
                    response.stopMoveResponse = await moveOnvifCamera(options,false)
                    response.ok = response.moveResponse.ok && response.stopMoveResponse.ok;
                }else{
                    response.ok = response.moveResponse.ok;
                }
            }else{
                response = await relativeMoveOnvif(options);
            }
        }else{
            if(direction === 'stopMove'){
                response = await moveGeneric(options,false)
            }else{
                // left, right, up, down, center
                response.moveResponse = await moveGeneric(options,true)
                if(stopCommandEnabled){
                    await asyncSetTimeout(controlUrlStopTimeout)
                    response.stopMoveResponse = await moveGeneric(options,false)
                    response.ok = response.moveResponse.ok && response.stopMoveResponse.ok;
                }else{
                    response.ok = response.moveResponse.ok;
                }
            }
        }
        if(callback)callback(response);
        return response;
    }
    const getPresetPositions = (options,callback) => {
        const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
        return s.runOnvifMethod({
            auth: {
                ke: options.ke,
                id: options.id,
                service: 'ptz',
                action: 'getPresets',
            },
            options: {
                ProfileToken: profileToken
            },
        },callback)
    }
    const setPresetForCurrentPosition = (options,callback) => {
        return new Promise((resolve) => {
            const nonStandardOnvif = s.group[options.ke].rawMonitorConfigurations[options.id].details.onvif_non_standard === '1'
            const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
            s.runOnvifMethod({
                auth: {
                    ke: options.ke,
                    id: options.id,
                    service: 'ptz',
                    action: 'setPreset',
                },
                options: {
                    ProfileToken: profileToken,
                    PresetToken: nonStandardOnvif ? '1' : options.PresetToken || profileToken,
                    PresetName: options.PresetName || nonStandardOnvif ? '1' : profileToken
                },
            },(response) => {
                if(callback)callback(response)
                resolve(response)
            })
        })
    }
    const moveToPresetPosition = (options,callback) => {
        const nonStandardOnvif = s.group[options.ke].rawMonitorConfigurations[options.id].details.onvif_non_standard === '1'
        const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
        return s.runOnvifMethod({
            auth: {
                ke: options.ke,
                id: options.id,
                service: 'ptz',
                action: 'gotoPreset',
            },
            options: {
                ProfileToken: profileToken,
                PresetToken: options.PresetToken || nonStandardOnvif ? '1' : profileToken,
                Speed: {
                   "x": 1,
                   "y": 1,
                   "z": 1
                },
            },
        },callback)
    }
    const moveToHomePosition = (options,callback) => {
        const groupKey = options.ke
        const monitorId = options.mid || options.id
        const nonStandardOnvif = s.group[groupKey].rawMonitorConfigurations[monitorId].details.onvif_non_standard === '1'
        const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
        return s.runOnvifMethod({
            auth: {
                ke: groupKey,
                id: monitorId,
                service: 'ptz',
                action: 'gotoHomePosition',
            },
            options: {
                ProfileToken: profileToken,
            },
        },callback)
    }
    const setHomePosition = (options,callback) => {
        return new Promise((resolve) => {
            const nonStandardOnvif = s.group[options.ke].rawMonitorConfigurations[options.id].details.onvif_non_standard === '1'
            const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
            s.runOnvifMethod({
                auth: {
                    ke: options.ke,
                    id: options.id,
                    service: 'ptz',
                    action: 'setHomePosition',
                },
                options: {
                    ProfileToken: profileToken,
                },
            },(response) => {
                if(callback)callback(response)
                resolve(response)
            })
        })
    }
    const moveToHomePositionTimeout = (event, returnTime = 7000) => {
        const groupKey = event.ke
        const monitorId = event.id
        const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
        const onvifHomeControlMethod = monitorConfig.details.onvif_non_standard
        const actionFunction = onvifHomeControlMethod === '2' ? moveToHomePosition : moveToPresetPosition
        clearTimeout(ptzTimeoutsUntilResetToHome[event.ke + event.id])
        ptzTimeoutsUntilResetToHome[event.ke + event.id] = setTimeout(() => {
            actionFunction({
                ke: event.ke,
                id: event.id,
            },(endData) => {
                s.debugLog(endData)
            })
        },returnTime)
    }
    const getLargestMatrix = (matrices,imgWidth,imgHeight) => {
        var largestMatrix = {width: 0, height: 0}
        const maxWidth = imgWidth * 0.95;
        const maxHeight = imgHeight * 0.95;
        matrices.forEach((matrix) => {
            const matrixWidth = matrix.width
            const matrixHeight = matrix.height
            if(
                matrixWidth > largestMatrix.width &&
                matrixHeight > largestMatrix.height &&
                matrixWidth <= maxWidth &&
                matrixHeight <= maxHeight
            )largestMatrix = matrix;
        })
        return largestMatrix.x ? largestMatrix : null
    }
    const moveCameraPtzToMatrix = function(event,trackingTarget){
        if(moveLock[event.ke + event.id])return;
        trackingTarget = trackingTarget || 'person'
        const imgHeight = event.details.imgHeight
        const imgWidth = event.details.imgWidth
        const thresholdX = imgWidth * 0.125
        const thresholdY = imgHeight * 0.125
        const imageCenterX = imgWidth / 2
        const imageCenterY = imgHeight / 2
        const matrices = event.details.matrices || []
        const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1),imgWidth,imgHeight)
        if(!largestMatrix)return;
        const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
        const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
        const turnSpeed = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
        const matrixCenterX = largestMatrix.x + (largestMatrix.width / 2)
        const matrixCenterY = largestMatrix.y + (largestMatrix.height / 2)
        const rawDistanceX = (matrixCenterX - imageCenterX)
        const rawDistanceY = (matrixCenterY - imageCenterY)
        const percentX = parseFloat((rawDistanceX / imgWidth).toFixed(2));
        const percentY = parseFloat((rawDistanceY / imgHeight).toFixed(2));
        const turnSpeedX = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
        const turnSpeedY = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
        const distanceX = imgWidth / rawDistanceX
        const distanceY = imgHeight / rawDistanceY
        const axisX = rawDistanceX > thresholdX || rawDistanceX < -thresholdX ? distanceX : 0
        const axisY = largestMatrix.y < 30 && largestMatrix.height > imgHeight * 0.8 ? 0.5 : rawDistanceY > thresholdY || rawDistanceY < -thresholdY ? -distanceY : 0
        if(axisX !== 0 || axisY !== 0){
            ptzControl({
                axis: [
                    {direction: 'x', amount: axisX === 0 ? 0 : axisX > 0 ? turnSpeedX : -turnSpeedX},
                    {direction: 'y', amount: axisY === 0 ? 0 : axisY > 0 ? invertedVerticalAxis ? -turnSpeedY : turnSpeedY : invertedVerticalAxis ? turnSpeedY : -turnSpeedY},
                    {direction: 'z', amount: 0},
                ],
                moveTimeout: 500,
                id: event.id,
                ke: event.ke
            },(msg) => {
                s.userLog(event,{
                    type: lang['Control'],
                    msg: msg
                });
                // console.log(msg)
                moveToHomePositionTimeout(event)
            })
        }else{
            moveToHomePositionTimeout(event)
        }
    }
    async function setHomePositionPreset(e){
        const groupKey = e.ke
        const monitorId = e.mid || e.id
        const controlOptions = {
            ke: groupKey,
            id: monitorId
        }
        const waitTime = 5000
        let response = {ok: false}
        const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
        const onvifHomeControlMethod = monitorConfig.details.onvif_non_standard
        const actionFunction = onvifHomeControlMethod === '2' ? setHomePosition : setPresetForCurrentPosition
        await asyncSetTimeout(waitTime)
        response = await actionFunction(controlOptions)
        if(response.ok === false){
            await asyncSetTimeout(waitTime)
            response = await actionFunction(controlOptions)
            if(response.ok === false){
                await asyncSetTimeout(waitTime)
                response = await actionFunction(controlOptions)
            }
        }
        return response
    }
    return {
        startMove,
        stopMove,
        ptzControl,
        getPresetPositions,
        setPresetForCurrentPosition,
        moveToPresetPosition,
        moveCameraPtzToMatrix,
        setHomePositionPreset,
        moveToHomePosition,
        moveToHomePositionTimeout,
    }
}
