# SPDX-FileCopyrightText: Copyright DB InfraGO AG
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations

import enum
import typing as t
import warnings

import capellambse.model as m

from . import behavior, capellacore, fa
from . import namespaces as ns

NS = ns.INTERACTION


@m.stringy_enum
@enum.unique
class InteractionOperatorKind(enum.Enum):
    ALT = "ALT"
    OPT = "OPT"
    PAR = "PAR"
    LOOP = "LOOP"
    CRITICAL = "CRITICAL"
    NEG = "NEG"
    ASSERT = "ASSERT"
    STRICT = "STRICT"
    SEQ = "SEQ"
    IGNORE = "IGNORE"
    CONSIDER = "CONSIDER"
    UNSET = "UNSET"


@m.stringy_enum
@enum.unique
class MessageKind(enum.Enum):
    """Identifies the type of message.

    This concept is similar to UML ``MessageSort``.
    """

    UNSET = "UNSET"
    """The message kind is not specified."""
    ASYNCHRONOUS_CALL = "ASYNCHRONOUS_CALL"
    """The message was generated by an asynchronous call to an operation.

    Equivalent to UML ``MessageSort::asynchCall``.
    """
    SYNCHRONOUS_CALL = "SYNCHRONOUS_CALL"
    """The message was generated by a synchronous call to an operation.

    Equivalent to UML ``MessageSort::synchCall``.
    """
    REPLY = "REPLY"
    """The message is a reply message to an operation call.

    Equivalent to UML ``MessageSort::reply``.
    """
    DELETE = "DELETE"
    """The message designates the termination of another lifeline.

    Equivalent to UML ``MessageSort::deleteMessage``.
    """
    CREATE = "CREATE"
    """The message designates the creation of an instance role."""
    TIMER = "TIMER"


@m.stringy_enum
@enum.unique
class ScenarioKind(enum.Enum):
    INTERFACE = "INTERFACE"
    DATA_FLOW = "DATA_FLOW"
    INTERACTION = "INTERACTION"
    FUNCTIONAL = "FUNCTIONAL"
    UNSET = "UNSET"


class SequenceMessage(capellacore.NamedElement):
    """A sequence message."""

    kind = m.EnumPOD("kind", MessageKind)
    exchange_context = m.Association["capellacore.Constraint"](
        (ns.CAPELLACORE, "Constraint"), "exchangeContext"
    )
    sending_end = m.Association["MessageEnd"]((NS, "MessageEnd"), "sendingEnd")
    receiving_end = m.Association["MessageEnd"](
        (NS, "MessageEnd"), "receivingEnd"
    )
    exchanged_items = m.Association["information.ExchangeItem"](
        (ns.INFORMATION, "ExchangeItem"), "exchangedItems"
    )
    valuations = m.Containment["SequenceMessageValuation"](
        "ownedSequenceMessageValuations", (NS, "SequenceMessageValuation")
    )

    if not t.TYPE_CHECKING:
        source = m.DeprecatedAccessor("sending_end")
        target = m.DeprecatedAccessor("receiving_end")


