%D \module
%D   [       file=mp-node.mpiv,
%D        version=1998.02.15,
%D          title=\CONTEXT\ \METAPOST\ graphics,
%D       subtitle=Node Based Graphics,
%D         author=Alan Braslau,
%D           date=\currentdate,
%D      copyright={Alan Braslau & \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D The crossing macros were written as part of this module but as they
%D can be of use elsewhere they are defined in mp-tool.

if known metafun_loaded_node : endinput ; fi ;

newinternal boolean metafun_loaded_node ; metafun_loaded_node := true ; immutable metafun_loaded_node ;

% Build a path from the node positions.
% Must be integer and continuous in index starting at 0.

vardef makenodepath(suffix p) =
    if unknown p :
        if not path p :
            d := dimension p ;
            if d>0 :
                scantokens("path " & prefix p & for i=1 upto d : "[]" & endfor " ;") ;
            else :
                path p ;
            fi
        fi
        save i ; i = -1 ;
        p = forever : exitif unknown p.pos[incr i] ;
                 p.pos[i] --
             endfor cycle ;
    fi
enddef ;

% can take a list:

def clearpath text t =
    save t ; path t ;
enddef ;

def clearnodepath = clearpath nodepath enddef ;

clearnodepath ;

% the trailing "," below handles when number of t<3

vardef makenode@#(text t) =
    for a = t :
        if (path a) or (unknown a) :
            mfun_makenode@#(t,)
        elseif (string a) and (length(a) = 0) :
            mfun_makenode@#(t,)
        else :
            mfun_makenode@#(nodepath, t,)
        fi
        exitif true ;
    endfor
enddef ;

vardef node@#(text t) =
    for a = t :
        if (path a) or (unknown a) :
            mfun_node@#(t,)
        elseif (string a) and (length(a) = 0) :
            mfun_node@#(t,)
        else :
            mfun_node@#(nodepath, t,)
        fi
        exitif true ;
    endfor
enddef ;

vardef nodeboundingpoint@#(text t) =
    for a = t :
        if (path a) or (unknown a) :
            mfun_nodeboundingpoint@#(t)
        elseif (string a) and (length(a) = 0) :
            mfun_nodeboundingpoint@#(t)
        else :
            mfun_nodeboundingpoint@#(nodepath,a)
        fi
        exitif true ;
    endfor
enddef ;

vardef fromto@#(expr d, f)(text t) =
    fromtopaths@#(d,nodepath,f,nodepath,t)
enddef ;

% returns a pair suffix if the path is unknown

vardef mfun_makenode@#(suffix p)(expr i)(text t) =
    save d, b ; string b ;
    d = dimension p ;
    if d > 0 :
        b := prefix p ;
        if not picture p.pic[i] : scantokens("picture " & b &
                                      for j=1 upto d : "[]" & endfor
                                      "pic[] ;") ; fi
        if not pair    p.pos[i] : scantokens("pair "    & b &
                                      for j=1 upto d : "[]" & endfor
                                      "pos[] ;") ; fi
    else :
        if not picture p.pic[i] : picture p.pic[] ; fi
        if not pair    p.pos[i] : pair    p.pos[] ; fi
    fi
    for a = t :
        if known p.pic[i] :
            addto p.pic[i] also
        else :
            p.pic[i] =
        fi
            if     picture a           : a
            elseif string  a           : if (length(a) > 0) : textext@#(a) else : nullpicture fi
            elseif numeric a           : textext@#(decimal a)
            elseif ((boolean a) and a) : image(draw origin withpen currentpen scaled 4)
            else                       : nullpicture
            fi ;
    endfor
    p.pos[i] if known p : := point i of p ; fi
enddef ;

% returns a picture

vardef mfun_node@#(suffix p)(expr i)(text t) =
    if pair mfun_makenode@#(p,i,t) :
        % nop: enclose in "if ... fi" to gobble the function return.
    fi
    if (unknown p) and (known p.pos[i]) :
        makenodepath(p) ;
    fi
    if known p.pic[i] :
        p.pic[i] if known p : shifted point i of p fi
    else :
        nullpicture
    fi
enddef ;

newinternal node_loopback_yscale ; node_loopback_yscale := 1 ;

% returns a path

vardef fromtopaths@#(expr d)(suffix p)(expr f)(suffix q)(text s) =
    save r, t, l ;
    path r[] ; picture l ;
    for a = s :
        if unknown t :
            t = a ;
            if (unknown p) and (known p.pos[f]) :
                makenodepath(p) ;
            fi
            if (unknown q) and (known q.pos[t]) :
                makenodepath(q) ;
            fi
            r0 = if ((not numeric d) and
                     (point f of p = point f of q) and
                     (point t of p = point t of q)) :
                     subpath (f,t) of p
                 else :
                     point f of p -- point t of q
                 fi ;
            save deviation ;
            deviation := if numeric d: d else: 0 fi ;
            r1 = if (point 0 of r0) = (point length r0 of r0) :
                     (fullcircle yscaled node_loopback_yscale rotated 180
                         if mfun_laboff@# <> origin :
                             rotated angle mfun_laboff@#
                             shifted     .5mfun_laboff@#
                         fi)
                         scaled deviation
                         shifted point 0 of r0
                 elseif deviation=0 :
                     r0
                 else :
                     point 0 of r0 ..
                     unitvector direction .5length r0 of r0 rotated 90
                         scaled deviation * arclength r0
                         shifted point .5length r0 of r0 ..
                     point length r0 of r0
                fi ;
        else :
            if known l :
                addto l also
            else :
                l :=
            fi
                if     picture a           : a
                elseif string  a           : if (length(a) > 0) : textext@#(a) else : nullpicture fi
                elseif numeric a           : textext@#(decimal a)
                elseif ((boolean a) and a) : image(draw origin withpen currentpen scaled 4)
                else                       : nullpicture
                fi ;
        fi
    endfor
    r2 = r1
        if known p.pic[f if cycle p: mod length p fi] :
            cutbefore boundingbox (p.pic[f if cycle p: mod length p fi] shifted point f of p)
        fi
        if known q.pic[t if cycle q: mod length q fi] :
            cutafter  boundingbox (q.pic[t if cycle q: mod length q fi] shifted point t of q)
        fi
        ;
    if known l :
        l := l shifted point .5length r2 of r2 ;
        draw l ;
        (r2 if str @# = "" : crossingunder l fi)
    else :
        r2
    fi
enddef ;

% returns pair: bounding point of the node picture

vardef mfun_nodeboundingpoint@#(suffix p)(expr i) =
    if known p.pic[i] :
        boundingpoint@#(p.pic[i])
    else :
        origin
    fi
enddef ;

% returns pair: scaled laboff direction

vardef relative@#(expr s) =
    (mfun_laboff@# scaled s)
enddef ;

% returns pair: vector between nodes (+ optional scale)

vardef betweennodes@#(suffix p)(expr f)(suffix q)(text s) =
    save t ;
    for a = s :
        if unknown t :
            t = a ;
            mfun_nodeboundingpoint@#(q,t) + mfun_nodeboundingpoint@#(p,f)
        else :
            + relative@#(a)
        fi
    endfor
enddef ;

% helpers that save passing tokens

def mfun_node_init(expr dx, dy, da) =
     save nodelattice ; pair nodelattice[] ;
     nodelattice0 = (dx,0) ;
     nodelattice1 = dy * dir(da) ;
     clearnodepath ;
     save nodecount ; nodecount = -1;
enddef ;

def mfun_node_make(expr x, y, s) =
    nodecount := nodecount + 1 ;
    makenode(nodecount,s) = x * nodelattice0 + y * nodelattice1 ;
enddef ;

def mfun_node_flush =
    for i=0 upto nodecount:
        draw node(i) ;
    endfor
enddef ;

vardef mfun_nodes_fromto@#(expr d, f)(text t) =
    fromtopaths@#(d,nodepath,f,nodepath,t)
enddef ;

permanent makenodepath, clearpath, clearnodepath, makenode, node, nodeboundingpoint, fromto, fromtopaths, relative, betweennodes ;
permanent node_loopback_yscale ;