import math

import pytest

import chainerx


def _check_cast_scalar_equals_data(scalar, data):
    assert bool(scalar) == bool(data)
    assert int(scalar) == int(data)
    assert float(scalar) == float(data)


all_scalar_values = [
    -2, 1, -1.5, 2.3, True, False, float('inf'), float('nan')]


@pytest.mark.parametrize('value,dtype', [
    (0, chainerx.int64),
    (-1, chainerx.int64),
    (0x7fffffffffffffff, chainerx.int64),
    (-0x8000000000000000, chainerx.int64),
    (0.0, chainerx.float64),
    (float('inf'), chainerx.float64),
    (float('nan'), chainerx.float64),
    (True, chainerx.bool_),
    (False, chainerx.bool_),
])
def test_init_without_dtype(value, dtype):
    scalar = chainerx.Scalar(value)
    assert scalar.dtype == dtype
    if math.isnan(value):
        assert math.isnan(scalar.tolist())
    else:
        assert scalar.tolist() == value
    assert isinstance(scalar.tolist(), type(value))


@pytest.mark.parametrize('value,cast_dtype,expected_value', [
    (0, chainerx.bool_, False),
    (0, chainerx.int8, 0),
    (0, chainerx.int16, 0),
    (0, chainerx.int32, 0),
    (0, chainerx.int64, 0),
    (0, chainerx.uint8, 0),
    (0, chainerx.float32, 0.0),
    (0, chainerx.float64, 0.0),
    (0.0, chainerx.bool_, False),
    (0.0, chainerx.int8, 0),
    (0.0, chainerx.int16, 0),
    (0.0, chainerx.int32, 0),
    (0.0, chainerx.int64, 0),
    (0.0, chainerx.uint8, 0),
    (0.0, chainerx.float32, 0.0),
    (0.0, chainerx.float64, 0.0),
    (1, chainerx.bool_, True),
    (1, chainerx.int8, 1),
    (1, chainerx.int16, 1),
    (1, chainerx.int32, 1),
    (1, chainerx.int64, 1),
    (1, chainerx.uint8, 1),
    (1, chainerx.float32, 1.0),
    (1, chainerx.float64, 1.0),
    (1.0, chainerx.bool_, True),
    (1.0, chainerx.int8, 1),
    (1.0, chainerx.int16, 1),
    (1.0, chainerx.int32, 1),
    (1.0, chainerx.int64, 1),
    (1.0, chainerx.uint8, 1),
    (1.0, chainerx.float32, 1.0),
    (1.0, chainerx.float64, 1.0),
    (-1, chainerx.bool_, True),
    (-1, chainerx.int8, -1),
    (-1, chainerx.int16, -1),
    (-1, chainerx.int32, -1),
    (-1, chainerx.int64, -1),
    (-1, chainerx.uint8, 0xff),
    (-1, chainerx.float32, -1.0),
    (-1, chainerx.float64, -1.0),
    (0x100, chainerx.bool_, True),
    (0x100, chainerx.int8, 0),
    (0x100, chainerx.int16, 0x100),
    (0x100, chainerx.int32, 0x100),
    (0x100, chainerx.int64, 0x100),
    (0x100, chainerx.uint8, 0),
    (0x10000, chainerx.bool_, True),
    (0x10000, chainerx.int8, 0),
    (0x10000, chainerx.int16, 0),
    (0x10000, chainerx.int32, 0x10000),
    (0x10000, chainerx.int64, 0x10000),
    (0x10000, chainerx.uint8, 0),
    (0x100000000, chainerx.bool_, True),
    (0x100000000, chainerx.int8, 0),
    (0x100000000, chainerx.int16, 0),
    (0x100000000, chainerx.int32, 0),
    (0x100000000, chainerx.int64, 0x100000000),
    (0x100000000, chainerx.uint8, 0),
    (0x7fffffffffffffff, chainerx.bool_, True),
    (0x7fffffffffffffff, chainerx.int8, -1),
    (0x7fffffffffffffff, chainerx.int16, -1),
    (0x7fffffffffffffff, chainerx.int32, -1),
    (0x7fffffffffffffff, chainerx.int64, 0x7fffffffffffffff),
    (0x7fffffffffffffff, chainerx.uint8, 255),
])
def test_init_casted(value, cast_dtype, expected_value):
    scalar = chainerx.Scalar(value, cast_dtype)
    assert scalar.dtype == cast_dtype
    if math.isnan(expected_value):
        assert math.isnan(scalar.tolist())
    else:
        assert scalar.tolist() == expected_value
    assert isinstance(scalar.tolist(), type(expected_value))


@pytest.mark.parametrize(
    'value',
    [0, 0.0, 1, 1.0, -1, 0x100, 0x10000, 0x100000000, 0x7fffffffffffffff])
