#!/usr/bin/python3
#
# SoundConverter - GNOME application for converting between audio formats.
# Copyright 2004 Lars Wirzenius
# Copyright 2005-2025 Gautier Portet
# Copyright 2020-2025 Sezanzeb
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3 of the License.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

import os
import unittest
import urllib.parse

from soundconverter.interface.batch import (
    prepare_files_list,
    use_memory_gsettings,
    validate_args,
)
from soundconverter.util.fileoperations import vfs_exists
from soundconverter.util.settings import get_gio_settings, settings

cwd = os.getcwd()


class BatchUtils(unittest.TestCase):
    def setUp(self):
        self.maxDiff = None

    def tearDown(self):
        # tests may change the cwd
        os.chdir(cwd)

    def test_prepare_files_list(self):
        settings["recursive"] = True
        parsed_files, subdirectories = prepare_files_list(["tests/test data/audio/b/"])
        self.assertListEqual(
            parsed_files,
            ["file://" + os.path.realpath("tests/test%20data/audio/b/c.mp3")],
        )
        self.assertListEqual(subdirectories, ["b/"])

    def test_prepare_files_list_nonrecursive(self):
        settings["recursive"] = False
        parsed_files, subdirectories = prepare_files_list(["tests/test data/audio/b/"])
        self.assertEqual(len(parsed_files), 0)
        self.assertEqual(len(subdirectories), 0)

    def test_prepare_files_list_multiple(self):
        settings["recursive"] = True
        parsed_files, subdirectories = prepare_files_list(["tests/test data/audio/"])

        self.assertEqual(len(parsed_files), 3)
        self.assertIn(
            "file://" + os.path.realpath("tests/test%20data/audio/b/c.mp3"),
            parsed_files,
        )
        self.assertIn(
            "file://" + os.path.realpath("tests/test%20data/audio/a.wav"), parsed_files
        )
        self.assertIn(
            "file://"
            + urllib.parse.quote(
                os.path.realpath("tests/test data/audio/strângë chàrs фズ.wav")
            ),
            parsed_files,
        )

        self.assertEqual(len(subdirectories), 3)
        self.assertIn("audio/b/", subdirectories)
        self.assertIn("audio/", subdirectories)
        self.assertIn("audio/", subdirectories)

    def test_two_dirs(self):
        settings["recursive"] = True
        parsed_files, subdirectories = prepare_files_list(
            ["tests/test data/audio/", "tests/test data/empty/"]
        )
        expected_files = [
            "file://" + os.path.realpath("tests/test%20data/audio/b/c.mp3"),
            "file://" + os.path.realpath("tests/test%20data/audio/a.wav"),
            "file://"
            + urllib.parse.quote(
                os.path.realpath("tests/test data/audio/strângë chàrs фズ.wav")
            ),
            "file://" + os.path.realpath("tests/test%20data/empty/a"),
            "file://" + os.path.realpath("tests/test%20data/empty/b/c"),
        ]
        for expected_file in expected_files:
            # make sure expected_files are correctly written
            self.assertTrue(
                vfs_exists(expected_file), f"expected {expected_file} to exist"
            )

        expected_files.sort()
        parsed_files.sort()
        self.assertEqual(parsed_files, expected_files)

        expected_dirs = ["audio/b/", "audio/", "audio/", "empty/", "empty/b/"]
        expected_dirs.sort()
        subdirectories.sort()
        self.assertEqual(subdirectories, expected_dirs)

    def test_single_dir_depth_input(self):
        settings["recursive"] = True
        os.chdir("tests")
        # at some point this did not work, keep this spec even if it doesn't
        # appear to add value over the other tests
        parsed_files, subdirectories = prepare_files_list(["test data"])

        expected_files = [
            "file://" + os.path.realpath("test%20data/audio/b/c.mp3"),
            "file://" + os.path.realpath("test%20data/audio/a.wav"),
            "file://"
            + urllib.parse.quote(
                os.path.realpath("test data/audio/strângë chàrs фズ.wav")
            ),
            "file://" + os.path.realpath("test%20data/empty/a"),
            "file://" + os.path.realpath("test%20data/empty/b/c"),
            "file://" + os.path.realpath("test%20data/image.jpg"),
            "file://" + os.path.realpath("test%20data/a.iso"),
            "file://" + os.path.realpath("test%20data/no%20tags/no-tags.mp3"),
            "file://" + os.path.realpath("test%20data/no%20tags/no-tags.ogg"),
            "file://" + os.path.realpath("test%20data/no%20tags/no-tags.flac"),
        ]
        for expected_file in expected_files:
            # make sure expected_files are correctly written
            self.assertTrue(
                vfs_exists(expected_file), f"expected {expected_file} to exist"
            )

        expected_files.sort()
        parsed_files.sort()
        self.assertEqual(parsed_files, expected_files)

        expected_dirs = [
            "test data/audio/b/",
            "test data/audio/",
            "test data/audio/",  # duplicate, because there are two files in there
            "test data/empty/",
            "test data/empty/b/",
            "test data/",
            "test data/",
            "test data/no tags/",
            "test data/no tags/",
            "test data/no tags/",
        ]
        expected_dirs.sort()
        subdirectories.sort()
        self.assertEqual(subdirectories, expected_dirs)

    def test_absolute(self):
        settings["recursive"] = True
        parsed_files, subdirectories = prepare_files_list(
            [os.path.realpath("tests/test data")]
        )

        expected_files = [
            "file://" + os.path.realpath("tests/test%20data/audio/b/c.mp3"),
            "file://" + os.path.realpath("tests/test%20data/audio/a.wav"),
            "file://"
            + urllib.parse.quote(
                os.path.realpath("tests/test data/audio/strângë chàrs фズ.wav")
            ),
            "file://" + os.path.realpath("tests/test%20data/empty/a"),
            "file://" + os.path.realpath("tests/test%20data/empty/b/c"),
            "file://" + os.path.realpath("tests/test%20data/image.jpg"),
            "file://" + os.path.realpath("tests/test%20data/a.iso"),
            "file://" + os.path.realpath("tests/test%20data/no%20tags/no-tags.mp3"),
            "file://" + os.path.realpath("tests/test%20data/no%20tags/no-tags.ogg"),
            "file://" + os.path.realpath("tests/test%20data/no%20tags/no-tags.flac"),
        ]
        expected_files.sort()
        parsed_files.sort()
        self.assertEqual(parsed_files, expected_files)

        expected_dirs = [
            "test data/audio/b/",
            "test data/audio/",
            "test data/audio/",  # duplicate, because there are two files in there
            "test data/empty/",
            "test data/empty/b/",
            "test data/",
            "test data/",
            "test data/no tags/",
            "test data/no tags/",
            "test data/no tags/",
        ]
        expected_dirs.sort()
        subdirectories.sort()
        self.assertEqual(subdirectories, expected_dirs)

    def test_files(self):
        settings["recursive"] = True
        parsed_files, subdirectories = prepare_files_list(
            ["tests/test data/audio/b/c.mp3", "tests/test data/audio/a.wav"]
        )

        self.assertEqual(
            parsed_files,
            [
                "file://" + os.path.realpath("tests/test%20data/audio/b/c.mp3"),
                "file://" + os.path.realpath("tests/test%20data/audio/a.wav"),
            ],
        )

        self.assertEqual(subdirectories, ["", ""])

    def test_validate_args(self):
        # working example
        self.assertTrue(
            validate_args(
                {"main": "batch", "output-path": ".", "format": "mp3", "quality": 5}
            )
        )
        # input and output
        self.assertFalse(
            validate_args(
                {"main": "batch", "format": "mp3", "mode": "cbr", "quality": 192}
            )
        )
        self.assertFalse(
            validate_args(
                {
                    "main": "batch",
                    "output-path": "",
                    "format": "mp3",
                    "mode": "cbr",
                    "quality": 192,
                }
            )
        )
        self.assertFalse(
            validate_args(
                {
                    "main": "batch",
                    "output-path": ".",
                    "format": "mp3",
                    "quality": 5,
                    "existing": "abc",
                }
            )
        )
        self.assertTrue(
            validate_args(
                {
                    "main": "batch",
                    "output-path": ".",
                    "format": "mp3",
                    "quality": 5,
                    "existing": "overwrite",
                }
            )
        )
        # formats
        self.assertFalse(
            validate_args({"main": "batch", "output-path": ".", "quality": 5})
        )
        self.assertFalse(
            validate_args(
                {
                    "main": "batch",
                    "output-path": ".",
                    "format": "mp3",
                    "quality": 192,  # False because it defaults to vbr
                }
            )
        )
        self.assertFalse(
            validate_args(
                {
                    "main": "batch",
                    "output-path": ".",
                    "format": "mp3",
                    "mode": "vbr",
                    "quality": 192,
                }
            )
        )
        self.assertFalse(
            validate_args(
                {
                    "main": "batch",
                    "output-path": ".",
                    "format": "mp3",
                    "mode": "abr",
                    "quality": 3,
                }
            )
        )
        self.assertFalse(
            validate_args(
                {
                    "main": "batch",
                    "output-path": ".",
                    "format": "mp3",
                    "mode": "cbr",
                    "quality": 400,
                }
            )
        )
        self.assertFalse(
            validate_args(
                {"main": "batch", "output-path": ".", "format": "opus", "quality": 600}
            )
        )
        self.assertFalse(
            validate_args(
                {"main": "batch", "output-path": ".", "format": "wav", "quality": 13}
            )
        )
        self.assertFalse(
            validate_args(
                {"main": "batch", "output-path": ".", "format": "flac", "quality": 20}
            )
        )
        self.assertFalse(
            validate_args(
                {"main": "batch", "output-path": ".", "format": "ogg", "quality": 20}
            )
        )
        self.assertFalse(validate_args({"output-resample": 160}))

    def test_use_memory_gsettings_cbr(self):
        use_memory_gsettings(
            {
                "output-path": ".",
                "main": "batch",
                "format": "mp3",
                "mode": "cbr",
                "quality": "320",
            }
        )
        gio_settings = get_gio_settings()
        self.assertEqual(gio_settings.get_string("mp3-mode"), "cbr")
        self.assertEqual(gio_settings.get_string("output-mime-type"), "audio/mpeg")
        self.assertEqual(gio_settings.get_int("mp3-cbr-quality"), 320)

    def test_use_memory_gsettings_default_mp3_mode(self):
        use_memory_gsettings(
            {"output-path": ".", "main": "batch", "format": "mp3", "quality": "5"}
        )
        gio_settings = get_gio_settings()
        self.assertEqual(gio_settings.get_string("mp3-mode"), "vbr")
        self.assertEqual(gio_settings.get_string("output-mime-type"), "audio/mpeg")
        self.assertEqual(gio_settings.get_int("mp3-vbr-quality"), 5)

    def test_use_memory_gsettings_ogg(self):
        use_memory_gsettings(
            {"output-path": ".", "main": "batch", "format": "ogg", "quality": "0.5"}
        )
        gio_settings = get_gio_settings()
        self.assertEqual(gio_settings.get_string("output-mime-type"), "audio/x-vorbis")
        self.assertEqual(gio_settings.get_double("vorbis-quality"), 0.5)

    def test_set_delete_original_false(self):
        gio_settings = get_gio_settings()
        gio_settings.set_boolean("delete-original", True)
        use_memory_gsettings(
            {"output-path": ".", "main": "batch", "format": "ogg", "quality": "0.5"}
        )
        gio_settings = get_gio_settings()
        self.assertFalse(gio_settings.get_boolean("delete-original"))

    def test_set_delete_original_true(self):
        gio_settings = get_gio_settings()
        gio_settings.set_boolean("delete-original", False)
        use_memory_gsettings(
            {
                "output-path": ".",
                "main": "batch",
                "format": "ogg",
                "quality": "0.5",
                "delete-original": True,
            }
        )
        gio_settings = get_gio_settings()
        self.assertTrue(gio_settings.get_boolean("delete-original"))

    def test_set_output_sample(self):
        gio_settings = get_gio_settings()
        self.assertFalse(gio_settings.get_boolean("output-resample"))
        self.assertEqual(48000, gio_settings.get_int("resample-rate"))

        use_memory_gsettings(
            {
                "output-path": ".",
                "main": "batch",
                "format": "ogg",
                "quality": "0.5",
                "output-resample": 44100,
            }
        )

        gio_settings = get_gio_settings()
        self.assertTrue(gio_settings.get_boolean("output-resample"))
        self.assertEqual(44100, gio_settings.get_int("resample-rate"))


if __name__ == "__main__":
    unittest.main()
