
:- lib(os_lib).
:- lib(real).
:- lib(options).
:- lib(debug_call).

:- lib(mtx).
:- lib( stoics_lib:kv_decompose/3 ).
:- lib( stoics_lib:list_proportions/3 ).

:- lib( stoics_lib:holds/2 ).
:- lib( stoics_lib:io_lines/2 ).
:- lib( stoics_lib:en_list/2 ).
:- lib( stoics_lib:portray_clauses/2 ).
:- lib( b_real:mtx_pheatmap/2 ).

svg_legend_defaults( Defs ) :- 
    Defs    = [
            cache(false),
            clr_theme(org),
            heatmaps(false),
            postfix(leg),   
            postfix_mtx(mtx),
            placement(bottom_right),
            place_with_space(200),
            place_with_decrement(20),
            place_with_space_min(100),
            upward(false)
        ].

:- debug( svg_legend ).

/** svg_legend( +FilesAndOpts )

Adds legend to svg files.
Atoms in Files and Opts are taken to be the files, and everything else is options.
See option atoms/1 in options_append/4.

If no Files are given, then svgs from current directory are used.
If one or more svg files with postfix fclr and a single svg with 
postfix (os_postfix/2) exist, then those are taken as the inputs,
else all svgs are considered.

Opts 

  * cache(Cache=false)
     whether to cache the intermediary (distance) matrices to files

  * clr_theme(Theme=org)
     alternatively use =bic= for bi-colour, =gates= for gated and =jyoti= for modern

  * heatmaps(Hmaps=false)
     whether to show matrix heatmaps

  * postfix(Psfx=leg)
     postfix for new svg
 
  * postfix_mtx(Msfx=mtx)
     postfix for the cache file of the intermediary matrices

  * placement(Plc=bottom_right)
    legend placement strategy. also implemented: 
     * at
        see place_at_*
     * on_rhs
        extends the svg canvas by ??? and adds the legened at bottom right of extension

  * place_at_x(X)
     known to placement: at. mandatory for this placement. pos values work from top, neg from bottom

  * place_at_y(y)
     known to placement: at. mandatory for this placement. pos values work from left, neg from right

  * place_with_space(Spc=200)
     space parameter for strategies (known to bottom_right)

  * place_with_decrement(Decr=20)
     decrement parameter with the amount with which Spc is reduced if placement is not possible

  * place_with_space_min(SpcMin=100)
     mimimum space required

  * upward(Upw=false)
     set to _true_ for the y to interpreted as the bottom of the legend (currently only supported in jyoti)

==
?- cd(pack(sanger/examples/svg) ).
?- svg_legend( testo.svg ).

% pupsh svg_legend *gated.svg clr_theme=gates placement=at place_at_x=1000 place_at_y=-160
% pupsh svg_legend *fclr.svg placement=at place_at_x=1000 place_at_y=-90
% pupsh svg_legend tng/rea_smp-e10_fclr_gated.svg clr_theme=upward placement=at place_at_x=100 place_at_y=-1
% pupsh svg_legend tnx/rea_xsm-e1_fclr_gated.svg clr_theme=upward placement=at place_at_x=160 place_at_y=-1
ERROR: Unknown message: could_not_mtx_csv_file('tnx/rea_xsm-e1_fclr_gated.csv')

==

@author nicos angelopoulos
@version  0.1 2016/12/10
@version  0.2 2017/8/21,  added placement: at (by passing distances, more or less properly)
@tbd optionise more

*/

svg_legend( Args ) :-
    % spy( options_def_append ),
    options_append( svg_legend, Args, Opts, [process(debug),atoms(Files)] ),
    ( Files = [] -> 
        svg_legend_dir( Opts )
        ;
        maplist( svg_legend_file(Opts), Files )
    ).

svg_legend_dir( Opts ) :-
    os_sel( os_files, ext(svg), Svgs ),
    ( ( include( os_postfix(fclr), Svgs, Fclrs ),
        Fclrs \== [],
        include( os_postfix(fisher), Svgs, [FisherSvg] )
      ) 
        -> 
            ToLeg = [FisherSvg|Fclrs]
            ;
            ToLeg = Svgs
    ),
    include( svg_legend_file(Opts), ToLeg, _Successes ).

/*
svg_legend_file( Opts, File ) :-
    memberchk( place_at_x(X), Opts ),
    memberchk( place_at_y(Y), Opts ),
    !,
    svg_legend_place( Opts, File, X, Y ).
    */
svg_legend_file( Opts, File ) :-
    debug( svg_legend, 'Svg input file: ~p', File ),
    Mtx = m, W = w, H = h,
    options( cache(Cache), Opts ),
    options( placement(Plc), Opts ),
    svg_legend_placement_requires_distance( Plc, Dist ),
    svg_cache( Cache, File, Mtx, Dist, W, H, Opts ),
    options( heatmaps(HmapsB), Opts ),
    heatmaps( HmapsB, Mtx, H, W ),
    Dims <- dim(Mtx),
    Dims = [Y, X],
    svg_legend_placement( Plc, Mtx, W, H, Y, X, Xi, Yi, Opts ),
    svg_legend_abs_x_coord( Xi, X, Xa ),
    svg_legend_abs_y_coord( Yi, Y, Ya ),
    % NegYi is Yi - Y, % fixme: add/subt 1 ?
    svg_legend_place( Opts, File, Xa, Ya ).

svg_legend_abs_x_coord( Rel, Edge, Abs ) :-
    Rel < 0,
    !,
    Abs is Edge + Rel.
svg_legend_abs_x_coord( Rel, _Edge, Abs ) :-
    Abs is Rel.

svg_legend_abs_y_coord( Rel, _Edge, Abs ) :-
    Rel < 0,
    !,
    Abs is Rel.
