"""Lilv Python interface"""

# Copyright 2016-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: ISC

__author__ = "David Robillard"
__copyright__ = "Copyright 2016-2025 David Robillard"
__license__ = "ISC"
__version__ = "0.28.0"
__maintainer__ = "David Robillard"
__email__ = "d@drobilla.net"
__status__ = "Production"

import sys

from ctypes import Structure, CDLL, POINTER, CFUNCTYPE
from ctypes import c_bool, c_double, c_float, c_int, c_size_t, c_uint, c_uint32
from ctypes import c_char, c_char_p, c_void_p
from ctypes import byref, cast

# Option constants
OPTION_DYN_MANIFEST = "http://drobilla.net/ns/lilv#dyn-manifest"
OPTION_FILTER_LANG = "http://drobilla.net/ns/lilv#filter-lang"
OPTION_LANG = "http://drobilla.net/ns/lilv#lang"
OPTION_LV2_PATH = "http://drobilla.net/ns/lilv#lv2-path"
OPTION_OBJECT_INDEX = "http://drobilla.net/ns/lilv#object-index"


class _LilvLib:
    """Object that represents the liblilv C library"""

    def __init__(self):
        if sys.platform == "darwin":
            self.lib = CDLL("liblilv-0.dylib")
        elif sys.platform == "win32":
            self.lib = CDLL("lilv-0.dll")
        else:
            self.lib = CDLL("liblilv-0.so")


# Load lilv C library and define library global (which is populated below)
c = _LilvLib()


def _is_string(obj):
    if sys.version_info[0] == 3:
        return isinstance(obj, str)
    else:
        return isinstance(obj, basestring)


def _as_uri(world, obj):
    """Utility function for converting some object into a URI node"""
    if type(obj) in [Plugin, PluginClass, UI]:
        return obj.get_uri()

    if _is_string(obj):
        if len(obj) == 0:
            raise ValueError("empty string given where URI is required")

        return world.new_uri(obj)

    if type(obj) == Node:
        assert obj.node
        return Node(obj.world, c.node_duplicate(obj.node))

    raise ValueError("%s given where URI is required" % type(obj))


# LV2 types

LV2_Handle = POINTER(None)
LV2_URID_Map_Handle = POINTER(None)
LV2_URID_Unmap_Handle = POINTER(None)
LV2_URID = c_uint32


class LV2_Feature(Structure):
    __slots__ = ["URI", "data"]
    _fields_ = [("URI", c_char_p), ("data", POINTER(None))]


class LV2_Descriptor(Structure):
    __slots__ = [
        "URI",
        "instantiate",
        "connect_port",
        "activate",
        "run",
        "deactivate",
        "cleanup",
        "extension_data",
    ]


LV2_Descriptor._fields_ = [
    ("URI", c_char_p),
    (
        "instantiate",
        CFUNCTYPE(
            LV2_Handle,
            POINTER(LV2_Descriptor),
            c_double,
            c_char_p,
            POINTER(POINTER(LV2_Feature)),
        ),
    ),
    ("connect_port", CFUNCTYPE(None, LV2_Handle, c_uint32, POINTER(None))),
    ("activate", CFUNCTYPE(None, LV2_Handle)),
    ("run", CFUNCTYPE(None, LV2_Handle, c_uint32)),
    ("deactivate", CFUNCTYPE(None, LV2_Handle)),
    ("cleanup", CFUNCTYPE(None, LV2_Handle)),
    ("extension_data", CFUNCTYPE(c_void_p, c_char_p)),
]


class LV2_URID_Map(Structure):
    __slots__ = ["handle", "map"]
    _fields_ = [
        ("handle", LV2_URID_Map_Handle),
        ("map", CFUNCTYPE(LV2_URID, LV2_URID_Map_Handle, c_char_p)),
    ]


class LV2_URID_Unmap(Structure):
    __slots__ = ["handle", "unmap"]
    _fields_ = [
        ("handle", LV2_URID_Unmap_Handle),
        ("unmap", CFUNCTYPE(c_char_p, LV2_URID_Unmap_Handle, LV2_URID)),
    ]


# Lilv types


