# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact https://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <https://www.gnu.org/licenses/>.
"""workflow related schemas"""

from yams.buildobjs import (
    EntityType,
    RelationType,
    RelationDefinition,
    SubjectRelation,
    RichString,
    String,
    Int,
)

from cubicweb import _
from cubicweb.schema import RQLConstraint
from cubicweb.schemas import PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, RO_REL_PERMS


class Workflow(EntityType):
    __permissions__ = PUB_SYSTEM_ENTITY_PERMS

    name = String(required=True, indexed=True, internationalizable=True, maxsize=256)
    description = RichString(
        default_format="text/rest",
        description=_("semantic description of this workflow"),
    )

    workflow_of = SubjectRelation(
        "CWEType",
        cardinality="+*",
        description=_("entity types which may use this workflow"),
        constraints=[RQLConstraint("O final FALSE")],
    )

    initial_state = SubjectRelation(
        "State",
        cardinality="?*",
        constraints=[
            RQLConstraint(
                "O state_of S", msg=_("state doesn't belong to this workflow")
            )
        ],
        description=_("initial state for this workflow"),
    )


class default_workflow(RelationType):
    """default workflow for an entity type"""

    __permissions__ = PUB_SYSTEM_REL_PERMS

    subject = "CWEType"
    object = "Workflow"
    cardinality = "?*"
    constraints = [
        RQLConstraint(
            "S final FALSE, O workflow_of S",
            msg=_("workflow isn't a workflow for this type"),
        )
    ]


class State(EntityType):
    """used to associate simple states to an entity type and/or to define
    workflows
    """

    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
    __unique_together__ = [("name", "state_of")]
    name = String(required=True, indexed=True, internationalizable=True, maxsize=256)
    description = RichString(
        default_format="text/rest", description=_("semantic description of this state")
    )

    # XXX should be on BaseTransition w/ AND/OR selectors when we will
    # implements #345274
    allowed_transition = SubjectRelation(
        "BaseTransition",
        cardinality="**",
        constraints=[
            RQLConstraint(
                "S state_of WF, O transition_of WF",
                msg=_("state and transition don't belong the the same workflow"),
            )
        ],
        description=_("allowed transitions from this state"),
    )
    state_of = SubjectRelation(
        "Workflow",
        cardinality="1*",
        composite="object",
        inlined=True,
        description=_("workflow to which this state belongs"),
    )


class BaseTransition(EntityType):
    """abstract base class for transitions"""

    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
    __unique_together__ = [("name", "transition_of")]

    name = String(required=True, indexed=True, internationalizable=True, maxsize=256)
    type = String(vocabulary=(_("normal"), _("auto")), default="normal")
    description = RichString(description=_("semantic description of this transition"))

    transition_of = SubjectRelation(
        "Workflow",
        cardinality="1*",
        composite="object",
        inlined=True,
        description=_("workflow to which this transition belongs"),
    )


class require_group(RelationDefinition):
    """group in which a user should be to be allowed to pass this transition"""

    __permissions__ = PUB_SYSTEM_REL_PERMS
    subject = "BaseTransition"
    object = "CWGroup"


class condition(RelationDefinition):
    """a RQL expression which should return some results, else the transition
    won't be available.

    This query may use X and U variables that will respectivly represents the
    current entity and the current user.
    """

    __permissions__ = PUB_SYSTEM_REL_PERMS
    subject = "BaseTransition"
    object = "RQLExpression"
    cardinality = "*?"
    composite = "subject"


class Transition(BaseTransition):
    """use to define a transition from one or multiple states to a destination
    states in workflow's definitions. Transition without destination state will
    go back to the state from which we arrived to the current state.
    """

    __specializes_schema__ = True

    destination_state = SubjectRelation(
        "State",
        cardinality="?*",
        constraints=[
            RQLConstraint(
                "S transition_of WF, O state_of WF",
                msg=_("state and transition don't belong the the same workflow"),
            )
        ],
        description=_("destination state for this transition"),
    )


class WorkflowTransition(BaseTransition):
    """special transition allowing to go through a sub-workflow"""

    __specializes_schema__ = True

    subworkflow = SubjectRelation(
        "Workflow",
        cardinality="1*",
        constraints=[
            RQLConstraint(
                "S transition_of WF, WF workflow_of ET, O workflow_of ET",
                msg=_(
                    "subworkflow isn't a workflow for the same types as the transition's workflow"
                ),
            )
        ],
    )
    # XXX use exit_of and inline it
    subworkflow_exit = SubjectRelation(
        "SubWorkflowExitPoint", cardinality="*1", composite="subject"
    )


