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-server-settings.xqm";
import module namespace adserver    = "http://art-decor.org/ns/art-decor-server" at "../../api/api-server-settings.xqm";

(:When called with localAssets=true then we need relative local paths, else 
    we need our server services URL, but with matching scheme (http or https)
    If the scheme is https and servicesUrl is http this leads to a security 
    problem in some browsers
:)
declare variable $resourcePath          := 'https://assets.art-decor.org/ADAR/rv/assets';

(: This date is used for the new scenario and any child transactions :)
declare variable $now1      := format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]');
declare variable $now2      := format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T00:00:00');

declare %private function local:copyTransaction($transaction as element(transaction), $statusCode as xs:string, $baseId as xs:string, $decor as element(decor), $now as xs:string, $includeDeprecated as xs:boolean, $currentDatasetIsNewest as xs:boolean, $currentDataset as element(dataset), $newDataset as element(dataset)) as item()* {

let $trid               := $transaction/@id
let $tred               := $transaction/@effectiveDate

let $nexttrlmLeaf       := $decor/scenarios/@nextTransactionLeaf
let $id                 := concat($baseId, '.', $nexttrlmLeaf)
let $nextid             := update value $nexttrlmLeaf with xs:integer($nexttrlmLeaf) + 1

return
    <transaction id="{$id}" statusCode="{$statusCode}" effectiveDate="{$now}">
    {
        $transaction/@type, $transaction/@label, $transaction/@model, $transaction/@canonicalUri, attribute lastModifiedDate {$now},
        $transaction/(node() except (transaction | representingTemplate))
        ,
        for $subNode in $transaction/transaction
        return
            switch ($subNode/@statusCode) 
            case 'deprecated'   return if ($includeDeprecated) then local:copyTransaction($subNode, $statusCode, $baseId, $decor, $now, $includeDeprecated, $currentDatasetIsNewest, $currentDataset, $newDataset) else ()
            case 'cancelled'    return ()
            case 'rejected'     return ()
            default             return local:copyTransaction($subNode, $statusCode, $baseId, $decor, $now, $includeDeprecated, $currentDatasetIsNewest, $currentDataset, $newDataset)
        ,
        for $subNode in $transaction/representingTemplate
        return
            if ($currentDataset[@id = $newDataset/@id][@effectiveDate = $newDataset/@effectiveDate]) then
                (: apparently no conversion of dataset is requested? :)
                $subNode
            else
            if ($subNode[@sourceDataset = $currentDataset/@id][not(@sourceDatasetFlexibility castable as xs:dateTime)][$currentDatasetIsNewest] | 
                $subNode[@sourceDataset = $currentDataset/@id][@sourceDatasetFlexibility = $currentDataset/@effectiveDate]) then (
                comment {' Based on new dataset '},
                <representingTemplate>
                {
                    attribute sourceDataset {$newDataset/@id},
                    attribute sourceDatasetFlexibility {$newDataset/@effectiveDate},
                    $subNode/@ref, $subNode/@flexibility,
                    $subNode/@representingQuestionnaire, $subNode/@representingQuestionnaireFlexibility,
                    for $subSubNode in $subNode/node()
                    return
                        if (name($subSubNode) = 'concept') then (
                            let $deid         := $subSubNode/@ref
                            let $newConcept   := $newDataset//inherit[@ref = $deid]/parent::concept
                            
                            return
                            if (empty($newConcept)) then 
                                comment {' did not find this concept in the new dataset. skipping: ', $deid, ' '} 
                            else (
                                <concept>
                                {
                                    attribute ref {$newConcept/@id},
                                    attribute flexibility {$newConcept/@effectiveDate},
                                    $subSubNode/(@* except (@ref | @flexibility)),
                                    $subSubNode/node()
                                }
                                </concept>
                            )
                        )
                        else $subSubNode
                }
                </representingTemplate>
            ) else (
                (: this is not the dataset we are looking for. just copy :)
                $subNode
            )
    }
    </transaction>
};