class Plugin(Structure):
    """LV2 Plugin."""

    @classmethod
    def wrap(cls, world, plugin):
        return Plugin(world, plugin) if world is not None and plugin else None

    def __init__(self, world, plugin):
        assert isinstance(world, World)
        assert type(plugin) == POINTER(Plugin)
        assert plugin

        self.world = world
        self.plugin = plugin

    def __eq__(self, other):
        return self.get_uri() == other.get_uri()

    def verify(self):
        """Check if `plugin` is valid.

        This is not a rigorous validator, but can be used to reject some
        malformed plugins that could cause bugs (e.g. plugins with missing
        required fields).

        Note that normal hosts do NOT need to use this - lilv does not load
        invalid plugins into plugin lists.  This is included for plugin testing
        utilities, etc.
        """
        return c.plugin_verify(self.plugin)

    def get_uri(self):
        """Get the URI of `plugin`.

        Any serialization that refers to plugins should refer to them by this.
        Hosts SHOULD NOT save any filesystem paths, plugin indexes, etc. in
        saved files pass save only the URI.

        The URI is a globally unique identifier for one specific plugin.  Two
        plugins with the same URI are compatible in port signature, and should
        be guaranteed to work in a compatible and consistent way.  If a plugin
        is upgraded in an incompatible way (eg if it has different ports), it
        MUST have a different URI than it's predecessor.
        """
        return Node.wrap(
            self.world, c.node_duplicate(c.plugin_get_uri(self.plugin))
        )

    def get_bundle_uri(self):
        """Get the (resolvable) URI of the plugin's "main" bundle.

        This returns the URI of the bundle where the plugin itself was found.
        Note that the data for a plugin may be spread over many bundles, that
        is, get_data_uris() may return URIs which are not within this bundle.

        Typical hosts should not need to use this function.  Note this always
        returns a fully qualified URI.  If you want a local filesystem path,
        use lilv.file_uri_parse().
        """
        return Node.wrap(
            self.world, c.node_duplicate(c.plugin_get_bundle_uri(self.plugin))
        )

    def get_data_uris(self):
        """Get the (resolvable) URIs of the RDF data files that define a plugin.

        Typical hosts should not need to use this function.  Note this always
        returns fully qualified URIs.  If you want local filesystem paths, use
        lilv.file_uri_parse().
        """
        return Nodes(self.world, c.plugin_get_data_uris(self.plugin), False)

    def get_library_uri(self):
        """Get the (resolvable) URI of the shared library for `plugin`.

        Note this always returns a fully qualified URI.  If you want a local
        filesystem path, use lilv.file_uri_parse().
        """
        return Node.wrap(
            self.world, c.node_duplicate(c.plugin_get_library_uri(self.plugin))
        )

    def get_name(self):
        """Get the name of `plugin`.

        This returns the name (doap:name) of the plugin.  The name may be
        translated according to the current locale, this value MUST NOT be used
        as a plugin identifier (use the URI for that).
        """
        return Node.wrap(self.world, c.plugin_get_name(self.plugin))

    def get_class(self):
        """Get the class this plugin belongs to (e.g. Filters)."""
        return PluginClass(self.world, c.plugin_get_class(self.plugin))

    def get_value(self, predicate):
        """Get a value associated with the plugin in a plugin's data files.

        `predicate` must be either a URI or a QName.

        Returns the ?object of all triples found of the form:

        plugin-uri predicate ?object

        May return None if the property was not found, or if object(s) is not
        sensibly represented as a LilvNodes (e.g. blank nodes).
        """
        p = _as_uri(self.world, predicate)
        return Nodes(self.world, c.plugin_get_value(self.plugin, p.node), True)

    def has_feature(self, feature_uri):
        """Return whether a feature is supported by a plugin.

        This will return true if the feature is an optional or required feature
        of the plugin.
        """
        f = _as_uri(self.world, feature_uri)
        return c.plugin_has_feature(self.plugin, f.node)

    def get_supported_features(self):
        """Get the LV2 Features supported (required or optionally) by a plugin.

        A feature is "supported" by a plugin if it is required OR optional.

        Since required features have special rules the host must obey, this
        function probably shouldn't be used by normal hosts.  Using
        get_optional_features() and get_required_features() separately is best
        in most cases.
        """
        return Nodes(
            self.world, c.plugin_get_supported_features(self.plugin), True
        )

    def get_required_features(self):
        """Get the LV2 Features required by a plugin.

        If a feature is required by a plugin, hosts MUST NOT use the plugin if
        they do not understand (or are unable to support) that feature.

        All values returned here MUST be return plugin_(self.plugin)ed to the
        plugin's instantiate method (along with data, if necessary, as defined
        by the feature specification) or plugin instantiation will fail.
        """
        return Nodes(
            self.world, c.plugin_get_required_features(self.plugin), True
        )

    def get_optional_features(self):
        """Get the LV2 Features optionally supported by a plugin.

        Hosts MAY ignore optional plugin features for whatever reasons.
        Plugins MUST operate (at least somewhat) if they are instantiated
        without being passed optional features.
        """
        return Nodes(
            self.world, c.plugin_get_optional_features(self.plugin), True
        )

    def has_extension_data(self, uri):
        """Return whether or not a plugin provides specific extension data."""
        u = _as_uri(self.world, uri)
        return c.plugin_has_extension_data(self.plugin, u.node)

    def get_extension_data(self):
        """Get a sequence of all extension data provided by a plugin.

        This can be used to find which URIs get_extension_data()
        will return a value for without instantiating the plugin.
        """
        return Nodes(
            self.world, c.plugin_get_extension_data(self.plugin), True
        )

    def get_num_ports(self):
        """Get the number of ports on this plugin."""
        return c.plugin_get_num_ports(self.plugin)

    def get_num_ports_of_class(self, *args):
        """Get the number of ports of some class(es) on this plugin."""
        classes = list(map(lambda n: _as_uri(self.world, n), args))
        c_nodes = list(map(lambda n: n.node, classes))
        return c.plugin_get_num_ports_of_class(
            self.plugin, *(c_nodes + [None])
        )

    def has_latency(self):
        """Return whether or not the plugin introduces (and reports) latency.

        The index of the latency port can be found with
        get_latency_port() ONLY if this function returns true.
        """
        return c.plugin_has_latency(self.plugin)

    def get_latency_port_index(self):
        """Return the index of the plugin's latency port.

        Returns None if the plugin has no latency port.

        Any plugin that introduces unwanted latency that should be compensated
        for (by hosts with the ability/need) MUST provide this port, which is a
        control rate output port that reports the latency for each cycle in
        frames.
        """
        return (
            c.plugin_get_latency_port_index(self.plugin)
            if self.has_latency()
            else None
        )

    def get_port(self, key):
        """Get a port on `plugin` by index or symbol."""
        if type(key) == int:
            return self.get_port_by_index(key)
        else:
            return self.get_port_by_symbol(key)

    def get_port_by_index(self, index):
        """Get a port on `plugin` by `index`."""
        assert type(index) == int
        return Port.wrap(self, c.plugin_get_port_by_index(self.plugin, index))

    def get_port_by_symbol(self, symbol):
        """Get a port on `plugin` by `symbol`.

        Note this function is slower than get_port_by_index(),
        especially on plugins with a very large number of ports.
        """
        assert _is_string(symbol) or isinstance(symbol, Node)
        if _is_string(symbol):
            symbol = self.world.new_string(symbol)

        assert isinstance(symbol, Node)
        assert symbol.node is not None
        return Port.wrap(
            self, c.plugin_get_port_by_symbol(self.plugin, symbol.node)
        )

    def get_port_by_designation(self, port_class, designation):
        """Get a port on `plugin` by its lv2:designation.

        The designation of a port describes the meaning, assignment, allocation
        or role of the port, e.g. "left channel" or "gain".  If found, the port
        with matching `port_class` and `designation` is be returned, otherwise
        None is returned.  The `port_class` can be used to distinguish the
        input and output ports for a particular designation.  If `port_class`
        is None, any port with the given designation will be returned.
        """
        pc = _as_uri(self.world, port_class)
        d = _as_uri(self.world, designation)
        return Port.wrap(
            self,
            c.plugin_get_port_by_designation(self.plugin, pc.node, d.node),
        )

    def get_project(self):
        """Get the project the plugin is a part of.

        More information about the project can be read via find_nodes(),
        typically using properties from DOAP (e.g. doap:name).
        """
        return Node.wrap(self.world, c.plugin_get_project(self.plugin))

    def get_author_name(self):
        """Get the full name of the plugin's author.

        Returns None if author name is not present.
        """
        return Node.wrap(self.world, c.plugin_get_author_name(self.plugin))

    def get_author_email(self):
        """Get the email address of the plugin's author.

        Returns None if author email address is not present.
        """
        return Node.wrap(self.world, c.plugin_get_author_email(self.plugin))

    def get_author_homepage(self):
        """Get the address of the plugin author's home page.

        Returns None if author homepage is not present.
        """
        return Node.wrap(self.world, c.plugin_get_author_homepage(self.plugin))

    def is_replaced(self):
        """Return true iff `plugin` has been replaced by another plugin.

        The plugin will still be usable, but hosts should hide them from their
        user interfaces to prevent users from using deprecated plugins.
        """
        return c.plugin_is_replaced(self.plugin)

    def get_related(self, resource_type):
        """Get the resources related to `plugin` with lv2:appliesTo.

        Some plugin-related resources are not linked directly to the plugin
        with rdfs:seeAlso and thus will not be automatically loaded along with
        the plugin data (usually for performance reasons).  All such resources
        of the given @c type related to `plugin` can be accessed with this
        function.

        If `resource_type` is None, all such resources will be returned,
        regardless of type.

        To actually load the data for each returned resource, use
        world.load_resource().
        """
        t = _as_uri(self.world, resource_type)
        return Nodes(
            self.world, c.plugin_get_related(self.plugin, t.node), True
        )

    def get_uis(self):
        """Get all UIs for `plugin`."""
        return UIs(self.world, c.plugin_get_uis(self.plugin))


class PluginClass(Structure):
    """Plugin Class (type/category)."""

    def __init__(self, world, plugin_class):
        assert isinstance(world, World)
        assert type(plugin_class) == POINTER(PluginClass)
        assert plugin_class

        self.world = world
        self.plugin_class = plugin_class

    def __str__(self):
        return self.get_uri().__str__()

    def get_parent_uri(self):
        """Get the URI of this class' superclass.

        May return None if class has no parent.
        """
        return Node.wrap(
            self.world,
            c.node_duplicate(c.plugin_class_get_parent_uri(self.plugin_class)),
        )

    def get_uri(self):
        """Get the URI of this plugin class."""
        return Node.wrap(
            self.world,
            c.node_duplicate(c.plugin_class_get_uri(self.plugin_class)),
        )

    def get_label(self):
        """Get the label of this plugin class, ie "Oscillators"."""
        return Node.wrap(
            self.world,
            c.node_duplicate(c.plugin_class_get_label(self.plugin_class)),
        )

    def get_children(self):
        """Get the subclasses of this plugin class."""
        return PluginClasses(
            self.world, c.plugin_class_get_children(self.plugin_class), True
        )