@chainerx.testing.parametrize_dtype_specifier('dtype_spec')
def test_init_with_dtype(value, dtype_spec):
    expected_dtype = chainerx.dtype(dtype_spec)
    scalar = chainerx.Scalar(value, dtype_spec)
    assert scalar.dtype == expected_dtype
    assert scalar == chainerx.Scalar(value, expected_dtype)


@pytest.mark.parametrize('value1,value2', [
    # TODO(niboshi): Support commented-out cases
    (0, 0),
    (1, 1),
    # (1, 1.0),
    (1.5, 1.5),
    (-1.5, -1.5),
    (True, True),
    (False, False),
    # (True, 1),
    # (True, 1.0),
    # (False, 0),
    # (False, 0.0),
    # (float('inf'), float('inf')),
])
def test_equality(value1, value2):
    scalar1 = chainerx.Scalar(value1)
    scalar2 = chainerx.Scalar(value2)

    assert scalar1 == scalar2
    assert scalar2 == scalar1

    assert scalar1 == value1
    assert value1 == scalar1

    assert scalar2 == value2
    assert value2 == scalar2

    assert scalar2 == value1
    assert value1 == scalar2

    assert scalar1 == value2
    assert value2 == scalar1


@pytest.mark.parametrize('value1,value2', [
    (0, 1),
    (-1, 1),
    (-1.0001, -1.0),
    (-1.0001, -1),
    (True, False),
    (True, 1.1),
    (1.0001, 1.0002),
    (float('nan'), float('nan')),
])
def test_inequality(value1, value2):
    scalar1 = chainerx.Scalar(value1)
    scalar2 = chainerx.Scalar(value2)

    assert scalar1 != scalar2
    assert scalar2 != scalar1

    assert scalar2 != value1
    assert value1 != scalar2

    assert scalar1 != value2
    assert value2 != scalar1


@pytest.mark.parametrize('value', [
    -2, 1, -1.5, 2.3, True, False
])
def test_cast(value):
    scalar = chainerx.Scalar(value)

    _check_cast_scalar_equals_data(scalar, value)
    _check_cast_scalar_equals_data(+scalar, +value)
    if isinstance(value, bool):
        with pytest.raises(chainerx.DtypeError):
            -scalar  # should not be able to negate bool
    else:
        _check_cast_scalar_equals_data(-scalar, -value)


@pytest.mark.parametrize('value', all_scalar_values)
def test_dtype(value):
    scalar = chainerx.Scalar(value)

    if isinstance(value, bool):
        assert scalar.dtype == chainerx.bool_
    elif isinstance(value, int):
        assert scalar.dtype == chainerx.int64
    elif isinstance(value, float):
        assert scalar.dtype == chainerx.float64
    else:
        assert False


@pytest.mark.parametrize('value', all_scalar_values)
def test_repr(value):
    scalar = chainerx.Scalar(value)

    assert repr(scalar) == repr(value)
    assert str(scalar) == str(value)


def test_init_invalid():
    with pytest.raises(TypeError):
        chainerx.Scalar("1")  # string, which is not a numeric

