# Copyright 2016-2021 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any
from typing import Generic
from typing import TypeVar
from typing import Union

from builtin_interfaces.msg import Time
from rclpy.exceptions import NoTypeSupportImportedException
from rosidl_pycommon.interface_base_classes import BaseAction
from rosidl_pycommon.interface_base_classes import BaseImpl
from rosidl_pycommon.interface_base_classes import BaseMessage
from rosidl_pycommon.interface_base_classes import BaseService
from service_msgs.msg import ServiceEventInfo
from typing_extensions import TypeAlias
from unique_identifier_msgs.msg import UUID


Msg: TypeAlias = BaseMessage
MsgT = TypeVar('MsgT', bound=BaseMessage)

SrvRequestT = TypeVar('SrvRequestT', bound=BaseMessage)
SrvResponseT = TypeVar('SrvResponseT', bound=BaseMessage)


class EventMessage(BaseMessage):
    info: ServiceEventInfo


Srv: TypeAlias = BaseService[SrvRequestT, SrvResponseT]


GoalT = TypeVar('GoalT', bound=BaseMessage)
ResultT = TypeVar('ResultT', bound=BaseMessage)
FeedbackT = TypeVar('FeedbackT', bound=BaseMessage)
ImplT = TypeVar('ImplT', bound=BaseImpl[Any, Any, Any])


class SendGoalServiceRequest(BaseMessage, Generic[GoalT]):
    goal_id: UUID
    goal: GoalT


class SendGoalServiceResponse(BaseMessage):
    accepted: bool
    stamp: Time


SendGoalService: TypeAlias = BaseService[Any, Any]


class GetResultServiceRequest(BaseMessage):
    goal_id: UUID


class GetResultServiceResponse(BaseMessage, Generic[ResultT]):
    status: int
    result: ResultT


GetResultService: TypeAlias = BaseService[Any, Any]


class FeedbackMessage(BaseMessage, Generic[FeedbackT]):
    goal_id: UUID
    feedback: FeedbackT


Action: TypeAlias = BaseAction[GoalT, ResultT, FeedbackT, ImplT]


# Can be used if https://github.com/python/typing/issues/548 ever gets approved.
SrvT = TypeVar('SrvT', bound=BaseService[Any, Any])
ActionT = TypeVar('ActionT', bound=BaseAction[Any, Any, Any, Any])


def check_for_type_support(msg_or_srv_type: type[Union[Msg,
                                                       Srv[Any, Any],
                                                       Action[Any, Any, Any, Any]]]) -> None:
    try:
        ts = msg_or_srv_type._TYPE_SUPPORT
    except AttributeError as e:
        e.args = (
            e.args[0] +
            ' This might be a ROS 1 message type but it should be a ROS 2 message type.'
            ' Make sure to source your ROS 2 workspace after your ROS 1 workspace.',
            *e.args[1:])
        raise
    if ts is None:
        msg_or_srv_type.__import_type_support__()
    if msg_or_srv_type._TYPE_SUPPORT is None:
        raise NoTypeSupportImportedException()


def check_is_valid_msg_type(msg_type: type[Msg]) -> None:
    check_for_type_support(msg_type)
    try:
        assert None not in (
            msg_type._CREATE_ROS_MESSAGE,
            msg_type._CONVERT_FROM_PY,
            msg_type._CONVERT_TO_PY,
            msg_type._DESTROY_ROS_MESSAGE,
        )
    except (AssertionError, AttributeError):
        raise RuntimeError(
            f'The message type provided is not valid ({msg_type}),'
            ' this might be a service or action'
        ) from None


def check_is_valid_srv_type(srv_type: type[Srv[Any, Any]]) -> None:
    check_for_type_support(srv_type)
    try:
        assert None not in (
            srv_type.Response,
            srv_type.Request,
        )
    except (AssertionError, AttributeError):
        raise RuntimeError(
            f'The service type provided is not valid ({srv_type}),'
            ' this might be a message or action'
        ) from None