class Port(Structure):
    """Port on a Plugin."""

    @classmethod
    def wrap(cls, plugin, port):
        if plugin is not None and port:
            return Port(plugin, port)

        return None

    def __init__(self, plugin, port):
        assert isinstance(plugin, Plugin)
        assert type(port) == POINTER(Port)
        assert port

        self.plugin = plugin
        self.port = port

    def get_node(self):
        """Get the RDF node of `port`.

        Ports nodes may be may be URIs or blank nodes.
        """
        return Node.wrap(
            self.plugin.world,
            c.node_duplicate(c.port_get_node(self.plugin, self.port)),
        )

    def get_value(self, predicate):
        """Port analog of Plugin.get_value()."""
        p = _as_uri(self.plugin.world, predicate)
        return Nodes(
            self.plugin.world,
            c.port_get_value(self.plugin.plugin, self.port, p.node),
            True,
        )

    def get(self, predicate):
        """Get a single property value of a port.

        This is equivalent to lilv_nodes_get_first(lilv_port_get_value(...))
        but is simpler to use in the common case of only caring about one
        value.  The caller is responsible for freeing the returned node.
        """
        p = _as_uri(self.plugin.world, predicate)
        return Node.wrap(
            self.plugin.world,
            c.port_get(self.plugin.plugin, self.port, p.node),
        )

    def get_properties(self):
        """Return the LV2 port properties of a port."""
        return Nodes(
            self.plugin.world,
            c.port_get_properties(self.plugin.plugin, self.port),
            True,
        )

    def has_property(self, property_uri):
        """Return whether a port has a certain property."""
        p = _as_uri(self.plugin.world, property_uri)
        return c.port_has_property(self.plugin.plugin, self.port, p.node)

    def supports_event(self, event_type):
        """Return whether a port supports a certain event type.

        More precisely, this returns true iff the port has an atom:supports or
        an ev:supportsEvent property with `event_type` as the value.
        """
        t = _as_uri(self.plugin.world, event_type)
        return c.port_supports_event(self.plugin.plugin, self.port, t.node)

    def get_index(self):
        """Get the index of a port.

        The index is only valid for the life of the plugin and may change
        between versions.  For a stable identifier, use the symbol.
        """
        return c.port_get_index(self.plugin.plugin, self.port)

    def get_symbol(self):
        """Get the symbol of a port.

        The 'symbol' is a short string, a valid C identifier.
        """
        return Node.wrap(
            self.plugin.world,
            c.node_duplicate(c.port_get_symbol(self.plugin.plugin, self.port)),
        )

    def get_name(self):
        """Get the name of a port.

        This is guaranteed to return the untranslated name (the doap:name in
        the data file without a language tag).
        """
        return Node.wrap(
            self.plugin.world, c.port_get_name(self.plugin.plugin, self.port)
        )

    def get_classes(self):
        """Get all the classes of a port.

        This can be used to determine if a port is an input, output, audio,
        control, midi, etc, etc, though it's simpler to use is_a().
        The returned list does not include lv2:Port, which is implied.
        Returned value is shared and must not be destroyed by caller.
        """
        return Nodes(
            self.plugin.world,
            c.port_get_classes(self.plugin.plugin, self.port),
            False,
        )

    def is_a(self, port_class):
        """Determine if a port is of a given class (input, output, audio, etc).

        For convenience/performance/extensibility reasons, hosts are expected
        to create a LilvNode for each port class they "care about".  Well-known
        type URI strings are defined (e.g. LILV_URI_INPUT_PORT) for
        convenience, but this function is designed so that Lilv is usable with
        any port types without requiring explicit support in Lilv.
        """
        k = _as_uri(self.plugin.world, port_class)
        return c.port_is_a(self.plugin.plugin, self.port, k.node)

    def get_range(self):
        """Return the default, minimum, and maximum values of a port as a tuple.
        """
        pdef = POINTER(Node)()
        pmin = POINTER(Node)()
        pmax = POINTER(Node)()
        c.port_get_range(
            self.plugin.plugin,
            self.port,
            byref(pdef),
            byref(pmin),
            byref(pmax),
        )
        return (
            Node.wrap(self.plugin.world, pdef),
            Node.wrap(self.plugin.world, pmin),
            Node.wrap(self.plugin.world, pmax),
        )

    def get_scale_points(self):
        """Get a list of the scale points (enumeration values) of a port.

        This returns a collection of 'interesting' named values of a port
        (e.g. appropriate entries for a UI selector associated with this port).
        """

        cpoints = c.port_get_scale_points(self.plugin.plugin, self.port)
        points = []
        it = c.scale_points_begin(cpoints)
        while not c.scale_points_is_end(cpoints, it):
            points += [
                ScalePoint(self.plugin.world, c.scale_points_get(cpoints, it))
            ]
            it = c.scale_points_next(cpoints, it)

        c.scale_points_free(cpoints)
        return points


class ScalePoint(Structure):
    """Scale point (detent)."""

    def __init__(self, world, point):
        assert isinstance(world, World)
        assert type(point) == POINTER(ScalePoint)
        assert point

        self.label = Node.wrap(
            world, c.node_duplicate(c.scale_point_get_label(point))
        )
        self.value = Node.wrap(
            world, c.node_duplicate(c.scale_point_get_value(point))
        )

    def get_label(self):
        """Get the label of this scale point (enumeration value)."""
        return self.label

    def get_value(self):
        """Get the value of this scale point (enumeration value)."""
        return self.value


class UI(Structure):
    """Plugin UI."""

    def __init__(self, world, ui):
        assert isinstance(world, World)
        assert type(ui) == POINTER(UI)
        assert ui
        self.world = world
        self.ui = ui

    def __str__(self):
        return str(self.get_uri())

    def __eq__(self, other):
        if _is_string(other) or type(other) == Node:
            return self.get_uri() == other

        return self.get_uri() == other.get_uri()

    def get_uri(self):
        """Get the URI of a Plugin UI."""
        return Node.wrap(self.world, c.node_duplicate(c.ui_get_uri(self.ui)))

    def get_classes(self):
        """Get the types (URIs of RDF classes) of a Plugin UI.

        Note that in most cases is_supported() should be used, which avoids
           the need to use this function (and type specific logic).
        """
        return Nodes(self.world, c.ui_get_classes(self.ui), False)

    def is_a(self, class_uri):
        """Check whether a plugin UI has a given type."""
        u = _as_uri(self.world, class_uri)
        return c.ui_is_a(self.ui, u.node)

    def get_bundle_uri(self):
        """Get the URI of the UI's bundle."""
        return Node.wrap(
            self.world, c.node_duplicate(c.ui_get_bundle_uri(self.ui))
        )

    def get_binary_uri(self):
        """Get the URI for the UI's shared library."""
        return Node.wrap(
            self.world, c.node_duplicate(c.ui_get_binary_uri(self.ui))
        )