[((39, 1, 104, 2), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(39, 25, 39, 58): '"""value,cast_dtype,expected_value"""', (39, 60, 104, 1): '[(0, chainerx.bool_, False), (0, chainerx.int8, 0), (0, chainerx.int16, 0),\n    (0, chainerx.int32, 0), (0, chainerx.int64, 0), (0, chainerx.uint8, 0),\n    (0, chainerx.float32, 0.0), (0, chainerx.float64, 0.0), (0.0, chainerx.\n    bool_, False), (0.0, chainerx.int8, 0), (0.0, chainerx.int16, 0), (0.0,\n    chainerx.int32, 0), (0.0, chainerx.int64, 0), (0.0, chainerx.uint8, 0),\n    (0.0, chainerx.float32, 0.0), (0.0, chainerx.float64, 0.0), (1,\n    chainerx.bool_, True), (1, chainerx.int8, 1), (1, chainerx.int16, 1), (\n    1, chainerx.int32, 1), (1, chainerx.int64, 1), (1, chainerx.uint8, 1),\n    (1, chainerx.float32, 1.0), (1, chainerx.float64, 1.0), (1.0, chainerx.\n    bool_, True), (1.0, chainerx.int8, 1), (1.0, chainerx.int16, 1), (1.0,\n    chainerx.int32, 1), (1.0, chainerx.int64, 1), (1.0, chainerx.uint8, 1),\n    (1.0, chainerx.float32, 1.0), (1.0, chainerx.float64, 1.0), (-1,\n    chainerx.bool_, True), (-1, chainerx.int8, -1), (-1, chainerx.int16, -1\n    ), (-1, chainerx.int32, -1), (-1, chainerx.int64, -1), (-1, chainerx.\n    uint8, 255), (-1, chainerx.float32, -1.0), (-1, chainerx.float64, -1.0),\n    (256, chainerx.bool_, True), (256, chainerx.int8, 0), (256, chainerx.\n    int16, 256), (256, chainerx.int32, 256), (256, chainerx.int64, 256), (\n    256, chainerx.uint8, 0), (65536, chainerx.bool_, True), (65536,\n    chainerx.int8, 0), (65536, chainerx.int16, 0), (65536, chainerx.int32, \n    65536), (65536, chainerx.int64, 65536), (65536, chainerx.uint8, 0), (\n    4294967296, chainerx.bool_, True), (4294967296, chainerx.int8, 0), (\n    4294967296, chainerx.int16, 0), (4294967296, chainerx.int32, 0), (\n    4294967296, chainerx.int64, 4294967296), (4294967296, chainerx.uint8, 0\n    ), (9223372036854775807, chainerx.bool_, True), (9223372036854775807,\n    chainerx.int8, -1), (9223372036854775807, chainerx.int16, -1), (\n    9223372036854775807, chainerx.int32, -1), (9223372036854775807,\n    chainerx.int64, 9223372036854775807), (9223372036854775807, chainerx.\n    uint8, 255)]'}, {}), "('value,cast_dtype,expected_value', [(0, chainerx.\n    bool_, False), (0, chainerx.int8, 0), (0, chainerx.int16, 0), (0,\n    chainerx.int32, 0), (0, chainerx.int64, 0), (0, chainerx.uint8, 0), (0,\n    chainerx.float32, 0.0), (0, chainerx.float64, 0.0), (0.0, chainerx.\n    bool_, False), (0.0, chainerx.int8, 0), (0.0, chainerx.int16, 0), (0.0,\n    chainerx.int32, 0), (0.0, chainerx.int64, 0), (0.0, chainerx.uint8, 0),\n    (0.0, chainerx.float32, 0.0), (0.0, chainerx.float64, 0.0), (1,\n    chainerx.bool_, True), (1, chainerx.int8, 1), (1, chainerx.int16, 1), (\n    1, chainerx.int32, 1), (1, chainerx.int64, 1), (1, chainerx.uint8, 1),\n    (1, chainerx.float32, 1.0), (1, chainerx.float64, 1.0), (1.0, chainerx.\n    bool_, True), (1.0, chainerx.int8, 1), (1.0, chainerx.int16, 1), (1.0,\n    chainerx.int32, 1), (1.0, chainerx.int64, 1), (1.0, chainerx.uint8, 1),\n    (1.0, chainerx.float32, 1.0), (1.0, chainerx.float64, 1.0), (-1,\n    chainerx.bool_, True), (-1, chainerx.int8, -1), (-1, chainerx.int16, -1\n    ), (-1, chainerx.int32, -1), (-1, chainerx.int64, -1), (-1, chainerx.\n    uint8, 255), (-1, chainerx.float32, -1.0), (-1, chainerx.float64, -1.0),\n    (256, chainerx.bool_, True), (256, chainerx.int8, 0), (256, chainerx.\n    int16, 256), (256, chainerx.int32, 256), (256, chainerx.int64, 256), (\n    256, chainerx.uint8, 0), (65536, chainerx.bool_, True), (65536,\n    chainerx.int8, 0), (65536, chainerx.int16, 0), (65536, chainerx.int32, \n    65536), (65536, chainerx.int64, 65536), (65536, chainerx.uint8, 0), (\n    4294967296, chainerx.bool_, True), (4294967296, chainerx.int8, 0), (\n    4294967296, chainerx.int16, 0), (4294967296, chainerx.int32, 0), (\n    4294967296, chainerx.int64, 4294967296), (4294967296, chainerx.uint8, 0\n    ), (9223372036854775807, chainerx.bool_, True), (9223372036854775807,\n    chainerx.int8, -1), (9223372036854775807, chainerx.int16, -1), (\n    9223372036854775807, chainerx.int32, -1), (9223372036854775807,\n    chainerx.int64, 9223372036854775807), (9223372036854775807, chainerx.\n    uint8, 255)])", False, 'import pytest\n'), ((115, 1, 117, 74), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(116, 4, 116, 11): '"""value"""', (117, 4, 117, 73): '[0, 0.0, 1, 1.0, -1, 256, 65536, 4294967296, 9223372036854775807]'}, {}), "('value', [0, 0.0, 1, 1.0, -1, 256, 65536, \n    4294967296, 9223372036854775807])", False, 'import pytest\n'), ((118, 1, 118, 59), 'chainerx.testing.parametrize_dtype_specifier', 'chainerx.testing.parametrize_dtype_specifier', ({(118, 46, 118, 58): '"""dtype_spec"""'}, {}), "('dtype_spec')", False, 'import chainerx\n'), ((126, 1, 140, 2), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(126, 25, 126, 40): '"""value1,value2"""', (126, 42, 140, 1): '[(0, 0), (1, 1), (1.5, 1.5), (-1.5, -1.5), (True, True), (False, False)]'}, {}), "('value1,value2', [(0, 0), (1, 1), (1.5, 1.5), (-1.5,\n    -1.5), (True, True), (False, False)])", False, 'import pytest\n'), ((185, 1, 187, 2), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(185, 25, 185, 32): '"""value"""', (185, 34, 187, 1): '[-2, 1, -1.5, 2.3, True, False]'}, {}), "('value', [-2, 1, -1.5, 2.3, True, False])", False, 'import pytest\n'), ((200, 1, 200, 52), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(200, 25, 200, 32): '"""value"""', (200, 34, 200, 51): 'all_scalar_values'}, {}), "('value', all_scalar_values)", False, 'import pytest\n'), ((214, 1, 214, 52), 'pytest.mark.parametrize', 'pytest.mark.parametrize', ({(214, 25, 214, 32): '"""value"""', (214, 34, 214, 51): 'all_scalar_values'}, {}), "('value', all_scalar_values)", False, 'import pytest\n'), ((30, 13, 30, 35), 'chainerx.Scalar', 'chainerx.Scalar', ({(30, 29, 30, 34): 'value'}, {}), '(value)', False, 'import chainerx\n'), ((32, 7, 32, 24), 'math.isnan', 'math.isnan', ({(32, 18, 32, 23): 'value'}, {}), '(value)', False, 'import math\n'), ((106, 13, 106, 47), 'chainerx.Scalar', 'chainerx.Scalar', ({(106, 29, 106, 34): 'value', (106, 36, 106, 46): 'cast_dtype'}, {}), '(value, cast_dtype)', False, 'import chainerx\n'), ((108, 7, 108, 33), 'math.isnan', 'math.isnan', ({(108, 18, 108, 32): 'expected_value'}, {}), '(expected_value)', False, 'import math\n'), ((120, 21, 120, 47), 'chainerx.dtype', 'chainerx.dtype', ({(120, 36, 120, 46): 'dtype_spec'}, {}), '(dtype_spec)', False, 'import chainerx\n'), ((121, 13, 121, 47), 'chainerx.Scalar', 'chainerx.Scalar', ({(121, 29, 121, 34): 'value', (121, 36, 121, 46): 'dtype_spec'}, {}), '(value, dtype_spec)', False, 'import chainerx\n'), ((142, 14, 142, 37), 'chainerx.Scalar', 'chainerx.Scalar', ({(142, 30, 142, 36): 'value1'}, {}), '(value1)', False, 'import chainerx\n'), ((143, 14, 143, 37), 'chainerx.Scalar', 'chainerx.Scalar', ({(143, 30, 143, 36): 'value2'}, {}), '(value2)', False, 'import chainerx\n'), ((172, 14, 172, 37), 'chainerx.Scalar', 'chainerx.Scalar', ({(172, 30, 172, 36): 'value1'}, {}), '(value1)', False, 'import chainerx\n'), ((173, 14, 173, 37), 'chainerx.Scalar', 'chainerx.Scalar', ({(173, 30, 173, 36): 'value2'}, {}), '(value2)', False, 'import chainerx\n'), ((189, 13, 189, 35), 'chainerx.Scalar', 'chainerx.Scalar', ({(189, 29, 189, 34): 'value'}, {}), '(value)', False, 'import chainerx\n'), ((202, 13, 202, 35), 'chainerx.Scalar', 'chainerx.Scalar', ({(202, 29, 202, 34): 'value'}, {}), '(value)', False, 'import chainerx\n'), ((216, 13, 216, 35), 'chainerx.Scalar', 'chainerx.Scalar', ({(216, 29, 216, 34): 'value'}, {}), '(value)', False, 'import chainerx\n'), ((123, 21, 123, 59), 'chainerx.Scalar', 'chainerx.Scalar', ({(123, 37, 123, 42): 'value', (123, 44, 123, 58): 'expected_dtype'}, {}), '(value, expected_dtype)', False, 'import chainerx\n'), ((223, 9, 223, 33), 'pytest.raises', 'pytest.raises', ({(223, 23, 223, 32): 'TypeError'}, {}), '(TypeError)', False, 'import pytest\n'), ((224, 8, 224, 28), 'chainerx.Scalar', 'chainerx.Scalar', ({(224, 24, 224, 27): '"""1"""'}, {}), "('1')", False, 'import chainerx\n'), ((194, 13, 194, 47), 'pytest.raises', 'pytest.raises', ({(194, 27, 194, 46): 'chainerx.DtypeError'}, {}), '(chainerx.DtypeError)', False, 'import pytest\n')]