# encoding: utf-8

from __future__ import unicode_literals

import unittest

import spotify
from spotify import utils
import tests
from tests import mock


class EventEmitterTest(unittest.TestCase):

    def test_listener_receives_event_args(self):
        listener_mock = mock.Mock()
        emitter = utils.EventEmitter()
        emitter.on('some_event', listener_mock)

        emitter.emit('some_event', 'abc', 'def')

        listener_mock.assert_called_with('abc', 'def')

    def test_listener_receives_both_user_and_event_args(self):
        listener_mock = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock, 1, 2, 3)
        emitter.emit('some_event', 'abc')

        listener_mock.assert_called_with('abc', 1, 2, 3)

    def test_multiple_listeners_for_same_event(self):
        listener_mock1 = mock.Mock()
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock1, 1, 2, 3)
        emitter.on('some_event', listener_mock2, 4, 5)
        emitter.emit('some_event', 'abc')

        listener_mock1.assert_called_with('abc', 1, 2, 3)
        listener_mock2.assert_called_with('abc', 4, 5)

    def test_removing_a_listener(self):
        listener_mock1 = mock.Mock()
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock1, 123)
        emitter.on('some_event', listener_mock1, 456)
        emitter.on('some_event', listener_mock2, 78)
        emitter.off('some_event', listener_mock1)
        emitter.emit('some_event')

        self.assertEqual(listener_mock1.call_count, 0)
        listener_mock2.assert_called_with(78)

    def test_removing_all_listeners_for_an_event(self):
        listener_mock1 = mock.Mock()
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock1)
        emitter.on('some_event', listener_mock2)
        emitter.off('some_event')
        emitter.emit('some_event')

        self.assertEqual(listener_mock1.call_count, 0)
        self.assertEqual(listener_mock2.call_count, 0)

    def test_removing_all_listeners_for_all_events(self):
        listener_mock1 = mock.Mock()
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock1)
        emitter.on('another_event', listener_mock2)
        emitter.off()
        emitter.emit('some_event')
        emitter.emit('another_event')

        self.assertEqual(listener_mock1.call_count, 0)
        self.assertEqual(listener_mock2.call_count, 0)

    def test_listener_returning_false_is_removed(self):
        listener_mock1 = mock.Mock(return_value=False)
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock1)
        emitter.on('some_event', listener_mock2)
        emitter.emit('some_event')
        emitter.emit('some_event')

        self.assertEqual(listener_mock1.call_count, 1)
        self.assertEqual(listener_mock2.call_count, 2)

    def test_num_listeners_returns_total_number_of_listeners(self):
        listener_mock1 = mock.Mock()
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        self.assertEqual(emitter.num_listeners(), 0)

        emitter.on('some_event', listener_mock1)
        self.assertEqual(emitter.num_listeners(), 1)

        emitter.on('another_event', listener_mock1)
        emitter.on('another_event', listener_mock2)
        self.assertEqual(emitter.num_listeners(), 3)

    def test_num_listeners_returns_number_of_listeners_for_event(self):
        listener_mock1 = mock.Mock()
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        self.assertEqual(emitter.num_listeners('unknown_event'), 0)

        emitter.on('some_event', listener_mock1)
        self.assertEqual(emitter.num_listeners('some_event'), 1)

        emitter.on('another_event', listener_mock1)
        emitter.on('another_event', listener_mock2)
        self.assertEqual(emitter.num_listeners('another_event'), 2)

    def test_call_fails_if_zero_listeners_for_event(self):
        emitter = utils.EventEmitter()

        with self.assertRaises(AssertionError):
            emitter.call('some_event')

    def test_call_fails_if_multiple_listeners_for_event(self):
        listener_mock1 = mock.Mock()
        listener_mock2 = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock1)
        emitter.on('some_event', listener_mock2)

        with self.assertRaises(AssertionError):
            emitter.call('some_event')

    def test_call_calls_and_returns_result_of_a_single_listener(self):
        listener_mock = mock.Mock()
        emitter = utils.EventEmitter()

        emitter.on('some_event', listener_mock, 1, 2, 3)
        result = emitter.call('some_event', 'abc')

        listener_mock.assert_called_with('abc', 1, 2, 3)
        self.assertEqual(result, listener_mock.return_value)


