"""Testcases for cssutils.css.CSSValue and CSSPrimitiveValue."""

import re

import pytest

import cssutils

CSS2 = (cssutils.profile.CSS_LEVEL_2,)
C3BUI = (cssutils.profile.CSS3_BASIC_USER_INTERFACE,)
C3BB = (cssutils.profile.CSS3_BACKGROUNDS_AND_BORDERS,)
CM3 = (cssutils.profile.CSS3_COLOR,)
FM3 = (cssutils.profile.CSS3_FONTS,)
C3PM = (cssutils.profile.CSS3_PAGED_MEDIA,)
C3T = (cssutils.profile.CSS3_TEXT,)
FM3FF = (cssutils.profile.CSS3_FONT_FACE,)
CSS2_CM3 = (CM3[0], CSS2[0])
CSS2_FM3 = (FM3[0], CSS2[0])


class TestProfiles:
    M1 = {'testvalue': 'x'}
    P1 = {
        '-test-tokenmacro': '({num}{w}){1,2}',
        '-test-macro': '{ident}|{percentage}',
        '-test-custommacro': '{testvalue}',
        # custom validation function
        '-test-funcval': lambda v: int(v) > 0,
    }

    def test_knownNames(self):
        "Profiles.knownNames"
        p = cssutils.profiles.Profiles()
        p.removeProfile(all=True)
        p.addProfile('test', self.P1, self.M1)
        assert p.knownNames == list(self.P1.keys())
        p.removeProfile(all=True)
        assert p.knownNames == []

    def test_profiles(self):
        "Profiles.profiles"
        p = cssutils.profiles.Profiles()
        p.removeProfile(all=True)
        p.addProfile('test', self.P1, self.M1)
        assert p.profiles == ['test']
        p.removeProfile(all=True)
        assert p.profiles == []

    def test_validate2(self):
        "Profiles.validate()"
        # save
        saved = cssutils.profile

        # test
        p = cssutils.profiles.Profiles()
        cssutils.profile = p

        pvs = [('color', 'red'), ('color', 'rgba(0,0,0,0)'), ('color', 'XXX')]

        def check(*results):
            for i, pv in enumerate(pvs):
                assert p.validate(*pv) == results[i]

        check(True, True, False)

        p.removeProfile(p.CSS3_COLOR)
        check(True, False, False)

        cssutils.profile.addProfile('test', {}, {'color': 'XXX'})
        check(False, False, True)

        p.removeProfile(all=True)
        check(False, False, False)

        # TODO: validateWithProfile

        # restore
        cssutils.profile = saved

    @pytest.mark.usefixtures('saved_profiles')
    def test_addProfile(self):
        "Profiles.addProfile with custom validation function"
        # unknown profile
        with pytest.raises(cssutils.profiles.NoSuchProfileException):
            list(cssutils.profile.propertiesByProfile('NOTSET'))

        # new profile
        cssutils.profile.addProfile('test', self.P1, self.M1)

        props = list(self.P1.keys())
        props.sort()
        assert props == list(cssutils.profile.propertiesByProfile('test'))

        cssutils.log.raiseExceptions = False
        tests = {
            ('-test-tokenmacro', '1'): True,
            ('-test-tokenmacro', '1 -2'): True,
            ('-test-tokenmacro', '1 2 3'): False,
            ('-test-tokenmacro', 'a'): False,
            ('-test-macro', 'a'): True,
            ('-test-macro', '0.1%'): True,
            ('-test-custommacro', 'x'): True,
            ('-test-custommacro', '1'): False,
            ('-test-custommacro', 'y'): False,
            ('-test-funcval', '1'): True,
            ('-test-funcval', '-1'): False,
            ('-test-funcval', 'x'): False,
        }
        for test, v in list(tests.items()):
            assert v == cssutils.profile.validate(*test)

            assert (v, v, ['test']) == cssutils.profile.validateWithProfile(*test)

        cssutils.log.raiseExceptions = True

        expmsg = re.escape("invalid literal for int() with base 10: 'x'")
        with pytest.raises(Exception, match=expmsg):
            cssutils.profile.validate('-test-funcval', 'x')

    def test_removeProfile(self):
        "Profiles.removeProfile()"
        p = cssutils.profiles.Profiles()
        assert 9 == len(p.profiles)
        p.removeProfile(p.CSS_LEVEL_2)
        assert 8 == len(p.profiles)
        p.removeProfile(all=True)
        assert 0 == len(p.profiles)

    def _gen_validation_inputs():
        CSS_LEVEL_2 = cssutils.profiles.Profiles.CSS_LEVEL_2
        CSS3_COLOR = cssutils.profiles.Profiles.CSS3_COLOR
        yield (
            ('color', 'red', None),
            (True, True, [CSS_LEVEL_2]),
        )
        yield (
            ('color', 'red', CSS_LEVEL_2),
            (True, True, [CSS_LEVEL_2]),
        )
        yield (
            ('color', 'red', CSS3_COLOR),
            (True, False, [CSS_LEVEL_2]),
        )
        yield pytest.param(
            ('color', 'rgba(0,0,0,0)', None),
            (True, True, [CSS3_COLOR]),
            marks=pytest.mark.xfail(reason="#37"),
        )
        yield pytest.param(
            ('color', 'rgba(0,0,0,0)', CSS_LEVEL_2),
            (True, False, [CSS3_COLOR]),
            marks=pytest.mark.xfail(reason="#37"),
        )
        yield pytest.param(
            ('color', 'rgba(0,0,0,0)', CSS3_COLOR),
            (True, True, [CSS3_COLOR]),
            marks=pytest.mark.xfail(reason="#37"),
        )
        yield pytest.param(
            ('color', '1px', None),
            (
                False,
                False,
                [CSS3_COLOR, CSS_LEVEL_2],
            ),
            marks=pytest.mark.xfail(reason="#37"),
        )
        yield pytest.param(
            ('color', '1px', CSS_LEVEL_2),
            (
                False,
                False,
                [CSS3_COLOR, CSS_LEVEL_2],
            ),
            marks=pytest.mark.xfail(reason="#37"),
        )
        yield pytest.param(
            ('color', '1px', CSS3_COLOR),
            (
                False,
                False,
                [CSS3_COLOR, CSS_LEVEL_2],
            ),
            marks=pytest.mark.xfail(reason="#37"),
        )
        yield (
            ('color', 'aliceblue', None),
            (True, True, [CSS_LEVEL_2]),
        )
        yield (
            ('opacity', '1', None),
            (True, True, [CSS3_COLOR]),
        )
        yield (
            ('opacity', '1', CSS_LEVEL_2),
            (True, False, [CSS3_COLOR]),
        )
        yield (
            ('opacity', '1', CSS3_COLOR),
            (True, True, [CSS3_COLOR]),
        )
        yield (
            ('opacity', '1px', None),
            (False, False, [CSS3_COLOR]),
        )
        yield (
            ('opacity', '1px', CSS_LEVEL_2),
            (False, False, [CSS3_COLOR]),
        )
        yield (
            ('opacity', '1px', CSS3_COLOR),
            (False, False, [CSS3_COLOR]),
        )
        yield (
            ('-x', '1', None),
            (False, False, []),
        )
        yield (
            ('-x', '1', CSS_LEVEL_2),
            (False, False, []),
        )
        yield (
            ('-x', '1', CSS3_COLOR),
            (False, False, []),
        )

    @pytest.mark.parametrize(('params', 'results'), _gen_validation_inputs())
    def test_validateWithProfile(self, params, results):
        "Profiles.validate(), Profiles.validateWithProfile()"
        p = cssutils.profiles.Profiles()
        assert p.validate(*params[:2]) == results[0]
        assert p.validateWithProfile(*params) == results

    def test_propertiesByProfile(self):
        "Profiles.propertiesByProfile"
        assert ['opacity'] == list(
            cssutils.profile.propertiesByProfile(cssutils.profile.CSS3_COLOR)
        )

    def test_csscolorlevel3(self):
        "CSS Color Module Level 3"
        # (propname, propvalue): (valid, validprofile)
        namedcolors = '''transparent, orange,
                         aqua, black, blue, fuchsia, gray, green, lime, maroon,
                         navy, olive, purple, red, silver, teal, white, yellow'''
        for color in namedcolors.split(','):
            color = color.strip()
            assert cssutils.profile.validate('color', color)

            assert (True, True, list(CSS2)) == cssutils.profile.validateWithProfile(
                'color', color
            )

        # CSS2 only:
        uicolor = (
            'ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|'
            'ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|'
            'HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|'
            'InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|'
            'ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|'
            'WindowFrame|WindowText'
        )
        for color in uicolor.split('|'):
            assert not cssutils.profile.validate('color', color)

            # TODO: fixme
            # assert (True, True, list(CSS2)) == cssutils.profile.validateWithProfile(
            #     'color', color
            # )

    def test_validate(self):
        "Profiles.validate()"
        tests = {
            # name, values: valid, matching, profile
            # background-position
            (
                'background-position',
                (
                    'inherit',
                    '0',
                    '1%',
                    '1px',
                    '0 0',
                    '1% 1%',
                    '1px 1px',
                    '1px 1%',
                    'top',
                    'bottom',
                    'left',
                    'right',
                    'center center',
                    'center',
                    'top left',
                    'top center',
                    'top right',
                    'bottom left',
                    'bottom center',
                    'bottom right',
                    'center left',
                    'center center',
                    'center right',
                    '0 center',
                    'center 0',
                    '0 top',
                    '10% bottom',
                    'left 0',
                    'right 10%',
                    '1% center',
                    'center 1%',
                ),
            ): (True, True, CSS2),
            ('background-position', ('0 left', 'top 0')): (False, False, CSS2),
            (
                'border-top-right-radius',
                (
                    '1px',
                    '1%',
                    '1% -1px',
                    '1% 0',
                ),
            ): (True, True, C3BB),
            ('border-top-right-radius', ('1px 2px 2px', '/ 1px', 'black')): (
                False,
                False,
                C3BB,
            ),
            (
                'border-radius',
                (
                    '1px',
                    '1%',
                    '0',
                    '1px 1px',
                    '1px/ 1px',
                    '1px /1px',
                    '1px  /  1px',
                    '1px 1px 1px 1px',
                    '1px 1px 1px 1px / 1px 1px 1px 1px',
                ),
            ): (True, True, C3BB),
            (
                'border-radius',
                (
                    '1px /',
                    '/ 1px',
                    '1px / 1px / 1px',
                    '1px 1px 1px 1px 1px',
                    '1px / 1px 1px 1px 1px 1px',
                    'black',
                ),
            ): (False, False, C3BB),
            (
                'border',
                (
                    '1px',
                    'solid',
                    'red',
                    '1px solid red',
                    '1px red solid',
                    'red 1px solid',
                    'red solid 1px',
                    'solid 1px red',
                    'solid red 1px',
                ),
            ): (True, True, C3BB),
            (
                'border',
                (
                    '1px 1px',
                    'red red 1px',
                ),
            ): (False, False, C3BB),
            (
                'box-shadow',
                (
                    'none',
                    '1px 1px',
                    '1px 1px 1px',
                    '1px 1px 1px 1px',
                    '1px 1px 1px 1px red',
                    'inset 1px 1px',
                    'inset 1px 1px 1px 1px black',
                ),
            ): (True, True, C3BB),
            (
                'box-shadow',
                (
                    '1px',
                    '1px 1px 1px 1px 1px',
                    'x 1px 1px',
                    'inset',
                    '1px black',
                    'black',
                ),
            ): (False, False, C3BB),
            # color
            (
                'color',
                (
                    'x',
                    '#',
                    '#0',
                    '#00',
                    '#0000',
                    '#00000',
                    '#0000000',
                    '#00j',
                    '#j00000',
                    'rgb(0.0,1,1)',
                    'rgb(0)',
                    'rgb(0, 1)',
                    'rgb(0, 1, 1, 1)',
                    'rgb(0, 1, 0%)',
                    'rgba(0)',
                    'rgba(0, 1, 1.0, 1)',
                    'rgba(0, 1)',
                    'rgba(0, 1, 1, 1, 1)',
                    'rgba(100%, 0%, 0%, 1%)',
                    'rgba(100%, 0%, 0, 1)',
                    'hsl(1.5,1%,1%)',
                    'hsl(1,1,1%)',
                    'hsl(1,1%,1)',
                    'hsla(1.5,1%,1%, 1)',
                    'hsla(1,1,1%, 1)',
                    'hsla(1,1%,1, 1)',
                    'hsla(1,1%,1%, 1%)',
                ),
            ): (False, False, CSS2_CM3),
            (
                'color',
                (
                    'inherit',
                    'black',
                    '#000',
                    '#000000',
                    'rgb(0,1,1)',
                    'rgb( 0 , 1 , 1 )',
                    'rgb(-10,555,1)',
                    'rgb(100%, 1.5%, 0%)',
                    'rgb(150%, -20%, 0%)',
                ),
            ): (True, True, CSS2),
            (
                'color',
                (
                    'currentcolor',
                    'aliceblue',
                    'rgba(1,1,1,1)',
                    'rgba( 1 , 1 , 1 , 1 )',
                    'rgba(100%, 0%, 0%, 1)',
                    'hsl(1,1%,1%)',
                    'hsl( 1 , 1% , 1% )',
                    'hsl(-1000,555.5%,-61.5%)',
                    'hsla(1,1%,1%,1)',
                    'hsla( 1, 1% , 1% , 1 )',
                    'hsla(-1000,555.5%,-61.5%, 0.5)',
                ),
            ): (True, True, CM3),
            (
                'content',
                (
                    'none',
                    'normal',
                    '""',
                    "'x'",
                ),
            ): (True, True, CSS2),
            (
                'cursor',
                ('url(1), auto', 'url(1) 2 3, help', 'wait', 'inherit', 'none'),
            ): (True, True, C3BUI),
            ('cursor', ('url(1), auto, wait', 'url(1) 2, help', '1')): (
                False,
                False,
                C3BUI,
            ),
            # FONTS
            ('font-family', ('serif, x',)): (True, True, CSS2),  # CSS2_FM3),
            (
                'font-family',
                (
                    'inherit',
                    'a, b',
                    'a,b,c',
                    'a, "b", c',
                    '"a", b, "c"',
                    '"a", "b", "c"',
                    '"x y"',
                    'serif',
                    # valid but CSS2: font with name serif, CSS3: same as `serif`
                    '"serif"',
                    'a  b',  # should use quotes but valid
                    'a, b   b, d',
                ),
            ): (True, True, CSS2),
            (
                'font-weight',
                (
                    'normal',
                    'bold',
                    'bolder',
                    'lighter',
                    'inherit',
                    '100',
                    '200',
                    '300',
                    '400',
                    '500',
                    '600',
                    '700',
                    '800',
                    '900',
                ),
            ): (True, True, CSS2),
            (
                'font-stretch',
                (
                    'normal',
                    'wider',
                    'narrower',
                    'ultra-condensed',
                    'extra-condensed',
                    'condensed',
                    'semi-condensed',
                    'semi-expanded',
                    'expanded',
                    'extra-expanded',
                    'ultra-expanded',
                    'inherit',
                ),
            ): (True, True, FM3),
            ('font-style', ('normal', 'italic', 'oblique', 'inherit')): (
                True,
                True,
                CSS2,
            ),
            ('font-variant', ('normal', 'small-caps', 'inherit')): (True, True, CSS2),
            ('font-size', ('-1em',)): (False, False, CSS2),
            (
                'font-size',
                (
                    'xx-small',
                    'x-small',
                    'small',
                    'medium',
                    'large',
                    'x-large',
                    'xx-large',
                    'larger',
                    'smaller',
                    '1em',
                    '1%',
                    'inherit',
                ),
            ): (True, True, CSS2),
            ('font-size-adjust', ('1.0', 'none', 'inherit')): (True, True, FM3),
            (
                'font',
                (
                    'italic small-caps bold 1px/3 a, "b", serif',
                    '12pt/14pt sans-serif',
                    '80% sans-serif',
                    'x-large/110% "new century schoolbook", serif',
                    'bold italic large Palatino, serif',
                    'normal small-caps 120%/120% fantasy',
                    'oblique 12pt "Helvetica Nue", serif',
                    'caption',
                    'icon',
                    'menu',
                    'message-box',
                    'small-caption',
                    'status-bar',
                    'inherit',
                ),
            ): (True, True, CSS2),
            ('nav-index', ('1', 'auto', 'inherit')): (True, True, C3BUI),
            ('nav-index', ('x', '1 2', '1px')): (False, False, C3BUI),
            (
                'opacity',
                (
                    'inherit',
                    '0',
                    '0.0',
                    '0.42342',
                    '1',
                    '1.0',
                    # should be clipped but valid
                    '-0',
                    '-0.1',
                    '-10',
                    '2',
                ),
            ): (True, True, CM3),
            ('opacity', ('a', '#000', '+1')): (False, False, CM3),
            (
                'outline',
                (
                    'red dotted 1px',
                    'dotted 1px red',
                    '1px red dotted',
                    'red',
                    '1px',
                    'dotted',
                    'red 1px',
                    '1px dotted',
                    'red dotted',
                    'inherit',
                ),
            ): (True, True, C3BUI),
            ('outline', ('red #fff', 'solid dotted', 'Url(x)', '1px 1px')): (
                False,
                False,
                C3BUI,
            ),
            ('outline-color', ('red', '#fff', 'inherit')): (True, True, C3BUI),
            ('outline-color', ('0', '1em')): (False, False, C3BUI),
            ('outline-offset', ('0', '1em', 'inherit')): (True, True, C3BUI),
            ('outline-offset', ('1%', 'red')): (False, False, C3BUI),
            ('outline-style', ('auto', 'dotted', 'inherit')): (True, True, C3BUI),
            ('outline-style', ('0', '1em', 'red')): (False, False, C3BUI),
            ('outline-width', ('0', '1em', 'inherit')): (True, True, C3BUI),
            ('outline-width', ('auto', 'red', 'dotted')): (False, False, C3BUI),
            ('resize', ('none', 'both', 'horizontal', 'vertical', 'inherit')): (
                True,
                True,
                C3BUI,
            ),
            ('resize', ('1', 'auto', '1px', '2%')): (False, False, C3BUI),
            (
                'size',
                (
                    '1cm',
                    '1mm 20cm',
                    'auto',
                    'landscape letter',
                    'a4 portrait',
                    'landscape',
                    'a5',
                    # 'inherit'
                ),
            ): (True, True, C3PM),
            ('size', ('portrait landscape', 'a5 letter', '2%')): (False, False, C3PM),
            (
                'src',
                (
                    'url(  a  )',
                    'local(  x  )',
                    'local("x")',
                    'local(  "x"  )',
                    'url(../fonts/LateefRegAAT.ttf) format(  "truetype-aat"  )',
                    'url(a) format(  "123x"  , "a"   )',
                    'url(a) format( "123x"  , "a"   ), '
                    'url(a) format( "123x"  , "a"   )',
                    'local(HiraKakuPro-W3), local(Meiryo), local(IPAPGothic)',
                    'local(Gentium), url(/fonts/Gentium.ttf)',
                    'local("Gentium"), url("/fonts/Gentium.ttf")',
                    'local(Futura-Medium), url(fonts.svg#MyGeometricModern) '
                    'format("svg")',
                ),
            ): (True, True, FM3FF),
            (
                'text-shadow',
                (
                    'none',
                    '1px 1px',
                    '1px 1px 1px',
                    '1px 1px 1px 1px',
                    '1px 1px 1px 1px red',
                    'inset 1px 1px',
                    'inset 1px 1px 1px 1px black',
                ),
            ): (True, True, C3T),
            (
                'text-shadow',
                (
                    '1px',
                    '1px 1px 1px 1px 1px',
                    'x 1px 1px',
                    'inset',
                    '1px black',
                    'black',
                ),
            ): (False, False, C3T),
            ('unicode-range', ('u+1', 'U+111111-ffffff', 'u+123456  ,  U+1-f')): (
                True,
                True,
                FM3FF,
            ),
        }
        # TODO!!!
        for (name, values), (valid, _matching, _profile) in list(tests.items()):
            for value in values:
                assert valid == cssutils.profile.validate(name, value)

                # assert (
                #     valid,
                #     matching,
                #     list(profile),
                # ) == cssutils.profile.validateWithProfile(name, value)

    def _gen_validation_inputs():
        yield (
            (FM3FF, 'font-family', ('y', '"y"')),
            (  # => name should be "y"!!!
                True,
                True,
                FM3FF,
            ),
        )
        yield ((FM3FF, 'font-family', ('"y", "a"', 'a, b', 'a a')), (True, False, CSS2))
        yield (
            (FM3FF, 'font-stretch', ('normal', 'wider', 'narrower', 'inherit')),
            (
                True,
                False,
                FM3,
            ),
        )
        yield ((FM3FF, 'font-style', ('inherit',)), (True, False, CSS2))
        yield (
            (
                FM3FF,
                'font-weight',
                (
                    'bolder',
                    'lighter',
                    'inherit',
                ),
            ),
            (True, False, CSS2),
        )

    @pytest.mark.parametrize(('params', 'results'), _gen_validation_inputs())
    @pytest.mark.xfail(reason="#37")
    def test_validateWithProfile_fonts(self, params, results):
        "Profiles.validateWithProfile()"
        # testing for valid values overwritten in a profile
        v, m, p = results
        expected = v, m, list(p)
        assert cssutils.profile.validateWithProfile(*params) == expected
