# The following license applies to the verbatim include of the Funktionsklempner
# boilerplate code until the mark `END OF FUNKTIONSKLEMPNER BOILERPLATE`.
#
# Authors: Malte J. Ziebarth (malte.ziebarth@tum.de)
#
# Copyright (c) 2025 Technical University of Munich
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#
#    1. Redistributions of source code must retain the above copyright notice, this
#       list of conditions and the following disclaimer.
#    2. Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#    3. Neither the name of the copyright holder nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
#
# SPDX-FileCopyrightText: 2025 Technical University of Munich
# SPDX-License-Identifier: BSD-3-Clause



from typing import ContextManager, Any # pyright: ignore[reportUnusedImport]
from pathlib import Path
from importlib.resources import (
    Anchor, is_resource, path # pyright: ignore[reportUnusedImport]
)
from ctypes import (
    CDLL, c_uint64, c_int64, c_double, c_char_p, # pyright: ignore[reportUnusedImport]
    POINTER, c_void_p, c_int8, c_uint8, c_short, # pyright: ignore[reportUnusedImport]
    c_ushort, c_int, c_uint, c_long, c_ulong,    # pyright: ignore[reportUnusedImport]
    c_longlong, c_ulonglong, c_int16, c_uint16,  # pyright: ignore[reportUnusedImport]
    c_int32, c_uint32, c_size_t, c_ssize_t,      # pyright: ignore[reportUnusedImport]
    c_byte, c_char, c_float, Structure, byref,   # pyright: ignore[reportUnusedImport]
    _Pointer             # pyright: ignore[reportUnusedImport, reportPrivateUsage]
)


#
# A context manager for keeping the database alive as a file
# until this module is closed:
#

class ContextRAII[T]:
    """
    Object lifetime wrapper around a context manager.
    """
    cmgr: ContextManager[T]
    resource: T

    def __init__(self, cmgr: ContextManager[T]):
        self.cmgr = cmgr
        self.resource = cmgr.__enter__()

    def __del__(self):
        self.cmgr.__exit__(None, None, None)


class ResourceProvider(ContextRAII[Path]):
    """
    Provide an importlib resource as a file system
    path (and file) throughout its lifetime.
    """
    def __init__(self, anchor: Anchor, name: str):
        # Make sure that the resource exists:
        name = str(name)
        if not is_resource(anchor, name):
            raise RuntimeError(
                "Name '" + name + "' from '" +
                str(anchor) + "' is not a resource."
            )

        # The following step may create a copy of
        # the resource:
        super().__init__(path(anchor, name))




# Paths to the extension:
def load_lib() -> tuple[ResourceProvider, CDLL]:
    """
    Iterate the possibly encountered library names and try
    to load the library.
    """
    rsrc = ResourceProvider(ANCHOR, SHARED_LIBRARY)
    return rsrc, CDLL(rsrc.resource)


_cdll_provider, _cdll = load_lib()



#
# Calling with the unified error interface:
#
class FKString(Structure):
    """
    Strings
    """
    _fields_ = [
        ('size', c_size_t),
        ('message', c_char_p)
    ]


_get_string = _cdll.funktionsklempner_get_string
_get_string.argtypes = (
    c_size_t,
    POINTER(FKString)
)

_delete_string = _cdll.funktionsklempner_delete_string
_delete_string.argtypes = (
    c_size_t,
)


def _call_with_error(fun: Any, *args: Any): # pyright: ignore[reportUnusedFunction]
    """
    Wrapper around the CDLL calls.
    """
    # Augment the arguments by an error:
    err_id = c_size_t()
    res: int = fun(byref(err_id), *args)

    # Check if an error occurred:
    if res != 0:
        # Get the error message:
        err = FKString()
        res0 = _get_string(err_id, err)
        if res0 != 0:
            _delete_string(err_id)
            raise RuntimeError(
                "An error occurred and the program failed to retrieve "
                "the error message."
            )
        err_msg = str(err.message.decode('utf-8'))

        # Delete it in the C++ world:
        res1 = _delete_string(err_id)
        if res1 != 0:
            raise RuntimeError(
                "An error occurred: '" + err_msg + "'. Furthermore, "
                "deleting this error message failed."
            )

        raise RuntimeError(err_msg)


# END OF FUNKTIONSKLEMPNER BOILERPLATE