/* <CoffeeScript>
{utils} = require('tztime')

</CoffeeScript> */
/**
 * @class Lumenize.functions
 * 
 * Rules about dependencies:
 * 
 *   * If a function can be calculated incrementally from an oldResult and newValues, then you do not need to specify dependencies
 *   * If a funciton can be calculated from other incrementally calculable results, then you need only specify those dependencies
 *   * If a function needs the full list of values to be calculated (like percentile coverage), then you must specify 'values'
 *   * To support the direct passing in of OLAP cube cells, you can provide a prefix (field name) so the key in dependentValues
 *     can be generated
 *   * 'count' is special and does not use a prefix because it is not dependent up a particular field
 *   * You should calculate the dependencies before you calculate the thing that is depedent. The OLAP cube does some
 *     checking to confirm you've done this.
 */
/* <CoffeeScript>
functions = {}

functions._populateDependentValues = (values, dependencies, dependentValues = {}, prefix = '') ->
  out = {}
  for d in dependencies
    if d == 'count'
      if prefix == ''
        key = 'count'
      else
        key = '_count'
    else
      key = prefix + d
    unless dependentValues[key]?
      dependentValues[key] = functions[d](values, undefined, undefined, dependentValues, prefix)
    out[d] = dependentValues[key]
  return out

</CoffeeScript> */
/**
 * @method sum
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The sum of the values
 */
/* <CoffeeScript>
functions.sum = (values, oldResult, newValues) ->
  if oldResult?
    temp = oldResult
    tempValues = newValues
  else
    temp = 0
    tempValues = values
  for v in tempValues
    temp += v
  return temp

</CoffeeScript> */
/**
 * @method product
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The product of the values
 */
/* <CoffeeScript>
functions.product = (values, oldResult, newValues) ->
  if oldResult?
    temp = oldResult
    tempValues = newValues
  else
    temp = 1
    tempValues = values
  for v in tempValues
    temp = temp * v
  return temp

</CoffeeScript> */
/**
 * @method sumSquares
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The sum of the squares of the values
 */
/* <CoffeeScript>
functions.sumSquares = (values, oldResult, newValues) ->
  if oldResult?
    temp = oldResult
    tempValues = newValues
  else
    temp = 0
    tempValues = values
  for v in tempValues
    temp += v * v
  return temp

</CoffeeScript> */
/**
 * @method sumCubes
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The sum of the cubes of the values
 */
/* <CoffeeScript>
functions.sumCubes = (values, oldResult, newValues) ->
  if oldResult?
    temp = oldResult
    tempValues = newValues
  else
    temp = 0
    tempValues = values
  for v in tempValues
    temp += v * v * v
  return temp


</CoffeeScript> */
/**
 * @method lastValue
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or newValues
 * @param {Number} [oldResult] Not used. It is included to make the interface consistent.
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The last value
 */
/* <CoffeeScript>
functions.lastValue = (values, oldResult, newValues) ->
  if newValues?
    return newValues[newValues.length - 1]
  return values[values.length - 1]

</CoffeeScript> */
/**
 * @method firstValue
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] Not used. It is included to make the interface consistent.
 * @return {Number} The first value
 */
/* <CoffeeScript>
functions.firstValue = (values, oldResult, newValues) ->
  if oldResult?
    return oldResult
  return values[0]

</CoffeeScript> */
/**
 * @method count
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The length of the values Array
 */
/* <CoffeeScript>
functions.count = (values, oldResult, newValues) ->
  if oldResult?
    return oldResult + newValues.length
  return values.length

</CoffeeScript> */
/**
 * @method min
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The minimum value or null if no values
 */
/* <CoffeeScript>
functions.min = (values, oldResult, newValues) ->
  if oldResult?
    return functions.min(newValues.concat([oldResult]))
  if values.length == 0
    return null
  temp = values[0]
  for v in values
    if v < temp
      temp = v
  return temp

</CoffeeScript> */
/**
 * @method max
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Number} The maximum value or null if no values
 */
/* <CoffeeScript>
functions.max = (values, oldResult, newValues) ->
  if oldResult?
    return functions.max(newValues.concat([oldResult]))
  if values.length == 0
    return null
  temp = values[0]
  for v in values
    if v > temp
      temp = v
  return temp

</CoffeeScript> */
/**
 * @method values
 * @member Lumenize.functions
 * @static
 * @param {Object[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Array} All values (allows duplicates). Can be used for drill down.
 */
/* <CoffeeScript>
functions.values = (values, oldResult, newValues) ->
  if oldResult?
    return oldResult.concat(newValues)
  return values
#  temp = []
#  for v in values
#    temp.push(v)
#  return temp

</CoffeeScript> */
/**
 * @method uniqueValues
 * @member Lumenize.functions
 * @static
 * @param {Object[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] for incremental calculation
 * @param {Number[]} [newValues] for incremental calculation
 * @return {Array} Unique values. This is good for generating an OLAP dimension or drill down.
 */
