// Collects anonymous usage stats and uncaught errors
// Reports back so that we can improve WebTorrent Desktop
module.exports = {
  init,
  logUncaughtError,
  logPlayAttempt
}

const crypto = require('crypto')
const electron = require('electron')
const https = require('https')
const os = require('os')
const url = require('url')

const config = require('../../config')

var telemetry

function init (state) {
  telemetry = state.saved.telemetry
  if (!telemetry) {
    telemetry = state.saved.telemetry = createSummary()
    reset()
  }

  var now = new Date()
  telemetry.timestamp = now.toISOString()
  telemetry.localTime = now.toTimeString()
  telemetry.screens = getScreenInfo()
  telemetry.system = getSystemInfo()
  telemetry.approxNumTorrents = getApproxNumTorrents(state)

  if (config.IS_PRODUCTION) {
    postToServer()
  } else {
    // Development: telemetry used only for local debugging
    // Empty uncaught errors, etc at the start of every run
    reset()
  }
}

function reset () {
  telemetry.uncaughtErrors = []
  telemetry.playAttempts = {
    total: 0,
    success: 0,
    timeout: 0,
    error: 0,
    abandoned: 0
  }
}

function postToServer () {
  // Serialize the telemetry summary
  var payload = new Buffer(JSON.stringify(telemetry), 'utf8')

  // POST to our server
  var options = url.parse(config.TELEMETRY_URL)
  options.method = 'POST'
  options.headers = {
    'Content-Type': 'application/json',
    'Content-Length': payload.length
  }

  var req = https.request(options, function (res) {
    if (res.statusCode === 200) {
      console.log('Successfully posted telemetry summary')
      reset()
    } else {
      console.error('Couldn\'t post telemetry summary, got HTTP ' + res.statusCode)
    }
  })
  req.on('error', function (e) {
    console.error('Couldn\'t post telemetry summary', e)
  })
  req.write(payload)
  req.end()
}

// Creates a new telemetry summary. Gives the user a unique ID,
// collects screen resolution, etc
function createSummary () {
  // Make a 256-bit random unique ID
  var userID = crypto.randomBytes(32).toString('hex')
  return { userID }
}

// Track screen resolution
function getScreenInfo () {
  return electron.screen.getAllDisplays().map((screen) => ({
    width: screen.size.width,
    height: screen.size.height,
    scaleFactor: screen.scaleFactor
  }))
}

// Track basic system info like OS version and amount of RAM
function getSystemInfo () {
  return {
    osPlatform: process.platform,
    osRelease: os.type() + ' ' + os.release(),
    architecture: os.arch(),
    totalMemoryMB: os.totalmem() / (1 << 20),
    numCores: os.cpus().length
  }
}

// Get the number of torrents, rounded to the nearest power of two
function getApproxNumTorrents (state) {
  var exactNum = state.saved.torrents.length
  if (exactNum === 0) return 0
  // Otherwise, return 1, 2, 4, 8, etc by rounding in log space
  var log2 = Math.log(exactNum) / Math.log(2)
  return 1 << Math.round(log2)
}

// An uncaught error happened in the main process or in one of the windows
function logUncaughtError (procName, err) {
  console.error('uncaught error', procName, err)

  // Not initialized yet? Ignore.
  // Hopefully uncaught errors immediately on startup are fixed in dev
  if (!telemetry) return

  var message, stack
  if (err instanceof Error) {
    message = err.message
    stack = err.stack
  } else {
    message = String(err)
    stack = ''
  }

  // We need to POST the telemetry object, make sure it stays < 100kb
  if (telemetry.uncaughtErrors.length > 20) return
  if (message.length > 1000) message = message.substring(0, 1000)
  if (stack.length > 1000) stack = stack.substring(0, 1000)

  telemetry.uncaughtErrors.push({process: procName, message, stack})
}

// The user pressed play. It either worked, timed out, or showed the
// 'Play in VLC' codec error
function logPlayAttempt (result) {
  if (!['success', 'timeout', 'error', 'abandoned'].includes(result)) {
    return console.error('Unknown play attempt result', result)
  }

  var attempts = telemetry.playAttempts
  attempts.total = (attempts.total || 0) + 1
  attempts[result] = (attempts[result] || 0) + 1
}
