// Latency compensation for osc clients
// use receiveSync() instead of receive()
// to send synchronized messages to all clients

// keep track of connected clients
// use global object to keep the list
// from resetting when hot-reloading the custom module
global.clients = global.clients || {}
var clients = global.clients

app.on('open', (data, client)=>{
    if (!clients[client.id]) clients[client.id] = {
        latencies: [],
        latency: 0,
    }
})

app.on('close', (data, client)=>{
    delete clients[client.id]
})

function pingClients(){
    // ping clients and make them include their id and ping date in the reply
    var date = Date.now()
    for (let id in clients) {
        receive('/SCRIPT', `send('custom-module:null', '/pong', "${id}", ${date})`)
    }
}

function handleClientPong(id, date) {
    // client's ping response to measure their latency
    // estimated as half of the roundtrip latency
    if (clients[id]) {

        // accumulate latency measures
        // keep 5 latest values
        if (clients[id].latencies.unshift((Date.now() - date) / 2)  > 5) clients[id].latencies.pop()

        if (clients[id].latencies.length < 2)
            clients[id].latency = clients[id].latencies[0]
        else
            clients[id].latency = filterLatencies(clients[id].latencies)

    }
}

function filterLatencies(arr) {
    // As described at https://web.archive.org/web/20160310125700/http://mine-control.com/zack/timesync/timesync.html
    // with bith from https://github.com/enmasseio/timesync/blob/master/src/stat.js

    //console.log(arr)
    // compute median
    var sorted = arr.slice().sort((a,b)=> a > b ? 1 : a < b ? -1 : 0)
    var median
    if (sorted.length % 2 === 0)
        // even
        median = (sorted[arr.length / 2 - 1] + sorted[arr.length / 2]) / 2
    else
        // odd
        median = sorted[(arr.length - 1) / 2]


    // compute variance (average of the squared differences from the mean)
    // in the end divide by (arr.length - 1) because  we are working on
    // a sample and not on a full dataset (https://www.mathsisfun.com/data/standard-deviation.html)
    var mean = arr.reduce((a,b)=>a + b) / arr.length;
    var variance = arr.map(x => Math.pow(x - mean, 2))
                      .reduce((a,b)=>a + b) / (arr.length - 1)

    // if all values are the same, return the first one
    if (variance === 0) return arr[0]
  
    // compute standard deviation
    var stdDeviation = Math.sqrt(variance)

    // discard values above 1 standard-deviation from the median
    var filtered = arr.filter(x=>x < median + stdDeviation)

    // return arithmetic mean of remaining values
    return filtered.reduce((a,b)=>a + b) / filtered.length
}


function receiveSync(address, ...args) {
    // Send a message to all clients with dalys based on their measured latency

    // get worst client latency
    var maxLatency = 0
    for (let id in clients) {
        if (clients[id].latency > maxLatency) maxLatency = clients[id].latency
    }

    // delay message by the latency delta with the worst client
    for (let id in clients) {
        setTimeout(()=>{
            //send(host, port, address, ...args, {clientId: id})
            receive(address, ...args, {clientId: id})
        }, maxLatency - clients[id].latency)
    }
}

// Measure clients latencies every 2 seconds
setInterval(pingClients, 250)



module.exports = {

    oscInFilter:function(data) {

      //console.log(data)

      var {host, port, address, args} = data

      if (address === '/playing') {
        //receive("/measure", args[0].value)
        //receive("/beat", args[1].value)
        //receive("/visual_click", 1)
        receiveSync("/measure", args[0].value)
        receiveSync("/beat", args[1].value)
        receiveSync("/visual_click", 1)
      }

      return

    },

    oscOutFilter:function(data){

        var {address, args, host, port} = data

        if (host === 'custom-module') {

            // handle client's ping response
            if (address === '/pong') handleClientPong(args[0].value, args[1].value)

            return // bypass original message
        }

        if (address.substring(1, 6) == 'mixer') {
					tokens = address.split('/')
					//piece = tokens[2]
					type = tokens[3]
					val = args[0].value
          if((type == "volume_master") || (type == "volume_click") || (type == "out_audio") || (type == "out_click")) {
            args = [{'type': 's', 'value': type}, {'type': 'f', 'value': val}]
          } else {
            index = tokens[4]
            args = [{'type': 's', 'value': type}, {'type': 's', 'value': index}, {'type': 'f', 'value': val}]
          }
					//args = [{'type': 's', 'value': piece}, {'type': 's', 'value': type}, {'type': 's', 'value': index}, {'type': 'f', 'value': val}]
          //console.log(data)
					address = '/mixer'
          return {host, port, address, args}
        }

				if (address === '/transport') {
          return {host, port, address, args}
        }

        return

    }

}

