xquery version "3.1";

(:~
 : This module expands the motifs, which are encoded as processing instructions,
 : to full TEI elements. By doing this, we create a kind of intermediate format
 : for the TEI files that serves as a basis for the HTML creation.
 : 
 : Each motif is converted to a tei:span encompassing everything that is between
 : the beginning of a comment/processing instruction and its end.
 :)

module namespace me="http://ahiqar.uni-goettingen.de/ns/motifs-expansion";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare namespace motifs="http://ahiqar.uni-goettingen.de/ns/motifs";

declare function me:main($pages as node()+)
as node()+ {
    me:transform-pis($pages)
    => me:transform-ends()
};

declare function me:transform-pis($nodes as node()*)
as node()* {
    for $node in $nodes return
        typeswitch ($node)

        case text() return
            $node

        case comment() return
            ()

        case processing-instruction() return
            if( not( starts-with(serialize($node), "<?oxy_comment_"))) then () else
            let $pi := local:pi-to-map($node)
            return
                switch ($pi("type"))
                    case "start" return
                        let $md5 := "md5" || (serialize($node) || util:random()) => util:hash("md5") => substring(1, 20)
                        return
                            element {QName("http://ahiqar.uni-goettingen.de/ns/motifs", $pi("comment"))} {
                                attribute type {"start"},
                                attribute md5 {$md5},
                                $pi("mid") ! attribute mid {.}
                            }
                    case "end" return
                        element {QName("http://ahiqar.uni-goettingen.de/ns/motifs", "end")} {
                            (: placeholder element to be corrected in me:transform-ends#1 :)
                            $pi("mid") ! attribute mid {.},
                            attribute type {"end"}
                        }
                    default return ()

        default return
            element {QName($node/namespace-uri(), $node/local-name())} {
                $node/@*,
                me:transform-pis($node/node())
            }
};


declare function local:pi-to-map($node as node())
as map(*){
    map:merge((
        if (string($node) ne "" and not( starts-with($node, "mid") )) then
            map:entry("type", "start") (: oxy_comment_start :)
        else
            map:entry("type", "end"), (: oxy_comment_end :)

        tokenize($node) ! map:entry(substring-before(., "="), substring-after(replace(., '"', ''), "="))
    ))
};

(: transforms motifs:end elemetns to represent their starting counterpart :)
declare function me:transform-ends($node as node()) {
    typeswitch ($node)
        case element(motifs:end) return
                let $startingNode :=
                    if($node/@mid) then
                        (: easy case :)
                        $node/preceding::motifs:*[@mid eq $node/@mid]
                    else
                        let $start :=
                            if( $node/preceding::motifs:*[not(@mid)][@type eq "start"][1] << $node/preceding::motifs:end[not(@mid)][1] )
                            then
                                if( $node/preceding::motifs:*[not(@mid)][@type eq "start"][2] << $node/preceding::motifs:end[not(@mid)][2] )
                                then
                                    if( $node/preceding::motifs:*[not(@mid)][@type eq "start"][3] << $node/preceding::motifs:end[not(@mid)][3] )
                                    then
                                        (: encapsulated case level 3 :)
                                        $node/preceding::motifs:*[not(@mid)][@type eq "start"][4]
                                    else
                                    (: encapsulated case level 2 :)
                                    $node/preceding::motifs:*[not(@mid)][@type eq "start"][3]
                                else
                                (: encapsulated case level 1 :)
                                $node/preceding::motifs:*[not(@mid)][@type eq "start"][2]
                            else
                                $node/preceding::motifs:*[@type eq "start"][not(@mid)][1]
                        let $test := if($start) then true() else error(QName("http://ahiqar.uni-goettingen.de/ns/motifs-expansion", "error1"), "no start node found for motif: " || $node)
                        return
                            $start
                let $startingNode := if($startingNode) then $startingNode else <error/>
                return
                    element {QName($node/namespace-uri(), $startingNode/local-name())} {
                        $node/@*,
                        $startingNode/@md5
                    }
            
        case text() return $node
        default return
            element {QName($node/namespace-uri(), $node/local-name())} {
                $node/@*,
                $node/node() ! me:transform-ends(.)
            }

};

(: adds starting and ending elements to a fragment :)
declare function me:fragment-extension($node as node()) {
    typeswitch ($node)
        case element(tei:body) return
            let $ends := $node//motifs:*[@type eq "end"]/string(@md5)
            let $starts := $node//motifs:*[@type eq "start"]/string(@md5)
            (: prepare new starting nodes :)
            let $startingNodes :=
                (: get all mofits with type="end" that do not start in scope :)
                let $targets := $ends[not(. = $starts)]
                for $target in $node//motifs:*[@md5 = $targets]
                return
                    element {QName($node/namespace-uri(), $node/local-name())} {
                        $node/@*[not(@type)],
                        attribute type {"start"}
                    }
            (: prepare new ending nodes :)
            let $endingNodes :=
                (: get all mofits with type="end" that do not start in scope :)
                let $targets := $starts[not(. = $ends)]
                for $target in $node//motifs:*[@md5 = $targets]
                return
                    element {QName($node/namespace-uri(), $node/local-name())} {
                        $node/@*[not(@type)],
                        attribute type {"end"}
                    }
            return
                (: body :)
                element {QName($node/namespace-uri(), $node/local-name())} {
                    $node/@*,
                    text { "ends found:", $ends[not(. = $starts)] },
                    $startingNodes,
                    $node/node() ! me:fragment-extension(.),
                    $endingNodes
                }
                (: TODO deal with over-spanning elements :)

        case text() return $node
        default return
            element {QName($node/namespace-uri(), $node/local-name())} {
                $node/@*,
                $node/node() ! me:fragment-extension(.)
            }
};
