var Fn = {
  debounce: require("lodash.debounce"),
  cloneDeep: require("lodash.clonedeep"),
};

/**
 * Format a number such that it has a given number of digits after the
 * decimal point.
 *
 * @param {Number} number - The number to format
 * @param {Number} [digits = 0] - The number of digits after the decimal point
 * @return {Number} Formatted number
 * @example
 * Fn.toFixed(5.4564, 2); // -> 5.46
 * @example
 * Fn.toFixed(1.5, 2); // -> 1.5
 */
Fn.toFixed = function(number, digits) {
  // Guard against error when number is null or undefined
  // Cast result as number
  return +(number || 0).toFixed(digits);
};


/**
 * Map a value (number) from one range to another. Based on Arduino's map().
 *
 * @param {Number} value    - value to map
 * @param {Number} fromLow  - low end of originating range
 * @param {Number} fromHigh - high end of originating range
 * @param {Number} toLow    - low end of target range
 * @param {Number} toHigh   - high end of target range
 * @return {Number} mapped value (integer)
 * @example
 * Fn.map(500, 0, 1000, 0, 255); // ->
 */

Fn.map = function(value, fromLow, fromHigh, toLow, toHigh) {
  return ((value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow) | 0;
};
// Alias
Fn.scale = Fn.map;

/**
 * Like map, but returns a Float32
 *
 * For @param info, @see Fn.map
 * @return {Float32}
 */
var f32A = new Float32Array(1);

Fn.fmap = function(value, fromLow, fromHigh, toLow, toHigh) {
  f32A[0] = (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
  return f32A[0];
};
// Alias
Fn.fscale = Fn.fmap;

/**
 * Constrains a number to be within a range. Based on Arduino's constrain()
 *
 * @param {Number} value
 * @param {Number} lower - lower bound of range for constraint
 * @param {Number} upper - upper bound of range for constraint
 * @return {Number | NaN} constrained number or NaN if any of the provided
 *   parameters are not a {Number}.
 */
Fn.constrain = function(value, lower, upper) {
  return Math.min(upper, Math.max(lower, value));
};

/**
 * Is value between the bounds of lower and upper?
 *
 * @param {Number} value
 * @param {Number} lower - Lower end of bounds to check
 * @param {Number} upper - Upper ends of bounds to check
 * @return {Boolean}
 */
Fn.inRange = function(value, lower, upper) {
  return value >= lower && value <= upper;
};

/**
 * Generate an Array of Numbers with values between lower and upper; the
 * step (increment/decrement) between each defined by tick.
 *
 * @param {Number} lower - The value of the lowest element in the resulting
 *                         Array. If `Fn.range` invoked with only one
 *                         argument, this parameter will instead define the
 *                         length of the Array, which will start from 0.
 * @param {Number} upper - The value of the final element of the Array.
 * @param {Number} [tick = 1] - The difference between each element in the
 *                              Array. This value may be negative.
 * @return {Array} of {Numbers}
 *
 * @example
 * Fn.range(5, 10); // -> [5, 6, 7, 8, 9, 10];
 * @example
 * Fn.range(5); // -> [0, 1, 2, 3, 4];
 * @example
 * Fn.range(3, 27, 3); // -> [3, 6, 9, 12, 15, 18, 21, 24, 27];
 * @example
 * Fn.range(0, -9, -3); // -> [0, -3, -6, -9];
 */
Fn.range = function(lower, upper, tick) {

  if (arguments.length === 1) {
    upper = lower - 1;
    lower = 0;
  }

  lower = lower || 0;
  upper = upper || 0;
  tick = tick || 1;

  var len = Math.max(Math.ceil((upper - lower) / tick), 0),
    idx = 0,
    range = [];

  while (idx <= len) {
    range[idx++] = lower;
    lower += tick;
  }

  return range;
};

/**
 * Adds prefix to each element in the range Array returned by Fn.range.
 *
 * Fn.range.prefixed( prefix, upper )
 * Fn.range.prefixed( prefix, lower, upper )
 * Fn.range.prefixed( prefix, lower, upper, tick )
 *
 * @param {*} prefix - You probably want to use a string, but you don't have to.
 * @return Array per range parameters, each element prefixed.
 * @see Fn.range
 * @example
 * Fn.range.prefixed("A", 0, 10, 2); // -> ["A0", "A2", "A4", "A6", "A8", "A10"]
 */
Fn.range.prefixed = function(prefix) {
  return Fn.range.apply(null, [].slice.call(arguments, 1)).map(function(val) {
    return prefix + val;
  });
};

/**
 * Generate a reasonably-unique ID string
 *
 * @return {String} - 36-character random-ish string
 */
Fn.uid = function() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(chr) {
    var rnd = Math.random() * 16 | 0;
    return (chr === "x" ? rnd : (rnd & 0x3 | 0x8)).toString(16);
  }).toUpperCase();
};


