# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2016, Bruno Cauet
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

from __future__ import (division, absolute_import, print_function,
                        unicode_literals)

import os.path
from mock import Mock, patch, call
from tempfile import mkdtemp
from shutil import rmtree

from test._common import unittest
from test.helper import TestHelper

from beetsplug.thumbnails import (ThumbnailsPlugin, NORMAL_DIR, LARGE_DIR,
                                  write_metadata_im, write_metadata_pil,
                                  PathlibURI, GioURI)


class ThumbnailsTest(unittest.TestCase, TestHelper):
    def setUp(self):
        self.setup_beets()

    def tearDown(self):
        self.teardown_beets()

    @patch('beetsplug.thumbnails.util')
    def test_write_metadata_im(self, mock_util):
        metadata = {"a": "A", "b": "B"}
        write_metadata_im("foo", metadata)
        try:
            command = "convert foo -set a A -set b B foo".split(' ')
            mock_util.command_output.assert_called_once_with(command)
        except AssertionError:
            command = "convert foo -set b B -set a A foo".split(' ')
            mock_util.command_output.assert_called_once_with(command)

    @patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
    @patch('beetsplug.thumbnails.os.stat')
    def test_add_tags(self, mock_stat, _):
        plugin = ThumbnailsPlugin()
        plugin.write_metadata = Mock()
        plugin.get_uri = Mock(side_effect={b"/path/to/cover":
                                           "COVER_URI"}.__getitem__)
        album = Mock(artpath=b"/path/to/cover")
        mock_stat.return_value.st_mtime = 12345

        plugin.add_tags(album, b"/path/to/thumbnail")

        metadata = {"Thumb::URI": b"COVER_URI",
                    "Thumb::MTime": "12345"}
        plugin.write_metadata.assert_called_once_with(b"/path/to/thumbnail",
                                                      metadata)
        mock_stat.assert_called_once_with(album.artpath)

    @patch('beetsplug.thumbnails.os')
    @patch('beetsplug.thumbnails.ArtResizer')
    @patch('beetsplug.thumbnails.has_IM')
    @patch('beetsplug.thumbnails.has_PIL')
    @patch('beetsplug.thumbnails.GioURI')
    def test_check_local_ok(self, mock_giouri, mock_pil, mock_im,
                            mock_artresizer, mock_os):
        # test local resizing capability
        mock_artresizer.shared.local = False
        plugin = ThumbnailsPlugin()
        self.assertFalse(plugin._check_local_ok())

        # test dirs creation
        mock_artresizer.shared.local = True

        def exists(path):
            if path == NORMAL_DIR:
                return False
            if path == LARGE_DIR:
                return True
            raise ValueError("unexpected path {0!r}".format(path))
        mock_os.path.exists = exists
        plugin = ThumbnailsPlugin()
        mock_os.makedirs.assert_called_once_with(NORMAL_DIR)
        self.assertTrue(plugin._check_local_ok())

        # test metadata writer function
        mock_os.path.exists = lambda _: True
        mock_pil.return_value = False
        mock_im.return_value = False
        with self.assertRaises(AssertionError):
            ThumbnailsPlugin()

        mock_pil.return_value = True
        self.assertEqual(ThumbnailsPlugin().write_metadata, write_metadata_pil)

        mock_im.return_value = True
        self.assertEqual(ThumbnailsPlugin().write_metadata, write_metadata_im)

        mock_pil.return_value = False
        self.assertEqual(ThumbnailsPlugin().write_metadata, write_metadata_im)

        self.assertTrue(ThumbnailsPlugin()._check_local_ok())

        # test URI getter function
        giouri_inst = mock_giouri.return_value
        giouri_inst.available = True
        self.assertEqual(ThumbnailsPlugin().get_uri, giouri_inst.uri)

        giouri_inst.available = False
        self.assertEqual(ThumbnailsPlugin().get_uri.im_class, PathlibURI)

    @patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
    @patch('beetsplug.thumbnails.ArtResizer')
    @patch('beetsplug.thumbnails.util')
    @patch('beetsplug.thumbnails.os')
    @patch('beetsplug.thumbnails.shutil')
    def test_make_cover_thumbnail(self, mock_shutils, mock_os, mock_util,
                                  mock_artresizer, _):
        thumbnail_dir = os.path.normpath(b"/thumbnail/dir")
        md5_file = os.path.join(thumbnail_dir, b"md5")
        path_to_art = os.path.normpath(b"/path/to/art")

        mock_os.path.join = os.path.join  # don't mock that function
        plugin = ThumbnailsPlugin()
        plugin.add_tags = Mock()

        album = Mock(artpath=path_to_art)
        mock_util.syspath.side_effect = lambda x: x
        plugin.thumbnail_file_name = Mock(return_value="md5")
        mock_os.path.exists.return_value = False

        def os_stat(target):
            if target == md5_file:
                return Mock(st_mtime=1)
            elif target == path_to_art:
                return Mock(st_mtime=2)
            else:
                raise ValueError("invalid target {0}".format(target))
        mock_os.stat.side_effect = os_stat

        plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)

        mock_os.path.exists.assert_called_once_with(md5_file)
        mock_os.stat.has_calls([call(md5_file), call(path_to_art)],
                               any_order=True)

        resize = mock_artresizer.shared.resize
        resize.assert_called_once_with(12345, path_to_art, md5_file)
        plugin.add_tags.assert_called_once_with(album, resize.return_value)
        mock_shutils.move.assert_called_once_with(resize.return_value,
                                                  md5_file)

        # now test with recent thumbnail & with force
        mock_os.path.exists.return_value = True
        plugin.force = False
        resize.reset_mock()

        def os_stat(target):
            if target == md5_file:
                return Mock(st_mtime=3)
            elif target == path_to_art:
                return Mock(st_mtime=2)
            else:
                raise ValueError("invalid target {0}".format(target))
        mock_os.stat.side_effect = os_stat

        plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
        self.assertEqual(resize.call_count, 0)

        # and with force
        plugin.config['force'] = True
        plugin.make_cover_thumbnail(album, 12345, thumbnail_dir)
        resize.assert_called_once_with(12345, path_to_art, md5_file)

    @patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok')
    def test_make_dolphin_cover_thumbnail(self, _):
        plugin = ThumbnailsPlugin()
        tmp = mkdtemp()
        album = Mock(path=tmp,
                     artpath=os.path.join(tmp, b"cover.jpg"))
        plugin.make_dolphin_cover_thumbnail(album)
        with open(os.path.join(tmp, b".directory"), "rb") as f:
            self.assertEqual(
                f.read().splitlines(),
                [b"[Desktop Entry]", b"Icon=./cover.jpg"]
            )

        # not rewritten when it already exists (yup that's a big limitation)
        album.artpath = b"/my/awesome/art.tiff"
        plugin.make_dolphin_cover_thumbnail(album)
        with open(os.path.join(tmp, b".directory"), "rb") as f:
            self.assertEqual(
                f.read().splitlines(),
                [b"[Desktop Entry]", b"Icon=./cover.jpg"]
            )

        rmtree(tmp)