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.
:)

module namespace gg             = "http://art-decor.org/ns/decor/governancegroups";
import module namespace get     = "http://art-decor.org/ns/art-decor-settings" at "../modules/art-decor-settings.xqm";
import module namespace art     = "http://art-decor.org/ns/art"                at "../modules/art-decor.xqm";
import module namespace templ   = "http://art-decor.org/ns/decor/template"     at "api-decor-template.xqm";
import module namespace vs      = "http://art-decor.org/ns/decor/valueset"     at "api-decor-valueset.xqm";
import module namespace adpfix  = "http://art-decor.org/ns/art-decor-permissions" at "api-permissions.xqm";
declare namespace error         = "http://art-decor.org/ns/decor/valueset/error";
declare namespace xs            = "http://www.w3.org/2001/XMLSchema";

(:  normally we return contents for ART XForms. These require serialize desc nodes. When we return for other purposes like 
:   XSL there is no need for serialization or worse it hurts :)
(:declare variable $gg:boolSerialize as xs:boolean    := true();:)
declare variable $gg:strDecorHostedGovernanceGroups := 'hosted-governance-groups.xml';
declare variable $gg:strDecorLinkedGovernanceGroups := 'governance-group-links.xml';

declare function gg:getHostedGovernanceGroups() as element(governance)? {
    let $createHostedGovernanceGroups   := 
        if (doc-available(concat($get:strArtData,'/',$gg:strDecorHostedGovernanceGroups))) then () else (
            xmldb:store($get:strArtData, $gg:strDecorHostedGovernanceGroups, 
                <governance xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://assets.art-decor.org/ADAR/rv/DECOR-governance-groups.xsd"/>)
        )
    
    return
        doc(concat($get:strArtData,'/',$gg:strDecorHostedGovernanceGroups))/governance
};

(: Returns any governance-group-links elements for a given projectId so we know what governance groups a project claims to be partOf 
<governance-group-links ref="2.16.840.1.113883.2.4.3.111.3.7">
    <partOf ref="2.16.840.1.113883.2.4.3.111"/>
</governance-group-links>:)
declare function gg:getLinkedGovernanceGroups($projectId as xs:string?) as element(governance-group-links)* {
    if (empty($projectId)) then
        $get:colDecorData/governance-group-links
    else
        $get:colDecorData/governance-group-links[@ref = $projectId]
};
(: Returns any governance-group-links elements for a given groupId so we know what project claims to be partOf that governance group
<governance-group-links ref="2.16.840.1.113883.2.4.3.111.3.7">
    <partOf ref="2.16.840.1.113883.2.4.3.111"/>
</governance-group-links>:)
declare function gg:getLinkedProjects($groupId as xs:string) as element(governance-group-links)* {
    $get:colDecorData/governance-group-links[partOf/@ref = $groupId]
};

(:~
:   Retrieves the list of currently available hosted governance groups. This could be 0..*. 
:   Each governance group has @id, @defaultLanguage and name. 
:   Output: 
:   <result>
:       {[
:       <group id="groupid" defaultLanguage="en-US">
:           <name .../>
:       </group>
:       ]}
:   </result>
:   
:   @return 0..* group elements wrapped in a <result/> element.
:   @since 2015-03-09
:)
declare function gg:getGovernanceGroupList() as element(result) {
    gg:getGovernanceGroupList(())
};
declare function gg:getGovernanceGroupList($language as xs:string?) as element(result) {
let $govgrps        := gg:getHostedGovernanceGroups()/group

return
<result>
{
    for $group in $govgrps
    let $defaultlang := $group/@defaultLanguage
    let $defaultname := 
        if   ($group/name[@language=$defaultlang]) 
        then ($group/name[@language=$defaultlang]) 
        else ($group/name[1])
    order by lower-case($defaultname)
    return
        <group>
        {
            $group/(@id|@defaultLanguage),
            $group/name,
            if ($language) then (
                if ($group/name[@language=$language]) then () else (
                    <name language="{$language}">{$defaultname/node()}</name>
                )
            ) else ()
        }
        </group>
}
</result>
};

(:~
:   Adds, Updates, Deletes governance groups on the input.
:   Expected input: 
:   <group id="groupid" defaultLanguage="en-US">
:       <edit id="previousgroupid"/> | <delete/>
:       <name .../>
:       <desc .../>
:       <copyright .../>
:   </group>
:   
:   @param $groups - optional. Groups to add/upate/delete
:   @return null or error() in case of an unsupported action
:   @since 2015-03-09
:)
declare function gg:saveGroups($groups as element(group)*) {
    let $results        := gg:deleteGroups($groups[*/name()='delete'])
    let $results        := gg:updateGroups($groups[edit])
    
    return ()
};
(:~
:   Adds, Updates governance groups on the input.
:   Expected input: 
:   <group id="groupid" defaultLanguage="en-US">
:       <edit id="previousgroupid"/>
:       <name .../>
:       <desc .../>
:       <copyright .../>
:   </group>
:   
:   @param $groups - optional. Groups to add/upate
:   @return null or error() in case of an unsupported action
:   @since 2015-08-07
:)
declare function gg:updateGroups($groups as element(group)*) {
    let $currentGroups  := gg:getHostedGovernanceGroups()
    
    for $group in $groups
    let $groupid                := $group/@id
    let $previousgroupid        := $group/edit/@id
    (: content of the whole new hosted-governance-group.xml file :)
    let $ggallcontent           := gg:prepareGovernanceGroupFile($group)
    let $resultupdategroup      := 
        if (not($currentGroups)) then (
            xmldb:store($get:strArtData, $gg:strDecorHostedGovernanceGroups, $ggallcontent)
        )
        else if ($currentGroups/group[@id=$groupid]) then (
            update replace $currentGroups/group[@id=$groupid] with $ggallcontent/group
        )
        else (
            update insert $ggallcontent/group into $currentGroups
        )
    (: delete group under old id and update governance group links if necessary :)
    let $resultupdatelink       :=
        if ($previousgroupid and not($groupid=$previousgroupid)) then (
            update delete $currentGroups/group[@id=$previousgroupid]
            ,
            gg:updateGroupLinks($previousgroupid, $groupid)
        ) else ()

    return ()
};
(:~
:   Deletes governance groups on the input.
:   Expected input: 
:   <group id="groupid" defaultLanguage="en-US">
:       <delete/>
:       <name .../>
:       <desc .../>
:       <copyright .../>
:   </group>
:   
:   @param $groups - optional. Groups to delete
:   @return null or error() in case of an unsupported action
:   @since 2015-03-09
:)
declare function gg:deleteGroups($groups as element(group)*) {
    let $currentGroups  := gg:getHostedGovernanceGroups()
    
    for $group in $groups
    let $groupid                := $group/@id
    let $previousgroupid        := $group/edit/@id
    (: content of the whole new hosted-governance-group.xml file :)
    let $ggallcontent           := gg:prepareGovernanceGroupFile($group)
    let $resultdelete           := update delete $currentGroups/group[@id=$groupid]
    let $resultunlink           := gg:unlinkGroup($groupid, ())
    
    return ()
};

(:~
:   Retrieves the list of currently available hosted governance groups. This could be 0..*. 
:   Each governance group has @id, @defaultLanguage and name. 
:   Output: 
:   <result>
:       {[
:       <group id="groupid" defaultLanguage="en-US">
:           <name .../>
:       </group>
:       ]}
:   </result>
:   
:   @return 0..* group elements wrapped in a <result/> element.
:   @since 2015-03-09
:)
declare function gg:getGovernanceGroup($groupid as xs:string?) as element(group)* {
let $govgrps        := gg:getHostedGovernanceGroups()/group
let $govgrps        := if ($groupid) then $govgrps[@id=$groupid] else $govgrps

return $govgrps
};
declare function gg:getGovernanceGroups($groupid as xs:string?, $addArtefactList as xs:boolean, $language as xs:string?, $serialize as xs:boolean) as element(result) {
(: get all governance groups :)
let $govgrps        := gg:getHostedGovernanceGroups()/group
(: trim down to one governance group based on groupid or stay with all groups :)
let $govgrps        := if (empty($groupid)) then $govgrps else $govgrps[@id = $groupid]
(: get all governance group links, linked to from a project :)
(:let $gglinks        := gg:getLinkedGovernanceGroups(()):)
(: trim down to links to our group based on groupid or stay with all links :)
(:let $gglinks        := if (empty($groupid)) then $gglinks else $gglinks[partOf/@ref = $groupid]:)

let $decors         := $get:colDecorData/decor

let $result :=
    <result>
    {
        for $group in $govgrps
        let $defaultlang    := $group/@defaultLanguage
        let $defaultname    := 
            if   ($group/name[@language=$defaultlang]) 
            then ($group/name[@language=$defaultlang]) 
            else ($group/name[1])
        let $defaultdesc    := 
            if   ($group/desc[@language=$defaultlang]) 
            then ($group/desc[@language=$defaultlang]) 
            else ($group/desc[1])
        let $linkmap        := map:merge(for $ref in gg:getLinkedProjects($group/@id) return map:entry($ref/@ref, ()))
        let $linkedprojects := 
            for $project in $decors/project[map:contains($linkmap, @id)]
            return gg:projectDetails($project, $addArtefactList)
        order by lower-case($defaultname)
        return
            <group>
            {
                $group/(@id|@defaultLanguage),
                $group/name,
                if ($language) then (
                    if ($group/name[@language=$language]) then () else (
                        <name language="{$language}">{$defaultname/node()}</name>
                    )
                ) else ()
                ,
                if ($serialize) then
                    for $node in $group/desc
                    return 
                        art:serializeNode($node)
                else (
                    $group/desc
                )
                ,
                if ($language) then (
                    if ($group/desc[@language=$language]) then () else (
                        <desc language="{$language}">{$defaultdesc/node()}</desc>
                    )
                ) else ()
                ,
                $group/(* except (edit|delete|name|desc|copyright)),
                (: add empty addrLine if no one so far for editing :)
                <copyright>
                {
                    $group/copyright/@*,
                    if (count($group/copyright/addrLine)=0) then <addrLine type=""/> else (),
                    for $addr in $group/copyright/addrLine
                    return
                        <addrLine type="{$addr/@type}">{$addr/(@* except @type),$addr/node()}</addrLine>
                }
                </copyright>
            }
            {
                (: 
                    project order mimics the art menu order
                :)
                (:normal projects:)
                for $project in $linkedprojects[@experimental='false'][@repository='false']
                let $defaultlang := $project/@defaultLanguage
                order by lower-case($project/name[@language=$defaultlang][1])
                return
                    $project
                ,
                (:experimental projects:)
                for $project in $linkedprojects[@experimental='true'][@repository='false']
                let $defaultlang := $project/@defaultLanguage
                order by lower-case($project/name[@language=$defaultlang][1])
                return
                    $project
                ,
                (:repositories:)
                for $project in $linkedprojects[@experimental='false'][@repository='true']
                let $defaultlang := $project/@defaultLanguage
                order by lower-case($project/name[@language=$defaultlang][1])
                return
                    $project
            }
            </group>
    }
    </result>

(: consolidate over all projects :)
let $allvstm :=
    <result>
    {
        for $a in $result//valueSet/valueSet | $result//template/template
        let $if  := concat(($a/@id | $a/@ref)[1], '-', $a/@effectiveDate)
        let $elm := $a/name()
        group by $if
        (: order by replace(replace(concat($if, '.'), '\.', '.0000000000'), '.0*([0-9]{9,})', '.$1'), $if :)
        return
            element {$elm[1]}
            {
                ($a/@id | $a/@ref)[1],
                ($a/@effectiveDate)[1]
                (:,
                $a :)
            }
    }
    </result>

return
    <result>
    {
        if ($addArtefactList=true()) then (
            attribute valuesetcount {count($allvstm/valueSet)},
            attribute templatecount {count($allvstm/template)}
        )
        else ()
        ,
        for $g in $result/group
        return
            <group>
            {
                $g/@*,
                $g/*,
                $allvstm
            }
            </group>
    }
    </result>
};

(:~
:   Links projects or unlinks projects to a governance group based on the @action attribute of the input.
:   Expected input: <group action="link|unlink" id="groupid" projectId="projectid"/>
:   
:   @param $groups - optional. The groups to link/unlink
:   @return null or error() in case of an unsupported action
:   @since 2015-03-09
:)
declare function gg:saveGroupLinks ($groups as element(group)*) {
    for $group in $groups
    let $action                 := $group/@action
    let $groupid                := $group/@id
    let $projectid              := $group/@projectId
    (: 
        if   :: no file exist => make one with the whole content, and done
        else :: if partOf element with this governance group already exist and unlink=true => delete this partOf element
    :)
    let $result :=
        if ($action='unlink') then 
            gg:unlinkGroup($groupid, $projectid)
        else if ($action='link') then (
            gg:linkGroup($groupid, $projectid)
        )
        else (
            error(QName('http://art-decor.org/ns/error', 'UnsupportedAction'), concat('Found unsupported action ''',$action,'''. Expected @action=link or @action=unlink'))
        )
    return ()
};

(:~
:   Links project to a governance group
:   
:   @param $groupid - required. The governance group id
:   @param $projectid - required. The decor project id
:   @return null
:   @since 2015-03-09
:)
declare function gg:linkGroup ($groupid as xs:string, $projectid as xs:string) {
    let $currentLinks           := gg:getLinkedGovernanceGroups($projectid)
    let $newLinks               := gg:prepareGovernanceGroupLink($groupid, $projectid)
    (: get project parent collection of project :)
    let $strProjectCollection   := util:collection-name($get:colDecorData//decor[project/@id=$projectid])
    let $update := 
        if (not($currentLinks)) then (
            let $f  := xmldb:store($strProjectCollection, $gg:strDecorLinkedGovernanceGroups, $newLinks)
            let $u  := try { adpfix:setDecorPermissions() } catch * {()}
            return ()
        )
        else (
            update insert $newLinks/partOf into $currentLinks
        )
    
    return ()
};

(:~
:   Updates project links for a governance group when the governance group id was updated.
:   
:   @param $previousgroupid - required. The previous governance group id
:   @param $newgroupid - required. The new governance group id
:   @return null
:   @since 2015-03-09
:)
declare function gg:updateGroupLinks ($previousgroupid as xs:string, $newgroupid as xs:string) {
    let $currentLinks           := gg:getLinkedGovernanceGroups(())
    let $update                 := update value $currentLinks/partOf[@ref=$previousgroupid]/@id with $newgroupid
    
    return ()
};

(:~
:   Unlinks projects from a governance group
:   
:   @param $groupid - required. The governance group id
:   @param $projectid - optional. The decor project id. Unlinks from every project if not given.
:   @return null
:   @since 2015-03-09
:)
declare function gg:unlinkGroup ($groupid as xs:string, $projectid as xs:string?) {
    let $currentLinks           := gg:getLinkedGovernanceGroups($projectid)
    let $update                 := update delete $currentLinks/partOf[@ref=$groupid]
    
    return ()
};

declare %private function gg:projectDetails($project as element(project),$addArtefactList as xs:boolean) as element() {
    <project>
    {
        $project/@id,
        $project/@prefix,
        $project/@defaultLanguage,
        attribute experimental {$project/@experimental = 'true'},
        attribute repository {$project/ancestor::decor/@repository = 'true'},
        attribute private {$project/ancestor::decor/@private = 'true'},
        if ($project/name[@language = $project/@defaultLanguage]) then () else (
            <name language="{$project/@defaultLanguage}">{$project/name/node()}</name>
        ),
        $project/name,
        (: 
            get governance group project template and value set list, 
            but only if this get call is for one single governance group only
            AND
            the parameter getall=true. This parameter is false in the first call to 
            just get the most important information about the governance gropup.
            A subsequent but typically asynchronous call will get the detailed
            list of templates and value sets then
        :)
        if ($addArtefactList=true()) then (
            (: 
                following is copy from get-valueset-list.xquery, 
                should use vs:getValueSetList ($id as xs:string?, $name as xs:string?, $flexibility as xs:string?, $prefix as xs:string?, $version as xs:string?)
                as vs:getValueSetList('','','',$project/@prefix) from valueset api but this does not work !?
                
                KH: 20151205: sinterklaas zei, nu werkt het ;-)
            :)
            (:
            for $valueSet in $project/../terminology/valueSet
            let $latestVersion  := max($valueSet/xs:dateTime(@effectiveDate))
            let $latestValueSet := if (empty($latestVersion)) then $valueSet[1] else ($valueSet[@effectiveDate=$latestVersion][1])
            let $name := $valueSet/@name
            group by $name
            order by if ($valueSet[1]/@displayName[string-length()>0]) then lower-case($valueSet[1]/@displayName) else (lower-case($valueSet[1]/@name))
            return
            <valueSet>
            {
                $latestValueSet/@name,
                $latestValueSet/@displayName,
                $latestValueSet/@effectiveDate,
                if ($latestValueSet/@id[string-length()>0]) then (
                    $latestValueSet/@id
                ) else (
                    $latestValueSet/@ref
                ),
                $latestValueSet/@statusCode
            }
            </valueSet>,
            :)
            
            vs:getValueSetList-v2((),(),(),$project/@prefix,(),true(),())/valueSet
            ,
            templ:getTemplateList((),(),(),$project/@prefix,(),false())/*
            
        ) else ()
    }
    </project>
};

declare %private function gg:prepareGovernanceGroupFile ($groups as element(group)*) as element(governance) {
    <governance xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://assets.art-decor.org/ADAR/rv/DECOR-governance-groups.xsd">
    {
        for $group in $groups
        return gg:prepareGovernanceGroup($group)
    }
    </governance>
};

declare %private function gg:prepareGovernanceGroup ($group as element()) as element(group) {
    <group>
    {
        $group/(@id|@defaultLanguage)
        ,
        $group/name
        ,
        for $desc in $group/desc
        return
            art:parseNode($desc)
        ,
        $group/(* except (edit|delete|name|desc|copyright|project)),
        <copyright>
        {
            $group/copyright/@*,
            for $addr in $group/copyright/addrLine[not(.='')]
            return
                <addrLine>{$addr/@*[not(.='')],$addr/node()}</addrLine>
        }
        </copyright>
    }
    </group>
};

declare %private function gg:prepareGovernanceGroupLink ($groupid as xs:string, $projectid as xs:string) as element(governance-group-links) {
    <governance-group-links ref="{$projectid}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://assets.art-decor.org/ADAR/rv/DECOR-governance-groups.xsd">
        <partOf ref="{$groupid}"/>
    </governance-group-links>
};