import osc from 'osc'

class OscSender {
  constructor (opts = {}) {
    this._ipAddress = opts.remoteAddress || '127.0.0.1'
    this._port = parseInt(opts.remotePort || 9000)

    if (opts.useWebSockets) {
      const
        express = require('express'),
        WebSocket = require('ws'),
        app = express()

      let server

      if (opts.secureWebSockets) {
        const
          fs = require('fs'),
          path = require('path'),
          key = fs.readFileSync(path.join(__statics, 'cert', 'key.pem'), 'utf8'),
          cert = fs.readFileSync(path.join(__statics, 'cert', 'cert.pem'), 'utf8'),
          https = require('https')

        server = https.createServer({ key, cert }, app)
        server.listen(this._port, this._ipAddress)
      }
      else {
        server = app.listen(this._port, this._ipAddress)
      }

      const _this = this
      this._wss = new WebSocket.Server({ server })
      this._sockets = []

      this._osc = {
        send (bundle) {
          for (let i in _this._sockets) {
            try {
              if (_this._sockets[i]) _this._sockets[i].send(bundle)
            }
            catch (e) {
              _this._sockets[i] = null
            }
          }
          if (_this._sockets.indexOf(null) > -1) {
            _this._sockets = _this._sockets.filter(socket => !!socket)
          }
        },
        close () {
          if (!this._sockets) return
          for (let socket of this._sockets) {
            try {
              if (socket && typeof socket.close === 'function') socket.close()
            }
            catch (err) {
              console.debug('socket close failed', err)
            }
          }
          this._sockets = undefined
        }
      }
    }

    this._udpConfig = {
      broadcast: opts.broadcast || true,
      localAddress: opts.localAddress || '127.0.0.1',
      localPort: opts.localPort || 50000 + Math.round(Math.random() * 9000)
    }
  }

  start () {
    if (Array.isArray(this._sockets)) {
      const _this = this
      this._wss.on('connection', function (socket) {
        const socketPort = new osc.WebSocketPort({ socket, metadata: true })
        _this._sockets.push(socketPort)
      })
    }
    else {
      this._osc = new osc.UDPPort(this._udpConfig)
      const _this = this
      return new Promise(resolve => {
        _this._osc.open()
        _this._osc.once('ready', resolve)
      })
    }
  }

  stop () {
    if (this._osc) {
      this._osc.close()
      this._osc = undefined
    }
    if (this._wss) {
      this._wss._server.close()
      this._wss = undefined
    }
  }

  sendFrame (frame, analysisFrame, metadata, scale = 1.0, delaySeconds = 0, filters = { joints: [], skeletons: [] }) {
    if (!this._osc) return
    try {
      const bundle = {
        timeTag: osc.timeTag(delaySeconds),
        packets: [{
          address: `/seconds`,
          args: [{ type: 'f', value: frame.t }]
        }]
      }
      for (let s in metadata.skeletons) {
        if (filters.skeletons.length && filters.skeletons.indexOf(metadata.skeletons[s]) === -1) continue
        // Iterate over all joints in the current skeleton
        for (let b in metadata.boneNames[s]) {
          if (filters.joints.length && filters.joints.indexOf(metadata.boneNames[s][b]) === -1) continue

          const jointValues = [
            // Position
            frame.d[s].p[b * 3] * scale,
            frame.d[s].p[b * 3 + 1] * scale,
            frame.d[s].p[b * 3 + 2] * scale,
            // Quaternion
            frame.d[s].r[b * 4],
            frame.d[s].r[b * 4 + 1],
            frame.d[s].r[b * 4 + 2],
            frame.d[s].r[b * 4 + 3]
          ]
          bundle.packets.push({
            address: `/${metadata.skeletons[s]}/${metadata.boneNames[s][b]}`,
            args: jointValues.filter(p => typeof p === 'number').map(p => {
              return {
                type: 'f',
                value: p
              }
            })
          })
        }
        if (analysisFrame) {
          const args = []
          for (let s in analysisFrame.r) {
            for (let a of analysisFrame.r[s]) {
              if (Array.isArray(a)) {
                for (let e of a) {
                  args.push({
                    type: 'f',
                    value: e
                  })
                }
              }
              else if (typeof a === 'number') {
                args.push({
                  type: 'f',
                  value: a
                })
              }
            }
            bundle.packets.push({
              address: `/${metadata.skeletons[s]}/analysis`,
              args
            })
          }
        }
      }
      this.sendOSC(bundle, this._ipAddress, this._port)
    }
    catch (err) {
      console.error('failed to send osc', err.message)
    }
  }

  sendAnnotation (annotationBody, delaySeconds = 0) {
    if (!this._osc) return
    const bundle = {
      timeTag: osc.timeTag(delaySeconds),
      packets: [{
        address: '/annotations',
        args: [{ value: annotationBody, type: 's' }]
      }]
    }
    this.sendOSC(bundle, this._ipAddress, this._port)
  }

  sendOSC (bundle, ip, port) {
    try {
      this._osc.send(bundle, ip, port)
    }
    catch (err) {
      if (err.code !== 'ECANCELED') {
        console.error('OSC send error', err)
      }
    }
  }
}

export default OscSender
