from __future__ import unicode_literals

import collections
import unittest

import spotify
from spotify.playlist_container import _PlaylistContainerCallbacks
import tests
from tests import mock


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

    def setUp(self):
        self.session = tests.create_session_mock()
        spotify._session_instance = self.session

    def tearDown(self):
        spotify._session_instance = None

    def test_life_cycle(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)

        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer)

        lib_mock.sp_playlistcontainer_add_ref.assert_called_with(
            sp_playlistcontainer)
        lib_mock.sp_playlistcontainer_add_callbacks.assert_called_with(
            sp_playlistcontainer, mock.ANY, mock.ANY)

        playlist_container = None  # noqa
        tests.gc_collect()

        lib_mock.sp_playlistcontainer_remove_callbacks.assert_called_with(
            sp_playlistcontainer, mock.ANY, mock.ANY)
        # FIXME Won't be called because lib_mock has references to the
        # sp_playlistcontainer object, and it thus won't be GC-ed.
        # lib_mock.sp_playlistcontainer_release.assert_called_with(
        #     sp_playlistcontainer)

    def test_cached_container(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)

        result1 = spotify.PlaylistContainer._cached(
            self.session, sp_playlistcontainer)
        result2 = spotify.PlaylistContainer._cached(
            self.session, sp_playlistcontainer)

        self.assertIsInstance(result1, spotify.PlaylistContainer)
        self.assertIs(result1, result2)

    @mock.patch('spotify.User', spec=spotify.User)
    @mock.patch('spotify.Link', spec=spotify.Link)
    def test_repr(self, link_mock, user_mock, lib_mock):
        link_instance_mock = link_mock.return_value
        link_instance_mock.uri = 'foo'
        user_instance_mock = user_mock.return_value
        user_instance_mock.link = link_instance_mock
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 0
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = repr(playlist_container)

        self.assertEqual(result, 'PlaylistContainer([])')

    def test_eq(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container1 = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container2 = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        self.assertTrue(playlist_container1 == playlist_container2)
        self.assertFalse(playlist_container1 == 'foo')

    def test_ne(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container1 = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container2 = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        self.assertFalse(playlist_container1 != playlist_container2)

    def test_hash(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container1 = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container2 = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        self.assertEqual(hash(playlist_container1), hash(playlist_container2))

    def test_is_loaded(self, lib_mock):
        lib_mock.sp_playlistcontainer_is_loaded.return_value = 1
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container.is_loaded

        lib_mock.sp_playlistcontainer_is_loaded.assert_called_once_with(
            sp_playlistcontainer)
        self.assertTrue(result)

    @mock.patch('spotify.utils.load')
    def test_load(self, load_mock, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.load(10)

        load_mock.assert_called_with(
            self.session, playlist_container, timeout=10)

    def test_len(self, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 8
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = len(playlist_container)

        lib_mock.sp_playlistcontainer_num_playlists.assert_called_with(
            sp_playlistcontainer)
        self.assertEqual(result, 8)

    def test_len_if_undefined(self, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = -1
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = len(playlist_container)

        lib_mock.sp_playlistcontainer_num_playlists.assert_called_with(
            sp_playlistcontainer)
        self.assertEqual(result, 0)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_getitem(self, playlist_lib_mock, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 1
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist_type.return_value = int(
            spotify.PlaylistType.PLAYLIST)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container[0]

        lib_mock.sp_playlistcontainer_playlist.assert_called_with(
            sp_playlistcontainer, 0)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(sp_playlist)
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, sp_playlist)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_getitem_with_negative_index(self, playlist_lib_mock, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 1
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist_type.return_value = int(
            spotify.PlaylistType.PLAYLIST)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container[-1]

        lib_mock.sp_playlistcontainer_playlist.assert_called_with(
            sp_playlistcontainer, 0)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(sp_playlist)
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, sp_playlist)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_getitem_with_slice(self, playlist_lib_mock, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 3
        lib_mock.sp_playlistcontainer_playlist_type.side_effect = [
            int(spotify.PlaylistType.PLAYLIST),
            int(spotify.PlaylistType.PLAYLIST),
            int(spotify.PlaylistType.PLAYLIST)]
        sp_playlist1 = spotify.ffi.cast('sp_playlist *', 43)
        sp_playlist2 = spotify.ffi.cast('sp_playlist *', 44)
        sp_playlist3 = spotify.ffi.cast('sp_playlist *', 45)
        lib_mock.sp_playlistcontainer_playlist.side_effect = [
            sp_playlist1, sp_playlist2, sp_playlist3
        ]
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container[0:2]

        # Entire collection of length 3 is created as a list
        self.assertEqual(lib_mock.sp_playlistcontainer_playlist.call_count, 3)
        self.assertEqual(playlist_lib_mock.sp_playlist_add_ref.call_count, 3)

        # Only a subslice of length 2 is returned
        self.assertIsInstance(result, list)
        self.assertEqual(len(result), 2)
        self.assertEqual(result[0]._sp_playlist, sp_playlist1)
        self.assertEqual(result[1]._sp_playlist, sp_playlist2)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_getitem_with_folder(self, playlist_lib_mock, lib_mock):
        folder_name = 'foobar'

        lib_mock.sp_playlistcontainer_num_playlists.return_value = 3
        lib_mock.sp_playlistcontainer_playlist_type.side_effect = [
            int(spotify.PlaylistType.START_FOLDER),
            int(spotify.PlaylistType.PLAYLIST),
            int(spotify.PlaylistType.END_FOLDER)]
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        lib_mock.sp_playlistcontainer_playlist_folder_id.side_effect = [
            1001, 1002]
        lib_mock.sp_playlistcontainer_playlist_folder_name.side_effect = (
            tests.buffer_writer(folder_name))
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container[0]

        lib_mock.sp_playlistcontainer_playlist_type.assert_called_with(
            sp_playlistcontainer, 0)
        lib_mock.sp_playlistcontainer_playlist_folder_id.assert_called_with(
            sp_playlistcontainer, 0)
        self.assertIsInstance(result, spotify.PlaylistFolder)
        self.assertEqual(result.id, 1001)
        self.assertEqual(result.name, 'foobar')
        self.assertEqual(result.type, spotify.PlaylistType.START_FOLDER)

        result = playlist_container[1]

        lib_mock.sp_playlistcontainer_playlist_type.assert_called_with(
            sp_playlistcontainer, 1)
        lib_mock.sp_playlistcontainer_playlist.assert_called_with(
            sp_playlistcontainer, 1)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(sp_playlist)
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, sp_playlist)

        result = playlist_container[2]

        lib_mock.sp_playlistcontainer_playlist_type.assert_called_with(
            sp_playlistcontainer, 2)
        lib_mock.sp_playlistcontainer_playlist_folder_id.assert_called_with(
            sp_playlistcontainer, 2)
        self.assertIsInstance(result, spotify.PlaylistFolder)
        self.assertEqual(result.id, 1002)
        # self.assertEqual(result.name, '')  # Needs better mock impl
        self.assertEqual(result.type, spotify.PlaylistType.END_FOLDER)

    def test_getitem_with_placeholder(self, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 1
        lib_mock.sp_playlistcontainer_playlist_type.return_value = int(
            spotify.PlaylistType.PLACEHOLDER)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container[0]

        self.assertEqual(lib_mock.sp_playlistcontainer_playlist.call_count, 0)
        self.assertIsInstance(result, spotify.PlaylistPlaceholder)

    def test_getitem_raises_index_error_on_too_low_index(self, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 1
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

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

    def test_getitem_raises_index_error_on_too_high_index(self, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 1
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

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

    def test_getitem_raises_type_error_on_non_integral_index(self, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 1
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

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

    def test_setitem_with_playlist_name(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=5)
        playlist_container.remove_playlist = mock.Mock()
        playlist_container.add_new_playlist = mock.Mock()

        playlist_container[0] = 'New playlist'

        playlist_container.add_new_playlist.assert_called_with(
            'New playlist', index=0)
        playlist_container.remove_playlist.assert_called_with(1)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_setitem_with_existing_playlist(self, playlist_lib_mock, lib_mock):
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=5)
        playlist_container.remove_playlist = mock.Mock()
        playlist_container.add_playlist = mock.Mock()

        playlist_container[0] = playlist

        playlist_container.add_playlist.assert_called_with(playlist, index=0)
        playlist_container.remove_playlist.assert_called_with(1)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_setitem_with_slice(self, playlist_lib_mock, lib_mock):
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=5)
        playlist_container.remove_playlist = mock.Mock()
        playlist_container.add_new_playlist = mock.Mock()
        playlist_container.add_playlist = mock.Mock()

        playlist_container[0:2] = ['New playlist', playlist]

        playlist_container.add_new_playlist.assert_called_with(
            'New playlist', index=0)
        playlist_container.add_playlist.assert_called_with(playlist, index=1)
        playlist_container.remove_playlist.assert_has_calls(
            [mock.call(3), mock.call(2)], any_order=False)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_setittem_with_slice_and_noniterable_value_fails(
            self, playlist_lib_mock, lib_mock):

        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=3)
        playlist_container.remove_playlist = mock.Mock()
        playlist_container.add_new_playlist = mock.Mock()

        with self.assertRaises(TypeError):
            playlist_container[0:2] = playlist

    def test_setitem_raises_error_on_unknown_playlist_type(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)
        playlist_container.remove_playlist = mock.Mock()
        playlist_container.add_new_playlist = mock.Mock(side_effect=ValueError)

        with self.assertRaises(ValueError):
            playlist_container[0] = False

        playlist_container.add_new_playlist.assert_called_with(False, index=0)
        self.assertEqual(playlist_container.remove_playlist.call_count, 0)

    def test_setitem_raises_index_error_on_negative_index(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)

        with self.assertRaises(IndexError):
            playlist_container[-1] = None

    def test_setitem_raises_index_error_on_too_high_index(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)

        with self.assertRaises(IndexError):
            playlist_container[1] = None

    def test_setitem_raises_type_error_on_non_integral_index(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)

        with self.assertRaises(TypeError):
            playlist_container['abc'] = None

    def test_delitem(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)
        playlist_container.remove_playlist = mock.Mock()

        del playlist_container[0]

        playlist_container.remove_playlist.assert_called_with(0)

    def test_delitem_with_slice(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=3)
        playlist_container.remove_playlist = mock.Mock()

        del playlist_container[0:2]

        # Delete items in reverse order, so the indexes doesn't change
        playlist_container.remove_playlist.assert_has_calls(
            [mock.call(1), mock.call(0)], any_order=False)

    def test_delitem_raises_index_error_on_negative_index(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)

        with self.assertRaises(IndexError):
            del playlist_container[-1]

    def test_delitem_raises_index_error_on_too_high_index(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)

        with self.assertRaises(IndexError):
            del playlist_container[1]

    def test_delitem_raises_type_error_on_non_integral_index(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=1)

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

    def test_insert_with_playlist_name(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=5)
        playlist_container.remove_playlist = mock.Mock()
        playlist_container.add_new_playlist = mock.Mock()

        playlist_container.insert(3, 'New playlist')

        playlist_container.add_new_playlist.assert_called_with(
            'New playlist', index=3)

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_insert_with_existing_playlist(self, playlist_lib_mock, lib_mock):
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.__len__ = mock.Mock(return_value=5)
        playlist_container.remove_playlist = mock.Mock()
        playlist_container.add_playlist = mock.Mock()

        playlist_container.insert(3, playlist)

        playlist_container.add_playlist.assert_called_with(playlist, index=3)

    def test_is_a_sequence(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        self.assertIsInstance(playlist_container, collections.Sequence)

    def test_is_a_mutable_sequence(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        self.assertIsInstance(playlist_container, collections.MutableSequence)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_add_new_playlist_to_end_of_container(
            self, playlist_lib_mock, lib_mock):

        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_add_new_playlist.return_value = (
            sp_playlist)

        result = playlist_container.add_new_playlist('foo bar')

        lib_mock.sp_playlistcontainer_add_new_playlist.assert_called_with(
            sp_playlistcontainer, mock.ANY)
        self.assertEqual(
            spotify.ffi.string(
                lib_mock.sp_playlistcontainer_add_new_playlist
                .call_args[0][1]),
            b'foo bar')
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, sp_playlist)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(sp_playlist)
        self.assertEqual(
            lib_mock.sp_playlistcontainer_move_playlist.call_count, 0)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_add_new_playlist_at_given_index(
            self, playlist_lib_mock, lib_mock):

        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_add_new_playlist.return_value = (
            sp_playlist)
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 100
        lib_mock.sp_playlistcontainer_move_playlist.return_value = int(
            spotify.ErrorType.OK)

        result = playlist_container.add_new_playlist('foo bar', index=7)

        lib_mock.sp_playlistcontainer_add_new_playlist.assert_called_with(
            sp_playlistcontainer, mock.ANY)
        self.assertEqual(
            spotify.ffi.string(
                lib_mock.sp_playlistcontainer_add_new_playlist
                .call_args[0][1]),
            b'foo bar')
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, sp_playlist)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(sp_playlist)
        lib_mock.sp_playlistcontainer_move_playlist.assert_called_with(
            sp_playlistcontainer, 99, 7, 0)

    def test_add_new_playlist_fails_if_name_is_space_only(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(ValueError):
            playlist_container.add_new_playlist('   ')

        # Spotify seems to accept e.g. tab-only names, but it doesn't make any
        # sense to allow it, so we disallow names with all combinations of just
        # whitespace.
        with self.assertRaises(ValueError):
            playlist_container.add_new_playlist('\t\t')
        with self.assertRaises(ValueError):
            playlist_container.add_new_playlist('\r\r')
        with self.assertRaises(ValueError):
            playlist_container.add_new_playlist('\n\n')
        with self.assertRaises(ValueError):
            playlist_container.add_new_playlist(' \t\r\n')

    def test_add_new_playlist_fails_if_name_is_too_long(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(ValueError):
            playlist_container.add_new_playlist('x' * 300)

    def test_add_new_playlist_fails_if_operation_fails(self, lib_mock):
        lib_mock.sp_playlistcontainer_add_new_playlist.return_value = (
            spotify.ffi.NULL)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(spotify.Error):
            playlist_container.add_new_playlist('foo bar')

    @mock.patch('spotify.link.lib', lib=spotify.lib)
    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_add_playlist_from_link(
            self, playlist_lib_mock, link_lib_mock, lib_mock):
        sp_link = spotify.ffi.cast('sp_link *', 43)
        link = spotify.Link(self.session, sp_link=sp_link, add_ref=False)
        sp_playlist = spotify.ffi.cast('sp_playlist *', 44)
        lib_mock.sp_playlistcontainer_add_playlist.return_value = sp_playlist
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container.add_playlist(link)

        lib_mock.sp_playlistcontainer_add_playlist.assert_called_with(
            sp_playlistcontainer, sp_link)
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, sp_playlist)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(sp_playlist)
        self.assertEqual(
            lib_mock.sp_playlistcontainer_move_playlist.call_count, 0)

    @mock.patch('spotify.link.lib', lib=spotify.lib)
    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_add_playlist_from_playlist(
            self, playlist_lib_mock, link_lib_mock, lib_mock):
        sp_link = spotify.ffi.cast('sp_link *', 43)
        link = spotify.Link(self.session, sp_link=sp_link, add_ref=False)
        existing_playlist = mock.Mock(spec=spotify.Playlist)
        existing_playlist.link = link
        added_sp_playlist = spotify.ffi.cast('sp_playlist *', 44)
        lib_mock.sp_playlistcontainer_add_playlist.return_value = (
            added_sp_playlist)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container.add_playlist(existing_playlist)

        lib_mock.sp_playlistcontainer_add_playlist.assert_called_with(
            sp_playlistcontainer, sp_link)
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, added_sp_playlist)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(
            added_sp_playlist)
        self.assertEqual(
            lib_mock.sp_playlistcontainer_move_playlist.call_count, 0)

    @mock.patch('spotify.link.lib', lib=spotify.lib)
    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_add_playlist_at_given_index(
            self, playlist_lib_mock, link_lib_mock, lib_mock):
        sp_link = spotify.ffi.cast('sp_link *', 43)
        link = spotify.Link(self.session, sp_link=sp_link, add_ref=False)
        sp_playlist = spotify.ffi.cast('sp_playlist *', 44)
        lib_mock.sp_playlistcontainer_add_playlist.return_value = sp_playlist
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 100
        lib_mock.sp_playlistcontainer_move_playlist.return_value = int(
            spotify.ErrorType.OK)

        result = playlist_container.add_playlist(link, index=7)

        lib_mock.sp_playlistcontainer_add_playlist.assert_called_with(
            sp_playlistcontainer, sp_link)
        self.assertIsInstance(result, spotify.Playlist)
        self.assertEqual(result._sp_playlist, sp_playlist)
        playlist_lib_mock.sp_playlist_add_ref.assert_called_with(sp_playlist)
        lib_mock.sp_playlistcontainer_move_playlist.assert_called_with(
            sp_playlistcontainer, 99, 7, 0)

    @mock.patch('spotify.link.lib', lib=spotify.lib)
    def test_add_playlist_already_in_the_container(
            self, link_lib_mock, lib_mock):
        sp_link = spotify.ffi.cast('sp_link *', 43)
        link = spotify.Link(self.session, sp_link=sp_link, add_ref=False)
        lib_mock.sp_playlistcontainer_add_playlist.return_value = (
            spotify.ffi.NULL)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container.add_playlist(link)

        lib_mock.sp_playlistcontainer_add_playlist.assert_called_with(
            sp_playlistcontainer, sp_link)
        self.assertIsNone(result)
        self.assertEqual(
            lib_mock.sp_playlistcontainer_move_playlist.call_count, 0)

    def test_add_playlist_from_unknown_type_fails(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(TypeError):
            playlist_container.add_playlist(None)

    def test_add_folder(self, lib_mock):
        lib_mock.sp_playlistcontainer_add_folder.return_value = int(
            spotify.ErrorType.OK)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.add_folder('foo bar', index=3)

        lib_mock.sp_playlistcontainer_add_folder.assert_called_with(
            sp_playlistcontainer, 3, mock.ANY)
        self.assertEqual(
            spotify.ffi.string(
                lib_mock.sp_playlistcontainer_add_folder.call_args[0][2]),
            b'foo bar')

    def test_add_folder_without_index_adds_to_end(self, lib_mock):
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 7
        lib_mock.sp_playlistcontainer_add_folder.return_value = int(
            spotify.ErrorType.OK)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.add_folder('foo bar')

        lib_mock.sp_playlistcontainer_add_folder.assert_called_with(
            sp_playlistcontainer, 7, mock.ANY)
        self.assertEqual(
            spotify.ffi.string(
                lib_mock.sp_playlistcontainer_add_folder.call_args[0][2]),
            b'foo bar')

    def test_add_folder_out_of_range_fails(self, lib_mock):
        lib_mock.sp_playlistcontainer_add_folder.return_value = int(
            spotify.ErrorType.INDEX_OUT_OF_RANGE)

        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(spotify.Error):
            playlist_container.add_folder('foo bar', index=3)

    def test_add_folder_fails_if_name_is_space_only(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(ValueError):
            playlist_container.add_folder('   ')

        # Spotify seems to accept e.g. tab-only names, but it doesn't make any
        # sense to allow it, so we disallow names with all combinations of just
        # whitespace.
        with self.assertRaises(ValueError):
            playlist_container.add_folder('\t\t')
        with self.assertRaises(ValueError):
            playlist_container.add_folder('\r\r')
        with self.assertRaises(ValueError):
            playlist_container.add_folder('\n\n')
        with self.assertRaises(ValueError):
            playlist_container.add_folder(' \t\r\n')

    def test_add_folder_fails_if_name_is_too_long(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(ValueError):
            playlist_container.add_folder('x' * 300)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_remove_playlist(self, playlist_lib_mock, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 9
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        lib_mock.sp_playlistcontainer_playlist_type.return_value = int(
            spotify.PlaylistType.PLAYLIST)
        lib_mock.sp_playlistcontainer_remove_playlist.return_value = int(
            spotify.ErrorType.OK)

        playlist_container.remove_playlist(5)

        lib_mock.sp_playlistcontainer_remove_playlist.assert_called_with(
            sp_playlistcontainer, 5)

    @mock.patch('spotify.playlist.lib', lib=spotify.lib)
    def test_remove_playlist_out_of_range_fails(
            self, playlist_lib_mock, lib_mock):

        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 9
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        lib_mock.sp_playlistcontainer_playlist_type.return_value = int(
            spotify.PlaylistType.PLAYLIST)
        lib_mock.sp_playlistcontainer_remove_playlist.return_value = int(
            spotify.ErrorType.INDEX_OUT_OF_RANGE)

        with self.assertRaises(spotify.Error):
            playlist_container.remove_playlist(3)

    def test_remove_start_folder_removes_end_folder_too(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 3
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        lib_mock.sp_playlistcontainer_playlist_type.side_effect = [
            int(spotify.PlaylistType.START_FOLDER),
            int(spotify.PlaylistType.PLAYLIST),
            int(spotify.PlaylistType.END_FOLDER),
        ]
        lib_mock.sp_playlistcontainer_playlist_folder_id.side_effect = [
            173, 173]
        playlist_container._find_folder_indexes = lambda *a: [0, 2]
        lib_mock.sp_playlistcontainer_remove_playlist.return_value = int(
            spotify.ErrorType.OK)

        playlist_container.remove_playlist(0)

        lib_mock.sp_playlistcontainer_playlist_type.assert_called_with(
            sp_playlistcontainer, 0)
        lib_mock.sp_playlistcontainer_playlist_folder_id.assert_called_with(
            sp_playlistcontainer, 0)
        lib_mock.sp_playlistcontainer_remove_playlist.assert_has_calls([
            mock.call(sp_playlistcontainer, 2),
            mock.call(sp_playlistcontainer, 0),
        ], any_order=False)

    def test_remove_end_folder_removes_start_folder_too(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 3
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        lib_mock.sp_playlistcontainer_playlist_type.side_effect = [
            int(spotify.PlaylistType.START_FOLDER),
            int(spotify.PlaylistType.PLAYLIST),
            int(spotify.PlaylistType.END_FOLDER),
        ]
        lib_mock.sp_playlistcontainer_playlist_folder_id.side_effect = [
            173, 173]
        playlist_container._find_folder_indexes = lambda *a: [0, 2]
        lib_mock.sp_playlistcontainer_remove_playlist.return_value = int(
            spotify.ErrorType.OK)

        playlist_container.remove_playlist(2)

        lib_mock.sp_playlistcontainer_playlist_type.assert_called_with(
            sp_playlistcontainer, 2)
        lib_mock.sp_playlistcontainer_playlist_folder_id.assert_called_with(
            sp_playlistcontainer, 2)
        lib_mock.sp_playlistcontainer_remove_playlist.assert_has_calls([
            mock.call(sp_playlistcontainer, 2),
            mock.call(sp_playlistcontainer, 0),
        ], any_order=False)

    def test_remove_folder_with_everything_in_it(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        lib_mock.sp_playlistcontainer_num_playlists.return_value = 3
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        lib_mock.sp_playlistcontainer_playlist.return_value = sp_playlist
        lib_mock.sp_playlistcontainer_playlist_type.side_effect = [
            int(spotify.PlaylistType.START_FOLDER),
            int(spotify.PlaylistType.PLAYLIST),
            int(spotify.PlaylistType.END_FOLDER),
        ]
        lib_mock.sp_playlistcontainer_playlist_folder_id.side_effect = [
            173, 173]
        playlist_container._find_folder_indexes = lambda *a: [0, 1, 2]
        lib_mock.sp_playlistcontainer_remove_playlist.return_value = int(
            spotify.ErrorType.OK)

        playlist_container.remove_playlist(0, recursive=True)

        lib_mock.sp_playlistcontainer_playlist_type.assert_called_with(
            sp_playlistcontainer, 0)
        lib_mock.sp_playlistcontainer_playlist_folder_id.assert_called_with(
            sp_playlistcontainer, 0)
        lib_mock.sp_playlistcontainer_remove_playlist.assert_has_calls([
            mock.call(sp_playlistcontainer, 2),
            mock.call(sp_playlistcontainer, 1),
            mock.call(sp_playlistcontainer, 0),
        ], any_order=False)

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_find_folder_indexes(self, playlist_lib_mock, lib_mock):
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        playlists = [
            spotify.PlaylistFolder(
                173, 'foo', spotify.PlaylistType.START_FOLDER),
            playlist,
            spotify.PlaylistFolder(
                173, '', spotify.PlaylistType.END_FOLDER),
        ]

        result = spotify.PlaylistContainer._find_folder_indexes(
            playlists, 173, recursive=False)

        self.assertEqual(result, [0, 2])

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_find_folder_indexes_with_unknown_id(
            self, playlist_lib_mock, lib_mock):

        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        playlists = [
            spotify.PlaylistFolder(
                173, 'foo', spotify.PlaylistType.START_FOLDER),
            playlist,
            spotify.PlaylistFolder(
                173, '', spotify.PlaylistType.END_FOLDER),
        ]

        result = spotify.PlaylistContainer._find_folder_indexes(
            playlists, 174, recursive=False)

        self.assertEqual(result, [])

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_find_folder_indexes_recursive(self, playlist_lib_mock, lib_mock):
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        playlists = [
            spotify.PlaylistFolder(
                173, 'foo', spotify.PlaylistType.START_FOLDER),
            playlist,
            spotify.PlaylistFolder(
                173, '', spotify.PlaylistType.END_FOLDER),
        ]

        result = spotify.PlaylistContainer._find_folder_indexes(
            playlists, 173, recursive=True)

        self.assertEqual(result, [0, 1, 2])

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_find_folder_indexes_without_end(
            self, playlist_lib_mock, lib_mock):

        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        playlists = [
            spotify.PlaylistFolder(
                173, 'foo', spotify.PlaylistType.START_FOLDER),
            playlist,
        ]

        result = spotify.PlaylistContainer._find_folder_indexes(
            playlists, 173, recursive=True)

        self.assertEqual(result, [0])

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_find_folder_indexes_without_start(
            self, playlist_lib_mock, lib_mock):

        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        playlists = [
            playlist,
            spotify.PlaylistFolder(
                173, '', spotify.PlaylistType.END_FOLDER),
        ]

        result = spotify.PlaylistContainer._find_folder_indexes(
            playlists, 173, recursive=True)

        self.assertEqual(result, [1])

    def test_move_playlist(self, lib_mock):
        lib_mock.sp_playlistcontainer_move_playlist.return_value = int(
            spotify.ErrorType.OK)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.move_playlist(5, 7)

        lib_mock.sp_playlistcontainer_move_playlist.assert_called_with(
            sp_playlistcontainer, 5, 7, 0)

    def test_move_playlist_dry_run(self, lib_mock):
        lib_mock.sp_playlistcontainer_move_playlist.return_value = int(
            spotify.ErrorType.OK)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.move_playlist(5, 7, dry_run=True)

        lib_mock.sp_playlistcontainer_move_playlist.assert_called_with(
            sp_playlistcontainer, 5, 7, 1)

    def test_move_playlist_out_of_range_fails(self, lib_mock):
        lib_mock.sp_playlistcontainer_move_playlist.return_value = int(
            spotify.ErrorType.INDEX_OUT_OF_RANGE)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        with self.assertRaises(spotify.Error):
            playlist_container.move_playlist(5, 7)

    @mock.patch('spotify.User', spec=spotify.User)
    def test_owner(self, user_mock, lib_mock):
        user_mock.return_value = mock.sentinel.user
        sp_user = spotify.ffi.cast('sp_user *', 43)
        lib_mock.sp_playlistcontainer_owner.return_value = sp_user
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        result = playlist_container.owner

        lib_mock.sp_playlistcontainer_owner.assert_called_with(
            sp_playlistcontainer)
        user_mock.assert_called_with(
            self.session, sp_user=sp_user, add_ref=True)
        self.assertEqual(result, mock.sentinel.user)

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    @mock.patch('spotify.playlist_unseen_tracks.lib', spec=spotify.lib)
    def test_get_unseen_tracks(
            self, unseen_lib_mock, playlist_lib_mock, lib_mock):

        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        unseen_lib_mock.sp_playlistcontainer_get_unseen_tracks.return_value = 0

        result = playlist_container.get_unseen_tracks(playlist)

        self.assertIsInstance(result, spotify.PlaylistUnseenTracks)
        self.assertEqual(len(result), 0)

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_clear_unseen_tracks(self, playlist_lib_mock, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        lib_mock.sp_playlistcontainer_clear_unseen_tracks.return_value = 0

        playlist_container.clear_unseen_tracks(playlist)

        lib_mock.sp_playlistcontainer_clear_unseen_tracks.assert_called_with(
            sp_playlistcontainer, sp_playlist)

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_clear_unseen_tracks_raises_error_on_failure(
            self, playlist_lib_mock, lib_mock):

        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        sp_playlist = spotify.ffi.cast('sp_playlist *', 43)
        playlist = spotify.Playlist(self.session, sp_playlist=sp_playlist)
        lib_mock.sp_playlistcontainer_clear_unseen_tracks.return_value = -1

        with self.assertRaises(spotify.Error):
            playlist_container.clear_unseen_tracks(playlist)

    def test_first_on_call_adds_obj_emitters_list(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.on(
            spotify.PlaylistContainerEvent.PLAYLIST_ADDED, lambda *args: None)

        self.assertIn(playlist_container, self.session._emitters)

        playlist_container.off()

    def test_last_off_call_removes_obj_from_emitters_list(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.on(
            spotify.PlaylistContainerEvent.PLAYLIST_ADDED, lambda *args: None)
        playlist_container.off(
            spotify.PlaylistContainerEvent.PLAYLIST_ADDED)

        self.assertNotIn(playlist_container, self.session._emitters)

    def test_other_off_calls_keeps_obj_in_emitters_list(self, lib_mock):
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 42)
        playlist_container = spotify.PlaylistContainer(
            self.session, sp_playlistcontainer=sp_playlistcontainer)

        playlist_container.on(
            spotify.PlaylistContainerEvent.PLAYLIST_ADDED, lambda *args: None)
        playlist_container.on(
            spotify.PlaylistContainerEvent.PLAYLIST_MOVED, lambda *args: None)
        playlist_container.off(
            spotify.PlaylistContainerEvent.PLAYLIST_ADDED)

        self.assertIn(playlist_container, self.session._emitters)

        playlist_container.off(
            spotify.PlaylistContainerEvent.PLAYLIST_MOVED)

        self.assertNotIn(playlist_container, self.session._emitters)


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

    def setUp(self):
        self.session = tests.create_session_mock()
        spotify._session_instance = self.session

    def tearDown(self):
        spotify._session_instance = None

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_playlist_added_callback(self, playlist_lib_mock, lib_mock):
        callback = mock.Mock()
        sp_playlist = spotify.ffi.cast('sp_playlist *', 42)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 43)
        playlist_container = spotify.PlaylistContainer._cached(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.on(
            spotify.PlaylistContainerEvent.PLAYLIST_ADDED, callback)

        _PlaylistContainerCallbacks.playlist_added(
            sp_playlistcontainer, sp_playlist, 7, spotify.ffi.NULL)

        callback.assert_called_once_with(playlist_container, mock.ANY, 7)
        playlist = callback.call_args[0][1]
        self.assertIsInstance(playlist, spotify.Playlist)
        self.assertEqual(playlist._sp_playlist, sp_playlist)

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_playlist_removed_callback(self, playlist_lib_mock, lib_mock):
        callback = mock.Mock()
        sp_playlist = spotify.ffi.cast('sp_playlist *', 42)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 43)
        playlist_container = spotify.PlaylistContainer._cached(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.on(
            spotify.PlaylistContainerEvent.PLAYLIST_REMOVED, callback)

        _PlaylistContainerCallbacks.playlist_removed(
            sp_playlistcontainer, sp_playlist, 7, spotify.ffi.NULL)

        callback.assert_called_once_with(playlist_container, mock.ANY, 7)
        playlist = callback.call_args[0][1]
        self.assertIsInstance(playlist, spotify.Playlist)
        self.assertEqual(playlist._sp_playlist, sp_playlist)

    @mock.patch('spotify.playlist.lib', spec=spotify.lib)
    def test_playlist_moved_callback(self, playlist_lib_mock, lib_mock):
        callback = mock.Mock()
        sp_playlist = spotify.ffi.cast('sp_playlist *', 42)
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 43)
        playlist_container = spotify.PlaylistContainer._cached(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.on(
            spotify.PlaylistContainerEvent.PLAYLIST_MOVED, callback)

        _PlaylistContainerCallbacks.playlist_moved(
            sp_playlistcontainer, sp_playlist, 7, 13, spotify.ffi.NULL)

        callback.assert_called_once_with(playlist_container, mock.ANY, 7, 13)
        playlist = callback.call_args[0][1]
        self.assertIsInstance(playlist, spotify.Playlist)
        self.assertEqual(playlist._sp_playlist, sp_playlist)

    def test_container_loaded_callback(self, lib_mock):
        callback = mock.Mock()
        sp_playlistcontainer = spotify.ffi.cast('sp_playlistcontainer *', 43)
        playlist_container = spotify.PlaylistContainer._cached(
            self.session, sp_playlistcontainer=sp_playlistcontainer)
        playlist_container.on(
            spotify.PlaylistContainerEvent.CONTAINER_LOADED, callback)

        _PlaylistContainerCallbacks.container_loaded(
            sp_playlistcontainer, spotify.ffi.NULL)

        callback.assert_called_once_with(playlist_container)


class PlaylistFolderTest(unittest.TestCase):

    def test_id(self):
        folder = spotify.PlaylistFolder(
            id=123, name='foo', type=spotify.PlaylistType.START_FOLDER)

        self.assertEqual(folder.id, 123)

    def test_image(self):
        folder = spotify.PlaylistFolder(
            id=123, name='foo', type=spotify.PlaylistType.START_FOLDER)

        self.assertEqual(folder.name, 'foo')

    def test_type(self):
        folder = spotify.PlaylistFolder(
            id=123, name='foo', type=spotify.PlaylistType.START_FOLDER)

        self.assertEqual(folder.type, spotify.PlaylistType.START_FOLDER)


class PlaylistTypeTest(unittest.TestCase):

    def test_has_constants(self):
        self.assertEqual(spotify.PlaylistType.PLAYLIST, 0)
        self.assertEqual(spotify.PlaylistType.START_FOLDER, 1)
        self.assertEqual(spotify.PlaylistType.END_FOLDER, 2)