class IntEnumTest(unittest.TestCase):

    def setUp(self):
        class Foo(utils.IntEnum):
            pass

        self.Foo = Foo

        self.Foo.add('bar', 1)
        self.Foo.add('baz', 2)

    def test_has_pretty_repr(self):
        self.assertEqual(repr(self.Foo.bar), '<Foo.bar: 1>')
        self.assertEqual(repr(self.Foo.baz), '<Foo.baz: 2>')

    def test_is_equal_to_the_int_value(self):
        self.assertEqual(self.Foo.bar, 1)
        self.assertEqual(self.Foo.baz, 2)

    def test_two_instances_with_same_value_is_identical(self):
        self.assertIs(self.Foo(1), self.Foo.bar)
        self.assertIs(self.Foo(2), self.Foo.baz)
        self.assertIsNot(self.Foo(2), self.Foo.bar)
        self.assertIsNot(self.Foo(1), self.Foo.baz)


@mock.patch('spotify.search.lib', spec=spotify.lib)
class SequenceTest(unittest.TestCase):

    def test_adds_ref_to_sp_obj_when_created(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=None,
            getitem_func=None)

        self.assertEqual(lib_mock.sp_search_add_ref.call_count, 1)

    def test_releases_sp_obj_when_sequence_dies(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=None,
            getitem_func=None)

        seq = None  # noqa
        tests.gc_collect()

        self.assertEqual(lib_mock.sp_search_release.call_count, 1)

    def test_len_calls_len_func(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        len_func = mock.Mock()
        len_func.return_value = 0
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=len_func,
            getitem_func=None)

        result = len(seq)

        self.assertEqual(result, 0)
        len_func.assert_called_with(sp_search)

    def test_getitem_calls_getitem_func(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        getitem_func = mock.Mock()
        getitem_func.return_value = mock.sentinel.item_one
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=lambda x: 1,
            getitem_func=getitem_func)

        result = seq[0]

        self.assertEqual(result, mock.sentinel.item_one)
        getitem_func.assert_called_with(sp_search, 0)

    def test_getitem_with_negative_index(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        getitem_func = mock.Mock()
        getitem_func.return_value = mock.sentinel.item_one
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=lambda x: 1,
            getitem_func=getitem_func)

        result = seq[-1]

        self.assertEqual(result, mock.sentinel.item_one)
        getitem_func.assert_called_with(sp_search, 0)

    def test_getitem_with_slice(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        getitem_func = mock.Mock()
        getitem_func.side_effect = [
            mock.sentinel.item_one,
            mock.sentinel.item_two,
            mock.sentinel.item_three,
        ]
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=lambda x: 3,
            getitem_func=getitem_func)

        result = seq[0:2]

        # Entire collection of length 3 is created as a list
        self.assertEqual(getitem_func.call_count, 3)

        # Only a subslice of length 2 is returned
        self.assertIsInstance(result, list)
        self.assertEqual(len(result), 2)
        self.assertEqual(result[0], mock.sentinel.item_one)
        self.assertEqual(result[1], mock.sentinel.item_two)

    def test_getitem_raises_index_error_on_too_low_index(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=lambda x: 1,
            getitem_func=None)

        with self.assertRaises(IndexError):
            seq[-3]

    def test_getitem_raises_index_error_on_too_high_index(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=lambda x: 1,
            getitem_func=None)

        with self.assertRaises(IndexError):
            seq[1]

    def test_getitem_raises_type_error_on_non_integral_index(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=lambda x: 1,
            getitem_func=None)

        with self.assertRaises(TypeError):
            seq['abc']

    def test_repr(self, lib_mock):
        sp_search = spotify.ffi.cast('sp_search *', 42)
        seq = utils.Sequence(
            sp_obj=sp_search,
            add_ref_func=lib_mock.sp_search_add_ref,
            release_func=lib_mock.sp_search_release,
            len_func=lambda x: 1,
            getitem_func=lambda s, i: 123)

        result = repr(seq)

        self.assertEqual(result, 'Sequence([123])')


class ToBytesTest(unittest.TestCase):

    def test_unicode_to_bytes_is_encoded_as_utf8(self):
        self.assertEqual(utils.to_bytes('æøå'), 'æøå'.encode('utf-8'))

    def test_bytes_to_bytes_is_passed_through(self):
        self.assertEqual(
            utils.to_bytes('æøå'.encode('utf-8')), 'æøå'.encode('utf-8'))

    def test_cdata_to_bytes_is_unwrapped(self):
        cdata = spotify.ffi.new('char[]', 'æøå'.encode('utf-8'))
        self.assertEqual(utils.to_bytes(cdata), 'æøå'.encode('utf-8'))

    def test_anything_else_to_bytes_fails(self):
        with self.assertRaises(ValueError):
            utils.to_bytes([])

        with self.assertRaises(ValueError):
            utils.to_bytes(123)


