
import env
import pytest
import asyncio

from olopy.utils import logging
from olopy.client import *



@pytest.mark.asyncio
async def test_fetch_and_commit ():

    session = await create_connection('localhost', 9000)
    assert isinstance(session, MappingClientProtocol)

    # fetch and change a primitive value
    x = await session.fetch('x')
    assert x == None

    await session.commit(Change('x', None, 10))
    x = await session.fetch('x')
    assert x == 10

    await session.commit(Change('x', 10, 11))
    x = await session.fetch('x')
    assert x == 11

    with pytest.raises(Exception):
        await session.commit(Change('x', -1, -2))
    x = await session.fetch('x')
    assert x == 11

    await session.commit(Change('x', 11, None))
    x = await session.fetch('x')
    assert x == None


    # fetch and change a dictionary
    o = await session.fetch('o')
    assert o == None

    await session.commit(Change('o', None, {'a':1, 'b':2, 'c':3}))
    a = await session.fetch('o.a')
    assert a == 1
    b = await session.fetch('o.b')
    assert b == 2
    c = await session.fetch('o.c')
    assert c == 3
    d = await session.fetch('o.d')
    assert d == None
    o = await session.fetch('o')
    assert type(o) == ObservableMapping
    assert o == {'a':1, 'b':2, 'c':3}

    count = await session.commit(Change('o', o, None))
    assert count == 1
    o = await session.fetch('o')
    assert o == None

    session.sendClose()
    report = await session.done
    assert report['reason'] == None



@pytest.mark.asyncio
async def test_subscription ():

    # connect and initialize the remote mapping
    session = await create_connection('localhost', 9000)
    assert isinstance(session, MappingClientProtocol)
    await session.commit(Change('o', None, {'x':10, 'y':20, 'z':30}))
    o = await session.fetch('o')
    assert o == {'x':10, 'y':20, 'z':30}

    # subscribe
    changes = asyncio.Queue()
    sid = await session.subscribe('o', lambda change: changes.put_nowait(change))

    # test of local change successfully committment
    await session.commit(Change('o.x', 10, 11))
    change = await changes.get()
    assert change == Change('x', 10, 11)

    # reset the remote mapping and disconnect
    o = await session.fetch('o')
    await session.commit(Change('o', o, None))
    o = await session.fetch('o')
    assert o == None
    session.sendClose()
    report = await session.done
    assert report['reason'] == None



@pytest.mark.asyncio
async def test_link ():

    # connect, initialize and subscribe to the remote mapping
    session = await create_connection('localhost', 9000)
    assert isinstance(session, MappingClientProtocol)
    await session.commit(Change('a', None, {'b': {'c': {'x':10, 'y':20, 'z':30}}}))

    # create the local mapping
    c = ObservableMapping()

    # create the link
    link = await Link(c, 'x', session, 'a.b.c.x')
    assert c['x'] == 10

    # create test observers
    local_changes = asyncio.Queue()
    @c.observer
    def observer (change):
        local_changes.put_nowait(change)

    remote_changes = asyncio.Queue()
    sid = await session.subscribe('a.b.c', lambda change: remote_changes.put_nowait(change))

    assert c['x'] == 10

    # test remote change
    x = await session.fetch('a.b.c.x')
    await session.commit(Change('a.b.c.x', x, x+1))
    local_change = await local_changes.get()
    assert local_change == Change('x', x, x+1)
    while not remote_changes.empty():
        remote_changes.get_nowait()

    # test local change
    x = c['x']
    c['x'] = x+1 
    remote_change = await remote_changes.get()
    assert remote_change == Change('x', x, x+1)

    # test rollback
    link._dispatch_local_change = False
    x = c['x']
    c['x'] = x+1
    link._dispatch_local_change = True
    while not local_changes.empty():
        local_changes.get_nowait()

    await session.commit(Change('a.b.c.x', x, x+10))
    local_change = await local_changes.get()
    assert c['x'] == x+10

    # remove the link
    await link.remove()

    # reset the remote mapping and disconnect
    a = await session.fetch('a')
    await session.commit(Change('a', a, None))
    a = await session.fetch('a')
    assert a == None
    session.sendClose()
    report = await session.done
    assert report['reason'] == None


