xquery version "3.1";
(:
    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 comp     = "http://art-decor.org/ns/art-decor-compile" at "../api/api-decor-compile.xqm";
import module namespace adserver = "http://art-decor.org/ns/art-decor-server" at "../api/api-server-settings.xqm";
import module namespace art      = "http://art-decor.org/ns/art" at "art-decor.xqm";
import module namespace artx     = "http://art-decor.org/ns/art/xpath" at "art-decor-xpath.xqm";
import module namespace adpfix   = "http://art-decor.org/ns/art-decor-permissions" at "../api/api-permissions.xqm";
import module namespace hist     = "http://art-decor.org/ns/decor/history" at "../api/api-decor-history.xqm";


declare option exist:serialize "indent=yes";
declare option exist:serialize "omit-xml-declaration=no";

declare variable $strArtURL      := adserver:getServerURLArt();

(: set output size limit to 9M nodes (XML fragments), default is 1M nodes :)
declare option exist:output-size-limit "9000000";

(:~ Documentation for input data that is expected
    
    @prefix is the project prefix on this server to compile
    @compile-language is the space spearated list of languages, e.g. "en-US nl-NL", to produce compilations for
    @versionLabel is the label for a release only
    @release='true' inserts a <release> element into the live project and triggers a compilation, @release='false' inserts a <version> element into the live project (see @development for exceptions)
    @publication-request triggers a compilation and prepares an ADRAM request file for publishing if ADRAM configured correctly.
    
    @development='true' prevents writing release/version info in the live file. this prevents clutter in your live file and allows for repeated tested
        your verbatim copy of the project in the compilation folder will therefor also not carry stated @versionLabel nor version/release in the project 
        as this would be compiled in from the live file.
        however, the compilations will be mixed in post compile with this information as-if they have always been there. this allows more realistic testing
    
    if you want a compile to occur you need either @publication-request='true' or @release='true', regardless of @development
    if @development='true' and both @publication-request and @release are 'false': effectively nothing happens
    
    <desc/> optionally contains description of the version/release. It is rewritten as <note> if @release='true', or copied as <desc> otherwise
    
    any other attributes or elements are ignored
:)
let $data                   := if (request:exists()) then request:get-data()/version else (
<version by="Alexander Henket" versionLabel="3.0.2" release="true" development="true" prefix="demo1-" compile-language="nl-NL" publication-request="false">
    <desc language="nl-NL">Dit is een release voor &lt;b&gt;ontwikkeldoeleinden&lt;/b&gt;</desc>
    <desc language="en-US">This is a release for &lt;b&gt;development purposes&lt;/b&gt;</desc>
</version>
)

let $projectPrefix          := if ($data) then data($data/@prefix) else ()
let $decor                  := $get:colDecorData//project[@prefix=$projectPrefix]/ancestor::decor

let $versionLabel           := if ($data) then data($data/@versionLabel[not(. = '')]) else ()
let $compile-language       := if ($data[string-length(@compile-language) gt 0]) then data($data/@compile-language) else ($decor/project/@defaultLanguage)
let $publication-request    := if ($data) then data($data/@publication-request) else ()
let $language               := if ($data) then data($data/@language) else ()
let $by                     := if ($data) then data($data/@by) else (get:strCurrentUserName())
let $release                := if ($data) then data($data/@release) else ()
let $development            := if ($data) then data($data/@development) else ()
let $noteOrDesc             := $data/desc

let $timeStamp              := if ($development = 'true') then 'development' else substring-before(xs:string(current-dateTime()), '.')

let $check                  :=
    if ($data) then (
        if ($decor) then () else (
            error(xs:QName('art:DecorNotFound'),concat('Project with prefix ''',$projectPrefix,''' not found'))
        ),
        if ($release castable as xs:boolean) then () else (
            error(xs:QName('art:ParameterInvalid'),concat('Parameter release shall be a boolean true|false found ''',$release,''''))
        ),
        if ($development castable as xs:boolean) then () else (
            error(xs:QName('art:ParameterInvalid'),concat('Parameter development shall be a boolean true|false found ''',$development,''''))
        ),
        for $language in tokenize($compile-language,'\s')
        return 
        if ($language castable as xs:language) then () else (
            error(xs:QName('art:ParameterInvalid'),concat('Parameter compile-language shall contain one or more languages e.g. en-US ''',$language,''''))
        ),
        if ($publication-request castable as xs:boolean) then () else (
            error(xs:QName('art:ParameterInvalid'),concat('Parameter publication-request shall be a boolean true|false found ''',$publication-request,''''))
        )
    ) else (
        error(xs:QName('art:InsufficientArguments'),'Need to post at least a version element with atributes by, versionLabel, release, development, prefix, compile-language, publication-request and date and 1 element desc containing the version note')
    )

let $decor-author        := $decor/project/author[@username = get:strCurrentUserName()]
let $decor-paramfile     := concat(util:collection-name($decor),'/decor-parameters.xml')
let $decor-params        := if (doc-available($decor-paramfile)) then doc($decor-paramfile)/decor-parameters else ()
let $decor-parameters    := 
    if (empty($decor-params)) then (
        <decor-parameters>
            <switchCreateSchematron1/>
            <switchCreateSchematronWithWrapperIncludes0/>
            <switchCreateSchematronWithWarningsOnOpen0/>
            <switchCreateSchematronClosed0/>
            <switchCreateSchematronWithExplicitIncludes0/>
            <switchCreateDocHTML1/>
            <switchCreateDocSVG1/>
            <switchCreateDocDocbook0/>
            <switchCreateDocPDF0/>
            <useLocalAssets1/>
            <useLocalLogos1/>
            <useCustomLogo0/>
            <useLatestDecorVersion1/>
            <hideColumns>45gh</hideColumns>
            <inDevelopment0/>
            <switchCreateDatatypeChecks1/>
            <createDefaultInstancesForRepresentingTemplates0/>
            <!--<artdecordeeplinkprefix></artdecordeeplinkprefix>-->
            <!--<useCustomRetrieve1 hidecolumns=""/>-->
            <logLevel>INFO</logLevel>
            <switchCreateTreeTableHtml1/>
        </decor-parameters>
    ) else (
        <decor-parameters> 
        {
            $decor-params/(* except (inDevelopment0|inDevelopment1|useLatestDecorVersion0|useLatestDecorVersion1)),
            <inDevelopment0/>,
            <useLatestDecorVersion1/>
        }
        </decor-parameters>
    )

(: store the version itself :)
let $versionOrRelease := 
    if ($release='true') then 
        element release {
            attribute date {$timeStamp},
            attribute by {if ($by) then $by else if ($decor-author[string-length()>0]) then data($decor-author) else get:strCurrentUserName()},
            if ($versionLabel) then attribute versionLabel {$versionLabel} else (),
            for $node in $noteOrDesc
            return
                element note {$node/@*, art:parseNode($node)/node()}
        }
    else
        element version {
            attribute date {$timeStamp},
            attribute by {if ($by) then $by else if ($decor-author[string-length()>0]) then data($decor-author) else get:strCurrentUserName()},
            for $node in $noteOrDesc
            return
                element desc {$node/@*, art:parseNode($node)/node()}
        }
let $result := 
    if ($development = 'true') then () else
    if ($decor/project[release | version]) 
    then update insert $versionOrRelease preceding $decor/project/(release | version)[1]
    else update insert $versionOrRelease into $decor/project

(: store version for each desired language, if any :)
let $overallresult := 
(: ==== PATCHZONE ===== :)
    if ( ($publication-request='true') or ($release='true') or starts-with($projectPrefix, 'onco-')) then (
        (: workaround for onco-. if we compile onco- then compile Dutch only :)
        (:let $compile-language   :=
            if ( ($publication-request='true') or ($release='true') )   then $compile-language  else 
            if (tokenize($compile-language, "\s+")[. = 'nl-NL'])        then 'nl-NL'            else (
                tokenize($compile-language, "\s+")[1]
            ):)
(: ==== PATCHZONE ===== :)
   (: if ( ($publication-request='true') or ($release='true') ) then ( :)
        (: will create version/project collection if it does not exist :)
        
        let $projectmap     := substring($projectPrefix, 1, string-length($projectPrefix) - 1)
        let $targetDir      :=
            if (xmldb:collection-available(concat($get:strDecorVersion, '/', $projectmap)))
            then concat($get:strDecorVersion, '/', $projectmap)
            else try {
                xmldb:create-collection($get:strDecorVersion, $projectmap)
            } catch * {
                <errors><error>Create collection failed '{$projectmap}': {$err:code}: {$err:description}</error></errors>
            }
        (: fix permission :)
        let $permfix        := 
            try {
                adpfix:setPermissions(concat($get:strDecorVersion, '/', $projectmap), 'admin', 'decor', 'rwxrwsr-x', 'admin', 'decor', 'rw-rw-r--')
            }
            catch * {
                <errors><error>Cannot set permissions '{$projectmap}': {$err:code}: {$err:description}</error></errors>
            }
        let $versionmap     := concat('version-', translate($timeStamp, '-:', ''))
        let $targetDir      := 
            if ($targetDir castable as xs:string or $permfix//error)
            then try {
                (: for development='true' we want a clean folder :)
                if ($development = 'true') then
                    if (xmldb:collection-available(concat($targetDir, '/', $versionmap))) then xmldb:remove(concat($targetDir, '/', $versionmap)) else ()
                else ()
                ,
                let $t              := xmldb:create-collection($targetDir, $versionmap)
                (: fix permission :)
                let $permfix        := 
                    try {
                        adpfix:setPermissions($t, 'admin', 'decor', 'rwxrwsr-x', 'admin', 'decor', 'rw-rw-r--')
                    }
                    catch * {
                        <errors><error>Cannot set permissions '{$t}': {$err:code}: {$err:description}</error></errors>
                    }
                return $t
            } catch * {
                <errors><error>Create collection failed for version {$versionmap}: {$err:code}: {$err:description}</error></errors>
            }
            else $targetDir
        let $resourcedir      := 
            if ($targetDir castable as xs:string)
            then try {
                let $t              := xmldb:create-collection($targetDir, 'resources')
                (: fix permission :)
                let $permfix        := 
                    try {
                        adpfix:setPermissions($t, 'admin', 'decor', 'rwxrwsr-x', 'admin', 'decor', 'rw-rw-r--')
                    }
                    catch * {
                        <errors><error>Cannot set permissions '{$t}': {$err:code}: {$err:description}</error></errors>
                    }
                return $t
            } catch * {
                <errors><error>Create collection failed for resources: {$err:code}: {$err:description}</error></errors>
            }
            else $targetDir
            
        let $languages   := distinct-values((tokenize($compile-language, "\s+"), '*'))
        
        (: Copy all child collections from project collection, logos/resources and potential others :)
        let $result      := 
            for $child-collection in xmldb:get-child-collections(util:collection-name($decor))
            return xmldb:copy-collection(concat(util:collection-name($decor), '/', $child-collection), $targetDir)
        
        (: store original decor file :)
        let $result                 := xmldb:store($targetDir, concat($projectPrefix, translate($timeStamp, '-:', ''), '-decor.xml'), $decor)
        
        (: copy any communities :)
        let $result                 := 
            for $community in $get:colDecorData//community[@projectId=$decor/project/@id]
            let $newReourceName          := concat('community-',$community/@name/string(),'-', translate($timeStamp, '-:', ''), '.xml')
            return
                xmldb:store($targetDir, $newReourceName, $community)
        
        let $filters                := comp:getCompilationFilters($decor, (), ())
        let $filters                := comp:getFinalCompilationFilters($decor, $filters)
        (:let $filters    :=  <filters filter="off"/>:)
        
        (: for every requested language:
            - compile and store result
            - merge xpaths into fullDatasetTree and store transactions.xml :)
        let $compiledProjects       := 
            for $language in $languages 
            let $filelang   := if ($language = '*') then ('all') else ($language)
            (: if $compiledProject is empty then something went wrong (e.g. no connection to repositories possible to resolve references) and simply give up with an error, otherwise continue to save the artifacts :)
            let $compiledResourceName       := concat($projectPrefix, translate($timeStamp, '-:', ''), '-', $filelang, '-decor-compiled.xml')
            let $storeCompilation           := 
                doc(xmldb:store($targetDir, $compiledResourceName, 
                    comp:compileDecor($decor, $language, $timeStamp, $filters, false(), false())
                ))/decor
            
            (: for development releases the @versionLabel will normally be empty. this populates the label provided by $data, if any :)
            let $storeVersionLabel          :=
                if ($development = 'true') then
                    if ($versionOrRelease/@versionLabel[not(. = '')]) then 
                        if ($storeCompilation/@versionLabel[not(. = '')]) then () else
                        if ($storeCompilation/@versionLabel) then (
                            update value $storeCompilation/@versionLabel with $versionOrRelease/@versionLabel
                        )
                        else (
                            update insert $versionOrRelease/@versionLabel into $storeCompilation
                        )
                    else ()
                else ()
            
            (: for development releases the $versionOrRelease is not inserted into the live project and thus not compiled either. add it here as if it was always there :)
            let $storeVersionOrRelease  :=
                if ($development = 'true') then
                    if ($storeCompilation/project[release | version]) then 
                        update insert $versionOrRelease preceding $storeCompilation/project/(release | version)[1]
                    else (
                        update insert $versionOrRelease into $storeCompilation/project
                    )
                else ()
            return
                $storeCompilation
        
        (: copy any foreign communities compiled in during compilation by art:getFullDataset() :)
        let $communityProjectMap    := 
            map:merge(
                for $pfx in $compiledProjects//dataset/@prefix[not(. = $decor/project/@prefix)]
                let $commPrefix     := $pfx
                group by $commPrefix
                return (
                    let $commDecor  := art:getDecorByPrefix($commPrefix[1])
                    return
                        if ($commDecor) then map:entry($commDecor/project/@id, $commPrefix) else ()
                )
            )
        let $commProjectIds         := map:keys($communityProjectMap)
        let $result                 := 
            for $community in $get:colDecorData//community[@projectId = $commProjectIds]
            let $commPrefix             := map:get($communityProjectMap, $community/@projectId)
            let $newReourceName         := concat('community-', $commPrefix, $community/@name/string(),'-', translate($timeStamp, '-:', ''), '.xml')
            return
                xmldb:store($targetDir, $newReourceName, 
                    element {name($community)} {
                        $community/(@* except @ident), 
                        attribute ident {$commPrefix}, 
                        $community/node()
                    }
                )
        
        (: store Xpaths in decor/releases/{projectPrefix}/version-{timestamp}/resources :)
        let $xpathResourceName          := concat($projectPrefix, 'xpaths.xml')
        let $storeXpaths                :=
            if ($compiledProjects[1]) then (
                let $compiledProject    := $compiledProjects[1]
                return
                doc(xmldb:store($resourcedir, $xpathResourceName,
                    <xpaths status="draft" version="{$timeStamp}" generated="{current-dateTime()}">
                    {
                        (:can only calculate meaningful paths if there are dataset concepts tied to them:)
                        if ($compiledProject[datasets//concept][rules/templateAssociation/concept]) then (
                            for $representingTemplate in $compiledProject//representingTemplate[@ref]
                            let $tr     := $representingTemplate/ancestor::transaction[1]
                            let $trid   := $tr/@id
                            let $tred   := $tr/@effectiveDate
                            return 
                                if ($filters[@filter = 'on']) then 
                                    if ($filters/transaction[@ref = $trid][@flexibility = $tred]) then 
                                        artx:getXpaths($compiledProject, $representingTemplate)
                                    else ()
                                else (
                                    artx:getXpaths($compiledProject, $representingTemplate)
                                )
                        ) else ()
                    }
                    </xpaths>
                ))
            ) else ()
        
        (: store transactions in decor/releases/{projectPrefix}/version-{timestamp}/{projectPrefix}-{timestamp}-{$language}-transactions.xml :)
        let $storeTransactions          := 
            for $compiledProject in $compiledProjects
            let $language                   := $compiledProject/@language
            let $filelang                   := if ($language = '*') then ('all') else ($language)
            let $transactionResourceName    := concat($projectPrefix, translate($timeStamp, '-:', ''), '-', $filelang, '-transactions.xml')
            return
                xmldb:store($targetDir, $transactionResourceName,
                    <transactionDatasets projectId="{$decor/project/@id}" prefix="{$projectPrefix}" versionDate="{$timeStamp}">
                    {
                        $compiledProject/@versionLabel[not(. = '')],
                        $compiledProject/@language[not(. = '')]
                    }
                    {
                        (:compilation handles filtering so it would contain exactly those ids we need:)
                        for $compiledTransaction in $compiledProject//transaction[representingTemplate[@sourceDataset]]
                        let $transaction    := art:getTransaction($compiledTransaction/@id, $compiledTransaction/@effectiveDate)
                        return art:getFullDatasetTree($transaction, (), (), $language, $storeXpaths, true(), ())
                    }
                    </transactionDatasets>
                )
        
        (: last action: store publication request if given :)
        let $result := 
            if ($publication-request = 'true') then (
                (: create publication request :)
                let $publicationdata := 
                    <publication projectId="{$decor/project/@id}" prefix="{$projectPrefix}" versionDate="{$timeStamp}" versionSignature="{translate($timeStamp, '-:', '')}" 
                        reference="{$decor/project/reference/@url}" compile-language="{$compile-language}" deeplinkprefix="{$strArtURL}">
                    {
                        $versionOrRelease,
                        $decor-parameters,
                        $filters
                    }
                        <request on="{$timeStamp}" status="OK"/>
                    </publication>
                return
                    xmldb:store($targetDir, concat($projectPrefix, replace($timeStamp, '\D', ''), '-publication-request.xml'), $publicationdata)
            )
            else ()
        
        (: return compile project :)
        return $compiledProjects
            
     ) else if ($release='false') then (
        (: create a version = archive only into history :)
        hist:AddHistory ('DECOR', $projectPrefix, 'version', $decor)
     ) else <ok/>

return
    <result status="{if (count($overallresult)>0 or ((string-length($compile-language) = 0) and $release='true')) then 'OK' else 'Project does not compile. Connected to repositories or properly cached?'}" date="{$timeStamp}">
    {
        element {$versionOrRelease/name()} {
            $versionOrRelease/@*,
            attribute publicationstatus {if ($release='true') then 'version' else '', if ($publication-request='true') then ' pending' else ''},
            $versionOrRelease/*
        }
    }
    </result>