# Copyright (C) 2013, 2014  Johannes Dewender
# Copyright (C) 2025, 2026  Philipp Wolfer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Please submit bug reports to GitHub:
# https://github.com/metabrainz/python-discid/issues
"""libdiscid dynamic loading code and constants

The code that works with Disc objects is in disc.py
"""

import ctypes
import os
import sys
from ctypes import CDLL, c_char_p, c_void_p
from ctypes.util import find_library

from discid.util import _decode

_LIB_BASE_NAME = "discid"
_LIB_MAJOR_VERSION = 0


def _find_library(name: str, version: int = 0) -> str:
    """Find a library by base-name and major version"""
    windows_names = [
        f"{name}.dll",
        f"lib{name}.dll",
        f"lib{name}-{version}.dll",
    ]

    lib_file = None

    # This seems to be necessary in a bundle/dmg
    if sys.platform == "darwin":
        lib_name = f"../Frameworks/lib{name}.{version}.dylib"
        if os.path.isfile(lib_name):
            lib_file = lib_name

    # force prefer current folder
    # for linux/UNIX-like and Windows
    if sys.platform in ["darwin", "cygwin"]:
        # these will already work fine with find_library
        pass
    elif sys.platform == "win32":
        for lib_name in windows_names:
            if os.path.isfile(lib_name):
                lib_file = lib_name
                break
    else:
        # that would be linux/UNIX-like
        # these need to prepend ./
        lib_name = f"./lib{name}.so.{version}"
        if os.path.isfile(lib_name):
            lib_file = lib_name

    # doesn't work on Windows
    # Darwin gives a full path when found system-wide
    # Linux and Cygwin give base filename when found
    # Darwin and Cygwin will find and prefer in current folder
    if lib_file is None:
        lib_file = find_library(name)

    # Windows needs complete filenames
    # and gives a full path when found system-wide
    # also searches in current folder, but system-wide preferred
    if lib_file is None and sys.platform == "win32":
        for lib_name in windows_names:
            if lib_file is None:
                lib_file = find_library(lib_name)

    if lib_file is None:
        # this won't help anymore,
        # but gives a nice platform dependent file in the error message
        if sys.platform == "win32":
            lib_file = f"{name}.dll"
        elif sys.platform == "darwin":
            lib_file = f"lib{name}.{version}.dylib"
        elif sys.platform == "cygwin":
            lib_file = f"cyg{name}-{version}.dll"
        else:
            lib_file = f"lib{name}.so.{version}"

    return lib_file


def _open_library(lib_name: str) -> CDLL:
    """Open a library by name or location"""
    try:
        return ctypes.cdll.LoadLibrary(lib_name)
    except OSError as exc:
        if lib_name not in str(exc):
            # replace uninformative Error on Windows
            raise OSError(f"could not find libdiscid: {lib_name}") from exc
        else:
            raise


_LIB_NAME = _find_library(_LIB_BASE_NAME, _LIB_MAJOR_VERSION)
_LIB = _open_library(_LIB_NAME)


try:
    _LIB.discid_get_version_string.argtypes = ()
    _LIB.discid_get_version_string.restype = c_char_p
except AttributeError:
    pass


def _get_version_string():
    """Get the version string of libdiscid"""
    try:
        version_string = _LIB.discid_get_version_string()
    except AttributeError:
        return "libdiscid < 0.4.0"
    else:
        return _decode(version_string)


_LIB.discid_get_default_device.argtypes = ()
_LIB.discid_get_default_device.restype = c_char_p


def get_default_device() -> str:
    """The default device to use for :func:`read` on this platform
    given as a :obj:`str <python:str>` object.
    """
    device = _LIB.discid_get_default_device()
    return _decode(device)


try:
    _LIB.discid_get_feature_list.argtypes = (c_void_p,)
    _LIB.discid_get_feature_list.restype = None
except AttributeError:
    _features_available = False
else:
    _features_available = True


def _get_features():
    """Get the supported features for the platform."""
    features: list[str] = []
    if _features_available:
        c_features = (c_char_p * 32)()
        _LIB.discid_get_feature_list(c_features)
        for feature in c_features:
            if feature:
                features.append(_decode(feature))
    else:
        # libdiscid <= 0.4.0
        features = ["read"]  # no generic platform yet
        try:
            # test for ISRC/MCN API (introduced 0.3.0)
            _LIB.discid_get_mcn  # noqa: B018 (useless-expression)
        except AttributeError:
            pass
        else:
            # ISRC/MCN API found -> libdiscid = 0.3.x
            if (
                sys.platform.startswith("linux")
                and not os.path.isfile("/usr/lib/libdiscid.so.0.3.0")
                and not os.path.isfile("/usr/lib64/libdiscid.so.0.3.0")
            ):
                features += ["mcn", "isrc"]
            elif sys.platform in ["darwin", "win32"]:
                features += ["mcn", "isrc"]

    return features


LIBDISCID_VERSION_STRING = _get_version_string()

FEATURES = _get_features()


# vim:set shiftwidth=4 smarttab expandtab:
