called-from = ()

vendors = moz webkit o ms official

// stringify the given arg

-string(arg)
  type(arg) + ' ' + arg

// require a color

require-color(color)
  unless color is a 'color'
    error('RGB or HSL value expected, got a ' + -string(color))

// require a unit

require-unit(n)
  unless n is a 'unit'
    error('unit expected, got a ' + -string(n))

// require a string

require-string(str)
  unless str is a 'string' or str is a 'ident'
    error('string expected, got a ' + -string(str))

// Math functions

abs(n) { math(n, 'abs') }
min(a, b) { a < b ? a : b }
max(a, b) { a > b ? a : b }

// Trigonometrics
PI = -math-prop('PI')

radians-to-degrees(angle)
  angle * (180 / PI)

degrees-to-radians(angle)
  unit(angle * (PI / 180),'')

sin(n)
  n = degrees-to-radians(n) if unit(n) == 'deg'
  round(math(n, 'sin'), 9)

cos(n)
  n = degrees-to-radians(n) if unit(n) == 'deg'
  round(math(n, 'cos'), 9)

// Rounding Math functions

ceil(n, precision = 0)
  multiplier = 10 ** precision
  math(n * multiplier, 'ceil') / multiplier

floor(n, precision = 0)
  multiplier = 10 ** precision
  math(n * multiplier, 'floor') / multiplier

round(n, precision = 0)
  multiplier = 10 ** precision
  math(n * multiplier, 'round') / multiplier

// return the sum of the given numbers

sum(nums)
  sum = 0
  sum += n for n in nums

// return the average of the given numbers

avg(nums)
  sum(nums) / length(nums)

// return a unitless number, or pass through

remove-unit(n)
  if typeof(n) is "unit"
    unit(n, "")
  else
    n

// convert a percent to a decimal, or pass through

percent-to-decimal(n)
  if unit(n) is "%"
    remove-unit(n) / 100
  else
    n

// check if n is an odd number

odd(n)
  1 == n % 2

// check if n is an even number

even(n)
  0 == n % 2

// check if color is light

light(color)
  lightness(color) >= 50%

// check if color is dark

dark(color)
  lightness(color) < 50%

// desaturate color by amount

desaturate(color, amount)
  adjust(color, 'saturation', - amount)

// saturate color by amount

saturate(color = '', amount = 100%)
  if color is a 'color'
    adjust(color, 'saturation', amount)
  else
    unquote( "saturate(" + color + ")" )

// darken by the given amount

darken(color, amount)
  adjust(color, 'lightness', - amount)

// lighten by the given amount

lighten(color, amount)
  adjust(color, 'lightness', amount)

// decrease opacity by amount

fade-out(color, amount)
  color - rgba(black, percent-to-decimal(amount))

// increase opacity by amount

fade-in(color, amount)
  color + rgba(black, percent-to-decimal(amount))

// spin hue by a given amount

spin(color, amount)
  color + unit(amount, deg)

// mix two colors by a given amount

mix(color1, color2, weight = 50%)
  unless weight in 0..100
    error("Weight must be between 0% and 100%")

  if length(color1) == 2
    weight = color1[0]
    color1 = color1[1]

  else if length(color2) == 2
    weight = 100 - color2[0]
    color2 = color2[1]

  require-color(color1)
  require-color(color2)

  p = unit(weight / 100, '')
  w = p * 2 - 1

  a = alpha(color1) - alpha(color2)

  w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2
  w2 = 1 - w1

  channels = (red(color1) red(color2)) (green(color1) green(color2)) (blue(color1) blue(color2))
  rgb = ()

  for pair in channels
    push(rgb, floor(pair[0] * w1 + pair[1] * w2))

  a1 = alpha(color1) * p
  a2 = alpha(color2) * (1 - p)
  alpha = a1 + a2

  rgba(rgb[0], rgb[1], rgb[2], alpha)

// invert colors, leave alpha intact

invert(color = '')
  if color is a 'color'
    rgba(#fff - color, alpha(color))
  else
    unquote( "invert(" + color + ")" )

// give complement of the given color

complement( color )
  spin( color, 180 )

// give grayscale of the given color

grayscale( color = '' )
  if color is a 'color'
    desaturate( color, 100% )
  else
    unquote( "grayscale(" + color + ")" )

// mix the given color with white

tint( color, percent )
  mix( white, color, percent )

// mix the given color with black

shade( color, percent )
  mix( black, color, percent )

// return the last value in the given expr

last(expr)
  expr[length(expr) - 1]

// return keys in the given pairs or object

keys(pairs)
  ret = ()
  if type(pairs) == 'object'
    for key in pairs
      push(ret, key)
  else
    for pair in pairs
      push(ret, pair[0]);
  ret

// return values in the given pairs or object

values(pairs)
  ret = ()
  if type(pairs) == 'object'
    for key, val in pairs
      push(ret, val)
  else
    for pair in pairs
      push(ret, pair[1]);
  ret

// join values with the given delimiter

join(delim, vals...)
  buf = ''
  vals = vals[0] if length(vals) == 1
  for val, i in vals
    buf += i ? delim + val : val

// add a CSS rule to the containing block

// - This definition allows add-property to be used as a mixin
// - It has the same effect as interpolation but allows users
//   to opt for a functional style

add-property-function = add-property
add-property(name, expr)
  if mixin
    {name} expr
  else
    add-property-function(name, expr)

prefix-classes(prefix)
  -prefix-classes(prefix, block)

// Caching mixin, use inside your functions to enable caching by extending.

$stylus_mixin_cache = {}
cache()
  $key = (current-media() or 'no-media') + '__' + called-from[0] + '__' + arguments
  if $key in $stylus_mixin_cache
    @extend {"$cache_placeholder_for_" + $stylus_mixin_cache[$key]}
  else if 'cache' in called-from
    {block}
  else
    $id = length($stylus_mixin_cache)

    &,
    /$cache_placeholder_for_{$id}
      $stylus_mixin_cache[$key] = $id
      {block}