class Node(Structure):
    """Data node (URI, string, integer, etc.).

    A Node can be converted to the corresponding Python datatype, and all nodes
    can be converted to strings, for example::

       >>> world = lilv.World()
       >>> i = world.new_int(42)
       >>> print(i)
       42
       >>> int(i) * 2
       84
    """

    @classmethod
    def wrap(cls, world, node):
        assert isinstance(world, World)
        assert (node is None) or (type(node) == POINTER(Node))
        if node:
            return Node(world, node)

        return None

    def __init__(self, world, node):
        assert type(node) == POINTER(Node)
        assert node
        self.world = world
        self.node = node

    def __del__(self):
        # Note that since Python 3.4, cycles are deleted and the world can be
        # destroyed before nodes (which contain a pointer to it).  This causes
        # a crash, so we only free here if the world is still alive.  It does
        # not seem possible to enforce the right order (it happens even if
        # everything has a reference to the world), but this normally only
        # happens on exit anyway so it shouldn't matter much.
        if self.world.world:
            c.node_free(self.node)

    def __eq__(self, other):
        if other is None:
            return False

        otype = type(other)
        if otype == Node:
            return c.node_equals(self.node, other.node)

        return otype(self) == other

    def __ne__(self, other):
        return not c.node_equals(self.node, other.node)

    def __str__(self):
        return c.node_as_string(self.node).decode("utf-8")

    def __int__(self):
        if not self.is_int():
            raise ValueError("node %s is not an integer" % str(self))
        return c.node_as_int(self.node)

    def __float__(self):
        if not self.is_float():
            raise ValueError("node %s is not a float" % str(self))
        return c.node_as_float(self.node)

    def __bool__(self):
        if not self.is_bool():
            raise ValueError("node %s is not a bool" % str(self))
        return c.node_as_bool(self.node)

    __nonzero__ = __bool__

    def get_turtle_token(self):
        """Return this value as a Turtle/SPARQL token."""
        c_str = c.node_get_turtle_token(self.node)
        string = cast(c_str, c_char_p).value.decode("utf-8")
        c.free(c_str)
        return string

    def is_uri(self):
        """Return whether the value is a URI (resource)."""
        return c.node_is_uri(self.node)

    def is_blank(self):
        """Return whether the value is a blank node (resource with no URI)."""
        return c.node_is_blank(self.node)

    def is_literal(self):
        """Return whether this value is a literal (i.e. not a URI)."""
        return c.node_is_literal(self.node)

    def is_string(self):
        """Return whether this value is a string literal.

        Returns true if value is a string value (and not numeric).
        """
        return c.node_is_string(self.node)

    def get_path(self, hostname=None):
        """Return the path of a file URI node.

        Returns None if value is not a file URI."""
        c_str = c.node_get_path(self.node, hostname)
        string = cast(c_str, c_char_p).value.decode("utf-8")
        if sys.platform != "win32":  # TODO: Memory comes from libserd
            c.free(c_str)
        return string

    def is_float(self):
        """Return whether this value is a decimal literal."""
        return c.node_is_float(self.node)

    def is_int(self):
        """Return whether this value is an integer literal."""
        return c.node_is_int(self.node)

    def is_bool(self):
        """Return whether this value is a boolean."""
        return c.node_is_bool(self.node)


class Iter(Structure):
    """Collection iterator."""

    def __init__(
        self,
        collection,
        iterator,
        constructor,
        iter_get,
        iter_next,
        iter_is_end,
    ):
        assert isinstance(collection, Collection)

        self.collection = collection
        self.iterator = iterator
        self.constructor = constructor
        self.iter_get = iter_get
        self.iter_next = iter_next
        self.iter_is_end = iter_is_end

    def get(self):
        """Get the current item."""
        return self.constructor(
            self.collection.world,
            self.iter_get(self.collection.collection, self.iterator),
        )

    def next(self):
        """Move to and return the next item."""
        if self.is_end():
            raise StopIteration
        elem = self.get()
        self.iterator = self.iter_next(
            self.collection.collection, self.iterator
        )
        return elem

    def is_end(self):
        """Return true if the end of the collection has been reached."""
        return self.iter_is_end(self.collection.collection, self.iterator)

    __next__ = next


class Collection(Structure):
    # Base class for all lilv collection wrappers.
    def __init__(
        self,
        world,
        collection,
        iter_begin,
        constructor,
        iter_get,
        iter_next,
        is_end,
    ):
        assert isinstance(world, World)
        self.world = world
        self.collection = collection
        self.constructor = constructor
        self.iter_begin = iter_begin
        self.iter_get = iter_get
        self.iter_next = iter_next
        self.is_end = is_end

    def __iter__(self):
        return Iter(
            self,
            self.iter_begin(self.collection),
            self.constructor,
            self.iter_get,
            self.iter_next,
            self.is_end,
        )

    def __getitem__(self, index):
        pos = 0
        it = self.iter_begin(self.collection)

        while not self.is_end(self.collection, it):
            if pos == index:
                return self.constructor(
                    self.world, self.iter_get(self.collection, it)
                )

            it = self.iter_next(self.collection, it)
            pos = pos + 1

        raise IndexError(index)

    def begin(self):
        return self.__iter__()

    def get(self, iterator):
        return iterator.get()


class Plugins(Collection):
    """Collection of plugins."""

    def __init__(self, world, collection):
        assert type(collection) == POINTER(Plugins)
        assert collection

        def constructor(world, plugin):
            return Plugin.wrap(world, plugin)

        super(Plugins, self).__init__(
            world,
            collection,
            c.plugins_begin,
            constructor,
            c.plugins_get,
            c.plugins_next,
            c.plugins_is_end,
        )
        self.world = world

    def __contains__(self, key):
        return bool(self.get_by_uri(_as_uri(self.world, key)))

    def __len__(self):
        return c.plugins_size(self.collection)

    def __getitem__(self, key):
        if type(key) == int:
            return super(Plugins, self).__getitem__(key)

        plugin = self.get_by_uri(key)
        if plugin is None:
            raise KeyError("Plugin not found: " + str(key))

        return plugin

    def get_by_uri(self, uri):
        u = _as_uri(self.world, uri)
        return Plugin.wrap(
            self.world, c.plugins_get_by_uri(self.collection, u.node)
        )


class PluginClasses(Collection):
    """Collection of plugin classes."""

    def __init__(self, world, collection, owning=False):
        assert type(collection) == POINTER(PluginClasses)
        assert collection

        self.owning = owning
        super(PluginClasses, self).__init__(
            world,
            collection,
            c.plugin_classes_begin,
            PluginClass,
            c.plugin_classes_get,
            c.plugin_classes_next,
            c.plugin_classes_is_end,
        )

    def __del__(self):
        if self.owning:
            c.plugin_classes_free(self.collection)

    def __contains__(self, key):
        return bool(self.get_by_uri(_as_uri(self.world, key)))

    def __len__(self):
        return c.plugin_classes_size(self.collection)

    def __getitem__(self, key):
        if type(key) == int:
            return super(PluginClasses, self).__getitem__(key)

        klass = self.get_by_uri(key)
        if klass is None:
            raise KeyError("Plugin class not found: " + str(key))

        return klass

    def get_by_uri(self, uri):
        u = _as_uri(self.world, uri)

        plugin_class = c.plugin_classes_get_by_uri(self.collection, u.node)
        return PluginClass(self.world, plugin_class) if plugin_class else None


