# Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
This module contains the classes for forward expression

"""

from typing import List, Optional, Union

from ansys.scadeone.core.common.exception import ScadeOneException
import ansys.scadeone.core.swan.common as common
import ansys.scadeone.core.swan.scopes as scopes


# Forward Expression
# ======================================================================
class ForwardLHS(common.SwanItem):  # numpydoc ignore=PR01
    """**forward** construct:

    *current_lhs* ::= *id* | [ *current_lhs* ]"""

    def __init__(self, lhs: Union[common.Identifier, "ForwardLHS"]) -> None:
        super().__init__()
        self._lhs = lhs

    @property
    def lhs(self) -> Union[common.Identifier, "ForwardLHS"]:
        """Returns current lhs as an Identifier or a ForwardLHS."""
        return self._lhs

    @property
    def is_id(self) -> bool:
        """True when current lhs is an ID."""
        return isinstance(self.lhs, common.Identifier)


class ForwardElement(common.SwanItem):  # numpydoc ignore=PR01
    """Forward current element:

    *current_elt* ::= *current_lhs* = *expr* ;"""

    def __init__(self, lhs: ForwardLHS, expr: common.Expression) -> None:
        super().__init__()
        self._lhs = lhs
        self._expr = expr

    @property
    def lhs(self) -> ForwardLHS:
        """Current element."""
        return self._lhs

    @property
    def expr(self) -> common.Expression:
        """Current element expression."""
        return self._expr


class ForwardDim(common.SwanItem):  # numpydoc ignore=PR01
    """**forward** construct dimension:

    *dim* ::= << *expr* >> [[ **with** (( << *id* >> | *current_elt* )) {{ *current_elt* }} ]]

    Note that:

    - there may be no **with** part,
    - or it is an ID followed by a possible empty list,
    - or if no ID, at least one *current_element*.

    The *is_valid()* method checks for that property.

    Parameters
    ----------
    expr: common.Expression
       Dimension expression.

    id: common.Identifier (optional)
       **with** ID.

    elems: List[ForwardElement] (optional)
       **with** elements part.

    protected: str (optional)
        Content of the dimension if it syntactically incorrect.
        In that case, all other parameters are None.

    """

    def __init__(
        self,
        expr: Optional[common.Expression] = None,
        dim_id: Optional[common.Identifier] = None,
        elems: Optional[List[ForwardElement]] = None,
        protected: Optional[str] = None,
    ) -> None:
        super().__init__()
        self._expr = expr
        self._dim_id = dim_id
        self._elems = elems
        self._is_protected = protected is not None
        self._protected = protected

    @property
    def is_protected(self) -> bool:
        """True when dimension is syntactically incorrect and protected."""
        return self._is_protected

    @property
    def expr(self) -> common.Expression:
        """**forward** dimension expression."""
        return self._expr

    @property
    def dim_id(self) -> Union[common.Identifier, None]:
        """**forward** dimension ID, or None."""
        return self._dim_id

    @property
    def elems(self) -> Union[List[ForwardElement], None]:
        """**forward** dimension elements or None."""
        return self._elems

    @property
    def is_valid(self) -> bool:
        """Returns True when ID is given, or list of elements is not empty."""
        if self.is_protected:
            return False
        if self.dim_id:
            return True
        if (self.elems is None) or (len(self.elems) > 0):
            return True
        return False

    @property
    def protected(self) -> Optional[str]:
        """Returns protected form as a string if dimension is syntactically invalid."""
        return self._protected


class ForwardLastDefault(common.SwanItem):  # numpydoc ignore=PR01
    """**forward** construct: *last_default*.

    *last_default* ::= **last** = *expr*
                   | **default** = *expr*
                   | **last** = *expr* **default** = *expr*
                   | **last** = **default** = *expr*

    Parameters
    ----------
    last: common.Expression (optional)
        **last** expression.

    default: common.Expression (optional)
        **default** expression.

    shared: common.Expression (optional)
        **last** and **default** share the same expression.
        *shared* cannot be used with *last* or *default*.
    """

    def __init__(
        self,
        last: Optional[common.Expression] = None,
        default: Optional[common.Expression] = None,
        shared: Optional[common.Expression] = None,
    ) -> None:
        super().__init__()
        self._last = last
        self._default = default
        self._shared = shared
        if (shared and (last or default)) or not (shared or last or default):
            raise ScadeOneException("Invalid ForwardLastDefault construction")

    @property
    def is_shared(self) -> bool:
        """True when **last** = **default** = *expr*."""
        return self._shared is not None

    @property
    def last(self) -> Union[common.Expression, None]:
        """Returns **last** expression or shared one."""
        if self._last:
            return self._last
        return self._shared

    @property
    def default(self) -> Union[common.Expression, None]:
        """Returns **default** expression or shared one."""
        if self._default:
            return self._default
        return self._shared

    @property
    def shared(self) -> Union[common.Expression, None]:
        """Return **shared** expression."""
        return self._shared


class ForwardItemClause(common.SwanItem):  # numpydoc ignore=PR01
    """**forward** construct:

    *item_clause* ::= *id* [[ : *last_default* ]]"""

    def __init__(
        self, id: common.Identifier, last_default: Optional[ForwardLastDefault] = None
    ) -> None:
        super().__init__()
        self._id = id
        self._last_default = last_default

    @property
    def id(self) -> common.Identifier:
        """Item_clause identifier."""
        return self._id

    @property
    def last_default(self) -> Union[ForwardLastDefault, None]:
        """Item_clause last default."""
        return self._last_default


class ForwardArrayClause(common.SwanItem):  # numpydoc ignore=PR01
    """**forward** construct:

    *returns_clause* ::= (( *item_clause* | *array_clause* ))
    *array_clause* ::= [ *returns_clause* ]
    """

    def __init__(self, return_clause: Union[ForwardItemClause, "ForwardArrayClause"]) -> None:
        super().__init__()
        self._return_clause = return_clause

    @property
    def return_clause(self) -> Union[ForwardItemClause, "ForwardArrayClause"]:
        """Return *array_clause* content."""
        return self._return_clause


class ForwardReturnItem(common.SwanItem):  # numpydoc ignore=PR01
    """Base class for *returns_item*."""

    def __init__(self) -> None:
        super().__init__()


class ForwardReturnItemClause(ForwardReturnItem):  # numpydoc ignore=PR01
    """**forward** construct: *returns_item* ::= *item_clause*."""

    def __init__(self, item_clause: ForwardItemClause) -> None:
        super().__init__()
        self._item_clause = item_clause

    @property
    def item_clause(self) -> ForwardItemClause:
        """Item clause."""
        return self._item_clause


class ForwardReturnArrayClause(ForwardReturnItem):  # numpydoc ignore=PR01
    """**forward** construct:

    *returns_item* ::= [[ *id* = ]] *array_clause*"""

    def __init__(
        self,
        array_clause: ForwardArrayClause,
        return_id: Optional[common.Identifier] = None,
    ) -> None:
        super().__init__()
        self._array_clause = array_clause
        self._return_id = return_id

    @property
    def array_clause(self) -> ForwardArrayClause:
        """Array clause."""
        return self._array_clause

    @property
    def return_id(self) -> Union[common.Identifier, None]:
        """Identifier of clause, or None."""
        return self._return_id


class ProtectedForwardReturnItem(common.ProtectedItem, ForwardReturnItem):  # numpydoc ignore=PR01
    """**forward** construct: protected *returns_item* with {syntax% ... %syntax} markup."""

    def __init__(self, data: str) -> None:
        super().__init__(data)


class ForwardBody(common.SwanItem):  # numpydoc ignore=PR01
    """
    **forward** construct:

    fwd_body ::= [[ unless expr ]] scope_sections [[ until expr ]]
    """

    def __init__(
        self,
        body: List[scopes.ScopeSection],
        unless_expr: Optional[common.Expression] = None,
        until_expr: Optional[common.Expression] = None,
    ) -> None:
        super().__init__()
        self._body = body
        self._unless_expr = unless_expr
        self._until_expr = until_expr
        common.SwanItem.set_owner(self, body)

    @property
    def body(self) -> List[scopes.ScopeSection]:
        return self._body

    @property
    def unless_expr(self) -> Optional[common.Expression]:
        return self._unless_expr

    @property
    def until_expr(self) -> Optional[common.Expression]:
        return self._until_expr


class Forward(common.Expression):  # numpydoc ignore=PR01
    """Forward expression:

    | *fwd_expr* ::= **forward** [[ *luid*]] [[ (( **restart** | **resume** )) ]] {{ *dim* }}+
    |                *fwd_body* **returns** ( *returns_group* )
    | *returns_group* ::= [[ *returns_item* {{ , *returns_item* }} ]]
    """

    def __init__(
        self,
        restart: Optional[bool],
        dimensions: List[ForwardDim],
        body: ForwardBody,
        returns: List[ForwardReturnItem],
        luid: Optional[common.Luid] = None,
    ) -> None:
        super().__init__()
        self._restart = restart
        self._dimensions = dimensions
        self._body = body
        self._returns = returns
        self._luid = luid
        common.SwanItem.set_owner(self, body)

    @property
    def restart(self) -> Optional[bool]:
        return self._restart

    @property
    def is_restart(self) -> bool:
        """True if restart mode, else resume mode.
        Note: default mode for a forward is restart,
        which is indicated by the restart property being None."""
        return self._restart is None or self._restart

    @property
    def dimensions(self) -> List[ForwardDim]:
        return self._dimensions

    @property
    def body(self) -> ForwardBody:
        return self._body

    @property
    def returns(self) -> List[ForwardReturnItem]:
        return self._returns

    @property
    def luid(self) -> Union[common.Luid, None]:
        return self._luid
