# Conversion from fundamental C++ types to CTypes.
#
# 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 get_args, overload, Literal, TYPE_CHECKING
from .abstract import Pointer, Const, Struct
from .fundamental import FundamentalBaseType, CTypeBaseType
if TYPE_CHECKING:
    # These are needed for type checking only (and otherwise
    # cause a circular import)
    from .python import PythonBaseType, CTypeType
    from .cpp import L1Type

TYPE_MAP: dict[FundamentalBaseType, CTypeBaseType] = {
    "signed char" : "c_int8",
    "unsigned char" : "c_uint8",
    "char" : "c_char",
    "short" : "c_short",
    "unsigned short" : "c_ushort",
    "int" : "c_int",
    "unsigned int" : "c_uint",
    "long" : "c_long",
    "unsigned long" : "c_ulong",
    "long long" : "c_longlong",
    "unsigned long long" : "c_ulonglong",
    "uint8_t" : "c_uint8",
    "int8_t" : "c_int8",
    "uint16_t" : "c_uint16",
    "int16_t" : "c_int16",
    "uint32_t" : "c_uint32",
    "int32_t" : "c_int32",
    "uint64_t" : "c_uint64",
    "int64_t" : "c_int64",
    "size_t" : "c_size_t",
    "ssize_t" : "c_ssize_t",
    "double" : "c_double",
    "float" : "c_float",
    "long double" : "c_longdouble"
}

REVERSE_TYPE_MAP: dict[
    CTypeBaseType,
    FundamentalBaseType | Pointer[Literal["char"]]
] = {
    "c_int8" : "signed char",
    "c_uint8" : "unsigned char",
    "c_char" : "char",
    "c_short" : "short",
    "c_ushort" : "unsigned short",
    "c_int" : "int",
    "c_uint" : "unsigned int",
    "c_long" : "long",
    "c_ulong" : "unsigned long",
    "c_longlong" : "long long",
    "c_ulonglong" : "unsigned long long",
    "c_uint8" : "uint8_t",
    "c_int8" : "int8_t",
    "c_uint16" : "uint16_t",
    "c_int16" : "int16_t",
    "c_uint32" : "uint32_t",
    "c_int32" : "int32_t",
    "c_uint64" : "uint64_t",
    "c_int64" : "int64_t",
    "c_size_t" : "size_t",
    "c_ssize_t" : "ssize_t",
    "c_double" : "double",
    "c_float" : "float",
    "c_longdouble" : "long double",
    "c_char_p" : Pointer("char")
}

NUMPY_DTYPE_MAP: dict[CTypeBaseType,str] = {
    "c_char" : "np.byte",
    "c_short" : "np.short",
    "c_ushort" : "np.ushort",
    "c_int" : "np.int_",
    "c_uint" : "np.uintc",
    "c_long" : "np.long",
    "c_ulong" : "np.ulong",
    "c_longlong" : "np.longlong",
    "c_ulonglong" : "np.ulonglong",
    "c_uint8" : "np.uint8",
    "c_int8" : "np.int8",
    "c_uint16" : "np.uint16",
    "c_int16" : "np.int16",
    "c_uint32" : "np.uint32",
    "c_int32" : "np.int32",
    "c_uint64" : "np.uint64",
    "c_int64" : "np.int64",
    "c_size_t" : "np.uintp",
    "c_ssize_t" : "np.intp",
    "c_double" : "np.double",
    "c_float" : "np.single",
    "c_longdouble" : "np.longdouble"
}

BASIC_PYTHON_DTYPE_MAP: dict[CTypeBaseType, "PythonBaseType"] = {
    "c_char" : "bytes",
    "c_short" : "int",
    "c_ushort" : "int",
    "c_int" : "int",
    "c_uint" : "int",
    "c_long" : "int",
    "c_ulong" : "int",
    "c_longlong" : "int",
    "c_ulonglong" : "int",
    "c_uint8" : "int",
    "c_int8" : "int",
    "c_uint16" : "int",
    "c_int16" : "int",
    "c_uint32" : "int",
    "c_int32" : "int",
    "c_uint64" : "int",
    "c_int64" : "int",
#    "c_size_t" : "np.uintp",
#    "c_ssize_t" : "np.intp",
    "c_double" : "float",
    "c_float" : "float",
    "c_longdouble" : "float"
}


#def _get_fbp(s: FundamentalBaseType) -> FundamentalBaseType:
#    return s

_S2FBP: dict[str, FundamentalBaseType] = {
    fbp : fbp for fbp in get_args(FundamentalBaseType)
}

def fundamental_base_type(s: str) -> FundamentalBaseType:
    """
    Ensures that a string is a fundamental base type.
    """
    the_fbp = _S2FBP.get(s, None)
    if the_fbp is None:
        raise RuntimeError(
            f"Type descriptor '{s}' is not a fundamental "
            "base type."
        )

    return the_fbp



@overload
def equivalent_ctypes_type(t: Struct["L1Type"]) -> Struct["CTypeType"]:
    ...

@overload
def equivalent_ctypes_type(t: Pointer["L1Type"]) -> Pointer["CTypeType"]:
    ...

@overload
def equivalent_ctypes_type(t: FundamentalBaseType) -> CTypeBaseType:
    ...

@overload
def equivalent_ctypes_type(t: Const["L1Type"]) -> "CTypeType":
    ...


def equivalent_ctypes_type(t: "L1Type") -> "CTypeType":
    """
    Computes the equivalent CTypes type.
    """
    if isinstance(t, Pointer):
        # Pointer translates to pointer:
        tval: "CTypeType" = equivalent_ctypes_type(t.val)
        if tval == "c_char":
            return "c_char_p"
        return Pointer(tval)
    elif isinstance(t, Const):
        # Drop const:
        return equivalent_ctypes_type(t.val)
    elif isinstance(t, Struct):
        return Struct["CTypeType"](
            t.name,
            (
                (equivalent_ctypes_type(m[0]),m[1])
                for m in t.members
            )
        )
    else:
        return TYPE_MAP[t]


def equivalent_cpp_type(t: "CTypeType") -> "L1Type":
    """
    Computes the equivalent C/C++ type.
    """
    if isinstance(t, Pointer):
        # Pointer translates to pointer:
        return Pointer(equivalent_cpp_type(t.val))

    elif isinstance(t, Const):
        # Const (which has no purpose in ctypes) translates
        # to const:
        return Const(equivalent_cpp_type(t.val))

    elif isinstance(t, Struct):
        return Struct["L1Type"](
            t.name,
            (
                (equivalent_cpp_type(m0), m1)
                for m0, m1 in t.members
            )
        )

    else:
        return REVERSE_TYPE_MAP[t]