(: get all projects for drop-down population :)
declare %private function local:getAllProjects($decor as element(decor)*, $language as xs:string?) as element(project)* {
    for $decor in collection($get:strDecorData)//decor[not(@private='true')] | $decor
    let $isDBA          := try {sm:is-dba(get:strCurrentUserName())} catch * {()}
    let $isAuthor       := if ($isDBA) then true() else decor:authorCanEditP($decor, $decor:SECTION-DATASETS)
    let $isBBR          := xs:string($decor/@repository='true')
    let $project        := $decor/project
    let $projectName    := if ($project/name[@language=$language]) then $project/name[@language=$language][1] else $project/name[1]
    where $isAuthor
    order by $isBBR, $projectName/lower-case(normalize-space())
    return
        <project>
        {
            $project/@id,
            $project/@prefix,
            $project/@defaultLanguage,
            attribute repository {$isBBR},
            attribute private {$project/parent::decor/@private='true'},
            attribute experimental {$project/@experimental='true'},
            attribute name {$projectName},
            attribute creationdate {xmldb:created(util:collection-name($project),util:document-name($project))},
            attribute modifieddate {xmldb:last-modified(util:collection-name($project),util:document-name($project))}
        }
        </project>
        
};

let $currentUser            := get:strCurrentUserName()

(: show or download :)
let $action                 := if (request:exists()) then request:get-parameter('action', 'show') else ('show')
let $projectPrefix          := if (request:exists()) then request:get-parameter('prefix', ())[string-length() gt 0] else ('e-overdracht-')

let $scenarioKey            := if (request:exists()) then request:get-parameter('scenario', ()) else ('2.16.840.1.113883.2.4.3.11.60.30.3.3|2018-01-01T00:00:00')
let $datasetKey             := if (request:exists()) then request:get-parameter('dataset', ()) else ('2.16.840.1.113883.2.4.3.11.60.30.1.3|2018-01-01T00:00:00')
let $datasetNewKey          := if (request:exists()) then request:get-parameter('datasetNew', ()) else ('2.16.840.1.113883.2.4.3.11.60.30.1.4|2021-01-26T00:00:00')

let $now                    := if (request:exists()) then request:get-parameter('now', ()) else ()
let $now                    := if ($now castable as xs:dateTime) then $now else format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01]')

let $scenarioId             := tokenize($scenarioKey, '\|')[1]
let $scenarioEd             := tokenize($scenarioKey, '\|')[2]

let $datasetIdCurrent       := tokenize($datasetKey,'\|')[1]
let $datasetEdCurrent       := tokenize($datasetKey,'\|')[2]

let $datasetIdNew           := tokenize($datasetNewKey,'\|')[1]
let $datasetEdNew           := tokenize($datasetNewKey,'\|')[2]

let $scenarioVlNew          := if (request:exists()) then request:get-parameter('scenarioVersionLabel', ())[string-length() gt 0] else ('4.0')
let $includeDeprecated      := if (request:exists()) then request:get-parameter('includeDeprecated', 'true') else ('false')
let $includeDeprecated      := if ($includeDeprecated = ('', 'true')) then true() else false()

let $language               := if (request:exists()) then request:get-parameter('language',()) else ('nl-NL')
let $language               := if ($language[string-length()>0]) then $language else (adserver:getServerLanguage())

