Source: rtc/mos.js

const rfactorConstants = {
  r0: 94.768,
  is: 1.42611
};

/**
 * Calculate the mos score of a stats object
 * @param {object} sample - Sample, must have rtt and jitter
 * @param {number} fractionLost - The fraction of packets that have been lost
     Calculated by packetsLost / totalPackets
 * @return {number} mos - Calculated MOS, 1.0 through roughly 4.5
 */
function calcMos(sample, fractionLost) {
  if (!sample ||
    !isPositiveNumber(sample.rtt) ||
    !isPositiveNumber(sample.jitter) ||
    !isPositiveNumber(fractionLost)) {
    return null;
  }

  const rFactor = calculateRFactor(sample.rtt, sample.jitter, fractionLost);

  const mos = 1 + (0.035 * rFactor) + (0.000007 * rFactor) *
    (rFactor - 60) * (100 - rFactor);

  // Make sure MOS is in range
  const isValid = (mos >= 1.0 && mos < 4.6);
  return isValid ? mos : null;
}

function calculateRFactor(rtt, jitter, fractionLost) {
  const effectiveLatency = rtt + (jitter * 2) + 10;
  let rFactor = 0;

  switch (true) {
    case effectiveLatency < 160 :
      rFactor = rfactorConstants.r0 - (effectiveLatency / 40);
      break;
    case effectiveLatency < 1000 :
      rFactor = rfactorConstants.r0 - ((effectiveLatency - 120) / 10);
      break;
    case effectiveLatency >= 1000 :
      rFactor = rfactorConstants.r0 - ((effectiveLatency) / 100 );
      break;
  }

  let multiplier = .01;
  switch (true) {
    case fractionLost === -1:
      multiplier = 0;
      rFactor = 0;
      break;
    case fractionLost <= (rFactor / 2.5):
      multiplier = 2.5;
      break;
    case fractionLost > (rFactor / 2.5) && fractionLost < 100 :
      multiplier = .25;
      break;
  }

  rFactor -= (fractionLost * multiplier);
  return rFactor;
}

function isPositiveNumber(n) {
  return typeof n === 'number' && !isNaN(n) && isFinite(n) && n >= 0;
}

module.exports = {
  calculate: calcMos
};