/* <CoffeeScript>
functions.uniqueValues = (values, oldResult, newValues) ->
  temp = {}
  if oldResult?
    for r in oldResult
      temp[r] = null
    tempValues = newValues
  else
    tempValues = values
  temp2 = []
  for v in tempValues
    temp[v] = null
  for key, value of temp
    temp2.push(key)
  return temp2

</CoffeeScript> */
/**
 * @method average
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] not used by this function but included so all functions have a consistent signature
 * @param {Number[]} [newValues] not used by this function but included so all functions have a consistent signature
 * @param {Object} [dependentValues] If the function can be calculated from the results of other functions, this allows
 *   you to provide those pre-calculated values.
 * @return {Number} The arithmetic mean
 */
/* <CoffeeScript>
functions.average = (values, oldResult, newValues, dependentValues, prefix) ->
  {count, sum} = functions._populateDependentValues(values, functions.average.dependencies, dependentValues, prefix)
  if count is 0
    return null
  else
    return sum / count

functions.average.dependencies = ['count', 'sum']

</CoffeeScript> */
/**
 * @method errorSquared
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] not used by this function but included so all functions have a consistent signature
 * @param {Number[]} [newValues] not used by this function but included so all functions have a consistent signature
 * @param {Object} [dependentValues] If the function can be calculated from the results of other functions, this allows
 *   you to provide those pre-calculated values.
 * @return {Number} The error squared
 */
/* <CoffeeScript>
functions.errorSquared = (values, oldResult, newValues, dependentValues, prefix) ->
  {count, sum} = functions._populateDependentValues(values, functions.errorSquared.dependencies, dependentValues, prefix)
  mean = sum / count
  errorSquared = 0
  for v in values
    difference = v - mean
    errorSquared += difference * difference
  return errorSquared

functions.errorSquared.dependencies = ['count', 'sum']

</CoffeeScript> */
/**
 * @method variance
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] not used by this function but included so all functions have a consistent signature
 * @param {Number[]} [newValues] not used by this function but included so all functions have a consistent signature
 * @param {Object} [dependentValues] If the function can be calculated from the results of other functions, this allows
 *   you to provide those pre-calculated values.
 * @return {Number} The variance
 */
/* <CoffeeScript>
functions.variance = (values, oldResult, newValues, dependentValues, prefix) ->
  {count, sum, sumSquares} = functions._populateDependentValues(values, functions.variance.dependencies, dependentValues, prefix)
  if count is 0
    return null
  else if count is 1
    return 0
  else
    return (count * sumSquares - sum * sum) / (count * (count - 1))

functions.variance.dependencies = ['count', 'sum', 'sumSquares']

</CoffeeScript> */
/**
 * @method standardDeviation
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] not used by this function but included so all functions have a consistent signature
 * @param {Number[]} [newValues] not used by this function but included so all functions have a consistent signature
 * @param {Object} [dependentValues] If the function can be calculated from the results of other functions, this allows
 *   you to provide those pre-calculated values.
 * @return {Number} The standard deviation
 */
/* <CoffeeScript>
functions.standardDeviation = (values, oldResult, newValues, dependentValues, prefix) ->
  return Math.sqrt(functions.variance(values, oldResult, newValues, dependentValues, prefix))

functions.standardDeviation.dependencies = functions.variance.dependencies

</CoffeeScript> */
/**
 * @method percentileCreator
 * @member Lumenize.functions
 * @static
 * @param {Number} p The percentile for the resulting function (50 = median, 75, 99, etc.)
 * @return {Function} A function to calculate the percentile
 * 
 * When the user passes in `p<n>` as an aggregation function, this `percentileCreator` is called to return the appropriate
 * percentile function. The returned function will find the `<n>`th percentile where `<n>` is some number in the form of
 * `##[.##]`. (e.g. `p40`, `p99`, `p99.9`).
 * 
 * There is no official definition of percentile. The most popular choices differ in the interpolation algorithm that they
 * use. The function returned by this `percentileCreator` uses the Excel interpolation algorithm which differs from the NIST
 * primary method. However, NIST lists something very similar to the Excel approach as an acceptible alternative. The only
 * difference seems to be for the edge case for when you have only two data points in your data set. Agreement with Excel,
 * NIST's acceptance of it as an alternative (almost), and the fact that it makes the most sense to me is why this approach
 * was chosen.
 * 
 * http://en.wikipedia.org/wiki/Percentile#Alternative_methods
 * 
 * Note: `median` is an alias for p50. The approach chosen for calculating p50 gives you the
 * exact same result as the definition for median even for edge cases like sets with only one or two data points.
 * 
 */