/**
 * Square your x!
 *
 * @param {Number} x
 * @return {Number| Nan} - x^2—unless you were goofy enough to provide a
 *   non-numeric x, in which case it's NaN for you!
 */
Fn.square = function(x) {
  return x * x;
};

/**
 * Get a sum for all the values in an Array. This works best if the elements
 * in the Array are Numbers.
 *
 * @param {Array} values
 * @return {Number | String} - You probably want a Number so you'll want to
 *                             pass a values Array entirely consisting of
 *                             numeric elements.
 */
Fn.sum = function sum(values) {
  var vals;
  if (Array.isArray(values)) {
    vals = values;
  } else {
    vals = [].slice.call(arguments);
  }
  return vals.reduce(function(accum, value) {
    return accum + value;
  }, 0);
};

/**
 * Fused multiply-add for precise floating-point calculations.
 */
// fma function
// Copyright (c) 2012, Jens Nockert
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//  1. Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
Fn.fma = function(a, b, c) {
  var aHigh = 134217729 * a;
  var aLow;

  aHigh = aHigh + (a - aHigh);
  aLow = a - aHigh;

  var bHigh = 134217729 * b;
  var bLow;

  bHigh = bHigh + (b - bHigh);
  bLow = b - bHigh;

  var r1 = a * b;
  var r2 = -r1 + aHigh * bHigh + aHigh * bLow + aLow * bHigh + aLow * bLow;

  var s = r1 + c;
  var t = (r1 - (s - c)) + (c - (s - r1));

  return s + (t + r2);
};
// end fma function copyright

/**
 * Return a value with the bit at the position indicated set (to 1).
 * From avr/io.h "BV" => Bit Value
 *
 * An example: logically OR these bits together:
 * var ORed = _BV(0) | _BV(2) | _BV(7);
 *
 * BIT         7  6  5  4  3  2  1  0
 * ---------------------------------------------------------
 * _BV(0)  =   0  0  0  0  0  0  0  1
 * _BV(2)  =   0  0  0  0  0  1  0  0
 * _BV(7)  =   1  0  0  0  0  0  0  0
 * ORed    =   1  0  0  0  0  1  0  1
 *
 * ORed === 133;
 *
 * @param {Number} bit - bit position to set
 * @return {Number}
 * @example
 * Fn.bitValue(0); // --> 1
 * @example
 * Fn.bitValue(4); // --> 16
 *
 */
Fn._BV = Fn.bitValue = Fn.bv = function(bit) {
  return 1 << bit;
};

/**
 * int16 Combine two bytes to make an signed 16-bit integer
 * @param  {byte} msb   Most signifcant byte
 * @param  {byte} lsb   Least signifcant byte
 * @return {word}       Signed 16-bit integer
 */
Fn.int16 = function(msb, lsb) {
  var result = (msb << 8) | lsb;

  // Check highest bit for sign. If on, value is negative
  return result >> 15 ? ((result ^ 0xFFFF) + 1) * -1 : result;
};

/**
 * uint16 Combine two bytes to make an unsigned 16-bit integer
 * @param  {byte} msb   Most signifcant byte
 * @param  {byte} lsb   Least signifcant byte
 * @return {word}       unsigned 16-bit integer
 */
Fn.uint16 = function(msb, lsb) {
  return (msb << 8) | lsb;
};

