# -*- coding: utf-8 -*-

import pytest

from mock import call, Mock
from pyee import BaseEventEmitter
from pyee.uplift import uplift


class UpliftedEventEmitter(BaseEventEmitter):
    pass


def test_uplift_emit():
    call_me = Mock()

    base_ee = BaseEventEmitter()

    @base_ee.on('base_event')
    def base_handler():
        call_me('base event on base emitter')

    @base_ee.on('shared_event')
    def shared_base_handler():
        call_me('shared event on base emitter')

    uplifted_ee = uplift(UpliftedEventEmitter, base_ee)

    assert isinstance(
        uplifted_ee,
        UpliftedEventEmitter
    ), 'Returns an uplifted emitter'

    @uplifted_ee.on('uplifted_event')
    def uplifted_handler():
        call_me('uplifted event on uplifted emitter')

    @uplifted_ee.on('shared_event')
    def shared_uplifted_handler():
        call_me('shared event on uplifted emitter')

    # Events on uplifted proxy correctly
    assert uplifted_ee.emit('base_event')
    assert uplifted_ee.emit('shared_event')
    assert uplifted_ee.emit('uplifted_event')

    call_me.assert_has_calls([
        call('base event on base emitter'),
        call('shared event on uplifted emitter'),
        call('shared event on base emitter'),
        call('uplifted event on uplifted emitter')
    ])

    call_me.reset_mock()

    # Events on underlying proxy correctly
    assert base_ee.emit('base_event')
    assert base_ee.emit('shared_event')
    assert base_ee.emit('uplifted_event')

    call_me.assert_has_calls([
        call('base event on base emitter'),
        call('shared event on base emitter'),
        call('shared event on uplifted emitter'),
        call('uplifted event on uplifted emitter')
    ])

    call_me.reset_mock()

    # Quick check for unwrap
    uplifted_ee.unwrap()

    with pytest.raises(AttributeError):
        getattr(uplifted_ee, 'unwrap')

    with pytest.raises(AttributeError):
        getattr(base_ee, 'unwrap')

    assert not uplifted_ee.emit('base_event')
    assert uplifted_ee.emit('shared_event')
    assert uplifted_ee.emit('uplifted_event')

    assert base_ee.emit('base_event')
    assert base_ee.emit('shared_event')
    assert not base_ee.emit('uplifted_event')

    call_me.assert_has_calls([
        # No listener for base event on uplifted
        call('shared event on uplifted emitter'),
        call('uplifted event on uplifted emitter'),
        call('base event on base emitter'),
        call('shared event on base emitter')
        # No listener for uplifted event on uplifted
    ])


@pytest.mark.parametrize('error_handling', [
  'new', 'underlying', 'neither'
])
def test_exception_handling(error_handling):
    base_ee = BaseEventEmitter()
    uplifted_ee = uplift(
        UpliftedEventEmitter, base_ee,
        error_handling=error_handling
    )

    # Exception handling alwyas prefers uplifted
    base_error = Exception('base error')
    uplifted_error = Exception('uplifted error')

    # Hold my beer
    base_error_handler = Mock()
    base_ee._emit_handle_potential_error = base_error_handler

    # Hold my other beer
    uplifted_error_handler = Mock()
    uplifted_ee._emit_handle_potential_error = uplifted_error_handler

    base_ee.emit('error', base_error)
    uplifted_ee.emit('error', uplifted_error)

    if error_handling == 'new':
        base_error_handler.assert_not_called()
        uplifted_error_handler.assert_has_calls([
            call('error', base_error),
            call('error', uplifted_error)
        ])
    elif error_handling == 'underlying':
        base_error_handler.assert_has_calls([
            call('error', base_error),
            call('error', uplifted_error)
        ])
        uplifted_error_handler.assert_not_called()
    elif error_handling == 'neither':
        base_error_handler.assert_called_once_with('error', base_error)
        uplifted_error_handler.assert_called_once_with('error', uplifted_error)
    else:
        raise Exception('unrecognized setting')


@pytest.mark.parametrize('proxy_new_listener', [
    'both',
    'neither',
    'forward',
    'backward'
])
def test_proxy_new_listener(proxy_new_listener):
    call_me = Mock()

    base_ee = BaseEventEmitter()

    uplifted_ee = uplift(
        UpliftedEventEmitter,
        base_ee,
        proxy_new_listener=proxy_new_listener
    )

    @base_ee.on('new_listener')
    def base_new_listener_handler(event, f):
        assert event in ('event', 'new_listener')
        call_me('base new listener handler', f)

    @uplifted_ee.on('new_listener')
    def uplifted_new_listener_handler(event, f):
        assert event in ('event', 'new_listener')
        call_me('uplifted new listener handler', f)

    def fresh_base_handler():
        pass

    def fresh_uplifted_handler():
        pass

    base_ee.on('event', fresh_base_handler)
    uplifted_ee.on('event', fresh_uplifted_handler)

    if proxy_new_listener == 'both':
        call_me.assert_has_calls([
            call('base new listener handler', fresh_base_handler),
            call('uplifted new listener handler', fresh_base_handler),
            call('uplifted new listener handler', fresh_uplifted_handler),
            call('base new listener handler', fresh_uplifted_handler)
        ])
    elif proxy_new_listener == 'neither':
        call_me.assert_has_calls([
            call('base new listener handler', fresh_base_handler),
            call('uplifted new listener handler', fresh_uplifted_handler)
        ])
    elif proxy_new_listener == 'forward':
        call_me.assert_has_calls([
            call('base new listener handler', fresh_base_handler),
            call('uplifted new listener handler', fresh_base_handler),
            call('uplifted new listener handler', fresh_uplifted_handler)
        ])
    elif proxy_new_listener == 'backward':
        call_me.assert_has_calls([
            call('base new listener handler', fresh_base_handler),
            call('uplifted new listener handler', fresh_uplifted_handler),
            call('base new listener handler', fresh_uplifted_handler)
        ])
    else:
        raise Exception('unrecognized proxy_new_listener')