class ToBytesOrNoneTest(unittest.TestCase):

    def test_null_becomes_none(self):
        self.assertEqual(utils.to_bytes_or_none(spotify.ffi.NULL), None)

    def test_char_becomes_bytes(self):
        result = utils.to_bytes_or_none(spotify.ffi.new('char[]', b'abc'))

        self.assertEqual(result, b'abc')

    def test_anything_else_fails(self):
        with self.assertRaises(ValueError):
            utils.to_bytes_or_none(b'abc')


class ToUnicodeTest(unittest.TestCase):

    def test_unicode_to_unicode_is_passed_through(self):
        self.assertEqual(utils.to_unicode('æøå'), 'æøå')

    def test_bytes_to_unicode_is_decoded_as_utf8(self):
        self.assertEqual(utils.to_unicode('æøå'.encode('utf-8')), 'æøå')

    def test_cdata_to_unicode_is_unwrapped_and_decoded_as_utf8(self):
        cdata = spotify.ffi.new('char[]', 'æøå'.encode('utf-8'))
        self.assertEqual(utils.to_unicode(cdata), 'æøå')

    def test_anything_else_to_unicode_fails(self):
        with self.assertRaises(ValueError):
            utils.to_unicode([])

        with self.assertRaises(ValueError):
            utils.to_unicode(123)


class ToUnicodeOrNoneTest(unittest.TestCase):

    def test_null_becomes_none(self):
        self.assertEqual(utils.to_unicode_or_none(spotify.ffi.NULL), None)

    def test_char_becomes_bytes(self):
        result = utils.to_unicode_or_none(
            spotify.ffi.new('char[]', 'æøå'.encode('utf-8')))

        self.assertEqual(result, 'æøå')

    def test_anything_else_fails(self):
        with self.assertRaises(ValueError):
            utils.to_unicode_or_none('æøå')


class ToCharTest(unittest.TestCase):

    def test_bytes_becomes_char(self):
        result = utils.to_char(b'abc')

        self.assertIsInstance(result, spotify.ffi.CData)
        self.assertEqual(spotify.ffi.string(result), b'abc')

    def test_unicode_becomes_char(self):
        result = utils.to_char('æøå')

        self.assertIsInstance(result, spotify.ffi.CData)
        self.assertEqual(spotify.ffi.string(result).decode('utf-8'), 'æøå')

    def test_anything_else_fails(self):
        with self.assertRaises(ValueError):
            utils.to_char(None)

        with self.assertRaises(ValueError):
            utils.to_char(123)


class ToCharOrNullTest(unittest.TestCase):

    def test_none_becomes_null(self):
        self.assertEqual(utils.to_char_or_null(None), spotify.ffi.NULL)

    def test_bytes_becomes_char(self):
        result = utils.to_char_or_null(b'abc')

        self.assertIsInstance(result, spotify.ffi.CData)
        self.assertEqual(spotify.ffi.string(result), b'abc')

    def test_unicode_becomes_char(self):
        result = utils.to_char_or_null('æøå')

        self.assertIsInstance(result, spotify.ffi.CData)
        self.assertEqual(spotify.ffi.string(result).decode('utf-8'), 'æøå')

    def test_anything_else_fails(self):
        with self.assertRaises(ValueError):
            utils.to_char_or_null(123)


class ToCountryCodeTest(unittest.TestCase):

    def test_unicode_to_country_code(self):
        self.assertEqual(utils.to_country_code('NO'), 20047)
        self.assertEqual(utils.to_country_code('SE'), 21317)

    def test_bytes_to_country_code(self):
        self.assertEqual(utils.to_country_code(b'NO'), 20047)
        self.assertEqual(utils.to_country_code(b'SE'), 21317)

    def test_fails_if_not_exactly_two_chars(self):
        with self.assertRaises(ValueError):
            utils.to_country_code('NOR')

    def test_fails_if_not_in_uppercase(self):
        with self.assertRaises(ValueError):
            utils.to_country_code('no')


class ToCountryTest(unittest.TestCase):

    def test_to_country(self):
        self.assertEqual(utils.to_country(20047), 'NO')
        self.assertEqual(utils.to_country(21317), 'SE')