/**
 * int24 Combine three bytes to make a signed 24-bit integer
 * @param  {byte} b16   b[16:23]
 * @param  {byte} b8    b[8:15]
 * @param  {byte} b0    b[0:7]
 * @return {word}       Signed 24-bit integer
 */
Fn.int24 = function(b16, b8, b0) {
  var result = (b16 << 16) | (b8 << 8) | b0;

  // Check highest bit for sign. If on, value is negative
  return result >> 23 ? ((result ^ 0xFFFFFF) + 1) * -1 : result;
};

/**
 * uint24 Combine three bytes to make an unsigned 24-bit integer
 * @param  {byte} b16   b[16:23]
 * @param  {byte} b8    b[8:15]
 * @param  {byte} b0    b[0:7]
 * @return {word}       Unsigned 24-bit integer
 */
Fn.uint24 = function(b16, b8, b0) {
  return (b16 << 16) | (b8 << 8) | b0;
};

/**
 * int32 Combine four bytes to make a signed 24-bit integer
 * @param  {byte} b24   b[24:31]
 * @param  {byte} b16   b[16:23]
 * @param  {byte} b8    b[8:15]
 * @param  {byte} b0    b[0:7]
 * @return {word}       Signed 32-bit integer
 */
Fn.int32 = function(b24, b16, b8, b0) {
  var result = (b24 << 24) | (b16 << 16) | (b8 << 8) | b0;
  // Check highest bit for sign. If on, value is negative
  return result >> 31 ? ((result ^ 0xFFFFFFFF) + 1) * -1 : result;
};

/**
 * int32 Combine four bytes to make an unsigned 32-bit integer
 * @param  {byte} b24   b[24:31]
 * @param  {byte} b16   b[16:23]
 * @param  {byte} b8    b[8:15]
 * @param  {byte} b0    b[0:7]
 * @return {Number}       unsigned 32-bit integer
 */
Fn.uint32 = function(b24, b16, b8, b0) {
  // Note: If you left-shift a byte by 24 in JS and that byte's
  // MSbit is 1, the resulting value will be negative because JS casts
  // bitwise operands (temporarily) to SIGNED 32-bit numbers. The
  // final >>> 0 causes the sign bit to be disregarded, making sure our
  // result is non-negative.
  return ((b24 << 24) | (b16 << 16) | (b8 << 8) | b0) >>> 0;
};

/**
 * bitSize Get the number of bits in a given number
 * @param  {number} n   The number to evaluate
 * @return {number}     The bit count
 */
Fn.bitSize = function(n) {
  return Math.round(Math.log2(n));
};

/**
 * The following generates functions and constants for utility when working
 * with binary numbers:
 *   - Fn.POW_2_0 through Fn.POW_2_53
 *   - Fn.u4(value) through Fn.u32(value)
 *   - Fn.s4(value) through Fn.s32(value)
 */
var POW = "POW_2_";
var U = "u";
var S = "s";
var MAX = Fn.bitSize(Number.MAX_SAFE_INTEGER) + 1;
var bitSizes = [ 4, 8, 10, 12, 16, 20, 24, 32 ];

/**
 * Generate "constants" that represent powers of 2. Available for powers
 * 0 through 53.
 * @example
 * Fn.POW_2_17; // -> 131072
 */
for (var i = 0; i < MAX; i++) {
  Fn[POW + i] = Math.pow(2, i);
}

