/**
 * Performs an exponential moving average of the specified range,
 * handling missing data in a way that does not distort the average.
 *
 * If the spcified window size is less than the range size, an array
 * is returned, so there is no need to call this function serveral
 * times in a row.
 *
 * @param alpha The geometric progression ratio.
 * @param windowSize The size of the moving average window.
 * @param values The range on which we will calculate the average.
 * @return The exponential moving average of the specified range.
 * @customfunction
 */
function ExponentialMovingAverage(alpha, windowSize, values) {
  var weights = [];
  for (var i = 0; i < windowSize; i++) {
    var w = Math.pow(alpha, windowSize - 1 - i);
    weights[weights.length] = w;
  }
  var average = WeightedMovingAverage(weights, values);
  return average;
}

/**
 * Performs a weighted moving average of the specified range,
 * handling missing data in a way that does not distort the average.
 *
 * @param weights An array with the weights to be applied.
 * @param values The range on which we will calculate the average.
 * @return The weighted moving average of the specified range.
 * @customfunction
 */
function WeightedMovingAverage(weights, values) {
  // If values is a scalar or weights is a scalar or 
  // weights has length 1, return values.
  if (!values.map || !weights.map || weights.length === 1) {
    return values;
  }
  var windowSize = weights.length;
  var rows = values.length;
  if (windowSize > rows) {
    weights.splice(0, windowSize - rows);
    windowSize = weights.length;
    Logger.log("WARNING: windowSize greater than number of rows" +
               " in the range, truncating the weitghts to " +
               "the number of rows and using the last weights.");
  }
  /* If this function is called from the spreadsheet with a range, 
   * weights will be array of columns, lets remove this extra 
   * indirection. */
  if (weights[0].map) {
    for (var i = 0; i < weights.length; i++) {
      weights[i] = weights[i][0];
    }
  }
  var average = [];
  for (var i = 0; i < rows - windowSize + 1; i++) {
    var sumValues = 0;
    var sumWeigths = 0;
    for (var j = 0; j < windowSize; j++) {
      var x = values[i + j][0];
      if (typeof x === "number") {
        var w = weights[j];
        if (typeof w === "number") {
          sumWeigths += w;
          sumValues += w * x;
        }
      }
    }
    var avg = sumValues / sumWeigths;
    average[average.length] = avg;
  }
  return average;
}