let $decor                  := if (empty($projectPrefix)) then () else art:getDecorByPrefix($projectPrefix)
let $allProjects            := local:getAllProjects($decor, $language)
return
    if (string-length(normalize-space($projectPrefix))=0 or string-length(normalize-space($datasetKey))=0) 
    then (
        if (response:exists()) then response:set-header('Content-Type','text/html; charset=utf-8') else (),
        <html>
            <head>
                <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
                <title>Create-new-decor-scenario-version</title>
                <link href="{$resourcePath}/css/default.css" rel="stylesheet" type="text/css"/>
                <style>tr:nth-child(even) {{ vertical-align: top; background-color: #eee; }}</style>
                <script>
                    function toggleDisplay(id) {{
                        var elm = document.getElementById(id);
                        if (elm != null) {{
                            if (elm.style.opacity == '1') {{ elm.style.opacity = '0.5'; }} else {{ elm.style.opacity = '1'; }}
                        }}
                    }};
                </script>
            </head>
            <body>
                <h1>Create new decor scenario version</h1>
                <div>Creates a new scenario from a source scenario including its original transactions. Updates representingTemplates based on a given dataset to a new dataset.</div>
                <div class="content">
                <form name="input" action="/art/modules/create-new-decor-scenario-version.xquery" method="post">
                    <table border="0">
                        <tr>
                            <td>Project:</td>
                            <td>
                                <select name="prefix" id="prefixSelector" onchange="javascript:location.href=window.location.pathname+'?'{
                                    for $q in ('prefix','includeDeprecated','now','action')
                                    return
                                    concat('+''',$q,'=''+document.getElementById(''',$q,'Selector'').options[document.getElementById(''',$q,'Selector'').selectedIndex].value+''&amp;''')
                                }" style="width: 500px;">
                                {
                                    for $p in $allProjects
                                    let $dprefix    := $p/@prefix
                                    return
                                        <option value="{$dprefix}">
                                        {
                                            if ($dprefix=$projectPrefix) then attribute {'selected'} {'true'} else (),
                                            if ($p/@repository='true') then '(BBR) ' else (),
                                            concat($p/@name,' (',$p/@prefix,' | ',$p/@defaultLanguage,')')
                                        } 
                                        </option>
                                }
                                </select>
                            </td>
                            <td>Required</td>
                        </tr>
                        <tr>
                            <td>Scenario:</td>
                            <td>
                                <div style="font-style: italic;{if (empty($decor)) then () else 'display: none;'}">Please select a project first</div>
                                <select name="scenario" id="scenarioSelector" style="width: 500px;{if ($decor) then () else 'display: none;'}">
                                {
                                    <option value="">--</option>,
                                    for $q in $decor/scenarios/scenario
                                    let $did    := $q/@id/string()
                                    let $ded    := $q/@effectiveDate/string()
                                    let $dnm    := if ($q/name[@language=$language]) then $q/name[@language=$language] else ($q/name[1])
                                    return
                                        <option value="{concat($did,'|',$ded)}">
                                        {
                                            if ($q[concat(@id,'|',@effectiveDate) = $scenarioKey]) then attribute selected {'true'} else ()
                                            ,
                                            concat($dnm,' ',$q/@versionLabel,' ID ',art:getNameForOID($did, $language, $q/ancestor::decor),' (',$ded,')')
                                        }
                                        </option>
                                }
                                </select>
                            </td>
                            <td>Required. Determines the source scenario to create the new scenario from.</td>
                        </tr>
                        <tr>
                            <td>Scenario new version label:</td>
                            <td>
                                <input type="text" name="scenarioVersionLabel"/>
                            </td>
                            <td>Optional. Determines the versionLabel to add to the new scenario. By default it will not receive a version label.</td>
                        </tr>
                        <tr>
                            <td>Dataset:</td>
                            <td>
                                <div style="font-style: italic;{if (empty($decor)) then () else 'display: none;'}">Please select a project first</div>
                                <select name="dataset" id="datasetSelector" style="width: 500px;{if ($decor) then () else 'display: none;'}">
                                {
                                    <option value="">--</option>,
                                    for $q in $decor/datasets/dataset
                                    let $did    := $q/@id/string()
                                    let $ded    := $q/@effectiveDate/string()
                                    let $dnm    := if ($q/name[@language=$language]) then $q/name[@language=$language] else ($q/name[1])
                                    return
                                        <option value="{concat($did,'|',$ded)}">
                                        {
                                            if ($q[concat(@id,'|',@effectiveDate) = $datasetKey]) then attribute selected {'true'} else ()
                                            ,
                                            concat($dnm,' ',$q/@versionLabel,' ID ',art:getNameForOID($did, $language, $q/ancestor::decor),' (',$ded,')')
                                        }
                                        </option>
                                }
                                </select>
                            </td>
                            <td>Required. Determines the source dataset inside this scenario to upgrade of the newer dataset.</td>
                        </tr>
                        <tr>
                            <td>Dataset new:</td>
                            <td>
                                <div style="font-style: italic;{if (empty($decor)) then () else 'display: none;'}">Please select a project first</div>
                                <select name="datasetNew" id="datasetNewSelector" style="width: 500px;{if ($decor) then () else 'display: none;'}">
                                {
                                    <option value="">--</option>,
                                    for $q in $decor/datasets/dataset
                                    let $did    := $q/@id/string()
                                    let $ded    := $q/@effectiveDate/string()
                                    let $dnm    := if ($q/name[@language=$language]) then $q/name[@language=$language] else ($q/name[1])
                                    return
                                        <option value="{concat($did,'|',$ded)}">
                                        {
                                            if ($q[concat(@id,'|',@effectiveDate) = $datasetKey]) then attribute selected {'true'} else ()
                                            ,
                                            concat($dnm,' ',$q/@versionLabel,' ID ',art:getNameForOID($did, $language, $q/ancestor::decor),' (',$ded,')')
                                        }
                                        </option>
                                }
                                </select>
                            </td>
                            <td>Required. Determines the target dataset to upgrade to from the source dataset.</td>
                        </tr>
                        <tr>
                            <td>Include deprecated:</td>
                            <td>
                                <select name="includeDeprecated" id="includeDeprecatedSelector">
                                    <option value="true">Yes</option>
                                    <option value="false">No</option>
                                </select>
                            </td>
                            <td><ul>
                            <li>If "Yes" then deprecated transactions in the base scenario. This is recommended</li>
                            <li>If "No" the deprecated transactions in the base scenario are NOT included</li></ul>
                            <div>Note that cancelled, and rejected transactions are never included as they are supposed to never have been published. Deprecated transactions however have been published and (potentially) implemented.</div>
                            </td>
                        </tr>
                        <tr>
                            <td>Choose effectiveDate:</td>
                            <td>
                                <select name="now" id="nowSelector">
                                    <option value="{$now1}">{$now1}</option>
                                    <option value="{$now2}" selected="true">{$now2}</option>
                                </select>
                            </td>
                            <td>Select full date time or date only. Determines the @effectiveDate on the new dataset and contained concepts.</td>
                        </tr>
                        <tr>
                            <td>Action:</td>
                            <td>
                                <select name="action" id="actionSelector">
                                    <option value="show">{if ($action='show') then attribute {'selected'} {'true'} else ()}Show</option>
                                    <option value="download">{if ($action='download') then attribute {'selected'} {'true'} else ()}Download</option>
                                    <option value="commit">{if ($action='commit') then attribute {'selected'} {'true'} else ()}Commit</option>
                                </select>
                            </td>
                            <td/>
                        </tr>
                        <tr>
                            <td style="padding-top: 1em;">&#160;</td>
                            <td style="padding-top: 1em;"><input type="submit" value="Submit" style="color: inherit;"/></td>
                            <td style="padding-top: 1em;">&#160;</td>
                        </tr>
                    </table>
                </form>
                </div>
            </body>
        </html>
    ) 
    else (
        if (response:exists()) then (
            response:set-header('Content-Type','text/xml; charset=utf-8'),
            if ($action = 'download') then (response:set-header('Content-Disposition', concat('attachment; filename=',$projectPrefix,$scenarioId,'-',replace($now,':',''),'.xml'))) else ()
        ) else (),
        let $check                  := if (empty($projectPrefix)) then error(xs:QName('art:MISSINGPARAMETER'), 'Parameter prefix is required') else ()
        
        let $isAuthor               := decor:authorCanEditP($decor, $decor:SECTION-SCENARIOS)
        
        let $check                  := if (count($decor) = 1) then () else error(xs:QName('art:MISSINGDECOR'), concat('Parameter prefix ', $projectPrefix, ' SHALL lead to exactly 1 project. Found ', count($decor)))
        let $check                  := if ($currentUser = 'admin' or $isAuthor) then () else error(xs:QName('art:PERMISSIONS'), concat('User ', $currentUser, ' does not have author permissions in project with prefix ', $projectPrefix))
        
        let $currentScenario        := $decor//scenario[@id = $scenarioId][@effectiveDate = $scenarioEd]
        let $newestCurrentDataset   := art:getDataset($datasetIdCurrent, ())
        let $currentDataset         := art:getDataset($datasetIdCurrent, $datasetEdCurrent)
        let $currentDatasetIsNewest := $newestCurrentDataset/@effectiveDate = $currentDataset/@effectiveDate
        let $newDataset             := art:getDataset($datasetIdNew, $datasetEdNew)
        
        let $check                  := if (count($currentScenario) = 1) then () else error(xs:QName('art:MISSINGSCENARIO'), concat('Parameter scid ', $scenarioId, ' + sced ', $scenarioEd, ' SHALL lead to exactly 1 scenario. Found ', count($currentScenario)))
        let $check                  := if (count($currentDataset) = 1) then () else error(xs:QName('art:MISSINGDATASET'), concat('Parameter dsid ', $datasetIdCurrent, ' + dsed ', $datasetEdCurrent, ' SHALL lead to exactly 1 dataset. Found ', count($currentDataset)))
        let $check                  := if (count($newDataset) = 1) then () else error(xs:QName('art:MISSINGDATASET'), concat('Parameter dsidnew ', $datasetIdNew, ' + dsed ', $datasetEdNew, ' SHALL lead to exactly 1 dataset. Found ', count($newDataset)))
        let $check                  := if ($currentScenario//representingTemplate[@sourceDataset = $datasetIdCurrent][not(@sourceDatasetFlexibility castable as xs:dateTime)][$currentDatasetIsNewest] |
                                           $currentScenario//representingTemplate[@sourceDataset = $datasetIdCurrent][@sourceDatasetFlexibility = $currentDataset/@effectiveDate]) then () else (
                                           error(xs:QName('art:MISSINGTRANSACTION'), concat('Scenario ''', data($currentScenario/name[1]), ''' does not have any transaction based on supplied dataset parameters. Nothing do to.'))
                                       )
        
        let $newScenarioId          := decor:getNextAvailableIdP($decor, $decor:OBJECTTYPE-SCENARIO, ())
        let $newTransactionId       := decor:getNextAvailableIdP($decor, $decor:OBJECTTYPE-TRANSACTION, ())
        
        let $baseIdsTR              := decor:getBaseIdsP($decor, $decor:OBJECTTYPE-TRANSACTION)
        let $defaultBaseIdTR        := decor:getDefaultBaseIdsP($decor, $decor:OBJECTTYPE-TRANSACTION)
        
        (: don't allow a baseId for transaction unless it is defined in the project :)
        let $baseIdTR               := if (request:exists()) then request:get-parameter('baseIdTR',())[. = $baseIdsTR/@id] else ()
        let $dfltTR                 := if (empty($baseIdTR)) then $defaultBaseIdTR/@id else $baseIdTR
        
        (: keep counter in db, so we make sure we don't issue the same id twice for sibling concepts :)
        let $insert         :=
            if ($decor/scenarios[@nextTransactionLeaf]) then
                update value $decor/scenarios/@nextTransactionLeaf with xs:integer($newTransactionId/@max) + 1
            else (
                update insert attribute nextTransactionLeaf {xs:integer($newTransactionId/@max) + 1} into $decor/scenarios
            )
        
        let $result         :=
            <scenario id="{$newScenarioId/@id}" statusCode="draft" effectiveDate="{$now}">
            {
                if (empty($scenarioVlNew)) then () else attribute versionLabel {$scenarioVlNew},
                $currentScenario/@canonicalUri, attribute lastModifiedDate {$now},
                $currentScenario/(* except transaction)
                ,
                for $node in $currentScenario/transaction
                return
                    local:copyTransaction($node, 'draft', $dfltTR, $decor, $now, $includeDeprecated, $currentDatasetIsNewest, $currentDataset, $newDataset)
            }
            </scenario>
        
        let $delete         := update delete $decor/scenarios/@nextTransactionLeaf
        
        let $commitds       := 
            if ($action = 'commit' and $isAuthor) then
                if ($decor/scenarios[questionnaire | questionnaireresponse]) then
                    update insert $result preceding ($decor/scenarios/questionnaire | $decor/scenarios/questionnaireresponse)[1]
                else (
                    update insert $result into $decor/scenarios
                )
            else ()
        
        return
            $result
    )