xquery version "1.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 hist            = "http://art-decor.org/ns/decor/history" at "../api/api-decor-history.xqm";

(: get dataset from request :)
let $editedDataset      := if (request:exists()) then (request:get-data()/dataset) else ()
(: dataset stored in DB :)
let $storedDataset      := art:getDataset($editedDataset/@id, $editedDataset/@effectiveDate)
(: the whole decor resource:)
let $decor              := $storedDataset/ancestor::decor
(: project prefix :)
let $projectPrefix      := $decor/project/@prefix


(: save history
:)
(:let $intention                      :=
    if ($storedDataset[@statusCode='active'][@effectiveDate = $editedDataset/@effectiveDate]) then 'patch' else 'version'
let $history                        :=
    if ($storedDataset) then 
        hist:AddHistory ($decor:OBJECTTYPE-DATASET, $projectPrefix, $intention, $storedDataset)
    else ():)
    
(:
   start with deleting concepts:
   if statusCode=new     delete concept
   if statusCode!=new    set statusCode=deprecated
:)
let $deletes           :=
    for $concept in $editedDataset//concept[edit/@mode='delete']
    let $storedConcept := $storedDataset//concept[@id=$concept/@id][@effectiveDate=$concept/@effectiveDate][not(ancestor::history)]
    let $lock          := decor:getLocks($concept/@id, $concept/@effectiveDate, (), true())
    return
        if ($editedDataset/@statusCode!='new') then
            if ($storedConcept/@statusCode='new' and $lock) then (
                hist:AddHistory($decor:OBJECTTYPE-DATASETCONCEPT, $projectPrefix, 'delete', $storedConcept),
                update delete $storedConcept,
                update delete $lock
            )
            else 
            if ($storedConcept/@statusCode!='new' and $lock) then (
                if ($concept/@type='item') then (
                    hist:AddHistory($decor:OBJECTTYPE-DATASETCONCEPT, $projectPrefix, 'delete', $storedConcept),
                    update replace $storedConcept with art:prepareItemForUpdate($concept, $storedConcept),
                    update delete $lock
                )
                else if ($concept/@type='group') then (
                    hist:AddHistory($decor:OBJECTTYPE-DATASETCONCEPT, $projectPrefix, 'delete', $storedConcept),
                    update replace $storedConcept with art:prepareGroupForUpdate($concept, $storedConcept),
                    update delete $lock
                ) else ()
            ) 
            else ()
        else (
            if ($lock) then (
                hist:AddHistory($decor:OBJECTTYPE-DATASETCONCEPT, $projectPrefix, 'delete', $storedConcept),
                update delete $storedConcept,
                update delete $lock
            ) else ()
        )
