
from pytest import raises

from glom import glom, Path, T, PathAccessError, GlomError

def test_list_path_access():
    assert glom(list(range(10)), Path(1)) == 1


def test_path():
    _obj = object()
    target = {'a': {'b.b': [None, {_obj: [None, None, 'd']}]}}

    assert glom(target, Path('a', 'b.b', 1, _obj, -1)) == 'd'


def test_empty_path_access():
    target = {}

    assert glom(target, Path()) is target
    assert glom(target, (Path(), Path(), Path())) is target

    dup_dict = glom(target, {'target': Path(),
                             'target2': Path()})
    dup_dict['target'] is target
    dup_dict['target2'] is target


def test_path_t_roundtrip():
    # check that T repr roundrips
    assert repr(T['a'].b.c()) == "T['a'].b.c()"

    # check that Path repr roundtrips
    assert repr(Path('a', 1, 'b.b', -1.0)) == "Path('a', 1, 'b.b', -1.0)"

    # check that Path repr roundtrips when it contains Ts
    assert repr(Path(T['a'].b, 'c', T['d'].e)) == "Path(T['a'].b, 'c', T['d'].e)"

    # check that T instances containing Path access revert to repring with Path
    assert repr(Path(T['a'].b, 'c', T['d'].e).path_t) == "Path(T['a'].b, 'c', T['d'].e)"

    # check that Paths containing only T objects reduce to a T (joining the T objects)
    assert repr(Path(T['a'].b, T.c())) == "T['a'].b.c()"

    # check that multiple nested paths reduce
    assert repr(Path(Path(Path('a')))) == "Path('a')"


def test_path_access_error_message():

    # test fuzzy access
    with raises(GlomError) as exc_info:
        glom({}, 'a.b')
    assert ("PathAccessError: could not access 'a', part 0 of Path('a', 'b'), got error: KeyError"
            in exc_info.exconly())
    assert repr(exc_info.value) == "PathAccessError(KeyError('a',), Path('a', 'b'), 0)"

    # test multi-part Path with T, catchable as a KeyError
    with raises(KeyError) as exc_info:
        # don't actually use glom to copy your data structures please
        glom({'a': {'b': 'c'}}, Path('a', T.copy(), 'd'))
    assert ("PathAccessError: could not access 'd', part 3 of Path('a', T.copy(), 'd'), got error: KeyError"
            in exc_info.exconly())
    assert repr(exc_info.value) == "PathAccessError(KeyError('d',), Path('a', T.copy(), 'd'), 3)"

    # test AttributeError
    with raises(GlomError) as exc_info:
        glom({'a': {'b': 'c'}}, Path('a', T.b))
    assert ("PathAccessError: could not access 'b', part 1 of Path('a', T.b), got error: AttributeError"
            in exc_info.exconly())
    assert repr(exc_info.value) == """PathAccessError(AttributeError("\'dict\' object has no attribute \'b\'",), Path(\'a\', T.b), 1)"""


def test_t_picklability():
    import pickle

    class TargetType(object):
        def __init__(self):
            self.attribute = lambda: None
            self.attribute.method = lambda: {'key': lambda x: x * 2}

    spec = T.attribute.method()['key'](x=5)

    rt_spec = pickle.loads(pickle.dumps(spec))
    assert repr(spec) == repr(rt_spec)

    assert glom(TargetType(), spec) == 10


def test_path_len():

    assert len(Path()) == 0
    assert len(Path('a', 'b', 'c')) == 3
    assert len(Path.from_text('1.2.3.4')) == 4

    assert len(Path(T)) == 0
    assert len(Path(T.a.b.c)) == 3
    assert len(Path(T.a()['b'].c.d)) == 5


def test_path_getitem():
    path = Path(T.a.b.c)

    assert path[0] == Path(T.a)
    assert path[1] == Path(T.b)
    assert path[2] == Path(T.c)
    assert path[-1] == Path(T.c)
    assert path[-2] == Path(T.b)

    with raises(IndexError, match='Path index out of range'):
        path[4]

    with raises(IndexError, match='Path index out of range'):
        path[-14]
    return


def test_path_slices():
    path = Path(T.a.b, 1, 2, T(test='yes'))

    assert path[::] == path

    # positive indices
    assert path[3:] == Path(2, T(test='yes'))
    assert path[1:3] == Path(T.b, 1)
    assert path[:3] == Path(T.a.b, 1)

    # positive indices backwards
    assert path[2:1] == Path()

    # negative indices forward
    assert path[-1:] == Path(T(test='yes'))
    assert path[:-2] == Path(T.a.b, 1)
    assert path[-3:-1] == Path(1, 2)

    # negative indices backwards
    assert path[-1:-3] == Path()

    # slicing and stepping
    assert path[1::2] == Path(T.b, 2)


def test_path_values():
    path = Path(T.a.b, 1, 2, T(test='yes'))

    assert path.values() == ('a', 'b', 1, 2, ((), {'test': 'yes'}))

    assert Path().values() == ()


def test_path_items():
    path = Path(T.a, 1, 2, T(test='yes'))

    assert path.items() == (('.', 'a'),
                            ('P', 1), ('P', 2),
                            ('(', ((), {'test': 'yes'})))

    assert Path().items() == ()


def test_path_eq():
    assert Path('a', 'b') == Path('a', 'b')
    assert Path('a') != Path('b')

    assert Path() != object()


def test_path_eq_t():
    assert Path(T.a.b) == T.a.b
    assert Path(T.a.b.c) != T.a.b