bitSizes.forEach(function(bitSize) {
  var decimal = Fn[POW + bitSize];
  var half = decimal / 2 >>> 0;
  var halfMinusOne = half - 1;

  /**
   * The function Fn["u" + bitSize] will constrain a value to an unsigned
   * value of that bit size.
   *
   * @param {Number} value
   * @return {Number} constrained to an unsigned int
   * @example
   * Fn.u8(255); // --> 255
   * Fn.u8(256); // --> 255
   * Fn.u8(-255); // --> 0
   * Fn.u8(-254); // -- 1
   */
  Fn[U + bitSize] = function(value) {
    if (value < 0) {
      value += decimal;
    }
    return Fn.constrain(value, 0, decimal - 1);
  };

  /**
   * The function Fn["s" + bitSize] will constrain a value to a signed value
   * of that bit size. Remember that, e.g., range for signed 8-bit numbers
   * is -128 to 127.
   *
   * @param {Number} value
   * @return {Number} constrained to a SIGNED integer in bitsize range
   * @example
   * Fn.s8(100); // --> 100
   * Fn.s8(128); // --> -128
   * Fn.s8(127); // --> 127
   * Fn.s8(255); // --> -1
   */
  Fn[S + bitSize] = function(value) {
    if (value > halfMinusOne) {
      value -= decimal;
    }
    return Fn.constrain(value, -half, halfMinusOne);
  };
});
/*
  Fn.POW_2_0   => 1
  Fn.POW_2_1   => 2
  Fn.POW_2_2   => 4
  Fn.POW_2_3   => 8
  Fn.POW_2_4   => 16
  Fn.POW_2_5   => 32
  Fn.POW_2_6   => 64
  Fn.POW_2_7   => 128
  Fn.POW_2_8   => 256
  Fn.POW_2_9   => 512
  Fn.POW_2_10  => 1024
  Fn.POW_2_11  => 2048
  Fn.POW_2_12  => 4096
  Fn.POW_2_13  => 8192
  Fn.POW_2_14  => 16384
  Fn.POW_2_15  => 32768
  Fn.POW_2_16  => 65536
  Fn.POW_2_17  => 131072
  Fn.POW_2_18  => 262144
  Fn.POW_2_19  => 524288
  Fn.POW_2_20  => 1048576
  Fn.POW_2_21  => 2097152
  Fn.POW_2_22  => 4194304
  Fn.POW_2_23  => 8388608
  Fn.POW_2_24  => 16777216
  Fn.POW_2_25  => 33554432
  Fn.POW_2_26  => 67108864
  Fn.POW_2_27  => 134217728
  Fn.POW_2_28  => 268435456
  Fn.POW_2_29  => 536870912
  Fn.POW_2_30  => 1073741824
  Fn.POW_2_31  => 2147483648
  Fn.POW_2_32  => 4294967296
  Fn.POW_2_33  => 8589934592
  Fn.POW_2_34  => 17179869184
  Fn.POW_2_35  => 34359738368
  Fn.POW_2_36  => 68719476736
  Fn.POW_2_37  => 137438953472
  Fn.POW_2_38  => 274877906944
  Fn.POW_2_39  => 549755813888
  Fn.POW_2_40  => 1099511627776
  Fn.POW_2_41  => 2199023255552
  Fn.POW_2_42  => 4398046511104
  Fn.POW_2_43  => 8796093022208
  Fn.POW_2_44  => 17592186044416
  Fn.POW_2_45  => 35184372088832
  Fn.POW_2_46  => 70368744177664
  Fn.POW_2_47  => 140737488355328
  Fn.POW_2_48  => 281474976710656
  Fn.POW_2_49  => 562949953421312
  Fn.POW_2_50  => 1125899906842624
  Fn.POW_2_51  => 2251799813685248
  Fn.POW_2_52  => 4503599627370496
  Fn.POW_2_53  => 9007199254740992

  Fn.u4(value) => 4-bit Unsigned Integer
  Fn.s4(value) => 4-bit Signed Integer

  Fn.u8(value) => 8-bit Unsigned Integer
  Fn.s8(value) => 8-bit Signed Integer

  Fn.u10(value) => 10-bit Unsigned Integer
  Fn.s10(value) => 10-bit Signed Integer

  Fn.u12(value) => 12-bit Unsigned Integer
  Fn.s12(value) => 12-bit Signed Integer

  Fn.u16(value) => 16-bit Unsigned Integer
  Fn.s16(value) => 16-bit Signed Integer

  Fn.u20(value) => 20-bit Unsigned Integer
  Fn.s20(value) => 20-bit Signed Integer

  Fn.u24(value) => 24-bit Unsigned Integer
  Fn.s24(value) => 24-bit Signed Integer

  Fn.u32(value) => 32-bit Unsigned Integer
  Fn.s32(value) => 32-bit Signed Integer

}
*/

Fn.RAD_TO_DEG = 180 / Math.PI;
Fn.DEG_TO_RAD = Math.PI / 180;
Fn.TAU = 2 * Math.PI;

module.exports = Fn;
