# Wrap input parameters.
#
# Author: Malte J. Ziebarth (malte.ziebarth@tum.de)
#
# Copyright (C) 2025-2026 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-2026 Technical University of Munich
# SPDX-License-Identifier: EUPL-1.2
# SPDX-FileContributor: Malte J. Ziebarth <malte.ziebarth@tum.de>

from typing import cast
from ..language.abstract import Struct, Input, PureInput, Pointer
from ..language.fundamental import FLOAT_TYPES, INT_TYPES
from ..language.conversion import equivalent_ctypes_type
from ..language.python import try_ctype_base_type, \
    is_ctype_base_type, CTypeBaseType
from .parameter import PythonInputParameter, CTypeParameterByRef,\
    CTypeParameterByValue, CppParameter
from ..language.cpp import L1Type
from .variablegraph import VariableGraph, VariableGraphNode


def wrap_input_parameter(
        name: str,
        argument_type: Input[L1Type] | PureInput[L1Type],
        vargraph: VariableGraph,
        root: VariableGraphNode | None,
        cpp_argument_id: int
    ):
    is_pure = isinstance(argument_type, PureInput)
    depends_on: tuple[VariableGraphNode] \
        = tuple[VariableGraphNode]() if root is None else (root,)
    cpp_type = argument_type.val

    # 0. Handle pointers and resolve the ctypes type
    is_pointer = isinstance(cpp_type, Pointer)
    if is_pointer:
        ctype_type = equivalent_ctypes_type(cpp_type.val)
    else:
        ctype_type = equivalent_ctypes_type(cpp_type)


    # 1. Handle floats and integers.
    is_float = ctype_type in FLOAT_TYPES
    is_int = ctype_type in INT_TYPES
    if is_float or is_int:
        ctype_base_type = try_ctype_base_type(ctype_type)
        if is_pure:
            rtype = ctype_base_type
        elif is_float:
            rtype = "float"
        else:
            rtype = "int"

        py_in = PythonInputParameter(
            name,
            Input(rtype),
            []
        )
        vgn = vargraph.insert(py_in, *depends_on)

        c_in_name = vargraph.get_temporary_name(name)
        init_code = [
            # Here we perform type ensurance through
            #    `{rtype}(name)`
            # and convert to ctypes type through
            #    {ctype_base_type}(...)
            f"    {c_in_name} = {ctype_base_type}({rtype}({name}))"
        ]
        if is_pointer:
            c_in = CTypeParameterByRef(
                c_in_name,
                ctype_base_type,
                init_code
            )
        else:
            c_in = CTypeParameterByValue(
                c_in_name,
                ctype_base_type,
                init_code
            )
        vgn = vargraph.insert(c_in, vgn)

        # The C++ parameter:
        cpp_in = CppParameter.forward_input(c_in, cpp_argument_id)
        vargraph.insert(cpp_in, vgn)


        return

    # 2. Structs.
    if isinstance(cpp_type, Struct):
        #
        # We create structs first as a Python output parameter.
        # Then we forward them, by reference, to the CDLL (since
        # the structs are native ctypes types).
        #
        assert isinstance(ctype_type, Struct)

        py_in = PythonInputParameter(
            name,
            Input(ctype_type),
            [
                f"    assert(isinstance({name}, {cpp_type.name})"
            ]
        )

        if is_pointer:
            c_in = CTypeParameterByRef.forward(py_in)
        else:
            c_in = CTypeParameterByValue.forward(py_in)

        vgn = vargraph.insert(py_in)
        vgn = vargraph.insert(c_in, vgn)

        # The C++ parameter:
        cpp_in = CppParameter.forward_input(c_in, cpp_argument_id)
        vargraph.insert(cpp_in, vgn)

        return

    # 3. ctype base types.
    if is_ctype_base_type(ctype_type):
        # Cast for making type checker happy:
        ctype_type = cast(CTypeBaseType, ctype_type)

        #
        # We create the variables first as a Python output parameter.
        # Then we forward them, by reference, to the CDLL (since
        # the variables are native ctypes types).
        #

        py_in = PythonInputParameter(
            name,
            Input(ctype_type),
            [
                f"    assert(isinstance({name}, {ctype_type})"
            ]
        )

        if is_pointer:
            c_in = CTypeParameterByRef.forward(py_in)
        else:
            c_in = CTypeParameterByValue.forward(py_in)

        vgn = vargraph.insert(py_in)
        vgn = vargraph.insert(c_in, vgn)

        # The C++ parameter:
        cpp_in = CppParameter.forward_input(c_in, cpp_argument_id)
        vargraph.insert(cpp_in, vgn)

        return




    # 4. NotImplemented.
    raise NotImplementedError(
        f"Wrapping output of type '{cpp_type}' not implemented."
    )