"""Tests for the core Sass functions, as defined in the original Sass
documentation:

http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

from scss.calculator import Calculator
from scss.extension.core import CoreExtension
from scss.types import Color, Number, String


# TODO many of these tests could also stand to test for failure cases

def calc(expression):
    return Calculator(CoreExtension.namespace).evaluate_expression(expression)


def render(expression):
    return calc(expression).render()


def assert_equivalent(actual, expected):
    assert render(actual) == render(expected)


# ------------------------------------------------------------------------------
# RGB functions

def test_rgb():
    assert calc('rgb(128, 192, 224)') == Color.from_rgb(128/255, 192/255, 224/255)
    assert calc('rgb(20%, 40%, 60%)') == Color.from_rgb(0.2, 0.4, 0.6)


def test_rgba():
    # four args (css-style)
    assert calc('rgba(128, 192, 224, 0.5)') == Color.from_rgb(128/255, 192/255, 224/255, 0.5)
    assert calc('rgba(20%, 40%, 60%, 0.7)') == Color.from_rgb(0.2, 0.4, 0.6, 0.7)

    # two args (modify alpha of existing color)
    assert calc('rgba(red, 0.4)') == Color.from_rgb(1., 0., 0., 0.4)


def test_red():
    assert calc('red(orange)') == Number(255)


def test_green():
    assert calc('green(orange)') == Number(165)


def test_blue():
    assert calc('blue(orange)') == Number(0)


def test_mix():
    # Examples from the Ruby docs
    # Note that the results have been adjusted slightly; Ruby floors the mixed
    # channels, but we round
    assert calc('mix(#f00, #00f)') == calc('#800080')
    assert calc('mix(#f00, #00f, 25%)') == calc('#4000bf')
    assert calc('mix(rgba(255, 0, 0, 0.5), #00f)') == calc('rgba(64, 0, 191, 0.75)')


# ------------------------------------------------------------------------------
# HSL functions

def test_hsl():
    # Examples from the CSS 3 color spec, which Sass uses
    # (http://www.w3.org/TR/css3-color/#hsl-color)
    assert calc('hsl(0, 100%, 50%)') == Color.from_rgb(1., 0., 0.)
    assert calc('hsl(120, 100%, 50%)') == Color.from_rgb(0., 1., 0.)
    assert calc('hsl(120, 100%, 25%)') == Color.from_rgb(0., 0.5, 0.)
    assert calc('hsl(120, 100%, 75%)') == Color.from_rgb(0.5, 1., 0.5)
    assert calc('hsl(120, 75%, 75%)') == Color.from_rgb(0.5625, 0.9375, 0.5625)


def test_hsla():
    # Examples from the CSS 3 color spec
    assert calc('hsla(120, 100%, 50%, 1)') == Color.from_rgb(0., 1., 0.,)
    assert calc('hsla(240, 100%, 50%, 0.5)') == Color.from_rgb(0., 0., 1., 0.5)
    assert calc('hsla(30, 100%, 50%, 0.1)') == Color.from_rgb(1., 0.5, 0., 0.1)


def test_hue():
    assert calc('hue(yellow)') == Number(60, unit='deg')


def test_saturation():
    assert calc('saturation(yellow)') == Number(100, unit='%')


def test_lightness():
    assert calc('lightness(yellow)') == Number(50, unit='%')


# HSL manipulation functions

def test_adjust_hue():
    # Examples from the Ruby docs
    assert calc('adjust-hue(hsl(120, 30%, 90%), 60deg)') == calc('hsl(180, 30%, 90%)')
    assert calc('adjust-hue(hsl(120, 30%, 90%), -60deg)') == calc('hsl(60, 30%, 90%)')
    assert calc('adjust-hue(#811, 45deg)') == Color.from_rgb(136/255, 106.25/255, 17/255)


def test_lighten():
    # Examples from the Ruby docs
    assert calc('lighten(hsl(0, 0%, 0%), 30%)') == calc('hsl(0, 0, 30)')
    assert calc('lighten(#800, 20%)') == calc('#e00')


def test_darken():
    # Examples from the Ruby docs
    assert calc('darken(hsl(25, 100%, 80%), 30%)') == calc('hsl(25, 100%, 50%)')
    assert calc('darken(#800, 20%)') == calc('#200')


def test_saturate():
    # Examples from the Ruby docs
    assert calc('saturate(hsl(120, 30%, 90%), 20%)') == calc('hsl(120, 50%, 90%)')
    assert calc('saturate(#855, 20%)') == Color.from_rgb(158.1/255, 62.9/255, 62.9/255)


def test_desaturate():
    # Examples from the Ruby docs
    assert calc('desaturate(hsl(120, 30%, 90%), 20%)') == calc('hsl(120, 10%, 90%)')
    assert calc('desaturate(#855, 20%)') == Color.from_rgb(113.9/255, 107.1/255, 107.1/255)


def test_grayscale():
    assert calc('grayscale(black)') == Color.from_rgb(0., 0., 0.)
    assert calc('grayscale(white)') == Color.from_rgb(1., 1., 1.)
    assert calc('grayscale(yellow)') == Color.from_rgb(0.5, 0.5, 0.5)


def test_grayscale_css_filter():
    # grayscale(number) is a CSS filter and should be left alone
    assert calc('grayscale(1)') == String("grayscale(1)")


def test_complement():
    assert calc('complement(black)') == Color.from_rgb(0., 0., 0.)
    assert calc('complement(white)') == Color.from_rgb(1., 1., 1.)
    assert calc('complement(yellow)') == Color.from_rgb(0., 0., 1.)


def test_invert():
    assert calc('invert(black)') == Color.from_rgb(1., 1., 1.)
    assert calc('invert(white)') == Color.from_rgb(0., 0., 0.)
    assert calc('invert(yellow)') == Color.from_rgb(0., 0., 1.)


def test_invert_css_filter():
    # invert(number) is a CSS filter and should be left alone
    assert calc('invert(50%)') == String("invert(50%)")


# ------------------------------------------------------------------------------
# Opacity functions

def test_alpha_opacity():
    assert calc('alpha(black)') == Number(1.)
    assert calc('alpha(rgba(black, 0.5))') == Number(0.5)
    assert calc('alpha(rgba(black, 0))') == Number(0.)

    # opacity is a synonym
    assert calc('opacity(black)') == Number(1.)
    assert calc('opacity(rgba(black, 0.5))') == Number(0.5)
    assert calc('opacity(rgba(black, 0))') == Number(0.)


def test_alpha_ie_filter():
    # alpha() is supposed to leave the IE filter syntax alone
    assert render('alpha(opacity=20)') == "alpha(opacity=20)"


def test_opacify_fadein():
    # Examples from the Ruby docs
    assert calc('opacify(rgba(0, 0, 0, 0.5), 0.1)') == calc('rgba(0, 0, 0, 0.6)')
    assert calc('opacify(rgba(0, 0, 17, 0.8), 0.2)') == calc('#001')

    # fade-in is a synonym
    assert calc('fade-in(rgba(0, 0, 0, 0.5), 0.1)') == calc('rgba(0, 0, 0, 0.6)')
    assert calc('fade-in(rgba(0, 0, 17, 0.8), 0.2)') == calc('#001')


def test_transparentize_fadeout():
    # Examples from the Ruby docs
    assert calc('transparentize(rgba(0, 0, 0, 0.5), 0.1)') == calc('rgba(0, 0, 0, 0.4)')
    assert calc('transparentize(rgba(0, 0, 0, 0.8), 0.2)') == calc('rgba(0, 0, 0, 0.6)')

    # fade-out is a synonym
    assert calc('fade-out(rgba(0, 0, 0, 0.5), 0.1)') == calc('rgba(0, 0, 0, 0.4)')
    assert calc('fade-out(rgba(0, 0, 0, 0.8), 0.2)') == calc('rgba(0, 0, 0, 0.6)')


# ------------------------------------------------------------------------------
# Other color functions

def test_adjust_color():
    # Examples from the Ruby docs
    assert calc('adjust-color(#102030, $blue: 5)') == calc('#102035')
    assert calc('adjust-color(#102030, $red: -5, $blue: 5)') == calc('#0b2035')
    assert calc('adjust-color(hsl(25, 100%, 80%), $lightness: -30%, $alpha: -0.4)') == calc('hsla(25, 100%, 50%, 0.6)')


def test_scale_color():
    # Examples from the Ruby docs
    assert calc('scale-color(hsl(120, 70, 80), $lightness: 50%)') == calc('hsl(120, 70, 90)')
    assert calc('scale-color(rgb(200, 150, 170), $green: -40%, $blue: 70%)') == calc('rgb(200, 90, 229)')
    assert calc('scale-color(hsl(200, 70, 80), $saturation: -90%, $alpha: -30%)') == calc('hsla(200, 7, 80, 0.7)')


def test_change_color():
    # Examples from the Ruby docs
    assert calc('change-color(#102030, $blue: 5)') == calc('#102005')
    assert calc('change-color(#102030, $red: 120, $blue: 5)') == calc('#782005')
    assert calc('change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8)') == calc('hsla(25, 100%, 40%, 0.8)')

    assert calc('change-color(red, $hue: 240)') == calc('blue')


def test_ie_hex_str():
    # Examples from the Ruby docs
    assert calc('ie-hex-str(#abc)') == calc('"#FFAABBCC"')
    assert calc('ie-hex-str(#3322BB)') == calc('"#FF3322BB"')
    assert calc('ie-hex-str(rgba(0, 255, 0, 0.5))') == calc('"#8000FF00"')


# ------------------------------------------------------------------------------
# String functions

def test_unquote():
    # Examples from the Ruby docs
    ret = calc('unquote("foo")')
    assert ret == String('foo')
    assert ret.quotes is None
    ret = calc('unquote(foo)')
    assert ret == String('foo')
    assert ret.quotes is None

    assert calc('unquote((one, two, three))') == String('one, two, three')


def test_quote():
    # Examples from the Ruby docs
    ret = calc('quote("foo")')
    assert ret == String('foo')
    assert ret.quotes == '"'
    ret = calc('quote(foo)')
    assert ret == String('foo')
    assert ret.quotes == '"'


# TODO more of these need quote checking too
def test_str_length():
    # Examples from the Ruby docs
    assert calc('str-length("foo")') == calc('3')


def test_str_insert():
    # Examples from the Ruby docs
    assert calc('str-insert("abcd", "X", 1)') == calc('"Xabcd"')
    assert calc('str-insert("abcd", "X", 4)') == calc('"abcXd"')
    # DEVIATION: see https://github.com/nex3/sass/issues/954
    assert calc('str-insert("abcd", "X", 5)') == calc('"abcdX"')


def test_str_index():
    # Examples from the Ruby docs
    assert calc('str-index(abcd, a)') == calc('1')
    assert calc('str-index(abcd, ab)') == calc('1')
    assert calc('str-index(abcd, X)') == calc('0')
    assert calc('str-index(abcd, c)') == calc('3')


def test_str_slice():
    # Examples from the Ruby docs
    assert calc('str-slice("abcd", 2, 3)') == calc('"bc"')
    assert calc('str-slice("abcd", 2)') == calc('"bcd"')
    assert calc('str-slice("abcd", -3, -2)') == calc('"bc"')
    assert calc('str-slice("abcd", 2, -2)') == calc('"bc"')
    assert calc('str-slice("abcd", 0, 3)') == calc('"abc"')
    assert calc('str-slice("abcd", 1, 3)') == calc('"abc"')
    assert calc('str-slice("abcd", 1, 30)') == calc('"abcd"')


def test_to_upper_case():
    # Examples from the Ruby docs
    assert calc('to-upper-case(abcd)') == calc('ABCD')


def test_to_lower_case():
    # Examples from the Ruby docs
    assert calc('to-lower-case(ABCD)') == calc('abcd')


# ------------------------------------------------------------------------------
# Number functions

def test_percentage():
    # Examples from the Ruby docs
    assert calc('percentage(100px / 50px)') == calc('200%')


def test_round():
    # Examples from the Ruby docs
    assert calc('round(10.4px)') == calc('10px')
    assert calc('round(10.6px)') == calc('11px')


def test_ceil():
    # Examples from the Ruby docs
    assert calc('ceil(10.4px)') == calc('11px')
    assert calc('ceil(10.6px)') == calc('11px')


def test_floor():
    # Examples from the Ruby docs
    assert calc('floor(10.4px)') == calc('10px')
    assert calc('floor(10.6px)') == calc('10px')


def test_abs():
    # Examples from the Ruby docs
    assert calc('abs(10px)') == calc('10px')
    assert calc('abs(-10px)') == calc('10px')


def test_min():
    # Examples from the Ruby docs
    assert calc('min(1px, 4px)') == calc('1px')
    assert calc('min(5em, 3em, 4em)') == calc('3em')


def test_max():
    # Examples from the Ruby docs
    assert calc('max(1px, 4px)') == calc('4px')
    assert calc('max(5em, 3em, 4em)') == calc('5em')


# ------------------------------------------------------------------------------
# List functions

def test_length():
    # Examples from the Ruby docs
    assert calc('length(10px)') == calc('1')
    assert calc('length(10px 20px 30px)') == calc('3')


def test_nth():
    # Examples from the Ruby docs
    assert calc('nth(10px 20px 30px, 1)') == calc('10px')
    assert calc('nth((Helvetica, Arial, sans-serif), 3)') == calc('sans-serif')
    assert calc('nth((width: 10px, length: 20px), 2)') == calc('length, 20px')

    assert calc('nth(10px 20px 30px, -1)') == calc('30px')


def test_join():
    # Examples from the Ruby docs
    assert_equivalent('join(10px 20px, 30px 40px)', '10px 20px 30px 40px')
    assert_equivalent('join((blue, red), (#abc, #def))', 'blue, red, #abc, #def')
    assert_equivalent('join(10px, 20px)', '10px 20px')
    assert_equivalent('join(10px, 20px, comma)', '10px, 20px')
    assert_equivalent('join((blue, red), (#abc, #def), space)', 'blue red #abc #def')


def test_append():
    # Examples from the Ruby docs
    assert calc('append(10px 20px, 30px)') == calc('10px 20px 30px')
    assert calc('append((blue, red), green)') == calc('blue, red, green')
    assert calc('append(10px 20px, 30px 40px)') == calc('10px 20px (30px 40px)')
    assert calc('append(10px, 20px, comma)') == calc('10px, 20px')
    assert calc('append((blue, red), green, space)') == calc('blue red green')

    # TODO need to test for commas here
    assert calc('append((a, b), c)') == calc('a, b, c')


def test_zip():
    # Examples from the Ruby docs
    assert calc('zip(1px 1px 3px, solid dashed solid, red green blue)') == calc('1px solid red, 1px dashed green, 3px solid blue')


def test_index():
    # Examples from the Ruby docs
    assert calc('index(1px solid red, solid)') == calc('2')
    assert calc('index(1px solid red, dashed)') == calc('false')


def test_list_separator():
    # Examples from the Ruby docs
    assert calc('list-separator(1px 2px 3px)') == calc('space')
    assert calc('list-separator(1px, 2px, 3px)') == calc('comma')
    assert calc('list-separator("foo")') == calc('space')


def test_set_nth():
    # Examples from the Ruby docs
    assert calc('set-nth($list: 10px 20px 30px, $n: 2, $value: -20px)') == calc('10px -20px 30px')


# ------------------------------------------------------------------------------
# Map functions


def test_map_get():
    # Examples from the Ruby docs
    assert calc('map-get(("foo": 1, "bar": 2), "foo")') == calc('1')
    assert calc('map-get(("foo": 1, "bar": 2), "bar")') == calc('2')
    assert calc('map-get(("foo": 1, "bar": 2), "baz")') == calc('null')


def test_map_merge():
    # Examples from the Ruby docs
    assert calc('map-merge(("foo": 1), ("bar": 2))') == calc('("foo": 1, "bar": 2)')
    assert calc('map-merge(("foo": 1, "bar": 2), ("bar": 3))') == calc('("foo": 1, "bar": 3)')


def test_map_keys():
    # Examples from the Ruby docs
    assert calc('map-keys(("foo": 1, "bar": 2))') == calc('"foo", "bar"')


def test_map_values():
    # Examples from the Ruby docs
    assert calc('map-values(("foo": 1, "bar": 2))') == calc('1, 2')
    assert calc('map-values(("foo": 1, "bar": 2, "baz": 1))') == calc('1, 2, 1')


def test_map_has_key():
    # Examples from the Ruby docs
    assert calc('map-has-key(("foo": 1, "bar": 2), "foo")') == calc('true')
    assert calc('map-has-key(("foo": 1, "bar": 2), "baz")') == calc('false')


# ------------------------------------------------------------------------------
# Introspection functions

def test_type_of():
    # Examples from the Ruby docs
    assert calc('type-of(100px)') == calc('number')
    assert calc('type-of(asdf)') == calc('string')
    assert calc('type-of("asdf")') == calc('string')
    assert calc('type-of(true)') == calc('bool')
    assert calc('type-of(#fff)') == calc('color')
    assert calc('type-of(blue)') == calc('color')


def test_unit():
    # Examples from the Ruby docs
    assert calc('unit(100)') == calc('""')
    assert calc('unit(100px)') == calc('"px"')
    assert calc('unit(3em)') == calc('"em"')
    assert calc('unit(10px * 5em)') == calc('"em*px"')
    # NOTE: the docs say "em*px/cm*rem", but even Ruby sass doesn't actually
    # return that
    assert calc('unit(10px * 5em / 30cm / 1rem)') == calc('"em/rem"')


def test_unitless():
    # Examples from the Ruby docs
    assert calc('unitless(100)') == calc('true')
    assert calc('unitless(100px)') == calc('false')


def test_comparable():
    # Examples from the Ruby docs
    assert calc('comparable(2px, 1px)') == calc('true')
    assert calc('comparable(100px, 3em)') == calc('false')
    assert calc('comparable(10cm, 3mm)') == calc('true')


# ------------------------------------------------------------------------------
# Miscellaneous functions

def test_if():
    # Examples from the Ruby docs
    assert calc('if(true, 1px, 2px)') == calc('1px')
    assert calc('if(false, 1px, 2px)') == calc('2px')