svg_legend_abs_y_coord( Rel, Edge, Abs ) :-
    Abs is Rel - Edge.

svg_legend_place( Opts, File, Xi, NegYi ) :-
    debug( svg_legend, 'Placing at: ~w,~w', [Xi,NegYi] ),
    options( postfix(Psfx), Opts ),
    os_postfix( Psfx, File, Lile ),
    io_lines( File, Lines ),
    once( append(Unchanged,[Penu,Last],Lines) ),
    options( clr_theme(Theme), Opts ),
    options( upward(UpW), Opts ),
    svg_add_lines( Theme, Xi, NegYi, UpW, File, AddLines ),
    append( AddLines, [Penu,Last], NewTail ),
    append( Unchanged, NewTail, New ),
    io_lines( Lile, New ),
    debug_call( svg_legend, wrote, Lile ).

/*  fixme: 
svg_add_lines( bic_gates_or_not, Xi, Yi, UpW, File, Add )

 see:  rearrange_muts-17.01.17_man

*/
gates_term_functor( Atom, Funcs ) :-
    atomic( Atom ),
    !,
    Funcs = [].
gates_term_functor( Term, [Token|FuncNest] ) :-
    Term =.. [Func|Args],
    gates_term_functor_atom( Func, Token ),
    maplist( gates_term_functor, Args, FuncNest ).

gates_term_functor_atom( o, or  ) :- !.
gates_term_functor_atom( a, and ) :- !.
gates_term_functor_atom( n, not ) :- !.
gates_term_functor_atom( x, xor ) :- !,
    throw( xor(ing) ).
gates_term_functor_atom( Oth, _ ) :- !,
    throw( unknown_gate_functor(Oth) ).

svg_add_lines_gates( [], _Xi, Y, Y, [] ).
svg_add_lines_gates( [G|Gs], Xi, Yi, Y5, [Ln1,Ln2,Ln3,Ln4|GatesAtms] ) :-
    svg_add_lines_gate( G, Xi, Yi, Ln1, Ln2, Ln3, Ln4 ),
    Yj is Yi - 20,
    svg_add_lines_gates( Gs, Xi, Yj, Y5, GatesAtms ).


svg_add_lines_gate( xor, _Xi, _Yi, _Yj, _L1, _L2, _L3, _L4 ) :-
    throw( xor(ing) ).
svg_add_lines_gate( not, Xi, Yi, NotL1, NotL2, NotL3, NotL4 ) :-
    % NOT gate 
    NotTxX is Xi + 30,
    NotTxY is Yi,
    NotX is Xi + 17,
    NotY is Yi - 5,
    NotL1a = '<text text-anchor="start" x="',
    NotL1b = '" y="',
    NotL1c = '" font-family="Times,serif" font-size="14.00">NOT gate</text>',
    NotL2 = '<g id="legNot" class="node"><title>LegNot</title>',
    NotL3a = '<ellipse fill="#87a96b" stroke="#87a96b" cx="', % asparagus
    NotL3b = '" cy="',
    NotL3c = '" rx="3" ry="3"/>',
    NotL4 = '</g>',
    atomic_list_concat( [NotL1a,NotTxX,NotL1b,NotTxY,NotL1c], NotL1 ),
    atomic_list_concat( [NotL3a,NotX,NotL3b,NotY,NotL3c], NotL3 ).
svg_add_lines_gate( and, Xi, Yi, AndLat, AndMat, AndNat, Aat ) :-
    % AND GATE
    T1 = '<text text-anchor="start" x="',
    T2 = '" y="',
    T3 = '" font-family="Times,serif" font-size="14.00">', 
    T4 = '</text>',
    AndX is Xi + 30,
    AndY is Yi,
    AndTxt = 'AND gate',
    AndMat = ' <g id="legAND" class="node"><title>LegAND</title>',
    AndN1 = ' <polygon fill="#87a96b" stroke="#87a96b" points="',
    maplist( plus(Xi), [13,17,21,13], AndXs ),
    maplist( plus(Yi), [-8,-1,-8,-8], AndYs ),
    % Whatevs = '6,-23 10,-20 14,-23 14,-27 6,-27 6,-23',
    maplist( atom_concat(','), AndYs, AndComYs ),
    maplist( atom_concat, AndXs, AndComYs, AndXYs ),
    atomic_list_concat( AndXYs, ' ', AndPoly ),
    AndN2 = '"/>',
    Aat  = '</g>',
    atomic_list_concat( [T1,AndX,T2,AndY,T3,AndTxt,T4], AndLat ),
    atomic_list_concat( [AndN1,AndPoly,AndN2], AndNat ).
svg_add_lines_gate( or, Xi, Yi, Lat, Mat, Nat, Oat ) :-
    % OR GATE
    T1 = '<text text-anchor="start" x="',
    T2 = '" y="',
    T3 = '" font-family="Times,serif" font-size="14.00">', 
    T4 = '</text>',
    OrX is Xi + 30,
    OrY is Yi,
    OrTxt = 'OR gate',
    Mat = ' <g id="legOR" class="node"><title>LegOR</title>',
    N1 = ' <polygon fill="#87a96b" stroke="#87a96b" points="',
    maplist( plus(Xi), [13,17,21,21,13,13], Xs ),
    % maplist( plus(Yi), [-54,-51,-54,-58,-58,-54], Ys ),
    % maplist( plus(Yi), [74,78,74,71,71,74], Ys ),
    maplist( plus(Yi), [-5,-1,-5,-8,-8,-5], Ys ),
    % Whatevs = '6,-23 10,-20 14,-23 14,-27 6,-27 6,-23',
    maplist( atom_concat(','), Ys, ComYs ),
    maplist( atom_concat, Xs, ComYs, XYs ),
    atomic_list_concat( XYs, ' ', Poly ),
    N2 = '"/>',
    Oat  = '</g>',
    atomic_list_concat( [T1,OrX,T2,OrY,T3,OrTxt,T4], Lat ),
    atomic_list_concat( [N1,Poly,N2], Nat ).