/* <CoffeeScript>
functions.percentileCreator = (p) ->
  f = (values, oldResult, newValues, dependentValues, prefix) ->
    unless values?
      {values} = functions._populateDependentValues(values, ['values'], dependentValues, prefix)
    if values.length is 0
      return null
    sortfunc = (a, b) ->
      return a - b
    vLength = values.length
    values.sort(sortfunc)
    n = (p * (vLength - 1) / 100) + 1
    k = Math.floor(n)
    d = n - k
    if n == 1
      return values[1 - 1]
    if n == vLength
      return values[vLength - 1]
    return values[k - 1] + d * (values[k] - values[k - 1])
  f.dependencies = ['values']
  return f

</CoffeeScript> */
/**
 * @method median
 * @member Lumenize.functions
 * @static
 * @param {Number[]} [values] Must either provide values or oldResult and newValues
 * @param {Number} [oldResult] not used by this function but included so all functions have a consistent signature
 * @param {Number[]} [newValues] not used by this function but included so all functions have a consistent signature
 * @param {Object} [dependentValues] If the function can be calculated from the results of other functions, this allows
 *   you to provide those pre-calculated values.
 * @return {Number} The median
 */
/* <CoffeeScript>
functions.median = functions.percentileCreator(50)

functions.expandFandAs = (a) ->
</CoffeeScript> */
  /**
   * @method expandFandAs
   * @member Lumenize.functions
   * @static
   * @param {Object} a Will look like this `{as: 'mySum', f: 'sum', field: 'Points'}`
   * @return {Object} returns the expanded specification
   * 
   * Takes specifications for functions and expands them to include the actual function and 'as'. If you do not provide
   * an 'as' property, it will build it from the field name and function with an underscore between. Also, if the
   * 'f' provided is a string, it is copied over to the 'metric' property before the 'f' property is replaced with the
   * actual function. `{field: 'a', f: 'sum'}` would expand to `{as: 'a_sum', field: 'a', metric: 'sum', f: [Function]}`.
   */
/* <CoffeeScript>
  utils.assert(a.f?, "'f' missing from specification: \n#{JSON.stringify(a, undefined, 4)}")
  if utils.type(a.f) == 'function'
    utils.assert(a.as?, 'Must provide "as" field with your aggregation when providing a user defined function')
    a.metric = a.f.toString()
  else if functions[a.f]?
    a.metric = a.f
    a.f = functions[a.f]
  else if a.f.substr(0, 1) == 'p'
    a.metric = a.f
    p = /\p(\d+(.\d+)?)/.exec(a.f)[1]
    a.f = functions.percentileCreator(Number(p))
  else
    throw new Error("#{a.f} is not a recognized built-in function")

  unless a.as?
    if a.metric == 'count'
      a.field = ''
      a.metric = 'count'
    a.as = "#{a.field}_#{a.metric}"
    utils.assert(a.field? or a.f == 'count', "'field' missing from specification: \n#{JSON.stringify(a, undefined, 4)}")
  return a

functions.expandMetrics = (metrics = [], addCountIfMissing = false, addValuesForCustomFunctions = false) ->
</CoffeeScript> */
  /**
   * @method expandMetrics
   * @member Lumenize.functions
   * @static
   * @private
   * 
   * This is called internally by several Lumenize Calculators. You should probably not call it.
   */
/* <CoffeeScript>
  confirmMetricAbove = (m, fieldName, aboveThisIndex) ->
    if m is 'count'
      lookingFor = '_' + m
    else
      lookingFor = fieldName + '_' + m
    i = 0
    while i < aboveThisIndex
      currentRow = metrics[i]
      if currentRow.as == lookingFor
        return true
      i++
    # OK, it's not above, let's now see if it's below. Then throw error.
    i = aboveThisIndex + 1
    metricsLength = metrics.length
    while i < metricsLength
      currentRow = metrics[i]
      if currentRow.as == lookingFor
        throw new Error("Depdencies must appear before the metric they are dependant upon. #{m} appears after.")
      i++
    return false

  assureDependenciesAbove = (dependencies, fieldName, aboveThisIndex) ->
    for d in dependencies
      unless confirmMetricAbove(d, fieldName, aboveThisIndex)
        if d == 'count'
          newRow = {f: 'count'}
        else
          newRow = {f: d, field: fieldName}
        functions.expandFandAs(newRow)
        metrics.unshift(newRow)
        return false
    return true

  # add values for custom functions
  if addValuesForCustomFunctions
    for m, index in metrics
      if utils.type(m.f) is 'function'
        unless m.f.dependencies?
          m.f.dependencies = []
        unless m.f.dependencies[0] is 'values'
          m.f.dependencies.push('values')
        unless confirmMetricAbove('values', m.field, index)
          valuesRow = {f: 'values', field: m.field}
          functions.expandFandAs(valuesRow)
          metrics.unshift(valuesRow)

  hasCount = false
  for m in metrics
    functions.expandFandAs(m)
    if m.metric is 'count'
      hasCount = true

  if addCountIfMissing and not hasCount
    countRow = {f: 'count'}
    functions.expandFandAs(countRow)
    metrics.unshift(countRow)

  index = 0
  while index < metrics.length  # intentionally not caching length because the loop can add rows
    metricsRow = metrics[index]
    if utils.type(metricsRow.f) is 'function'
      dependencies = ['values']
    if metricsRow.f.dependencies?
      unless assureDependenciesAbove(metricsRow.f.dependencies, metricsRow.field, index)
        index = -1
    index++

  return metrics

exports.functions = functions
</CoffeeScript> */