# Generate Python wrapper code.
#
# Author: Malte J. Ziebarth (malte.ziebarth@tum.de)
#
# Copyright (C) 2025 Technische Universität München
#
# Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
# the European Commission - subsequent versions of the EUPL (the "Licence");
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at:
#
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Licence is distributed on an "AS IS" basis,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the Licence for the specific language governing permissions and
# limitations under the Licence.
#
# SPDX-FileCopyrightText: 2025 Technical University of Munich
# SPDX-License-Identifier: EUPL-1.2
# SPDX-FileContributor: Malte J. Ziebarth <malte.ziebarth@tum.de>

from importlib import resources
from typing import Iterable
from itertools import islice
from ..wrap.library import WrappedLibrary
from ..language.strings import type_string
from ..language.arrays import NDArray
from .boilerplate import generate_boilerplate
from ..tools.views import join


with resources.open_text(
        'funktionsklempner.templates','wrapper.py', encoding='utf-8'
    ) as f:
    WRAPPER_PY: str = f.read()


CDLL_NAME = "_cdll"



def generate_python_source(
        lib: WrappedLibrary
    ) -> Iterable[str]:
    """

    """
    # Boilerplate:
    yield from generate_boilerplate(lib, 80, 'Python')

    # Define the package and the shared library name for finding them
    # among the package resources:
    yield "\n\nANCHOR = '"
    module = lib.module()
    yield from join(islice(module, 0, len(module)-1), ".")
    yield f"'\nSHARED_LIBRARY = '"
    yield lib.shared_library_name()
    yield "'\nPACKAGE = '"
    yield lib.module()[0]
    yield "'\n\n"

    # Include the boilerplate glue code:
    yield WRAPPER_PY

    # Import the quantity array. Use a relative import going up to the
    # root of this package and then back down to _funktionsklempner.
    yield (
        f"\n\nfrom {"." * (len(lib.module()) - 2)}"
         "._funktionsklempner.quantityarray import QuantityArray\n\n"
    )

    # Import NumPy if we encountered any arrays:
    if lib.requires_numpy():
        yield "\nimport numpy as np\nfrom typing import Iterable\n"

    # Structures:
    structs = lib.structs()
    if len(structs) > 0:
        yield "\n"
        for s in structs:
            yield f"class {s.name}(Structure):\n"
            if len(s.members) == 0:
                yield "    _fields_ = []\n\n"
            else:
                yield "    _fields_ = [\n"
                yield from (
                    f"        ('{m[1]}', {type_string(m[0])}),\n"
                    for m in s.members
                )
                yield "    ]\n\n"

    # Functions:
    yield "# Shared library functions:\n"
    # Keep track of whether any NumPy array occurs. This allows us to
    # only conditionally include the NumPy types and keep NumPy an
    # optional dependency.
    has_np_array: bool = False
    for f in lib.functions:
        yield from f.compose_ctypes_function(CDLL_NAME)
        if any(isinstance(p.dtype.val, NDArray) for p in f.python_input):
            has_np_array = True
        if any(isinstance(p.dtype.val, NDArray) for p in f.python_output):
            has_np_array = True


    yield "# Python interface:\n"
    if has_np_array:
        yield (
            "from typing import TYPE_CHECKING\n"
            "if TYPE_CHECKING:\n"
            "    from numpy.typing import NDArray\n"
            "    import numpy as np\n\n"
        )

    yield from (
        body for f in lib.functions for body in f.compose_python_body()
    )