gates_sort( GatesBag, Gates ) :-
        sort( GatesBag, GatesOrd ),
        reverse( GatesOrd, GatesInRev ),
        ( select(not,GatesInRev,GatesNoNot) ->
            Gates = [not|GatesNoNot]
            ;
            Gates = GatesInRev
        ).

svg_line_segs_as( A1, A2, A3, A4 ) :-
    A1 = '<text text-anchor="start" x="', % 856
    A2 = '" y="', % -85
    A3 = '" font-family="Times,serif" font-size="14.00">',
    A4 = '</text>'.
svg_line_segs_bs( B1, B2, B3 ) :-
    B1 = '<path fill="none" stroke-width="2.8" stroke="#',
    B2 = '" d="M',
    B3 = ' l 25,0"/>'.
    
svg_add_lines_compos( [], _File, _Xi, _Xt, _Yi, _Dir, [] ).
svg_add_lines_compos( [C|Cs], File, Xi, Xt, Yi, Dir, [Lns|TLns] ) :-
    svg_add_lines_compo( C, File, Xi, Xt, Yi, IncY, Lns ),
    Expr =.. [Dir,Yi,IncY],
    Yj is Expr,
    % Yj is Yi + IncY,
    svg_add_lines_compos( Cs, File, Xi, Xt, Yj, Dir, TLns ).

svg_add_lines_compo( slide, _File, Xi, Xt, Yi, 20, [I,J] ) :-
    % compo: slide
    svg_line_segs_as( A1, A2, A3, A4 ),
    L5 = 'Fisher test odds',
    atomic_list_concat( [A1,Xt,A2,Yi,A3,L5,A4], '', I ),
    J1 = '<polygon points="',
    Tx1 is Xi, Ty1 is Yi,
    Tx2 is Tx1 + 25, Ty2 is Ty1,
    Tx3 is Tx2, Ty3 is Ty2 - 6,
    J1b = '" style="fill:#BF812D;stroke:#35978F;stroke-width:1.4" />',
    atomic_list_concat( [J1,Tx1,',',Ty1,' ',Tx2,',',Ty2,' ',Tx3,',',Ty3,J1b], J ).

svg_add_lines_compo( cooc, _File, Xi, Xt, Yi, 20, [A,E] ) :-
    svg_line_segs_as( A1, A2, A3, A4 ),
    svg_line_segs_bs( B1, B2, B3 ),
    L1 = 'Co-occur (shown odds=4)',
    atomic_list_concat( [A1,Xt,A2,Yi,A3,L1,A4], '', A ),
    Ya is Yi - 5,
    atomic_list_concat( [B1,'35978F',B2,Xi,',',Ya,B3], E ).

svg_add_lines_compo( muex, _File, Xi, Xt, Yi, 20, [A,E] ) :-
    svg_line_segs_as( A1, A2, A3, A4 ),
    svg_line_segs_bs( B1, B2, B3 ),
    L2 = 'Mut.excl (shown odds=0.25)',
    Ya is Yi - 5,
    atomic_list_concat( [A1,Xt,A2,Yi,A3,L2,A4], '', A ),
    atomic_list_concat( [B1,'BF812D',B2,Xi,',',Ya,B3], E ).

svg_add_lines_compo( pval, File, Xi, Xt, Yi, Pad, Lns ) :-
    svg_line_segs_as( A1, A2, A3, A4 ),
    svg_line_segs_bs( _B1, B2, _B3 ),
    os_ext( _, csv, File, CsvFPrv ),
    ( exists_file(CsvFPrv) -> CsvF = CsvFPrv; os_postfix(gated,CsvF,CsvFPrv) ),
    ( mtx(CsvF,Mets) -> true; throw( could_not_mtx_csv_file(CsvF) ) ),
    maplist( arg(3), Mets, Pvals ),
    max_list( Pvals, MaxPv ),
    ( MaxPv < 0.05  ->   % fixme: = means no significance...
        Pad is 0,
        Lns = []
        ;
        Pad is 20,
        L3 = '0.05 &lt; q.val',
        D1 = '<path fill="none" stroke-width="2.8" stroke-dasharray="5,2" stroke="#',
        atomic_list_concat( [A1,Xt,A2,Yi,A3,L3,A4], '', C ),
        Yim is Yi - 5,
        C3 = ' l 11,0"/>',
        atomic_list_concat( [D1,'35978F',B2,Xi,',',Yim,C3], DA ),
        Xib is Xi + 14,
        atomic_list_concat( [D1,'BF812D',B2,Xib,',',Yim,C3], DB ),
        Lns = [C,DA,DB]
    ).

