@prefix cstr: <https://comp-rob2b.github.io/metamodels/task/constraint#> .
@prefix cstr-ext: <https://secorolab.github.io/metamodels/task/constraint#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix qudt: <http://qudt.org/schema/qudt/> .
@prefix qudt_quant: <http://qudt.org/vocab/quantitykind/> .
@prefix time: <http://www.w3.org/2006/time#> .
@prefix geom-coord: <https://comp-rob2b.github.io/metamodels/geometry/coordinates#> .

cstr-ext:ConstraintExpression
    a rdfs:Class, sh:NodeShape ;
    sh:message "A constraint expression must contain one or more atomic constraints or nested constraint expressions." ;
    sh:property [
        sh:path cstr-ext:has-constraint ;
        sh:minCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:or (
            [ sh:class cstr:Constraint ]
            [ sh:class cstr-ext:ConstraintConjunction ]
            [ sh:class cstr-ext:ConstraintDisjunction ]
        )
    ] .

cstr-ext:ConstraintConjunction
    a rdfs:Class, sh:NodeShape ;
    rdfs:subClassOf cstr-ext:ConstraintExpression ;
    sh:targetClass cstr-ext:ConstraintConjunction ;
    sh:message "A constraint conjunction must contain one or more atomic constraints or nested constraint expressions." ;
    sh:node cstr-ext:ConstraintExpression .

cstr-ext:ConstraintDisjunction
    a rdfs:Class, sh:NodeShape ;
    rdfs:subClassOf cstr-ext:ConstraintExpression ;
    sh:targetClass cstr-ext:ConstraintDisjunction ;
    sh:message "A constraint disjunction must contain one or more atomic constraints or nested constraint expressions." ;
    sh:node cstr-ext:ConstraintExpression .

# Operands compared against cstr:quantity; the classes below kind-check a tolerance with them.
_:ConstraintPaths
    sh:alternativePath (
        cstr:upper-threshold
        cstr:lower-threshold
        cstr:threshold
        cstr:reference-value
        cstr-ext:tolerance
    ) .

cstr-ext:OutsideConstraint
    a rdfs:Class, sh:NodeShape ;
    rdfs:subClassOf cstr:Constraint ;
    sh:targetClass cstr-ext:OutsideConstraint ;
    sh:message "Keeps its quantity outside the [lower, upper] band -- the complement of a bilateral constraint." ;
    sh:property [
        sh:path cstr:lower-threshold ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI
    ] ;
    sh:property [
        sh:path cstr:upper-threshold ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI
    ] .

cstr-ext:AngleConstraint
    a rdfs:Class, sh:NodeShape ;
    rdfs:subClassOf cstr:Constraint ;
    sh:targetClass cstr-ext:AngleConstraint ;
    sh:message "Constrains a plane-angle quantity against a plane-angle threshold." ;
    sh:property [
        sh:path cstr:quantity ;
        sh:minCount 1 ;
        sh:class qudt_quant:PlaneAngle
    ] ;
    sh:property [
        sh:path _:ConstraintPaths ;
        sh:minCount 1 ;
        sh:class qudt_quant:PlaneAngle
    ] .

cstr-ext:AngularDistanceConstraint
    a rdfs:Class, sh:NodeShape ;
    rdfs:subClassOf cstr:Constraint ;
    sh:targetClass cstr-ext:AngularDistanceConstraint ;
    sh:message "Constrains an angular distance (rotation angle) against a plane-angle threshold." ;
    sh:property [
        sh:path cstr:quantity ;
        sh:minCount 1 ;
        sh:class geom-coord:AngularDistanceCoordinate
    ] ;
    sh:property [
        sh:path _:ConstraintPaths ;
        sh:minCount 1 ;
        sh:class qudt_quant:PlaneAngle
    ] .

cstr-ext:OrientationConstraint
    a rdfs:Class, sh:NodeShape ;
    rdfs:subClassOf cstr:Constraint ;
    sh:targetClass cstr-ext:OrientationConstraint ;
    sh:message "Constrains an orientation against a reference orientation, within a plane-angle tolerance." ;
    sh:property [
        sh:path cstr:quantity ;
        sh:minCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI
    ] ;
    sh:property [
        sh:path cstr-ext:tolerance ;
        sh:class qudt_quant:PlaneAngle
    ] .

cstr-ext:TimeConstraint
    a rdfs:Class, sh:NodeShape ;
    rdfs:subClassOf cstr:Constraint ;
    sh:targetClass cstr-ext:TimeConstraint ;
    sh:message "Constrains a duration -- the interval between two instants -- against a duration threshold." ;
    # The elapsed side is runtime state, so it is a Time-kind quantity carrying the unit its
    # clock ticks in and no authored magnitude -- the same value-less quantity every other
    # measured operand in this metamodel is.
    sh:property [
        sh:path cstr:quantity ;
        sh:minCount 1 ;
        sh:class qudt:Quantity ;
        sh:node [
            sh:property [ sh:path qudt:hasQuantityKind ; sh:minCount 1 ; sh:hasValue qudt_quant:Time ] ;
            sh:property [ sh:path qudt:unit ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ] ;
            sh:property [ sh:path qudt:value ; sh:maxCount 0 ]
        ]
    ] ;
    sh:property [
        sh:path _:ConstraintPaths ;
        sh:minCount 1 ;
        sh:class time:Duration ;
        sh:node time:AuthoredDurationShape
    ] .

# A sampled clock never lands exactly on the reference, so an equality over a duration
# is only decidable with a band around it.
cstr-ext:TimeEqualityToleranceShape
    a sh:NodeShape ;
    sh:targetClass cstr-ext:TimeConstraint ;
    sh:message "An equality over a duration needs a tolerance." ;
    sh:or (
        [ sh:not [ sh:class cstr:EqualityConstraint ] ]
        [ sh:property [ sh:path cstr-ext:tolerance ; sh:minCount 1 ] ]
    ) .

# |error| <= tolerance: a specification band, not measurement uncertainty. Every constraint
# kind carries one. An equality's band is the only way its measure-zero target is ever met;
# a unilateral or bilateral gate's band is how close to the threshold counts as arrived,
# which a motion that only asymptotes onto its boundary needs just as much.
cstr-ext:ConstraintToleranceShape
    a sh:NodeShape ;
    sh:targetSubjectsOf cstr-ext:tolerance ;
    sh:message "Only a constraint takes a tolerance, and at most one." ;
    sh:class cstr:Constraint ;
    sh:property [
        sh:path cstr-ext:tolerance ;
        sh:maxCount 1
    ] .

cstr-ext:ToleranceShape
    a sh:NodeShape ;
    sh:targetObjectsOf cstr-ext:tolerance ;
    sh:message "A tolerance is a non-negative quantity carrying the kind and unit of the value it tolerances." ;
    sh:nodeKind sh:BlankNodeOrIRI ;
    sh:or (
        [
            sh:class qudt:Quantity ;
            sh:property [ sh:path qudt:hasQuantityKind ; sh:minCount 1 ; sh:maxCount 1 ] ;
            sh:property [ sh:path qudt:unit ; sh:minCount 1 ; sh:maxCount 1 ] ;
            sh:property [ sh:path qudt:value ; sh:minInclusive 0 ]
        ]
        [
            sh:class time:Duration ;
            sh:node time:AuthoredDurationShape ;
            sh:property [ sh:path qudt:value ; sh:minInclusive 0 ]
        ]
    ) .