class Scenario(capellacore.Namespace, behavior.AbstractBehavior):
    kind = m.EnumPOD("kind", ScenarioKind)
    is_merged = m.BoolPOD("merged")

    precondition = m.Single["capellacore.Constraint"](
        m.Association((ns.CAPELLACORE, "Constraint"), "preCondition")
    )
    postcondition = m.Single["capellacore.Constraint"](
        m.Association((ns.CAPELLACORE, "Constraint"), "postCondition")
    )
    instance_roles = m.Containment["InstanceRole"](
        "ownedInstanceRoles", (NS, "InstanceRole")
    )
    messages = m.Containment["SequenceMessage"](
        "ownedMessages", (NS, "SequenceMessage")
    )
    fragments = m.Containment["InteractionFragment"](
        "ownedInteractionFragments",
        (NS, "InteractionFragment"),
        legacy_by_type=True,
    )
    time_lapses = m.Containment["TimeLapse"](
        "ownedTimeLapses", (NS, "TimeLapse"), legacy_by_type=True
    )
    events = m.Containment["Event"](
        "ownedEvents", (NS, "Event"), legacy_by_type=True
    )
    formal_gates = m.Containment["Gate"]("ownedFormalGates", (NS, "Gate"))
    scenario_realizations = m.Containment["ScenarioRealization"](
        "ownedScenarioRealization", (NS, "ScenarioRealization")
    )
    realized_scenarios = m.Allocation["Scenario"](
        "ownedScenarioRealization",
        (NS, "ScenarioRealization"),
        (NS, "Scenario"),
        attr="targetElement",
        backattr="sourceElement",
    )
    realizing_scenarios = m.Backref["Scenario"](
        (NS, "Scenario"), "realized_scenarios"
    )
    constraint_durations = m.Containment["ConstraintDuration"](
        "ownedConstraintDurations", (NS, "ConstraintDuration")
    )

    @property
    def related_functions(self) -> m.ElementList[fa.AbstractFunction]:
        return self.fragments.map("function")


class InteractionFragment(capellacore.NamedElement, abstract=True):
    covered_instance_roles = m.Association["InstanceRole"](
        (NS, "InstanceRole"), "coveredInstanceRoles", legacy_by_type=True
    )

    if not t.TYPE_CHECKING:
        covered = m.DeprecatedAccessor("covered_instance_roles")


class AbstractEnd(InteractionFragment, abstract=True):
    event = m.Single["Event"](m.Association((NS, "Event"), "event"))


class MessageEnd(AbstractEnd):
    pass


class TimeLapse(capellacore.NamedElement, abstract=True):
    start = m.Single["InteractionFragment"](
        m.Association((NS, "InteractionFragment"), "start")
    )
    finish = m.Single["InteractionFragment"](
        m.Association((NS, "InteractionFragment"), "finish")
    )


class Execution(TimeLapse):
    pass


class ExecutionEnd(AbstractEnd):
    pass


class Event(capellacore.NamedElement, behavior.AbstractEvent, abstract=True):
    pass


class CreationEvent(Event):
    pass


class DestructionEvent(Event):
    pass


class ExecutionEvent(Event):
    pass


class InstanceRole(capellacore.NamedElement):
    represented_instance = m.Single["information.AbstractInstance"](
        m.Association(
            (ns.INFORMATION, "AbstractInstance"), "representedInstance"
        )
    )

    if not t.TYPE_CHECKING:
        instance = m.DeprecatedAccessor("represented_instance")


class _EventOperation(Event):
    # TODO delete this class when removing deprecated features
    operation = m.Association["information.AbstractEventOperation"](
        (ns.INFORMATION, "AbstractEventOperation"), "operation"
    )


class EventReceiptOperation(_EventOperation):
    operation = m.Association["information.AbstractEventOperation"](
        (ns.INFORMATION, "AbstractEventOperation"), "operation"
    )


class EventSentOperation(_EventOperation):
    operation = m.Association["information.AbstractEventOperation"](
        (ns.INFORMATION, "AbstractEventOperation"), "operation"
    )


class MergeLink(capellacore.Trace):
    pass


class RefinementLink(capellacore.Trace):
    pass


class AbstractCapabilityRealization(capellacore.Allocation):
    pass