svg_add_lines_compo( node, File, Xi, Xt, Yi, 20, [G,H] ) :-
    svg_line_segs_as( A1, A2, A3, A4 ),
    svg_line_segs_bs( _B1, B2, B3 ),
    L4 = '# of events (median=', L3b = ')',
    atomic_list_concat( [FStem|_], '-', File ),
    os_ext( dat, FStem, DatFile ),
    write( 'dat.file'(DatFile) ), nl,
    mtx( DatFile, DatMtx, sep(0' ) ),
    mtx_value_column_frequencies( DatMtx, 1, Freqs ),
    kv_decompose( Freqs, _Lbls, Times ),
    TmMedian <- as.integer( median( Times ) ),
    list_proportions( Times, Propos, to_range(r(1,4)) ),
    NwMedian <- median( Propos ),
    % Yl is Yk + 20,
    atomic_list_concat( [A1,Xt,A2,Yi,A3,L4,TmMedian,L3b,A4], '', G ),
    G1 = '<path fill="none" stroke-width="', G1b= '" stroke="#',
    Yg is Yi - 5,
    atomic_list_concat( [G1,NwMedian,G1b,'000000',B2,Xi,',',Yg,B3], H ).

svg_add_lines( upward, Xi, Yi, UpW, File, Add ) :-
    ( UpW == true -> throw(unimplemented(upward,UpW)) ; true ),  % ! what ?
    ( atom_concat(Stem,'fclr_gated.svg',File) ->
        atom_concat(Stem,'prns_gates_best.csv',GatesF),
        mtx( GatesF, GatesMtx, match(false) ),
        findall( Gate, (  member(Row,GatesMtx), arg(6,Row,RowGatesAtm),
                          atom_to_term(RowGatesAtm,RowGates,[]),
                          gates_term_functor(RowGates,Gate)
                       )
                        , GatesNestBag ),
        flatten( GatesNestBag, GatesBag ),
        gates_sort( GatesBag, Gates ),
        ( Gates == [] ->
            Y5 = Yi, GatesAtms = []
            ;
            svg_add_lines_gates( Gates, Xi, Yi, Y5, GatesAtms )
        )
        ;
        Y5 = Yi, GatesAtms = []
    ),
    Xt is Xi + 30,
    svg_add_lines_compos( [node,pval,cooc,muex,slide], File, Xi, Xt, Y5, -, CompoLnsNest ),
    flatten( CompoLnsNest, CompoLns ),
    append( GatesAtms, CompoLns, AllLns ),
    % maplist( atom_codes, GatesAtms, Add ).
    maplist( atom_codes, AllLns, Add ).

svg_add_lines( jyoti, Xi, Yi, _UpW, File, Add ) :-
    L1 = 'Co-occur (shown odds=4)',
    L2 = 'Mut.excl (shown odds=0.25)',
    svg_line_segs_as( A1, A2, A3, A4 ),
    svg_line_segs_bs( B1, B2, B3 ),
    Xt is Xi + 30,
    Yh is Yi + 20,

    atomic_list_concat( [A1,Xt,A2,Yh,A3,L1,A4], '', A ),
    Yj is Yh + 20,
    atomic_list_concat( [A1,Xt,A2,Yj,A3,L2,A4], '', B ),
    Ya is Yh - 5,
    atomic_list_concat( [B1,'35978F',B2,Xi,',',Ya,B3], E ),
    Yb is Yj - 5,
    atomic_list_concat( [B1,'BF812D',B2,Xi,',',Yb,B3], F ),

    % L3 = 'events (shown events=', L3b = ')',

    os_ext( _, csv, File, CsvF ),
    ( mtx(CsvF,Mets) -> true; throw( could_not_mtx_csv_file(CsvF) ) ),
    maplist( arg(3), Mets, Pvals ),
    max_list( Pvals, MaxPv ),
    ( MaxPv < 0.05  ->   % fixme: = means no significance...
        Atoms = [A,B,E,F, G,H, I,J],
        Yk is Yj
        ;
        L3 = '0.05 &lt; q.val',
        Yk is Yj + 20,
        D1 = '<path fill="none" stroke-width="2.8" stroke-dasharray="5,2" stroke="#',
        atomic_list_concat( [A1,Xt,A2,Yk,A3,L3,A4], '', C ),
        Ykm is Yk - 5,
        C3 = ' l 11,0"/>',
        atomic_list_concat( [D1,'35978F',B2,Xi,',',Ykm,C3], DA ),
        Xib is Xi + 14,
        atomic_list_concat( [D1,'BF812D',B2,Xib,',',Ykm,C3], DB ),
        Atoms = [A,B,E,F, C,DA,DB, G,H, I,J]
    ), 

    L4 = '# of events (median=', L3b = ')',
    atomic_list_concat( [FStem|_], '-', File ),
    os_ext( dat, FStem, DatFile ),
    write( 'dat.file'(DatFile) ), nl,
    mtx( DatFile, DatMtx, sep(0' ) ),
    mtx_value_column_frequencies( DatMtx, 1, Freqs ),
    kv_decompose( Freqs, _Lbls, Times ),
    TmMedian <- as.integer( median( Times ) ),
    list_proportions( Times, Propos, to_range(r(1,4)) ),
    NwMedian <- median( Propos ),
    Yl is Yk + 20,
    atomic_list_concat( [A1,Xt,A2,Yl,A3,L4,TmMedian,L3b,A4], '', G ),
    G1 = '<path fill="none" stroke-width="', G1b= '" stroke="#',
    Yg is Yl - 5,
    atomic_list_concat( [G1,NwMedian,G1b,'000000',B2,Xi,',',Yg,B3], H ),

    % Pl1 is <polygon points="200,10 250,190 160,210" style="fill:lime;stroke:purple;stroke-width:1" />
    svg_add_lines_compo( [slide], File, Xt, Xi, Yi, +, [I,J] ),
    maplist( atom_codes, Atoms, Add ).
    % maplist( atom_codes, [A,B,E,F, C,G], [ACs,BCs,ECs,FCs, CCs, GCs] ),
    % Add = [ACs,ECs,BCs,FCs, CCs,GCs].

svg_add_lines( bic, Xi, Yi, _UpW, _File, Add ) :-
    L1 = 'Co-occur',
    L2 = 'Mut.excl',
    A1 = '<text text-anchor="start" x="', % 856
    A2 = '" y="', % -85
    A3 = '" font-family="Times,serif" font-size="14.00">',
    A4 = '</text>',
    Xt is Xi + 30,
    atomic_list_concat( [A1,Xt,A2,Yi,A3,L1,A4], '', A ),
    Yj is Yi + 20,
    atomic_list_concat( [A1,Xt,A2,Yj,A3,L2,A4], '', B ),
    B1 = '<path fill="none" stroke="#',
    B2 = '" d="M',
    B3 = ' l 25,0"/>',
    Ya is Yi - 5,
    atomic_list_concat( [B1,'35978F',B2,Xi,',',Ya,B3], E ),
    Yb is Yj - 5,
    atomic_list_concat( [B1,'BF812D',B2,Xi,',',Yb,B3], F ),
    maplist( atom_codes, [A,B,E,F], [ACs,BCs,ECs,FCs] ),
    Add = [ACs,ECs,BCs,FCs].

svg_add_lines( gates, Xi, Yi, UpW, File, Add ) :-
    T1 = '<text text-anchor="start" x="',
    T2 = '" y="',
    T3 = '" font-family="Times,serif" font-size="14.00">', 
    T4 = '</text>',
    
    % inter gate edge 
    InTxX is Xi + 30,
    InTxY is Yi + 140,
    InTxt = 'Inter gates edge',

    B1 = '<path fill="none" stroke="#',
    B2 = '" d="M',
    B3 = ' l 25,0"/>',
    Ya is Yi + 135,
    atomic_list_concat( [B1,'000000',B2,Xi,',',Ya,B3], I1atm ),  % black
    atomic_list_concat( [T1,InTxX,T2,InTxY,T3,InTxt,T4], I2atm ),
    maplist( atom_codes, [I1atm,I2atm], [I1,I2] ),

    % NOT gate 
    NotTxX is Xi + 30,
    NotTxY is Yi + 120,
    NotX is Xi + 17,
    NotY is Yi + 115 ,
    NotL1a = '<text text-anchor="start" x="',
    NotL1b = '" y="',
    NotL1c = '" font-family="Times,serif" font-size="14.00">NOT gate</text>',
    NotL2 = '<g id="legNot" class="node"><title>LegNot</title>',
    NotL3a = '<ellipse fill="#87a96b" stroke="#87a96b" cx="', % asparagus
    NotL3b = '" cy="',
    NotL3c = '" rx="3" ry="3"/>',
    NotL4 = '</g>',
    atomic_list_concat( [NotL1a,NotTxX,NotL1b,NotTxY,NotL1c], NotL1 ),
    atomic_list_concat( [NotL3a,NotX,NotL3b,NotY,NotL3c], NotL3 ),

    maplist( atom_codes, [NotL1,NotL2,NotL3,NotL4], [Not1,Not2,Not3,Not4] ),

    % OR GATE
    OrX is Xi + 30,
    OrY is Yi + 80,
    OrTxt = 'OR gate',
    Mat = ' <g id="legOR" class="node"><title>LegOR</title>',
    N1 = ' <polygon fill="#87a96b" stroke="#87a96b" points="',
    maplist( plus(Xi), [13,17,21,21,13,13], Xs ),
    % maplist( plus(Yi), [-54,-51,-54,-58,-58,-54], Ys ),
    maplist( plus(Yi), [74,78,74,71,71,74], Ys ),
    % Whatevs = '6,-23 10,-20 14,-23 14,-27 6,-27 6,-23',
    maplist( atom_concat(','), Ys, ComYs ),
    maplist( atom_concat, Xs, ComYs, XYs ),
    atomic_list_concat( XYs, ' ', Poly ),
    N2 = '"/>',
    Oat  = '</g>',
    atomic_list_concat( [T1,OrX,T2,OrY,T3,OrTxt,T4], Lat ),
    atomic_list_concat( [N1,Poly,N2], Nat ),

    maplist( atom_codes, [Lat, Mat, Nat, Oat], [LCs,MCs,NCs,OCs] ),

    % AND GATE
    AndX is Xi + 30,
    AndY is Yi + 100,
    AndTxt = 'AND gate',
    AndMat = ' <g id="legAND" class="node"><title>LegAND</title>',
    AndN1 = ' <polygon fill="#87a96b" stroke="#87a96b" points="',
    maplist( plus(Xi), [13,17,21,13], AndXs ),
    maplist( plus(Yi), [92,99,92,92], AndYs ),
    % Whatevs = '6,-23 10,-20 14,-23 14,-27 6,-27 6,-23',
    maplist( atom_concat(','), AndYs, AndComYs ),
    maplist( atom_concat, AndXs, AndComYs, AndXYs ),
    atomic_list_concat( AndXYs, ' ', AndPoly ),
    AndN2 = '"/>',
    atomic_list_concat( [T1,AndX,T2,AndY,T3,AndTxt,T4], AndLat ),
    atomic_list_concat( [AndN1,AndPoly,AndN2], AndNat ),

    maplist( atom_codes, [AndLat,AndMat,AndNat], [AndLCs,AndMCs,AndNCs] ),
    svg_add_lines( org, Xi, Yi, UpW, File, OrgAdd ),
    Add = [Not1,Not2,Not3,Not4,I1,I2,LCs,MCs,NCs,OCs,AndLCs,AndMCs,AndNCs,OCs|OrgAdd].

svg_add_lines( org, Xi, Yi, _UpW, _File, Add ) :-
    L1 = 'Co-occur (signif)',
    L2 = 'Mut.excl (signif)',
    L3 = 'Co-occur',
    L4 = 'Mut.excl',
    A1 = '<text text-anchor="start" x="', % 856
    A2 = '" y="', % -85
    A3 = '" font-family="Times,serif" font-size="14.00">',
    A4 = '</text>',
    Xt is Xi + 30,
    atomic_list_concat( [A1,Xt,A2,Yi,A3,L1,A4], '', A ),
    Yj is Yi + 20,
    atomic_list_concat( [A1,Xt,A2,Yj,A3,L2,A4], '', B ),
    Yk is Yj + 20,
    atomic_list_concat( [A1,Xt,A2,Yk,A3,L3,A4], '', C ),
    Yl is Yk + 20,
    atomic_list_concat( [A1,Xt,A2,Yl,A3,L4,A4], '', D ),
    B1 = '<path fill="none" stroke="#',
    % BF812D
    B2 = '" d="M',
    % 823,-75
    B3 = ' l 25,0"/>',
    Ya is Yi - 5,
    atomic_list_concat( [B1,'35978F',B2,Xi,',',Ya,B3], E ),
    Yb is Yj - 5,
    atomic_list_concat( [B1,'BF812D',B2,Xi,',',Yb,B3], F ),
    Yc is Yk - 5,
    % 4B5320, army green
    % #008000, % Ao
    atomic_list_concat( [B1,'008000',B2,Xi,',',Yc,B3], G ),  % this and the one below were switched prior to 17.01.06
    Yd is Yl - 5,
    atomic_list_concat( [B1,'CC0000',B2,Xi,',',Yd,B3], H ),
    %
    maplist( atom_codes, [A,B,C,D], [ACs,BCs,CCs,DCs] ),
    maplist( atom_codes, [E,F,G,H], [ECs,FCs,GCs,HCs] ),
    Add = [ACs,ECs,BCs,FCs,CCs,GCs,DCs,HCs].

svg_legend_placement( at, _Mtx, _W, _H, _Y, _X, Xi, Yi, Opts ) :-
    options( place_at_x(Xi), Opts ),
    options( place_at_y(Yi), Opts ).
svg_legend_placement( bottom_right, Mtx, W, H, Y, X, Xi, Yi, Opts ) :-
    options( place_with_space(Spc), Opts ),
    options( place_with_decrement(Dcr), Opts ),
    options( place_with_space_min(Smn), Opts ),
    svg_bottom_right( Mtx, W, H, Y, X, Spc, Dcr, Smn, Xi, Yi ).

svg_bottom_right( Mtx, W, H, Y, X, Spc, _Dcr, _Smn, Xi, Yi ) :-
    debug( svg_legend, 'Trying bottom_right with space: ~w', Spc ),
    svg_bottom_right( Mtx, W, H, Y, X, Spc, Xi, Yi ),
    debug( svg_legend, 'Committing to space: ~w', Spc ),
    !.
svg_bottom_right( Mtx, W, H, Y, X, Spc, Dcr, Smn, Xi, Yi ) :-
    New is Spc - Dcr,
    Spc =< Smn,  % fixme, error if this fails
    svg_bottom_right( Mtx, W, H, Y, X, New, Dcr, Smn, Xi, Yi ).

svg_bottom_right( Mtx, W, H, Y, X, Xspc, Xi, Yi ) :-
    Yspc is 36,
    StartY is Y - Yspc,
    StartX is X - Xspc,
    svg_bottom_right( StartY, StartX, Yspc, Xspc, Mtx, W, H, Y, X, Xi, Yi ).

svg_bottom_right( Y, X, Yspc, Xspc, _Mtx, W, H, _Yc, _Xc, Xo, Yo ) :-
    Wv <- W[Y,X],
    Xspc =< Wv,
    Hv <- H[Y,X],
    Yspc =< Hv,
    Ydwn is Yspc - 1,
    svg_slide_box( Ydwn, Y, X, Yspc, Xspc, W, H ),
    !,
    Xo = X, Yo = Y.
svg_bottom_right( Y, X, Yspc, Xspc, Mtx, W, H, Yc, Xc, Xo, Yo ) :-
    Xp is X - 1, 
    ( 0 < Xp -> 
        X1 is Xp,
        Y1 is Y
        ;
        X1 is Xc - Xspc,
        Y1 is Y - 1,
        0 < Y1 
    ),
    svg_bottom_right( Y1, X1, Yspc, Xspc, Mtx, W, H, Yc, Xc, Xo, Yo ).

svg_slide_box( 0, _Y, _X, _Yspc, _Xspc, _W, _H ) :- !.
svg_slide_box( I, Y, X, Yspc, Xspc, W, H ) :-
    Yi is Y + I, 
    Wv <- W[Yi,X],
    Xspc =< Wv,
    Hv <- H[Yi,X],
    Yspc =< Hv,
    Im is I - 1,
    svg_slide_box( Im, Y, X, Yspc, Xspc, W, H ).

svg_cache( false, File, Mtx, Dist, W, H, _Opts ) :-
    read_svg_lines( File, Mtx, Dist, W, H ).
svg_cache( true, File, Mtx, Dist, W, H, Opts ) :-
    options( postfix_mtx(Msfx), Opts ),
    os_postfix( Msfx, File, Mile ), 
    os_ext( _, pl, Mile, Cile ),
    holds( exists_file(Cile), Cexists ),
    svg_cache_on( Cexists, Cile, File, Mtx, Dist, W, H ).

svg_cache_on( true, Cile, _File, Mtx, _Dist, W, H ) :-
    % fixme: 
    mtx_from_file( Cile ),
    debug_call( svg_legend, read, Cile ),
    % fixme: make sure those were created...
    Mtx = m, W = w, H = h,  % ensures those where as per loaded
    % heatmaps( Mtx, H, W ),
    true.
svg_cache_on( false, Cile, File, Mtx, Dist, W, H ) :-
    read_svg_lines( File, Mtx, Dist, W, H ),
    mtx_to_file( [Mtx,W,H], Cile ),
    debug_call( svg_legend, wrote, Cile ).

mtx_from_file( File ) :-
    tmp:ensure_loaded( File ),
    mtx_from_module( tmp ),
    abolish( matrix/2 ).

mtx_from_module( Mod ) :-
    Mod:matrix(Rtx,X),
    Row =.. [Rtx,Arg],
    findall( Arg, (call(Mod:Row),length(Arg,X)), Args ),
    length( Args, NRows ),
    Rtx <- matrix(0, nrow=NRows, ncol=X),
    Rtx <- Args,
    abolish( Rtx/1 ),
    fail.
mtx_from_module( _ ).

mtx_to_file( MtcPrv, File ) :-
    en_list( MtcPrv, Mtc ),
    maplist( mtx_to_clauses, Mtc, Infos, ClausesNest ),
    flatten( ClausesNest, Clauses ),
    append( Infos, Clauses, Alauses ),
    portray_clauses( Alauses, file(File) ).

% @tbd headers, then move to b_real ?
mtx_to_clauses( Rtx, Info, Clauses ) :-
    Mtx <- Rtx,
    Mtx = [First|_],
    length( First, Len ),
    Info = matrix(Rtx,Len),
    findall( Row, (member(RRow,Mtx),Row=..[Rtx,RRow]), Clauses ).

read_svg_lines( SvgF, Mtx, Dist, W, H ) :-
    open( SvgF, read, In ),
    read_line_to_codes( In, Line ),
    read_svg_stream( Line, In, Mtx, Dist, W, H ),
    close( In ).

read_svg_mtx( end_of_file, _Pfx, _X, _Y, _In, _Mtx ) :- !.
read_svg_mtx( Line, Pfx, Xc, Yc, In, Mtx ) :-
    append( Pfx, Rest, Line ),
    !,
    read_svg_int( XCs, Rest, [0',|Rem] ),
    number_codes( X, XCs ),
    read_svg_int( YCs, Rem, RemXY ),
    number_codes( Y, YCs ),
    read_svg_to_space( RemXY, Shift ),
    read_svg_int( PCs, Shift, _Leftovers ),
    number_codes( P, PCs ),
    % mtx_pheatmap( Rws, [cluster_rows='FALSE',cluster_cols='FALSE'] ),
    read_svg_mtx_score( Mtx, Xc, Yc, X, P, Y ),
    read_line_to_codes( In, NewLine ),
    read_svg_mtx( NewLine, Pfx, Xc, Yc, In, Mtx ).
read_svg_mtx( _Line, Pfx, Xc, Yc, In, Mtx ) :-
    read_line_to_codes( In, NewLine ),
    read_svg_mtx( NewLine, Pfx, Xc, Yc, In, Mtx ).

read_svg_mtx_score( Mtx, _Xc, Yc, X, P, Y ) :-
    Max is integer( X + 12 ),
    Min is integer( P - 12 ),
    % fixme: check Y is negative
    MxY is Yc + integer( Y + 36 ),
    MnY is Yc + integer(Y),
    findall( _, (   between(Min,Max,AnX),
                    between(MnY,MxY,AnY),
                Mtx[AnY,AnX] <- 1
                ), 
            _ 
          ).

read_svg_to_space --> 
    [C], { C \== 0' },
    !,
    read_svg_to_space.
read_svg_to_space --> [_C].

read_svg_stream( Line, In, Mtx, Dist, W, H ) :-
    append( `<svg width="`, Psfx, Line ),
    !,
    phrase( read_svg_int(XCs), Psfx, Rem ),
    number_codes( X, XCs ),
    write( x_canvas(X) ), nl,
    once( append(`pt" height="`,EndBit,Rem) ),
    phrase( read_svg_int(YCs), EndBit, _ ),
    number_codes( Y, YCs ),
    write( y_canvas(Y) ), nl,
    Mtx <- matrix( 0, nrow=Y, ncol=X ),
    read_line_to_codes( In, NewLine ),
    atom_codes( '<path fill="none" stroke="black" d="M', Pfx ),
    read_svg_mtx( NewLine, Pfx, X, Y, In, Mtx ),
    svg_distances( Dist, X, Y, Mtx, H, W ).
read_svg_stream( _Line, In, Mtx, Dist, W, H ) :-
    read_line_to_codes( In, Line ),
    read_svg_stream( Line, In, Mtx, Dist, W, H ).

svg_legend_placement_requires_distance( at, false ) :- !.
svg_legend_placement_requires_distance( _, true ).

heatmaps( false, _Mtx, _H, _W ).
heatmaps( true, Mtx, H, W ) :-
    Ms <- Mtx,
    findall( Rw, (member(AnM,Ms),Rw=..[row|AnM]), Rws ),
    <- x11(width=12,height=1.4),
    mtx_pheatmap( Rws, [cluster_rows='FALSE',cluster_cols='FALSE'] ),
    <- x11(),
    Hs <- H,
    findall( Rw, (member(AnM,Hs),Rw=..[row|AnM]), RwsH ),
    mtx_pheatmap( RwsH, [cluster_rows='FALSE',cluster_cols='FALSE'] ),
    <- x11(),
    Ws <- W,
    findall( Rw, (member(AnM,Ws),Rw=..[row|AnM]), RwsW ),
    mtx_pheatmap( RwsW, [cluster_rows='FALSE',cluster_cols='FALSE'] ).

/*
    scan x-wise assuming max free space on first 1, scan back updating
*/

svg_distances( true, X, Y, Mtx, H, W ) :-
    svg_distances( X, Y, Mtx, H, W ).
svg_distances( false, _X, _Y, _Mtx, _H, _W ).

svg_distances( X, Y, Mtx, H, W ) :-
    H <- matrix( 0, nrow=Y, ncol=X ),
    W <- matrix( 0, nrow=Y, ncol=X ),
    debug( svg_legend, 'Creating x distances', true ),
    svg_distances_x( Y, X, Mtx, W ),
    debug( svg_legend, 'Creating y distances', true ),
    svg_distances_y( X, Y, Mtx, H ),
    debug( svg_legend, 'Done with distances', true ).

svg_distances_x( 0, _X, _Mtx, _W ) :- !.
svg_distances_x( Y, X, Mtx, W ) :-
    svg_distances_x_row( X, Y, 1, Mtx, W ),
    Y1 is Y - 1,
    svg_distances_x( Y1, X, Mtx, W ).

svg_distances_x_row( 0, _Y, _Spc, _Mtx, _W ) :- !.
svg_distances_x_row( X, Y, Spc, Mtx, W ) :-
    Mv <- Mtx[Y,X],
    ( Mv =:= 1 -> 
        W[Y,X] <- 0,
        Npc is 1
        ; 
        W[Y,X] <- Spc,
        Npc is Spc + 1
    ),
    X1 is X - 1,
    svg_distances_x_row( X1, Y, Npc, Mtx, W ).

svg_distances_y( 0, _Y, _Mtx, _H ) :- !.
svg_distances_y( X, Y, Mtx, H ) :-
    svg_distances_y_row( Y, X, 1, Mtx, H ),
    X1 is X - 1,
    svg_distances_y( X1, Y, Mtx, H ).

svg_distances_y_row( 0, _X, _Spc, _Mtx, _H ) :- !.
svg_distances_y_row( Y, X, Spc, Mtx, H ) :-
    Mv <- Mtx[Y,X],
    ( Mv =:= 1 -> 
        H[Y,X] <- 0,
        Npc is 1
        ; 
        H[Y,X] <- Spc,
        Npc is Spc + 1
    ),
    Y1 is Y - 1,
    svg_distances_y_row( Y1, X, Npc, Mtx, H ).

/*
svg_distances( X, Y, Mtx, H, W ) :-
    H <- matrix( 0, nrow=Y, ncol=X ),
    W <- matrix( 0, nrow=Y, ncol=X ),
    Y > 1,
    svg_distances_loop( true, 1, Y, 1, X, Mtx, H, W ).

svg_distances_loop( false, _J, Y, I, X, Mtx, H, W ) :-
    Ip is I + 1,
    holds( Ip =< X, ContX ),
    svg_distances_outer( ContX, Ip, X, Y, Mtx, H, W ).

svg_distances_loop( true, J, Y, I, X, Mtx, H, W ) :-
    % ( (J=:=1,I=189) -> trace; true ),
    Mv <- Mtx[J,I],
    ( Mv =:= 1 -> 
        Hd = 0, Wd = 0
        ; 
        svg_point_distances( Mtx, I, J, X, Y, Hd, Wd )
    ),
    H[J,I] <- Hd,
    W[J,I] <- Wd,
    Jp is J + 1,
    holds( Jp =< Y, ContY ),
    svg_distances_loop( ContY, Jp, Y, I, X, Mtx, H, W ).

svg_distances_outer( false, _Ip, _X, _Y, _Mtx, _H, _W ).
svg_distances_outer( true, Ip, X, Y, Mtx, H, W ) :-
    svg_distances_loop( true, 1, Y, Ip, X, Mtx, H, W ).

svg_point_distances( Mtx, I, J, X, Y, Hd, Wd ) :-
    Ip is I + 1,
    holds( Ip =< X, T ),
    svg_x_point_distance( T, Ip, X, J, Mtx, 0, Wd ),
    Jp is J + 1,
    holds( Jp =< Y, F ),
    svg_y_point_distance( F, Jp, Y, I, Mtx, 0, Hd ).

svg_y_point_distance( true, J, Y, I, Mtx, CurrD, D ) :-
    Mv <- Mtx[J,I],
    holds( Mv =:= 0, T ),
    svg_y_point_distance_cont( T, J, Y, I, Mtx, CurrD, D ).
svg_y_point_distance( false, _J, _Y, _I, _Mtx, D, D ).
    
svg_y_point_distance_cont( true, J, Y, I, Mtx, CurrD, D ) :-
    NextD is CurrD + 1,
    Jp is J + 1,
    holds( Jp =< Y, T ),
    svg_y_point_distance( T, Jp, Y, I, Mtx, NextD, D ).
svg_y_point_distance_cont( false, _J, _Y, _I, _Mtx, D, D ).

svg_x_point_distance( true, I, X, J, Mtx, CurrD, D ) :-
    Mv <- Mtx[J,I],
    holds( Mv =:= 0, T ),
    svg_x_point_distance_cont( T, I, X, J, Mtx, CurrD, D ).
svg_x_point_distance( false, _I, _X, _J, _Mtx, D, D ).

svg_x_point_distance_cont( true, I, X, J, Mtx, CurrD, D ) :-
    NextD is CurrD + 1,
    Ip is I + 1,
    holds( Ip =< X, T ),
    svg_x_point_distance( T, Ip, X, J, Mtx, NextD, D ).
svg_x_point_distance_cont( false, _I, _X, _J, _Mtx, D, D ).
*/

read_svg_int( IntCs ) --> 
    [C],
    { 0'0 =< C, C=<0'9, !, IntCs = [C|Cs] },
    read_svg_int( Cs ).
read_svg_int( IntCs ) --> 
    [C],
    { 0'- =:= C, !, IntCs = [C|Cs] },
    read_svg_int( Cs ).
read_svg_int( IntCs ) --> 
    [C],
    { 0'. =:= C, !, IntCs = [C|Cs] },
    read_svg_int( Cs ).
read_svg_int( [] ) --> [].
