# Python types.
#
# 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 overload, Literal, Any, cast
from .fundamental import CTypeBaseType, PythonBaseType, CTYPE_BASE_TYPES,\
    PYTHON_BASE_TYPES
from .abstract import Pointer, Struct, Input, Output, PureInput, PureOutput, \
    Const
from .arrays import NDArray, QuantityArray

#
# ctypes can also be more complicated:
#
CTypeType = CTypeBaseType | Const["CTypeType"] | Pointer["CTypeType"] | Struct["CTypeType"]


P1Type = PythonBaseType | CTypeType

P2Type = P1Type | NDArray | QuantityArray


PythonInputType = Input[P2Type] | PureInput[CTypeType]
PythonOutputType = Output[P2Type] | PureOutput[CTypeType]
PythonArgumentType = PythonInputType | PythonOutputType



#
# 2. Type checking.
# =================
#
@overload
def is_ctype_base_type(type_obj: CTypeBaseType) -> Literal[True]:
    ...

@overload
def is_ctype_base_type(
        type_obj: PythonBaseType | NDArray | QuantityArray
            | Pointer[CTypeType] | Struct[CTypeType] | Const[CTypeType]
    ) -> Literal[False]:
    ...


def is_ctype_base_type(type_obj: P2Type) -> bool:
    return isinstance(type_obj, str) \
        and type_obj in CTYPE_BASE_TYPES


# Some overloads for type checking (even though these constraints are not used...)
@overload
def is_ctype_type(type_obj: CTypeType) -> Literal[True]:
    ...

@overload
def is_ctype_type(type_obj: PythonBaseType | NDArray | QuantityArray) -> Literal[False]:
    ...

def is_ctype_type(type_obj: P2Type) -> bool:
    """
    Checks whether a type is a ctypes type.
    """
    if isinstance(type_obj, str):
        return type_obj in CTYPE_BASE_TYPES
    if isinstance(type_obj, Pointer):
        return is_ctype_type(
            type_obj.val # pyright: ignore[reportUnknownMemberType]
        )
    elif isinstance(type_obj, Const):
        return is_ctype_type(
            type_obj.val  # pyright: ignore[reportUnknownMemberType]
        )
    elif isinstance(type_obj, Struct):
        return all(
            is_ctype_type(t)
            for t,_ in type_obj.members # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
        )

    return False



def is_python_type(type_obj: Any) -> bool:
    """
    Checks whether a type is a Python type.
    """
    if is_ctype_type(type_obj):
        return True
    elif isinstance(type_obj, (NDArray, QuantityArray)):
        # Quantity arrays do not differentiate between Python
        # and C++ types here in this code base.
        return True
    elif isinstance(type_obj, str):
        return type_obj in PYTHON_BASE_TYPES
    elif isinstance(type_obj, (Input, Output)):
        return is_python_type(
            cast(Input[Any], type_obj).val
        )

    return False


def assert_ctypes_type(t: P2Type) -> CTypeType:
    assert is_ctype_type(t)

    # We now know it's a CTypeType (even though
    # type checking cannot infer that)
    return t # pyright: ignore[reportReturnType]



def try_ctype_base_type(type_obj: CTypeType) -> CTypeBaseType:
    """
    Returns the type_obj if it's a CTypeBaseType.
    """
    if isinstance(type_obj, str) and type_obj in CTYPE_BASE_TYPES:
        return type_obj
    raise TypeError(
        f"{type_obj} is not a CTypeBaseType."
    )