class AbstractCapability(
    capellacore.Structure,
    capellacore.InvolverElement,
    fa.AbstractFunctionalChainContainer,
    abstract=True,
):
    precondition = m.Single["capellacore.Constraint"](
        m.Association((ns.CAPELLACORE, "Constraint"), "preCondition")
    )
    postcondition = m.Single["capellacore.Constraint"](
        m.Association((ns.CAPELLACORE, "Constraint"), "postCondition")
    )
    scenarios = m.Containment["Scenario"]("ownedScenarios", (NS, "Scenario"))
    extends = m.Containment["AbstractCapabilityExtend"](
        "extends", (NS, "AbstractCapabilityExtend")
    )
    extended_by = m.Backref["AbstractCapabilityExtend"](
        (NS, "AbstractCapabilityExtend"), "target"
    )
    extension_points = m.Containment["AbstractCapabilityExtensionPoint"](
        "abstractCapabilityExtensionPoints",
        (NS, "AbstractCapabilityExtensionPoint"),
    )
    generalizations = m.Containment["AbstractCapabilityGeneralization"](
        "superGeneralizations", (NS, "AbstractCapabilityGeneralization")
    )
    generalized_by = m.Backref["AbstractCapability"](
        (NS, "AbstractCapability"), "target"
    )
    includes = m.Containment["AbstractCapabilityInclude"](
        "includes", (NS, "AbstractCapabilityInclude")
    )
    included_by = m.Backref["AbstractCapabilityInclude"](
        (NS, "AbstractCapabilityInclude"), "included"
    )
    chain_involvements = m.Containment[
        "FunctionalChainAbstractCapabilityInvolvement"
    ](
        "ownedFunctionalChainAbstractCapabilityInvolvements",
        (NS, "FunctionalChainAbstractCapabilityInvolvement"),
    )
    involved_chains = m.Allocation["fa.FunctionalChain"](
        "ownedFunctionalChainAbstractCapabilityInvolvements",
        (NS, "FunctionalChainAbstractCapabilityInvolvement"),
        (ns.FA, "AbstractFunction"),
        attr="involved",
    )
    function_involvements = m.Containment[
        "AbstractFunctionAbstractCapabilityInvolvement"
    ](
        "ownedAbstractFunctionAbstractCapabilityInvolvements",
        (NS, "AbstractFunctionAbstractCapabilityInvolvement"),
    )
    involved_functions = m.Allocation["fa.AbstractFunction"](
        "ownedAbstractFunctionAbstractCapabilityInvolvements",
        (NS, "AbstractFunctionAbstractCapabilityInvolvement"),
        (ns.FA, "AbstractFunction"),
        attr="involved",
    )
    available_in_states = m.Association["capellacommon.State"](
        (ns.CAPELLACOMMON, "State"), "availableInStates"
    )
    capability_realizations = m.Containment["AbstractCapabilityRealization"](
        "ownedAbstractCapabilityRealizations",
        (NS, "AbstractCapabilityRealization"),
    )
    realized_capabilities = m.Allocation["AbstractCapability"](
        "ownedAbstractCapabilityRealizations",
        (NS, "AbstractCapabilityRealization"),
        (NS, "AbstractCapability"),
        attr="targetElement",
    )
    realizing_capabilities = m.Backref["AbstractCapability"](
        (NS, "AbstractCapability"), "realized_capabilities"
    )

    if not t.TYPE_CHECKING:
        generalizes = m.DeprecatedAccessor("generalizations")
        states = m.DeprecatedAccessor("available_in_states")


class AbstractCapabilityExtend(capellacore.Relationship):
    _xmltag = "extends"

    source = m.Alias["m.ModelElement"]("parent")
    extended = m.Single["AbstractCapability"](
        m.Association((NS, "AbstractCapability"), "extended")
    )
    target = m.Alias["AbstractCapability"]("extended")
    extension_location = m.Single["AbstractCapabilityExtensionPoint"](
        m.Association(
            (NS, "AbstractCapabilityExtensionPoint"), "extensionLocation"
        )
    )


class AbstractCapabilityExtensionPoint(capellacore.NamedRelationship):
    extend_links = m.Association["AbstractCapabilityExtend"](
        (NS, "AbstractCapabilityExtend"), "extendLinks"
    )


class AbstractCapabilityGeneralization(capellacore.Relationship):
    _xmltag = "superGeneralizations"

    source = m.Alias["m.ModelElement"]("parent")
    super = m.Single["AbstractCapability"](
        m.Association((NS, "AbstractCapability"), "super")
    )
    target = m.Alias["AbstractCapability"]("super")