class ScalePoints(Structure):
    """Collection of scale points."""

    pass


class UIs(Collection):
    """Collection of plugin UIs."""

    def __init__(self, world, collection):
        assert type(collection) == POINTER(UIs)
        assert collection
        super(UIs, self).__init__(
            world,
            collection,
            c.uis_begin,
            UI,
            c.uis_get,
            c.uis_next,
            c.uis_is_end,
        )

    def __del__(self):
        if self.world.world:
            c.uis_free(self.collection)

    def __contains__(self, uri):
        return bool(self.get_by_uri(_as_uri(self.world, uri)))

    def __len__(self):
        return c.uis_size(self.collection)

    def __getitem__(self, key):
        if type(key) == int:
            return super(UIs, self).__getitem__(key)

        ui = self.get_by_uri(key)
        if ui is None:
            raise KeyError("Plugin UI not found: " + str(key))

        return ui

    def get_by_uri(self, uri):
        u = _as_uri(self.world, uri)
        ui = c.uis_get_by_uri(self.collection, u.node)
        return UI(self.world, ui) if ui else None


class Nodes(Collection):
    """Collection of data nodes."""

    @classmethod
    def constructor(cls, world, node):
        assert isinstance(world, World)
        assert type(node) == POINTER(Node)
        return Node.wrap(world, c.node_duplicate(node))

    def __init__(self, world, collection, owning=False):
        assert type(collection) == POINTER(Nodes)

        self.owning = owning
        super(Nodes, self).__init__(
            world,
            collection,
            c.nodes_begin,
            Nodes.constructor,
            c.nodes_get,
            c.nodes_next,
            c.nodes_is_end,
        )

    def __del__(self):
        if self.owning and self.world.world:
            c.nodes_free(self.collection)

    def __contains__(self, value):
        if type(value) in [Plugin, PluginClass, UI]:
            value = value.get_uri()

        return c.nodes_contains(self.collection, value.node)

    def __len__(self):
        return c.nodes_size(self.collection)

    def merge(self, b):
        return Nodes(
            self.world, c.nodes_merge(self.collection, b.collection), True
        )


class Namespace:
    """Namespace prefix.

    Use attribute syntax to easily create URIs within this namespace, for
    example::

       >>> world = lilv.World()
       >>> ns = Namespace(world, "http://example.org/")
       >>> print(ns.foo)
       http://example.org/foo
    """

    def __init__(self, world, prefix):
        assert isinstance(world, World)
        assert _is_string(prefix)

        self.world = world
        self.prefix = prefix

    def __add__(self, suffix):
        return self.world.new_uri(self.prefix + suffix)

    def __eq__(self, other):
        return str(self) == str(other)

    def __str__(self):
        return self.prefix

    def __coerce__(self, ignored):
        return None

    def __getattr__(self, suffix):
        return self.world.new_uri(self.prefix + suffix)


