import pytest
from six import string_types

from _plotly_utils.basevalidators import StringValidator
import numpy as np


# Fixtures
# --------
@pytest.fixture()
def validator():
    return StringValidator('prop', 'parent')


@pytest.fixture()
def validator_values():
    return StringValidator('prop', 'parent', values=['foo', 'BAR', ''])


@pytest.fixture()
def validator_no_blanks():
    return StringValidator('prop', 'parent', no_blank=True)


@pytest.fixture()
def validator_strict():
    return StringValidator('prop', 'parent', strict=True)


@pytest.fixture
def validator_aok():
    return StringValidator('prop', 'parent', array_ok=True, strict=False)


@pytest.fixture
def validator_aok_strict():
    return StringValidator('prop', 'parent', array_ok=True, strict=True)

@pytest.fixture
def validator_aok_values():
    return StringValidator('prop', 'parent', values=['foo', 'BAR', '', 'baz'], array_ok=True)


@pytest.fixture()
def validator_no_blanks_aok():
    return StringValidator('prop', 'parent', no_blank=True, array_ok=True)


# Array not ok
# ------------
# Not strict
# ### Acceptance ###
@pytest.mark.parametrize('val',
                         ['bar', 234, np.nan,
                          'HELLO!!!', 'world!@#$%^&*()', '', u'\u03BC'])
def test_acceptance(val, validator):
    expected = str(val) if not isinstance(val, string_types) else val
    assert validator.validate_coerce(val) == expected


# ### Rejection by value ###
@pytest.mark.parametrize('val',
                         [(), [], [1, 2, 3], set()])
def test_rejection(val, validator):
    with pytest.raises(ValueError) as validation_failure:
        validator.validate_coerce(val)

    assert 'Invalid value' in str(validation_failure.value)


# Valid values
# ------------
@pytest.mark.parametrize('val',
                         ['foo', 'BAR', ''])
def test_acceptance_values(val, validator_values):
    assert validator_values.validate_coerce(val) == val


@pytest.mark.parametrize('val',
                         ['FOO', 'bar', 'other', '1234'])
def test_rejection_values(val, validator_values):
    with pytest.raises(ValueError) as validation_failure:
        validator_values.validate_coerce(val)

    assert 'Invalid value'.format(val=val) in str(validation_failure.value)
    assert "['foo', 'BAR', '']" in str(validation_failure.value)


# ### No blanks ###
@pytest.mark.parametrize('val',
                         ['bar', 'HELLO!!!', 'world!@#$%^&*()',  u'\u03BC'])
def test_acceptance_no_blanks(val, validator_no_blanks):
    assert validator_no_blanks.validate_coerce(val) == val


@pytest.mark.parametrize('val',
                         [''])
def test_rejection_no_blanks(val, validator_no_blanks):
    with pytest.raises(ValueError) as validation_failure:
        validator_no_blanks.validate_coerce(val)

    assert 'A non-empty string' in str(validation_failure.value)


# Strict
# ------
# ### Acceptance ###
@pytest.mark.parametrize('val',
                         ['bar', 'HELLO!!!', 'world!@#$%^&*()', '', u'\u03BC'])
def test_acceptance_strict(val, validator_strict):
    assert validator_strict.validate_coerce(val) == val


# ### Rejection by value ###
@pytest.mark.parametrize('val',
                         [(), [], [1, 2, 3], set(), np.nan, np.pi, 23])
def test_rejection_strict(val, validator_strict):
    with pytest.raises(ValueError) as validation_failure:
        validator_strict.validate_coerce(val)

    assert 'Invalid value' in str(validation_failure.value)


# Array ok
# --------
# ### Acceptance ###
@pytest.mark.parametrize('val',
                         ['foo', 'BAR', '', 'baz', u'\u03BC'])
def test_acceptance_aok_scalars(val, validator_aok):
    assert validator_aok.validate_coerce(val) == val


@pytest.mark.parametrize('val',
                         ['foo',
                          ['foo'],
                          np.array(['BAR', '', u'\u03BC'], dtype='object'),
                          ['baz', 'baz', 'baz'],
                          ['foo', None, 'bar', u'\u03BC']])
def test_acceptance_aok_list(val, validator_aok):
    coerce_val = validator_aok.validate_coerce(val)
    if isinstance(val, np.ndarray):
        assert isinstance(coerce_val, np.ndarray)
        assert np.array_equal(coerce_val,
                              np.array(val, dtype=coerce_val.dtype))
    elif isinstance(val, list):
        assert validator_aok.present(val) == tuple(val)
    else:
        assert coerce_val == val


# ### Rejection by type ###
@pytest.mark.parametrize('val',
                         [['foo', ()], ['foo', 3, 4], [3, 2, 1]])
def test_rejection_aok(val, validator_aok_strict):
    with pytest.raises(ValueError) as validation_failure:
        validator_aok_strict.validate_coerce(val)

    assert 'Invalid element(s)' in str(validation_failure.value)


# ### Rejection by value ###
@pytest.mark.parametrize('val',
                         [['foo', 'bar'],
                          ['3', '4'],
                          ['BAR', 'BAR', 'hello!'],
                          ['foo', None]])
def test_rejection_aok_values(val, validator_aok_values):
    with pytest.raises(ValueError) as validation_failure:
        validator_aok_values.validate_coerce(val)

    assert 'Invalid element(s)' in str(validation_failure.value)


# ### No blanks ###
@pytest.mark.parametrize('val',
                         ['123',
                          ['bar', 'HELLO!!!'],
                          np.array(['bar', 'HELLO!!!'], dtype='object'),
                          ['world!@#$%^&*()', u'\u03BC']])
def test_acceptance_no_blanks_aok(val, validator_no_blanks_aok):
    coerce_val = validator_no_blanks_aok.validate_coerce(val)
    if isinstance(val, np.ndarray):
        assert np.array_equal(coerce_val,
                              np.array(val, dtype=coerce_val.dtype))
    elif isinstance(val, list):
        assert validator_no_blanks_aok.present(coerce_val) == tuple(val)
    else:
        assert coerce_val == val


@pytest.mark.parametrize('val',
                         ['',
                          ['foo', 'bar', ''],
                          np.array(['foo', 'bar', ''], dtype='object'),
                          [''],
                          np.array([''], dtype='object')])
def test_rejection_no_blanks_aok(val,
                                 validator_no_blanks_aok):
    with pytest.raises(ValueError) as validation_failure:
        validator_no_blanks_aok.validate_coerce(val)

    assert 'A non-empty string' in str(validation_failure.value)