class AbstractCapabilityInclude(capellacore.Relationship):
    _xmltag = "includes"

    source = m.Alias["m.ModelElement"]("parent")
    included = m.Single["AbstractCapability"](
        m.Association((NS, "AbstractCapability"), "included")
    )
    target = m.Alias["AbstractCapability"]("included")


class InteractionState(InteractionFragment):
    state = m.Single["capellacommon.AbstractState"](
        m.Association(
            (ns.CAPELLACOMMON, "AbstractState"), "relatedAbstractState"
        )
    )
    function = m.Single["fa.AbstractFunction"](
        m.Association((ns.FA, "AbstractFunction"), "relatedAbstractFunction")
    )


class AbstractFragment(TimeLapse, abstract=True):
    gates = m.Containment["Gate"]("ownedGates", (NS, "Gate"))


class InteractionUse(AbstractFragment):
    referenced_scenario = m.Single["Scenario"](
        m.Association((NS, "Scenario"), "referencedScenario")
    )


class CombinedFragment(AbstractFragment):
    operator = m.EnumPOD("operator", InteractionOperatorKind)
    operands = m.Association["InteractionOperand"](
        (NS, "InteractionOperand"), "referencedOperands"
    )


class Gate(MessageEnd):
    pass


class InteractionOperand(InteractionFragment):
    fragments = m.Association["InteractionFragment"](
        (NS, "InteractionFragment"), "referencedInteractionFragments"
    )
    guard = m.Single["capellacore.Constraint"](
        m.Association((ns.CAPELLACORE, "Constraint"), "guard")
    )


class FragmentEnd(InteractionFragment):
    pass


class FunctionalChainAbstractCapabilityInvolvement(capellacore.Involvement):
    pass


class AbstractFunctionAbstractCapabilityInvolvement(capellacore.Involvement):
    pass


class ScenarioRealization(capellacore.Allocation):
    pass


class StateFragment(TimeLapse):
    state = m.Single["capellacommon.AbstractState"](
        m.Association(
            (ns.CAPELLACOMMON, "AbstractState"), "relatedAbstractState"
        )
    )
    function = m.Single["fa.AbstractFunction"](
        m.Association((ns.FA, "AbstractFunction"), "relatedAbstractFunction")
    )


class ArmTimerEvent(Event):
    pass


class CancelTimerEvent(Event):
    pass


class ConstraintDuration(capellacore.NamedElement):
    duration = m.StringPOD("duration")
    start = m.Single["InteractionFragment"](
        m.Association((NS, "InteractionFragment"), "start")
    )
    finish = m.Single["InteractionFragment"](
        m.Association((NS, "InteractionFragment"), "finish")
    )


class SequenceMessageValuation(capellacore.CapellaElement):
    element = m.Single["information.ExchangeItemElement"](
        m.Association(
            (ns.INFORMATION, "ExchangeItemElement"), "exchangeItemElement"
        )
    )
    value = m.Single["modellingcore.ValueSpecification"](
        m.Association((ns.MODELLINGCORE, "ValueSpecification"), "value")
    )


if not t.TYPE_CHECKING:

    def __getattr__(name):
        if name == "Exchange":
            warnings.warn(
                "The Exchange class has been removed, use capellacore.Relationship instead",
                DeprecationWarning,
                stacklevel=2,
            )
            return capellacore.Relationship

        if name == "AbstractInvolvement":
            warnings.warn(
                "interaction.AbstractInvolvement has been moved to capellacore.Involvement",
                DeprecationWarning,
                stacklevel=2,
            )
            return capellacore.Involvement

        if name == "EventOperation":
            warnings.warn(
                (
                    "EventOperation has been removed,"
                    " use EventSentOperation/EventReceiptOperation directly"
                    " or another common super class instead"
                ),
                DeprecationWarning,
                stacklevel=2,
            )
            return _EventOperation

        raise AttributeError(name)


from . import capellacommon, information, modellingcore  # noqa: F401