class Namespaces:
    """Set of namespaces.

    Use to easily construct uris, like: ns.lv2.InputPort"""

    def __init__(self, world):
        assert isinstance(world, World)
        self.world = world
        self.atom = Namespace(world, "http://lv2plug.in/ns/ext/atom#")
        self.doap = Namespace(world, "http://usefulinc.com/ns/doap#")
        self.foaf = Namespace(world, "http://xmlns.com/foaf/0.1/")
        self.lilv = Namespace(world, "http://drobilla.net/ns/lilv#")
        self.lv2 = Namespace(world, "http://lv2plug.in/ns/lv2core#")
        self.midi = Namespace(world, "http://lv2plug.in/ns/ext/midi#")
        self.owl = Namespace(world, "http://www.w3.org/2002/07/owl#")
        self.rdf = Namespace(
            world, "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        )
        self.rdfs = Namespace(world, "http://www.w3.org/2000/01/rdf-schema#")
        self.ui = Namespace(world, "http://lv2plug.in/ns/extensions/ui#")
        self.xsd = Namespace(world, "http://www.w3.org/2001/XMLSchema#")


class World(Structure):
    """Library context.

    Includes a set of namespaces as the instance variable `ns`, so URIs can be
    constructed like::

        uri = world.ns.lv2.Plugin

    Common LV2 namespace prefixes: atom, doap, foaf, lilv, lv2, midi, owl, rdf,
    rdfs, ui, xsd.
    """

    def __init__(self):
        self.world = c.world_new()
        self.ns = Namespaces(self)

    def __del__(self):
        c.world_free(self.world)
        self.world = None

    def set_option(self, uri, value):
        """Set a world option.

        Currently recognized options:
        lilv.OPTION_DYN_MANIFEST
        lilv.OPTION_FILTER_LANG
        lilv.OPTION_LANG
        lilv.OPTION_LV2_PATH
        lilv.OPTION_OBJECT_INDEX
        """
        return c.world_set_option(self.world, uri, value.node)

    def load_all(self):
        """Load all installed LV2 bundles on the system.

        This is the recommended way for hosts to load LV2 data.  It implements
        the established/standard best practice for discovering all LV2 data on
        the system.  The environment variable LV2_PATH may be used to control
        where this function will look for bundles.

        Hosts should use this function rather than explicitly load bundles,
        except in special circumstances (e.g. development utilities, or hosts
        that ship with special plugin bundles which are installed to a known
        location).
        """
        c.world_load_all(self.world)

    def load_bundle(self, bundle_uri):
        """Load a specific bundle.

        `bundle_uri` must be a fully qualified URI to the bundle directory,
        with the trailing slash, eg. file:///usr/lib/lv2/foo.lv2/

        Normal hosts should not need this function (use load_all()).

        Hosts MUST NOT attach any long-term significance to bundle paths
        (e.g. in save files), since there are no guarantees they will remain
        unchanged between (or even during) program invocations. Plugins (among
        other things) MUST be identified by URIs (not paths) in save files.
        """
        u = _as_uri(self, bundle_uri)
        c.world_load_bundle(self.world, u.node)

    def load_specifications(self):
        """Load all specifications from currently loaded bundles.

        This is for hosts that explicitly load specific bundles, its use is not
        necessary when using load_all().  This function parses the
        specifications and adds them to the model.
        """
        c.world_load_specifications(self.world)

    def load_plugin_classes(self):
        """Load all plugin classes from currently loaded specifications.

        Must be called after load_specifications().  This is for hosts that
        explicitly load specific bundles, its use is not necessary when using
        load_all().
        """
        c.world_load_plugin_classes(self.world)

    def unload_bundle(self, bundle_uri):
        """Unload a specific bundle.

        This unloads statements loaded by load_bundle().  Note that this is not
        necessarily all information loaded from the bundle.  If any resources
        have been separately loaded with load_resource(), they must be
        separately unloaded with unload_resource().
        """
        u = _as_uri(self, bundle_uri)
        return c.world_unload_bundle(self.world, u.node)

    def load_resource(self, resource):
        """Load all the data associated with the given `resource`.

        The resource parameter must be a URI.
        Returns the number of files parsed, or -1 on error.

        All accessible data files linked to `resource` with rdfs:seeAlso will
        be loaded into the world model.
        """
        uri = _as_uri(self, resource)
        ret = c.world_load_resource(self.world, uri.node)
        return ret

    def unload_resource(self, resource):
        """Unload all the data associated with the given `resource`.

        The resource must be a subject (i.e. a URI or a blank node).

        This unloads all data loaded by a previous call to
        load_resource() with the given `resource`.
        """
        uri = _as_uri(self, resource)
        ret = c.world_unload_resource(self.world, uri.node)
        return ret

    def get_plugin_class(self):
        """Get the parent of all other plugin classes, lv2:Plugin."""
        return PluginClass(self, c.world_get_plugin_class(self.world))

    def get_plugin_classes(self):
        """Return a list of all found plugin classes."""
        return PluginClasses(self, c.world_get_plugin_classes(self.world))

    def get_all_plugins(self):
        """Return a list of all found plugins.

        The returned list contains just enough references to query
        or instantiate plugins.  The data for a particular plugin will not be
        loaded into memory until a call to an lilv_plugin_* function results in
        a query (at which time the data is cached with the LilvPlugin so future
        queries are very fast).

        The returned list and the plugins it contains are owned by `world`
        and must not be freed by caller.
        """
        return Plugins(self, c.world_get_all_plugins(self.world))

    def find_nodes(self, subject, predicate, obj):
        """Find nodes matching a triple pattern.

        Either `subject` or `object` may be None (i.e. a wildcard), but not
        both.  Returns all matches for the wildcard field, or None.
        """
        return Nodes(
            self,
            c.world_find_nodes(
                self.world,
                subject.node if subject is not None else None,
                predicate.node if predicate is not None else None,
                obj.node if obj is not None else None,
            ),
            True,
        )

    def get(self, subject, predicate, obj):
        """Find a single node that matches a pattern.

        Exactly one of `subject`, `predicate`, `object` must be None.

        Returns the first matching node, or None if no matches are found.
        """
        return Node.wrap(
            self,
            c.world_get(
                self.world,
                subject.node if subject is not None else None,
                predicate.node if predicate is not None else None,
                obj.node if obj is not None else None,
            ),
        )

    def ask(self, subject, predicate, obj):
        """Return true iff a statement matching a certain pattern exists.

        This is useful for checking if particular statement exists without
        having to bother with collections and memory management.
        """
        return c.world_ask(
            self.world,
            subject.node if subject is not None else None,
            predicate.node if predicate is not None else None,
            obj.node if obj is not None else None,
        )

    def get_symbol(self, subject):
        """Get an LV2 symbol for some subject.

        This will return the lv2:symbol property of the subject if it is given
        explicitly, and otherwise will attempt to derive a symbol from the URI.

        Returns a string, which is possibly empty on error.
        """
        if isinstance(subject, Port):
            return subject.get_symbol()

        uri = _as_uri(self, subject)
        ret = ""
        if uri is not None:
            node = c.world_get_symbol(self.world, uri.node)
            ret = c.node_as_string(node).decode("ascii") if node else ""
            c.node_free(node)

        return ret

    def new_uri(self, uri):
        """Create a new URI node."""
        c_node = c.new_uri(self.world, uri)
        if not c_node:
            raise ValueError("invalid URI '%s'" % uri)

        return Node.wrap(self, c_node)

    def new_file_uri(self, host, path):
        """Create a new file URI node.  The host may be None."""
        return Node.wrap(self, c.new_file_uri(self.world, host, path))

    def new_string(self, string):
        """Create a new string node."""
        return Node.wrap(self, c.new_string(self.world, string))

    def new_int(self, val):
        """Create a new int node."""
        return Node.wrap(self, c.new_int(self.world, val))

    def new_float(self, val):
        """Create a new float node."""
        return Node.wrap(self, c.new_float(self.world, val))

    def new_bool(self, val):
        """Create a new bool node."""
        return Node.wrap(self, c.new_bool(self.world, val))


class Instance(Structure):
    """Plugin instance."""

    __slots__ = [
        "lv2_descriptor",
        "lv2_handle",
        "pimpl",
        "plugin",
        "rate",
        "instance",
    ]
    _fields_ = [
        ("lv2_descriptor", POINTER(LV2_Descriptor)),
        ("lv2_handle", LV2_Handle),
        ("pimpl", POINTER(None)),
    ]

    def __init__(self, plugin, rate, features=None):
        assert isinstance(plugin, Plugin)
        self.plugin = plugin
        self.rate = rate
        self.instance = c.plugin_instantiate(plugin.plugin, rate, features)

    def __del__(self):
        if hasattr(self, "instance"):
            c.instance_free(self.instance[0])

    def get_uri(self):
        """Get the URI of the plugin which `instance` is an instance of.

           Returned string is shared and must not be modified or deleted.
        """
        return self.get_descriptor().URI.decode("utf-8")

    def connect_port(self, port_index, data):
        """Connect a port to a data location.

           This may be called regardless of whether the plugin is activated,
           activation and deactivation does not destroy port connections.
        """
        import numpy

        if data is None:
            self.get_descriptor().connect_port(
                self.get_handle(), port_index, data
            )
        elif isinstance(data, Structure):
            self.get_descriptor().connect_port(
                self.get_handle(), port_index, cast(byref(data), c_void_p)
            )
        elif type(data) == numpy.ndarray:
            self.get_descriptor().connect_port(
                self.get_handle(),
                port_index,
                data.ctypes.data_as(POINTER(c_float)),
            )
        else:
            raise Exception("Unsupported data type")

    def activate(self):
        """Activate a plugin instance.

        This resets all state information in the plugin, except for port data
        locations (as set by connect_port()).  This MUST be called before
        calling run().
        """
        if self.get_descriptor().activate:
            self.get_descriptor().activate(self.get_handle())

    def run(self, sample_count):
        """Run `instance` for `sample_count` frames.

        If the hint lv2:hardRTCapable is set for this plugin, this function is
        guaranteed not to block.
        """
        self.get_descriptor().run(self.get_handle(), sample_count)

    def deactivate(self):
        """Deactivate a plugin instance.

        Note that to run the plugin after this you must activate it, which will
        reset all state information (except port connections).
        """
        if self.get_descriptor().deactivate:
            self.get_descriptor().deactivate(self.get_handle())

    def get_extension_data(self, uri):
        """Get extension data from the plugin instance.

        The type and semantics of the data returned is specific to the
        particular extension, though in all cases it is shared and must not be
        deleted.
        """
        if self.get_descriptor().extension_data:
            return self.get_descriptor().extension_data(
                str(uri).encode("utf-8")
            )

    def get_descriptor(self):
        """Get the LV2_Descriptor of the plugin instance.

        Normally hosts should not need to access the LV2_Descriptor directly,
        use the lilv_instance_* functions.
        """
        return self.instance[0].lv2_descriptor[0]

    def get_handle(self):
        """Get the LV2_Handle of the plugin instance.

        Normally hosts should not need to access the LV2_Handle directly, use
        the lilv_instance_* functions.
        """
        return self.instance[0].lv2_handle


class State(Structure):
    """Plugin state (TODO)."""

    pass


class VariadicFunction(object):
    # Wrapper for calling C variadic functions
    def __init__(self, function, restype, argtypes):
        self.function = function
        self.function.restype = restype
        self.argtypes = argtypes

    def __call__(self, *args):
        fixed_args = []
        i = 0
        for argtype in self.argtypes:
            fixed_args.append(argtype.from_param(args[i]))
            i += 1
        return self.function(*(fixed_args + list(args[i:])))


# Set up C bindings


class String(str):
    # Wrapper for string parameters to pass as raw C UTF-8 strings
    def from_param(cls, obj):
        assert _is_string(obj)
        return obj.encode("utf-8")

    from_param = classmethod(from_param)


def _cfunc(name, restype, *argtypes):
    """Set the `name` attribute of the `c` global to a C function"""
    assert isinstance(c, _LilvLib)
    f = getattr(c.lib, "lilv_" + name)
    f.restype = restype
    f.argtypes = argtypes
    setattr(c, name, f)


def P(x):
    """Shorthand for ctypes.POINTER"""
    return POINTER(x)


_cfunc("free", None, c_void_p)

# Node

_cfunc("file_uri_parse", c_char_p, String, P(c_char_p))
_cfunc("new_uri", P(Node), P(World), String)
_cfunc("new_file_uri", P(Node), P(World), c_char_p, String)
_cfunc("new_string", P(Node), P(World), String)
_cfunc("new_int", P(Node), P(World), c_int)
_cfunc("new_float", P(Node), P(World), c_float)
_cfunc("new_bool", P(Node), P(World), c_bool)
_cfunc("node_free", None, P(Node))
_cfunc("node_duplicate", P(Node), P(Node))
_cfunc("node_equals", c_bool, P(Node), P(Node))
_cfunc("node_get_turtle_token", P(c_char), P(Node))
_cfunc("node_is_uri", c_bool, P(Node))
_cfunc("node_as_uri", c_char_p, P(Node))
_cfunc("node_is_blank", c_bool, P(Node))
_cfunc("node_as_blank", c_char_p, P(Node))
_cfunc("node_is_literal", c_bool, P(Node))
_cfunc("node_is_string", c_bool, P(Node))
_cfunc("node_as_string", c_char_p, P(Node))
_cfunc("node_get_path", P(c_char), P(Node), P(P(c_char)))
_cfunc("node_is_float", c_bool, P(Node))
_cfunc("node_as_float", c_float, P(Node))
_cfunc("node_is_int", c_bool, P(Node))
_cfunc("node_as_int", c_int, P(Node))
_cfunc("node_is_bool", c_bool, P(Node))
_cfunc("node_as_bool", c_bool, P(Node))

# Collections

_cfunc("plugin_classes_free", None, P(PluginClasses))
_cfunc("plugin_classes_size", c_uint, P(PluginClasses))
_cfunc("plugin_classes_begin", P(Iter), P(PluginClasses))
_cfunc("plugin_classes_get", P(PluginClass), P(PluginClasses), P(Iter))
_cfunc("plugin_classes_next", P(Iter), P(PluginClasses), P(Iter))
_cfunc("plugin_classes_is_end", c_bool, P(PluginClasses), P(Iter))
_cfunc("plugin_classes_get_by_uri", P(PluginClass), P(PluginClasses), P(Node))
_cfunc("scale_points_free", None, P(ScalePoints))
_cfunc("scale_points_size", c_uint, P(ScalePoints))
_cfunc("scale_points_begin", P(Iter), P(ScalePoints))
_cfunc("scale_points_get", P(ScalePoint), P(ScalePoints), P(Iter))
_cfunc("scale_points_next", P(Iter), P(ScalePoints), P(Iter))
_cfunc("scale_points_is_end", c_bool, P(ScalePoints), P(Iter))
_cfunc("uis_free", None, P(UIs))
_cfunc("uis_size", c_uint, P(UIs))
_cfunc("uis_begin", P(Iter), P(UIs))
_cfunc("uis_get", P(UI), P(UIs), P(Iter))
_cfunc("uis_next", P(Iter), P(UIs), P(Iter))
_cfunc("uis_is_end", c_bool, P(UIs), P(Iter))
_cfunc("uis_get_by_uri", P(UI), P(UIs), P(Node))
_cfunc("nodes_free", None, P(Nodes))
_cfunc("nodes_size", c_uint, P(Nodes))
_cfunc("nodes_begin", P(Iter), P(Nodes))
_cfunc("nodes_get", P(Node), P(Nodes), P(Iter))
_cfunc("nodes_next", P(Iter), P(Nodes), P(Iter))
_cfunc("nodes_is_end", c_bool, P(Nodes), P(Iter))
_cfunc("nodes_get_first", P(Node), P(Nodes))
_cfunc("nodes_contains", c_bool, P(Nodes), P(Node))
_cfunc("nodes_merge", P(Nodes), P(Nodes), P(Nodes))
_cfunc("plugins_size", c_uint, P(Plugins))
_cfunc("plugins_begin", P(Iter), P(Plugins))
_cfunc("plugins_get", P(Plugin), P(Plugins), P(Iter))
_cfunc("plugins_next", P(Iter), P(Plugins), P(Iter))
_cfunc("plugins_is_end", c_bool, P(Plugins), P(Iter))
_cfunc("plugins_get_by_uri", P(Plugin), P(Plugins), P(Node))

# World

_cfunc("world_new", P(World))
_cfunc("world_set_option", None, P(World), String, P(Node))
_cfunc("world_free", None, P(World))
_cfunc("world_load_all", None, P(World))
_cfunc("world_load_bundle", None, P(World), P(Node))
_cfunc("world_load_specifications", None, P(World))
_cfunc("world_load_plugin_classes", None, P(World))
_cfunc("world_unload_bundle", c_int, P(World), P(Node))
_cfunc("world_load_resource", c_int, P(World), P(Node))
_cfunc("world_unload_resource", c_int, P(World), P(Node))
_cfunc("world_get_plugin_class", P(PluginClass), P(World))
_cfunc("world_get_plugin_classes", P(PluginClasses), P(World))
_cfunc("world_get_all_plugins", P(Plugins), P(World))
_cfunc("world_find_nodes", P(Nodes), P(World), P(Node), P(Node), P(Node))
_cfunc("world_get", P(Node), P(World), P(Node), P(Node), P(Node))
_cfunc("world_ask", c_bool, P(World), P(Node), P(Node), P(Node))
_cfunc("world_get_symbol", P(Node), P(World), P(Node))

# Plugin

_cfunc("plugin_verify", c_bool, P(Plugin))
_cfunc("plugin_get_uri", P(Node), P(Plugin))
_cfunc("plugin_get_bundle_uri", P(Node), P(Plugin))
_cfunc("plugin_get_data_uris", P(Nodes), P(Plugin))
_cfunc("plugin_get_library_uri", P(Node), P(Plugin))
_cfunc("plugin_get_name", P(Node), P(Plugin))
_cfunc("plugin_get_class", P(PluginClass), P(Plugin))
_cfunc("plugin_get_value", P(Nodes), P(Plugin), P(Node))
_cfunc("plugin_has_feature", c_bool, P(Plugin), P(Node))
_cfunc("plugin_get_supported_features", P(Nodes), P(Plugin))
_cfunc("plugin_get_required_features", P(Nodes), P(Plugin))
_cfunc("plugin_get_optional_features", P(Nodes), P(Plugin))
_cfunc("plugin_has_extension_data", c_bool, P(Plugin), P(Node))
_cfunc("plugin_get_extension_data", P(Nodes), P(Plugin))
_cfunc("plugin_get_num_ports", c_uint32, P(Plugin))

c.plugin_get_num_ports_of_class = VariadicFunction(
    c.lib.lilv_plugin_get_num_ports_of_class, c_uint32, [P(Plugin), P(Node)]
)

_cfunc("plugin_has_latency", c_bool, P(Plugin))
_cfunc("plugin_get_latency_port_index", c_uint32, P(Plugin))
_cfunc("plugin_get_port_by_index", P(Port), P(Plugin), c_uint32)
_cfunc("plugin_get_port_by_symbol", P(Port), P(Plugin), P(Node))
_cfunc("plugin_get_port_by_designation", P(Port), P(Plugin), P(Node), P(Node))
_cfunc("plugin_get_project", P(Node), P(Plugin))
_cfunc("plugin_get_author_name", P(Node), P(Plugin))
_cfunc("plugin_get_author_email", P(Node), P(Plugin))
_cfunc("plugin_get_author_homepage", P(Node), P(Plugin))
_cfunc("plugin_is_replaced", c_bool, P(Plugin))
_cfunc("plugin_get_related", P(Nodes), P(Plugin), P(Node))

# Port

_cfunc("port_get_node", P(Node), P(Plugin), P(Port))
_cfunc("port_get_value", P(Nodes), P(Plugin), P(Port), P(Node))
_cfunc("port_get", P(Node), P(Plugin), P(Port), P(Node))
_cfunc("port_get_properties", P(Nodes), P(Plugin), P(Port))
_cfunc("port_has_property", c_bool, P(Plugin), P(Port), P(Node))
_cfunc("port_supports_event", c_bool, P(Plugin), P(Port), P(Node))
_cfunc("port_get_index", c_uint32, P(Plugin), P(Port))
_cfunc("port_get_symbol", P(Node), P(Plugin), P(Port))
_cfunc("port_get_name", P(Node), P(Plugin), P(Port))
_cfunc("port_get_classes", P(Nodes), P(Plugin), P(Port))
_cfunc("port_is_a", c_bool, P(Plugin), P(Port), P(Node))

_cfunc(
    "port_get_range",
    None,
    P(Plugin),
    P(Port),
    P(P(Node)),
    P(P(Node)),
    P(P(Node)),
)

_cfunc("port_get_scale_points", P(ScalePoints), P(Plugin), P(Port))

# Plugin State

_cfunc("state_new_from_world", P(State), P(World), P(LV2_URID_Map), P(Node))

_cfunc(
    "state_new_from_file", P(State), P(World), P(LV2_URID_Map), P(Node), String
)

_cfunc("state_new_from_string", P(State), P(World), P(LV2_URID_Map), String)

LilvGetPortValueFunc = CFUNCTYPE(
    c_void_p, c_char_p, P(None), P(c_uint32), P(c_uint32)
)

_cfunc(
    "state_new_from_instance",
    P(State),
    P(Plugin),
    P(Instance),
    P(LV2_URID_Map),
    c_char_p,
    c_char_p,
    c_char_p,
    String,
    LilvGetPortValueFunc,
    P(None),
    c_uint32,
    P(P(LV2_Feature)),
)

_cfunc("state_free", None, P(State))
_cfunc("state_equals", c_bool, P(State), P(State))
_cfunc("state_get_num_properties", c_uint, P(State))
_cfunc("state_get_plugin_uri", P(Node), P(State))
_cfunc("state_get_uri", P(Node), P(State))
_cfunc("state_get_bundle_path", c_char_p, P(State))
_cfunc("state_get_label", c_char_p, P(State))
_cfunc("state_set_label", None, P(State), String)

_cfunc(
    "state_set_metadata",
    c_int,
    P(State),
    c_uint32,
    P(None),
    c_size_t,
    c_uint32,
    c_uint32,
)

LilvSetPortValueFunc = CFUNCTYPE(
    None, c_char_p, P(None), P(None), c_uint32, c_uint32
)
_cfunc("state_emit_port_values", None, P(State), LilvSetPortValueFunc, P(None))

_cfunc(
    "state_restore",
    None,
    P(State),
    P(Instance),
    LilvSetPortValueFunc,
    P(None),
    c_uint32,
    P(P(LV2_Feature)),
)

_cfunc(
    "state_save",
    c_int,
    P(World),
    P(LV2_URID_Map),
    P(LV2_URID_Unmap),
    P(State),
    c_char_p,
    c_char_p,
    String,
)

_cfunc(
    "state_to_string",
    c_char_p,
    P(World),
    P(LV2_URID_Map),
    P(LV2_URID_Unmap),
    P(State),
    c_char_p,
    String,
)

_cfunc("state_delete", c_int, P(World), P(State))

# Scale Point

_cfunc("scale_point_get_label", P(Node), P(ScalePoint))
_cfunc("scale_point_get_value", P(Node), P(ScalePoint))

# Plugin Class

_cfunc("plugin_class_get_parent_uri", P(Node), P(PluginClass))
_cfunc("plugin_class_get_uri", P(Node), P(PluginClass))
_cfunc("plugin_class_get_label", P(Node), P(PluginClass))
_cfunc("plugin_class_get_children", P(PluginClasses), P(PluginClass))

# Plugin Instance

_cfunc(
    "plugin_instantiate", P(Instance), P(Plugin), c_double, P(P(LV2_Feature))
)

_cfunc("instance_free", None, P(Instance))
_cfunc("plugin_get_uis", P(UIs), P(Plugin))

# Plugin UI

_cfunc("ui_get_uri", P(Node), P(UI))
_cfunc("ui_get_classes", P(Nodes), P(UI))
_cfunc("ui_is_a", c_bool, P(UI), P(Node))

LilvUISupportedFunc = CFUNCTYPE(c_uint, c_char_p, c_char_p)

_cfunc(
    "ui_is_supported", c_uint, P(UI), LilvUISupportedFunc, P(Node), P(P(Node))
)

_cfunc("ui_get_bundle_uri", P(Node), P(UI))
_cfunc("ui_get_binary_uri", P(Node), P(UI))

# Define URI constants for compatibility with old Python bindings

LILV_NS_DOAP = "http://usefulinc.com/ns/doap#"
LILV_NS_FOAF = "http://xmlns.com/foaf/0.1/"
LILV_NS_LILV = "http://drobilla.net/ns/lilv#"
LILV_NS_LV2 = "http://lv2plug.in/ns/lv2core#"
LILV_NS_OWL = "http://www.w3.org/2002/07/owl#"
LILV_NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
LILV_NS_RDFS = "http://www.w3.org/2000/01/rdf-schema#"
LILV_NS_XSD = "http://www.w3.org/2001/XMLSchema#"
LILV_URI_ATOM_PORT = "http://lv2plug.in/ns/ext/atom#AtomPort"
LILV_URI_AUDIO_PORT = "http://lv2plug.in/ns/lv2core#AudioPort"
LILV_URI_CONTROL_PORT = "http://lv2plug.in/ns/lv2core#ControlPort"
LILV_URI_CV_PORT = "http://lv2plug.in/ns/lv2core#CVPort"
LILV_URI_EVENT_PORT = "http://lv2plug.in/ns/ext/event#EventPort"
LILV_URI_INPUT_PORT = "http://lv2plug.in/ns/lv2core#InputPort"
LILV_URI_MIDI_EVENT = "http://lv2plug.in/ns/ext/midi#MidiEvent"
LILV_URI_OUTPUT_PORT = "http://lv2plug.in/ns/lv2core#OutputPort"
LILV_URI_PORT = "http://lv2plug.in/ns/lv2core#Port"
LILV_OPTION_DYN_MANIFEST = "http://drobilla.net/ns/lilv#dyn-manifest"
LILV_OPTION_FILTER_LANG = "http://drobilla.net/ns/lilv#filter-lang"