class SubWorkflowExitPoint(EntityType):
    """define how we get out from a sub-workflow"""

    subworkflow_state = SubjectRelation(
        "State",
        cardinality="1*",
        constraints=[
            RQLConstraint(
                "T subworkflow_exit S, T subworkflow WF, O state_of WF",
                msg=_("exit state must be a subworkflow state"),
            )
        ],
        description=_("subworkflow state"),
    )
    destination_state = SubjectRelation(
        "State",
        cardinality="?*",
        constraints=[
            RQLConstraint(
                "T subworkflow_exit S, T transition_of WF, O state_of WF",
                msg=_(
                    "destination state must be in the same workflow as our parent transition"
                ),
            )
        ],
        description=_(
            "destination state. No destination state means that transition "
            "should go back to the state from which we've entered the "
            "subworkflow."
        ),
    )


class TrInfo(EntityType):
    """workflow history item"""

    # 'add' security actually done by hooks
    __permissions__ = {
        "read": (
            "managers",
            "users",
            "guests",
        ),  # XXX U has_read_permission O ?
        "add": (
            "managers",
            "users",
            "guests",
        ),
        "delete": (),  # XXX should we allow managers to delete TrInfo?
        "update": (
            "managers",
            "owners",
        ),
    }
    # The unique_together constraint ensures that 2 repositories
    # sharing the db won't be able to fire a transition simultaneously
    # on the same entity tr_count is filled in the FireTransitionHook
    # to the number of TrInfo attached to the entity on which we
    # attempt to fire a transition. In other word, it contains the
    # rank of the TrInfo for that entity, and the constraint says we
    # cannot have 2 TrInfo with the same rank.
    __unique_together__ = [("tr_count", "wf_info_for")]
    from_state = SubjectRelation("State", cardinality="1*", inlined=True)
    to_state = SubjectRelation("State", cardinality="1*", inlined=True)
    # make by_transition optional because we want to allow managers to set
    # entity into an arbitrary state without having to respect wf transition
    by_transition = SubjectRelation("BaseTransition", cardinality="?*")
    comment = RichString(fulltextindexed=True, default_format="text/plain")
    tr_count = Int(
        description="autocomputed attribute used to ensure transition coherency"
    )
    # get actor and date time using owned_by and creation_date


class from_state(RelationType):
    __permissions__ = RO_REL_PERMS.copy()
    inlined = True


class to_state(RelationType):
    __permissions__ = RO_REL_PERMS.copy()
    inlined = True


class by_transition(RelationType):
    # 'add' security actually done by hooks
    __permissions__ = {
        "read": (
            "managers",
            "users",
            "guests",
        ),
        "add": (
            "managers",
            "users",
            "guests",
        ),
        "delete": (),
    }
    inlined = True


class workflow_of(RelationType):
    """link a workflow to one or more entity type"""

    __permissions__ = PUB_SYSTEM_REL_PERMS


class state_of(RelationType):
    """link a state to one or more workflow"""

    __permissions__ = PUB_SYSTEM_REL_PERMS
    inlined = True


class transition_of(RelationType):
    """link a transition to one or more workflow"""

    __permissions__ = PUB_SYSTEM_REL_PERMS
    inlined = True


class destination_state(RelationType):
    """destination state of a transition"""

    __permissions__ = PUB_SYSTEM_REL_PERMS
    inlined = True


class allowed_transition(RelationType):
    """allowed transitions from this state"""

    __permissions__ = PUB_SYSTEM_REL_PERMS


class initial_state(RelationType):
    """indicate which state should be used by default when an entity using
    states is created
    """

    __permissions__ = PUB_SYSTEM_REL_PERMS
    inlined = True


class subworkflow(RelationType):
    __permissions__ = PUB_SYSTEM_REL_PERMS
    inlined = True


class exit_point(RelationType):
    __permissions__ = PUB_SYSTEM_REL_PERMS


class subworkflow_state(RelationType):
    __permissions__ = PUB_SYSTEM_REL_PERMS
    inlined = True


# "abstract" relations, set by WorkflowableEntityType ##########################


class custom_workflow(RelationType):
    """allow to set a specific workflow for an entity"""

    __permissions__ = PUB_SYSTEM_REL_PERMS

    cardinality = "?*"
    constraints = [
        RQLConstraint(
            "S is ET, O workflow_of ET",
            msg=_("workflow isn't a workflow for this type"),
        )
    ]
    object = "Workflow"


class wf_info_for(RelationType):
    """link a transition information to its object"""

    # 'add' security actually done by hooks
    __permissions__ = {
        "read": (
            "managers",
            "users",
            "guests",
        ),
        "add": (
            "managers",
            "users",
            "guests",
        ),
        "delete": (),
    }
    inlined = True

    cardinality = "1*"
    composite = "object"
    fulltext_container = composite
    subject = "TrInfo"


class in_state(RelationType):
    """indicate the current state of an entity"""

    __permissions__ = RO_REL_PERMS

    # not inlined intentionnally since when using ldap sources, user'state
    # has to be stored outside the CWUser table
    inlined = False

    cardinality = "1*"
    constraints = [
        RQLConstraint(
            "S is ET, O state_of WF, WF workflow_of ET",
            msg=_("state doesn't apply to this entity's type"),
        )
    ]
    object = "State"
