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

[((928, 3003), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""value,cast_dtype,expected_value"""', '[(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)])\n", (951, 3003), False, 'import pytest\n'), ((3515, 3619), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""value"""', '[0, 0.0, 1, 1.0, -1, 256, 65536, 4294967296, 9223372036854775807]'], {}), "('value', [0, 0.0, 1, 1.0, -1, 256, 65536, \n    4294967296, 9223372036854775807])\n", (3538, 3619), False, 'import pytest\n'), ((3629, 3687), 'chainerx.testing.parametrize_dtype_specifier', 'chainerx.testing.parametrize_dtype_specifier', (['"""dtype_spec"""'], {}), "('dtype_spec')\n", (3673, 3687), False, 'import chainerx\n'), ((3934, 4052), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""value1,value2"""', '[(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)])\n", (3957, 4052), False, 'import pytest\n'), ((5160, 5225), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""value"""', '[-2, 1, -1.5, 2.3, True, False]'], {}), "('value', [-2, 1, -1.5, 2.3, True, False])\n", (5183, 5225), False, 'import pytest\n'), ((5600, 5651), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""value"""', 'all_scalar_values'], {}), "('value', all_scalar_values)\n", (5623, 5651), False, 'import pytest\n'), ((5986, 6037), 'pytest.mark.parametrize', 'pytest.mark.parametrize', (['"""value"""', 'all_scalar_values'], {}), "('value', all_scalar_values)\n", (6009, 6037), False, 'import pytest\n'), ((698, 720), 'chainerx.Scalar', 'chainerx.Scalar', (['value'], {}), '(value)\n', (713, 720), False, 'import chainerx\n'), ((761, 778), 'math.isnan', 'math.isnan', (['value'], {}), '(value)\n', (771, 778), False, 'import math\n'), ((3241, 3275), 'chainerx.Scalar', 'chainerx.Scalar', (['value', 'cast_dtype'], {}), '(value, cast_dtype)\n', (3256, 3275), False, 'import chainerx\n'), ((3321, 3347), 'math.isnan', 'math.isnan', (['expected_value'], {}), '(expected_value)\n', (3331, 3347), False, 'import math\n'), ((3754, 3780), 'chainerx.dtype', 'chainerx.dtype', (['dtype_spec'], {}), '(dtype_spec)\n', (3768, 3780), False, 'import chainerx\n'), ((3794, 3828), 'chainerx.Scalar', 'chainerx.Scalar', (['value', 'dtype_spec'], {}), '(value, dtype_spec)\n', (3809, 3828), False, 'import chainerx\n'), ((4300, 4323), 'chainerx.Scalar', 'chainerx.Scalar', (['value1'], {}), '(value1)\n', (4315, 4323), False, 'import chainerx\n'), ((4338, 4361), 'chainerx.Scalar', 'chainerx.Scalar', (['value2'], {}), '(value2)\n', (4353, 4361), False, 'import chainerx\n'), ((4916, 4939), 'chainerx.Scalar', 'chainerx.Scalar', (['value1'], {}), '(value1)\n', (4931, 4939), False, 'import chainerx\n'), ((4954, 4977), 'chainerx.Scalar', 'chainerx.Scalar', (['value2'], {}), '(value2)\n', (4969, 4977), False, 'import chainerx\n'), ((5267, 5289), 'chainerx.Scalar', 'chainerx.Scalar', (['value'], {}), '(value)\n', (5282, 5289), False, 'import chainerx\n'), ((5688, 5710), 'chainerx.Scalar', 'chainerx.Scalar', (['value'], {}), '(value)\n', (5703, 5710), False, 'import chainerx\n'), ((6073, 6095), 'chainerx.Scalar', 'chainerx.Scalar', (['value'], {}), '(value)\n', (6088, 6095), False, 'import chainerx\n'), ((3892, 3930), 'chainerx.Scalar', 'chainerx.Scalar', (['value', 'expected_dtype'], {}), '(value, expected_dtype)\n', (3907, 3930), False, 'import chainerx\n'), ((6209, 6233), 'pytest.raises', 'pytest.raises', (['TypeError'], {}), '(TypeError)\n', (6222, 6233), False, 'import pytest\n'), ((6243, 6263), 'chainerx.Scalar', 'chainerx.Scalar', (['"""1"""'], {}), "('1')\n", (6258, 6263), False, 'import chainerx\n'), ((5438, 5472), 'pytest.raises', 'pytest.raises', (['chainerx.DtypeError'], {}), '(chainerx.DtypeError)\n', (5451, 5472), False, 'import pytest\n')]