xquery version "3.0";
(:
    ART-DECOR® STANDARD COPYRIGHT AND LICENSE NOTE
    Copyright © ART-DECOR Expert Group and ART-DECOR Open Tools GmbH
    see https://docs.art-decor.org/copyright and https://docs.art-decor.org/licenses

    This file is part of the ART-DECOR® tools suite.
:)

import module namespace get             = "http://art-decor.org/ns/art-decor-settings" at "art-decor-settings.xqm";
import module namespace art             = "http://art-decor.org/ns/art" at  "art-decor.xqm";
import module namespace decor           = "http://art-decor.org/ns/decor" at "../api/api-decor.xqm";
import module namespace templ           = "http://art-decor.org/ns/decor/template" at "../api/api-decor-template.xqm";
import module namespace hist            = "http://art-decor.org/ns/decor/history" at "../api/api-decor-history.xqm";

declare %private function local:processElement($element as element(),$templateId as xs:string,$elementId as xs:string) as element()? {
    if ($element/@selected) then
        if ($element/name()='element') then
            <element>
            {
                (: attributes :)
                $element/@name[not(.='')],
                if ($element[@contains[not(.='')]]) then (
                    $element/@contains,
                    $element/@flexibility[. castable as xs:dateTime or . = 'dynamic']
                ) else ()
                ,
                if ($element/@datatype[not(.='')]) then (
                    $element/@datatype,
                    $element/@strength[not(.='')]
                ) else ()
                ,
                $element/@minimumMultiplicity[not(.='')],
                $element/@maximumMultiplicity[not(.='')],
                $element/@conformance[not(.=('','O'))],
                $element/@isMandatory[.='true'],
                $element/@isClosed[.=('true','false')],
                if ($element/@concept) then
                   attribute id {$elementId}
                else(
                    $element/@id[not(.='')]
                )
                ,
                (: this part is in the schema, but not implemented (yet) :)
                $element/@include[not(.='')],
                $element/@useWhere[not(.='')],
                $element/@displayName[not(.='')],
                $element/@statusCode[not(.='')],
                $element/@versionLabel[not(.='')],
                $element/@effectiveDate[. castable as xs:dateTime],
                $element/@expirationDate[. castable as xs:dateTime],
                $element/@officialReleaseDate[. castable as xs:dateTime]
            }
            {
                (: elements :)
                for $desc in $element/desc
                return
                art:parseNode($desc)
                ,
                for $item in $element/item[string-length(@label)>0]
                return
                    <item>
                    {
                        $item/@label,
                        for $desc in $item/desc[string-length(string-join(.//text(),''))>0]
                        return
                        art:parseNode($desc)
                    }
                    </item>
                ,
                for $example in $element/example[string-length(string-join(.//text(),''))>0]
                let $parsedExample  := art:parseNodeByType($example, 'xml')
                let $parsedExample  := 
                    if ($parsedExample/*:placeholder[namespace-uri() = 'urn:art-decor:example']) then $parsedExample/*:placeholder/node() else $parsedExample/node()
                return
                    <example>
                    {
                        $example/@*[not(.='')],
                        $parsedExample
                    }
                    </example>
                ,
                for $vocabulary in $element/vocabulary[@*[not(.='')]]
                return
                    <vocabulary>
                    {
                        if ($vocabulary/@valueSet[not(.='')]) then (
                            $vocabulary/@valueSet,
                            $vocabulary/@flexibility[. castable as xs:dateTime or . = 'dynamic']
                        ) 
                        else (
                            $vocabulary/@code[not(.='')],
                            $vocabulary/@codeSystem[not(.='')],
                            $vocabulary/@codeSystemName[not(.='')],
                            $vocabulary/@codeSystemVersion[not(.='')],
                            $vocabulary/@displayName[not(.='')]
                        )
                        ,
                        $vocabulary/@domain[not(.='')]
                        ,
                        $vocabulary/node()
                    }
                    </vocabulary>
                ,
                for $property in $element/property[@*[not(.='')]]
                return
                    <property>
                    {
                        $property/@*[not(.='')]
                    }
                    </property>
                ,
                $element/text
                ,
                for $attribute in $element/attribute
                return 
                    local:processAttribute($attribute,$templateId,$elementId)
                ,
                for $item in $element/(element|include|choice|let|assert|report|defineVariable|constraint)
                return
                    local:processElement($item,$templateId,$elementId)
            }
            </element>
        else if ($element/name()='choice') then
            <choice>
            {
                (: attributes :)
                $element/@minimumMultiplicity[not(.='')],
                $element/@maximumMultiplicity[not(.='')]
            }
            {
                (: elements :)
                for $desc in $element/desc
                return
                    art:parseNode($desc)
                ,
                for $item in $element/item[string-length(@label)>0]
                return
                    <item>
                    {
                        $item/@label,
                        for $desc in $item/desc[string-length(string-join(.//text(),''))>0]
                        return
                        art:parseNode($desc)
                    }
                    </item>
                ,
                for $item in $element/(element|include|constraint)
                return
                    local:processElement($item,$templateId,$elementId)
            }
            </choice>
        else if ($element/name()='include') then
            <include>
            {
                (: attributes :)
                if ($element[@ref[not(.='')]]) then (
                    $element/@ref,
                    $element/@flexibility[. castable as xs:dateTime or . = 'dynamic']
                ) else ()
                ,
                $element/@minimumMultiplicity[not(.='')],
                $element/@maximumMultiplicity[not(.='')],
                $element/@conformance[not(.=('','O'))],
                $element/@isMandatory[.='true'],
                (: this part is in the schema, but not implemented (yet) :)
                $element/@scenario[not(.='')],
                $element/@effectiveDate[. castable as xs:dateTime]
           }
           {
                (: elements :)
                for $desc in $element/desc
                return
                    art:parseNode($desc)
                ,
                for $item in $element/item[string-length(@label)>0]
                return
                    <item>
                    {
                        $item/@label,
                        for $desc in $item/desc[string-length(string-join(.//text(),''))>0]
                        return
                        art:parseNode($desc)
                    }
                    </item>
                ,
                for $example in $element/example[string-length(string-join(.//text(),''))>0]
                let $parsedExample  := art:parseNodeByType($example, 'xml')
                let $parsedExample  := 
                    if ($parsedExample/*:placeholder[namespace-uri() = 'urn:art-decor:example']) then $parsedExample/*:placeholder/node() else $parsedExample/node()
                return
                    <example>
                    {
                        $example/@*[not(.='')],
                        $parsedExample
                    }
                    </example>
                ,
                for $item in $element/constraint
                return
                    local:processElement($item,$templateId,$elementId)
            }
            </include>
        else if ($element/name()='let') then
            <let>
            {
                $element/@name,
                $element/@value,
                $element/node()
            }
            </let>
        else if ($element/name()='assert') then
            <assert>
            {
                $element/@role[not(.='')],
                $element/@test[not(.='')],
                $element/@flag[not(.='')],
                $element/@see[not(.='')],
                art:parseNodeByType($element, 'xml')/node()
            }
            </assert>
        else if ($element/name()='report') then
            <report>
            {
                $element/@role[not(.='')],
                $element/@test[not(.='')],
                $element/@flag[not(.='')],
                $element/@see[not(.='')],
                art:parseNodeByType($element, 'xml')/node()
            }
            </report>
        else if ($element/name()='defineVariable') then
            <defineVariable>
            {
                $element/@name[not(.='')],
                $element/@path[not(.='')],
                $element/node()
            }
            </defineVariable>
        else if ($element/name()='constraint') then
            <constraint>
            {
                $element/@language[not(.='')],
                $element/@lastTranslated[not(.='')],
                $element/@mimeType[not(.='')],
                art:parseNode($element)/node()
            }
            </constraint>
        else()
    else ()
};

declare %private function local:processAttribute($attribute as element(),$templateId as xs:string,$elementId as xs:string) as element()? {
    if ($attribute/@selected) then
        <attribute>
        {
            $attribute/(@name|@classCode|@contextConductionInd|@contextControlCode|
                        @determinerCode|@extension|@independentInd|@institutionSpecified|
                        @inversionInd|@mediaType|@moodCode|@negationInd|
                        @nullFlavor|@operator|@qualifier|@representation|
                        @root|@typeCode|@unit|@use)[not(.='')]
            ,
            if ($attribute[@name='root'][matches(@value,'[a-zA-Z]')]/parent::element/@name[matches(.,'([^:]+:)?templateId')]) then
                attribute value {$templateId}
            else (
                $attribute/@value[not(.='')]
            )
            ,
            if ($attribute/@datatype[not(.='')]) then (
                $attribute/@datatype,
                $attribute/@strength[not(.='')]
            ) else ()
            ,
            if ($attribute/@conf='prohibited' or $attribute/@conformance='NP') then
                attribute prohibited {'true'}
            else 
            if ($attribute/@conf='isOptional' or $attribute/@conformance='O') then
                attribute isOptional {'true'}
            else ()
            ,
            if ($attribute/@concept) then
                attribute id {$elementId}
            else(
                $attribute/@id[not(.='')]
            )
            ,
            for $desc in $attribute/desc
            return
                art:parseNode($desc)
            ,
            for $item in $attribute/item[@label[not(.='')]]
            return
                <item>
                {
                    $item/@label,
                    for $desc in $item/desc[string-length(string-join(.//text(),''))>0]
                    return
                    art:parseNode($desc)
                }
                </item>
            ,
            for $vocabulary in $attribute/vocabulary[@*[not(.='')]]
            return
                <vocabulary>
                {
                    if ($vocabulary/@valueSet[not(.='')]) then (
                        $vocabulary/@valueSet,
                        $vocabulary/@flexibility[. castable as xs:dateTime or . = 'dynamic']
                    ) 
                    else (
                        $vocabulary/@code[not(.='')],
                        $vocabulary/@codeSystem[not(.='')],
                        $vocabulary/@codeSystemName[not(.='')],
                        $vocabulary/@codeSystemVersion[not(.='')],
                        $vocabulary/@displayName[not(.='')]
                    )
                    ,
                    $vocabulary/@domain[not(.='')]
                    ,
                    $vocabulary/node()
                }
                </vocabulary>
        }
        </attribute>
    else ()
};

let $inputTemplate      := if (request:exists()) then request:get-data()/template else (
<template projectPrefix="demo1-" baseId="2.16.840.1.113883.3.1937.99.62.3.25" statusCode="new" id="2.16.840.1.113883.3.1937.99.62.3.10" originalId="1.2.999.1" name="ttttttt" displayName="tttt" effectiveDate="2016-12-01T13:07:51" ident="demo1-" isClosed="false" versionLabel="" canonicalUri="">
    <desc language="nl-NL"/>
    <context id="" path="" selected=""/>
    <example type="" caption=""/>
</template>
)
let $mode               := if (request:exists()) then request:get-parameter('mode','') else ('new')
let $deleteLock         := if (request:exists()) then not(request:get-parameter('deletelock','true')='false') else (true())

let $update             :=
    if ($inputTemplate) then () else (
        error(QName('http://art-decor.org/ns/art-decor-permissions', 'NoTemplate'), 'There''s no template to create or save.')
    )
let $update             :=
    if ($mode=('new','edit','version','adapt')) then () else (
        error(QName('http://art-decor.org/ns/art-decor-permissions', 'UnsupportedMode'), concat('This mode is unsupported. ''',$mode,''''))
    )

(:get decor file from project prefix:)
let $decor              := art:getDecorByPrefix($inputTemplate/@projectPrefix)
(: get user for permission check:)
let $user               := get:strCurrentUserName()

let $originalId         := $inputTemplate/@originalId
let $currentTemplate    := $decor/rules/template[@id = $originalId][@effectiveDate = $inputTemplate/@effectiveDate]

let $locks              := decor:getLocks($inputTemplate/lock/@ref, $inputTemplate/lock/@effectiveDate, (), true())

(:relevant only for mode=new and when a concept is connected somewhere, but doesn't hurt to generate for all cases:)
let $newTemplateElementId   := 
    if (decor:authorCanEditP($decor, $decor:SECTION-RULES)) then (
        if ($locks or not($mode='edit')) then (
            let $templateElementRoot    := decor:getDefaultBaseIdsP($decor, $decor:OBJECTTYPE-TEMPLATEELEMENT)/@id
            let $templateElementIdsAll  := $decor//@id[matches(.,concat('^',$templateElementRoot,'\.\d+$'))]
            
            return
                if ($templateElementIdsAll) then (
                    concat($templateElementRoot,'.',max($templateElementIdsAll/xs:integer(tokenize(.,'\.')[last()]))+1)
                )
                else (
                    concat($templateElementRoot,'.',1)
                )
        ) else ()
    ) else ()

let $preparedTemplate   :=
    (:check if user id author in project:)
    if (decor:authorCanEditP($decor, $decor:SECTION-RULES)) then (
        if ($locks or not($mode='edit')) then (
            (:if a baseId is present use it, else use default template baseId:)
            let $templateRoot           :=
                if (string-length($inputTemplate/@baseId)>0) then
                    $inputTemplate/@baseId/string()
                else (
                    decor:getDefaultBaseIdsP($decor, $decor:OBJECTTYPE-TEMPLATE)[1]/@id
                )
            let $templateIdsAll         := 
                $decor//@id[matches(.,concat('^',$templateRoot,'\.\d+$'))]
            
            (:generate new id if mode is 'new' or 'adapt', else use existing id:)
            let $newTemplateId             := 
                if ($mode=('new','adapt')) then
                    if ($templateIdsAll) then
                        concat($templateRoot,'.',max($templateIdsAll/xs:integer(tokenize(.,'\.')[last()]))+1)
                    else (
                        concat($templateRoot,'.',1)
                    )
                else (
                    $inputTemplate/@id
                )
            
            let $templateDisplayName    :=
                if ($inputTemplate/@displayName[not(.='')]) then 
                    $inputTemplate/@displayName
                else (
                    $inputTemplate/@name
                )
            (:keep effectiveDate for mode is 'edit', else generate new:)
            let $templateEffectiveTime  := 
                if ($mode=('edit') and $currentTemplate[@effectiveDate castable as xs:dateTime]) then
                    $currentTemplate/@effectiveDate
                else (
                    format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]')
                )
            (:set draft effectiveDate for mode is 'edit', else generate new:)
            let $templateStatusCode     := 
                if ($inputTemplate/@statusCode[.=('','new')]) then
                    'draft'
                else (
                    $inputTemplate/@statusCode
                )
            
            return (
                <template>
                {
                    attribute id {$newTemplateId},
                    attribute name {$inputTemplate/@name},
                    attribute displayName {$templateDisplayName},
                    attribute effectiveDate {$templateEffectiveTime},
                    attribute statusCode {$templateStatusCode},
                    $inputTemplate/@isClosed[not(.='')],
                    $inputTemplate/@expirationDate[not(.='')],
                    $inputTemplate/@officialReleaseDate[not(.='')],
                    $inputTemplate/@versionLabel[not(.='')],
                    $inputTemplate/@canonicalUri[not(.='')],
                    attribute lastModifiedDate {substring(string(current-dateTime()), 1, 19)}
                    ,
                    for $desc in $inputTemplate/desc
                    return
                        art:parseNode($desc)
                    ,
                    for $classification in $inputTemplate/classification[@*[not(.='')]]
                    return
                        <classification>
                        {
                            $classification/@*[not(.='')],
                            $classification/tag[not(.='')],
                            $classification/property[not(.='')]
                        }
                        </classification>
                    ,
                    for $relationship in $inputTemplate/relationship
                    return
                        <relationship>
                        {
                            $relationship/@type,
                            $relationship/@*[name()=$relationship/@selected],
                            $relationship/@flexibility[not(.='')]
                        }
                        </relationship>
                    ,
                    if ($inputTemplate/context[@selected = 'id'][@id[not(. = '')]]) then
                        <context>{$inputTemplate/context[1]/@id}</context>
                    else
                    if ($inputTemplate/context[@selected = 'path'][@path[not(. = '')]]) then
                        <context>{$inputTemplate/context[1]/@path}</context>
                    else ()
                    ,
                    for $item in $inputTemplate/item[string-length(@label)>0]
                    return
                        <item>
                        {
                            $item/@label,
                            for $desc in $item/desc[string-length(string-join(.//text(),''))>0]
                            return
                            art:parseNode($desc)
                        }
                        </item>
                    ,
                    for $example in $inputTemplate/example[string-length(string-join(.//text(),''))>0]
                    let $parsedExample  := art:parseNodeByType($example, 'xml')
                    let $parsedExample  := 
                        if ($parsedExample/*:placeholder[namespace-uri() = 'urn:art-decor:example']) then $parsedExample/*:placeholder/node() else $parsedExample/node()
                    return
                        <example>
                        {
                            $example/@*[not(.='')],
                            $parsedExample
                        }
                        </example>
                    ,
                    for $authority in $inputTemplate/publishingAuthority[string-length(@name)>0][empty(@inherited)]
                    return
                        <publishingAuthority>
                        {
                            $authority/(@id|@name)[not(.='')],
                            for $addrLine in $authority/addrLine[string-length(string-join(.//text(),''))>0]
                            return
                                <addrLine>
                                {
                                    $addrLine/@type[not(.='')],
                                    $addrLine/node()
                                }
                                </addrLine>
                        }
                        </publishingAuthority>
                    ,
                    for $node in $inputTemplate/purpose
                    return
                        art:parseNode($node)
                    ,
                    for $node in $inputTemplate/copyright[empty(@inherited)]
                    return
                        art:parseNode($node)
                    ,
                    for $authority in $inputTemplate/endorsingAuthority[string-length(@name)>0][empty(@inherited)]
                    return
                        <endorsingAuthority>
                        {
                            $authority/(@id|@name)[not(.='')],
                            for $addrLine in $authority/addrLine[string-length(string-join(.//text(),''))>0]
                            return
                                <addrLine>
                                {
                                    $addrLine/@type[not(.='')],
                                    $addrLine/node()
                                }
                                </addrLine>
                        }
                        </endorsingAuthority>
                    ,
                    
                    for $history in $inputTemplate/revisionHistory[string-length(string-join(.//text(),''))>0]
                    return
                        <revisionHistory>
                        {
                            $history/@*[not(.='')],
                            art:parseNode($history)/node()
                        }
                        </revisionHistory>
                    ,
                    for $attribute in $inputTemplate/attribute
                    return
                        local:processAttribute($attribute, $newTemplateId, $newTemplateElementId)
                    ,
                    for $item in $inputTemplate/(element|include|choice|let|assert|report|defineVariable|constraint)
                    return
                        local:processElement($item, $newTemplateId, $newTemplateElementId)
                }
                </template>
            )
        )
        else if ($locks) then (
            error(QName('http://art-decor.org/ns/art-decor-permissions', 'UnsupportedMode'), concat('This mode is unsupported. ''',$mode,''''))
        )
        else (
            error(QName('http://art-decor.org/ns/art-decor-permissions', 'NoPermission'), 'You don''t have permission to save this template. You''re missing a lock')
        )
    )
    else (
        error(QName('http://art-decor.org/ns/art-decor-permissions', 'NoPermission'), concat('You don''t have permission to save this template as user ''',get:strCurrentUserName(),''''))
    )

(: Note: this step also rolls up multiple templateAssociation elements into one. This is unlikely to occur unless manually. :)
let $relatedTemplateAssociations    := $decor//templateAssociation[@templateId = $originalId][@effectiveDate = $inputTemplate/@effectiveDate]
let $preparedTemplateAssociation    :=
    <templateAssociation templateId="{$preparedTemplate/@id}" effectiveDate="{$preparedTemplate/@effectiveDate}">
    {
        (:  keep all concepts associated with element/@id | attribute/@id in the prepared template :)
        for $association in ($relatedTemplateAssociations/concept[@elementId = ($preparedTemplate//element/@id | $preparedTemplate//attribute/@id)] |
                             $inputTemplate/staticAssociations/*[@elementId = ($preparedTemplate//element/@id | $preparedTemplate//attribute/@id)])
        let $deid   := $association/@ref
        let $deed   := $association/@effectiveDate
        let $elid   := $association/@elementId
        let $key    := concat($deid, $deed, $elid)
        group by $key
        return
            <concept ref="{$deid[1]}" effectiveDate="{$deed[1]}" elementId="{$elid[1]}"/>
        ,
        (:if mode is 'new' and conceptId is present, create new templateAssociation for concept:)
        if ($mode='new' and $inputTemplate[@conceptId]//@concept) then
            <concept ref="{$inputTemplate/@conceptId}" effectiveDate="{$inputTemplate/@conceptEffectiveDate}" elementId="{$newTemplateElementId}"/>
        else ()
    }
    </templateAssociation>

let $update                 :=
    if ($preparedTemplate) then
        <update>
        {
            (: ======= do history first ======= :)
            let $intention          :=
                if ($currentTemplate[@statusCode='active'][@effectiveDate = $inputTemplate/@effectiveDate]) then 'patch' else 'version'
            let $history            :=
                if ($currentTemplate) then 
                    hist:AddHistory ($decor:OBJECTTYPE-TEMPLATE, $inputTemplate/@projectPrefix, $intention, $currentTemplate)
                else ()
            let $savetemplate       := 
                if ($currentTemplate) then (
                    if ($mode='edit') then 
                        (:if we're editing, then overwrite the previous version:)
                        update replace $currentTemplate with $preparedTemplate
                    else (
                        (:if this is a new version, insert below old version:)
                        update insert $preparedTemplate following $currentTemplate
                    )
                )
                else if ($decor/rules/template) then (
                    update insert $preparedTemplate following $decor/rules/template[last()]
                )
                else if ($decor/rules) then (
                    update insert $preparedTemplate into $decor/rules
                )
                else (
                    update insert <rules>{$preparedTemplate}</rules> following $decor/terminology
                )
            let $addids             :=
                templ:addTemplateElementAndAttributeIds($decor, $preparedTemplate/@id, ($preparedTemplate/@effectiveDate)[1])
            
            (: Add templateAssociation element with 0..* concept refs. Remove any existing first. :)
            let $currentTemplate    := $decor/rules/template[@id = $preparedTemplate/@id][@effectiveDate = $preparedTemplate/@effectiveDate]
            let $saveassocs         := 
                if ($relatedTemplateAssociations) then (
                    if ($mode = 'edit') then 
                        update delete $relatedTemplateAssociations
                    else (
                        update delete $relatedTemplateAssociations[@templateId = $preparedTemplateAssociation/@templateId][@effectiveDate = $preparedTemplateAssociation/@effectiveDate]
                    ),
                    update insert $preparedTemplateAssociation preceding $currentTemplate
                )
                else (
                    update insert text {'&#x0a;        '} preceding $currentTemplate,
                    update insert comment {concat(' ',$preparedTemplate/@displayName,' ')} preceding $currentTemplate,
                    update insert $preparedTemplateAssociation preceding $currentTemplate
                )
            
            let $dellocks       :=
                if ($inputTemplate[lock]) then 
                    if ($deleteLock) then 
                        decor:deleteLock($inputTemplate/lock/@ref, $inputTemplate/lock/@effectiveDate, ()) 
                    else ()
                else ()
            
            return ()
        }
        </update>
    else (
        error(QName('http://art-decor.org/ns/art-decor-permissions', 'GenericFailure'), 'No template to save.')
    )

return
<data-safe id="{$preparedTemplate/@id}" effectiveDate="{($preparedTemplate/@effectiveDate)[1]}" input-id="{$inputTemplate/@id}" input-effectiveDate="{$inputTemplate/@effectiveDate}" mode="{$mode}">true</data-safe>