(:
   move concepts
   - local move
      - delete stored concept
      - insert moved concept after editedDataset preceding-sibling or into parent dataset/concept
   - move to other dataset (unsupported!!)
:)
let $moves :=
    for $concept in $editedDataset//concept[move]
    let $de-id              := $concept/@id
    let $de-ed              := $concept/@effectiveDate
    let $storedConcept      := $storedDataset//concept[@id = $de-id][@effectiveDate = $de-ed][not(ancestor::history)]
    
    let $sourceHistory      := $storedConcept/parent::*
    let $sourceType         := if (name($sourceHistory) = 'dataset') then $decor:OBJECTTYPE-DATASET else $decor:OBJECTTYPE-DATASETCONCEPT
    let $sourceIntention    := if ($concept[edit/@mode = 'edit']) then 'move/version' else 'move'
    
    let $lock               := decor:getLocks($de-id, $de-ed, (), true())
    let $check              :=
        if ($lock) then () else (
            error(QName('http://art-decor.org/ns/error', 'InconsistentConcept'), concat('Concept id=''',$de-id,''' and effectiveDate=''',$de-ed,''' does not have lock for current user (anymore)'))
        )
    let $check              :=
        if (count($storedConcept)=1) then () else (
            error(QName('http://art-decor.org/ns/error', 'InconsistentConcept'), concat('Concept id=''',$de-id,''' and effectiveDate=''',$de-ed,''' gave ',count($storedConcept),' stored concepts. Expected exactly 1.', if (count($storedConcept)=0) then () else concat(' Found concept in projects: ',string-join(for $s in $storedConcept return $s/ancestor::decor/project/@prefix,' / '))))
        )
    (:  if move + edit: use what was sent to us
        if just move: use what we already had stored on the server
    :)
    let $preparedConcept    :=
        if ($concept[@type = 'item']) then
            if ($concept[edit/@mode='edit']) then
                art:prepareItemForUpdate($concept, $storedConcept)
            else
                art:prepareItemForUpdate($storedConcept, $storedConcept)
        else
        if ($concept[@type = 'group']) then
            if ($concept[edit/@mode='edit']) then
                art:prepareGroupForUpdate($concept, $storedConcept)
            else
                art:prepareGroupForUpdate($storedConcept, $storedConcept)
        else (
            (:huh? ... :)
        )
    let $check              :=
        if (count($preparedConcept)=1) then () else (
            error(QName('http://art-decor.org/ns/error', 'InconsistentConcept'), concat('Concept id=''',$de-id,''' and effectiveDate=''',$de-ed,''' has unknown type ',$concept/@type,'. Expected item or group.'))
        )
    return
        <update>
        {
            $de-id, $de-ed,
            (: preceding-concept should already be at the correct position as it was processed in the previous 
               iteration of the 'for' loop
            :)
            if ($concept[preceding-sibling::concept]) then (
                let $destHistory    := $storedDataset//concept[@id = $concept/@id][not(ancestor::history)]/parent::*
                let $destType       := if (name($destHistory) = 'dataset') then $decor:OBJECTTYPE-DATASET else $decor:OBJECTTYPE-DATASETCONCEPT
                return (
                    hist:AddHistory($sourceType, $projectPrefix, $sourceIntention, $sourceHistory),
                    hist:AddHistory($destType, $projectPrefix, $sourceIntention, $destHistory),
                    update insert $preparedConcept following $storedDataset//concept[@id = $concept/preceding-sibling::concept[1]/@id][not(ancestor::history)]
                )
            )
            else 
            if ($concept[parent::dataset]) then (
                if ($storedDataset[concept | history]) then (
                    let $destHistory    := $storedDataset
                    let $destType       := if (name($destHistory) = 'dataset') then $decor:OBJECTTYPE-DATASET else $decor:OBJECTTYPE-DATASETCONCEPT
                    return (
                        hist:AddHistory($sourceType, $projectPrefix, $sourceIntention, $sourceHistory),
                        hist:AddHistory($destType, $projectPrefix, $sourceIntention, $destHistory),
                        (: top level concept in dataset, but others already exist in stored dataset :)
                        update insert $preparedConcept preceding ($storedDataset/concept | $storedDataset/history)[1]
                    )
                )
                else (
                    let $destHistory    := $storedDataset
                    let $destType       := if (name($destHistory) = 'dataset') then $decor:OBJECTTYPE-DATASET else $decor:OBJECTTYPE-DATASETCONCEPT
                    return (
                        hist:AddHistory($sourceType, $projectPrefix, $sourceIntention, $sourceHistory),
                        hist:AddHistory($destType, $projectPrefix, $sourceIntention, $destHistory),
                        (: top level concept in dataset, and no others exist in stored dataset :)
                        update insert $preparedConcept into $storedDataset
                    )
                )
            )
            else 
            if ($concept[parent::concept] and $storedDataset//concept[@id = $concept/parent::concept/@id][not(ancestor::history)]) then (
                let $storedParent   := $storedDataset//concept[@id = $concept/parent::concept/@id][not(ancestor::history)]
                let $destHistory    := $storedParent
                let $destType       := if (name($destHistory) = 'dataset') then $decor:OBJECTTYPE-DATASET else $decor:OBJECTTYPE-DATASETCONCEPT
                return (
                    hist:AddHistory($sourceType, $projectPrefix, $sourceIntention, $sourceHistory),
                    hist:AddHistory($destType, $projectPrefix, $sourceIntention, $destHistory),
                    if ($storedParent[concept | history]) then
                        (: first concept in concept group, but others already exist in stored dataset concept group :)
                        update insert $preparedConcept preceding ($storedParent/concept | $storedParent/history)[1]
                    else (
                        (: first concept in concept group, and no others exist in stored dataset concept group :)
                        update insert $preparedConcept into $storedParent
                    )
                )
            )
            else (
                error(QName('http://art-decor.org/ns/art/dataset', 'ContextNotFound'), concat('Could not determine context for saving concept with id ',$concept/@id,' and name ',$concept/name[1]))
            )
            ,
            update delete $storedConcept,
            update delete $lock
        }
        </update>

let $updates :=
    for $concept in $editedDataset//concept[edit/@mode='edit'][not(move)]
    let $de-id              := $concept/@id
    let $de-ed              := $concept/@effectiveDate
    let $storedConcept      := $storedDataset//concept[@id = $de-id][@effectiveDate = $de-ed][not(ancestor::history)]
    let $lock               := decor:getLocks($de-id, $de-ed, (), true())
    let $check              :=
        if ($lock) then () else (
            error(QName('http://art-decor.org/ns/error', 'InconsistentConcept'), concat('Concept id=''',$de-id,''' and effectiveDate=''',$de-ed,''' does not have lock for current user (anymore)'))
        )
    let $check              :=
        if (count($storedConcept)=1) then () else (
            error(QName('http://art-decor.org/ns/error', 'InconsistentConcept'), concat('Concept id=''',$de-id,''' and effectiveDate=''',$de-ed,''' gave ',count($storedConcept),' stored concepts. Expected exactly 1.', if (count($storedConcept)=0) then () else concat(' Found concept in projects: ',string-join(for $s in $storedConcept return $s/ancestor::decor/project/@prefix,' / '))))
        )
    let $preparedConcept    :=
        if ($concept[@type = 'item']) then
            art:prepareItemForUpdate($concept,$storedConcept)
        else if ($concept[@type = 'group']) then
            art:prepareGroupForUpdate($concept,$storedConcept)
        else (
            (:huh? ... :)
        )
    let $check              :=
        if (count($preparedConcept)=1) then () else (
            error(QName('http://art-decor.org/ns/error', 'InconsistentConcept'), concat('Concept id=''',$de-id,''' and effectiveDate=''',$de-ed,''' has unknown type ',$concept/@type,'. Expected item or group.'))
        )
    return
        <update>
        {
            $de-id, $de-ed,
            hist:AddHistory($decor:OBJECTTYPE-DATASETCONCEPT, $projectPrefix, 'version', $storedConcept),
            update replace $storedConcept with $preparedConcept,
            update delete $lock
        }
        </update>

let $datasetNameUpdate :=
    for $name in $editedDataset/name
    return
        art:parseNode($name)
let $datasetDescUpdate :=
    for $desc in $editedDataset/desc
    return
        art:parseNode($desc)
(:new since 2015-04-21:)
let $datasetPropUpdate :=
    for $property in $editedDataset/property[@name[not(.='')]]
    return
        art:parseNode(<property>{$property/@name, $property/node()}</property>)
(:new since 2015-04-21:)
let $datasetRelsUpdate :=
    for $relationship in $editedDataset/relationship[string-length(@ref) gt 0]
    return
    <relationship>{$relationship/@type[not(. = '')], $relationship/@ref[not(.='')], $relationship/@flexibility[not(.='')]}</relationship>
let $datasetUpdate :=
    (
        update delete $storedDataset/(name|desc|property|relationship),
        if ($storedDataset[*]) then
            update insert ($datasetNameUpdate|$datasetDescUpdate|$datasetPropUpdate|$datasetRelsUpdate) preceding $storedDataset/*[1]
        else (
            update insert ($datasetNameUpdate|$datasetDescUpdate|$datasetPropUpdate|$datasetRelsUpdate) into $storedDataset
        )
        ,
        update value $storedDataset/@statusCode with $editedDataset/@statusCode,
        if ($editedDataset/@versionLabel[string-length(normalize-space()) gt 0]) then (
            if ($storedDataset/@versionLabel) then 
                update value $storedDataset/@versionLabel with $editedDataset/@versionLabel
            else (
                update insert attribute versionLabel {$editedDataset/@versionLabel} into $storedDataset
            )
        ) else (
            update delete $storedDataset/@versionLabel
        ),
        if ($editedDataset/@canonicalUri[string-length(normalize-space()) gt 0]) then (
            if ($storedDataset/@canonicalUri) then 
                update value $storedDataset/@canonicalUri with $editedDataset/@canonicalUri
            else (
                update insert attribute canonicalUri {$editedDataset/@canonicalUri} into $storedDataset
            )
        ) else (
            update delete $storedDataset/@canonicalUri
        )
    )

(:store any terminologyAssociations that were saved in the dataset after deInherit
    only store stuff where the concept(List) still exists
:)
let $terminologyAssociations        := ($editedDataset//terminologyAssociation[ancestor::concept/edit/@mode='edit'][not(ancestor::history)] |
                                        $editedDataset//terminologyAssociation[ancestor::concept/move][not(ancestor::history)])
let $terminologyAssociationUpdates  :=
    art:addTerminologyAssociations($terminologyAssociations, $decor, false(), false())

(:store any identifierAssociations that were saved in the dataset after deInherit
    only store stuff where the concept(List) still exists
:)
let $identifierAssociations        := ($editedDataset//identifierAssociation[ancestor::concept/edit/@mode='edit'][not(ancestor::history)] |
                                        $editedDataset//identifierAssociation[ancestor::concept/move][not(ancestor::history)])
let $identifierAssociationUpdates  :=
    art:addIdentifierAssociations($identifierAssociations, $decor, false(), false())

return
    <data-safe>true</data-safe>
