% This work may be distributed and modified under the
% conditions of the LaTeX Project Public License, either version 1.3
% of this license or (at your option) any later version.
% The latest version of this license is in
%   http://mirrors.ctan.org/help/Catalogue/licenses.lppl.html
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.


% The author of this work is F. Pantigny (fpantigny at wanadoo point fr)

\NeedsTeXFormat{LaTeX2e}[1994/06/01]


\RequirePackage{l3keys2e}

\ProvidesExplPackage
  {WithArrows}
  {2017/08/09}
  {1.0}
  {Draws arrows for explanations on the right side of a alignment.} 


\RequirePackage{expl3}
\RequirePackage{xparse}

\RequirePackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{bending}



\RequirePackage{footnote}



% There is a style for the tips of arrow because someone will use this style
% if he wants draw an arrow directly with a Tikz command in his document (probably using the Tikz nodes created by
% {WithArrows} in the \halign.
\tikzset{TipsOfWithArrows/.style= { > = {Straight~Barb[scale=1.2,bend]}} }


% The following variables are for the options ; these options can be set at 3 levels : see the groups of keys defined in
% the \keys_define:nn comand below.

% if the beginning of the arrow use a "r" option (if not, it's a "l" option)
\bool_new:N \l_wa_initial_r_bool
\bool_set_true:N \l_wa_initial_r_bool

% if the end of the arrow use a "r" option (if not, it's à "l" option)
\bool_new:N \l_wa_final_r_bool
\bool_set_true:N \l_wa_final_r_bool

% if the arrow use a "i" option (in this case the options "l" and "r" for the ends are meaningless)
\bool_new:N \l_wa_i_bool
\bool_set_false:N \l_wa_i_bool

% to translate the whole arrow to the right
\dim_new:N \l_wa_xoffset_dim 
\dim_set:Nn \l_wa_xoffset_dim {3mm}

% to indicate if the nodes will be drawn (in red) ; useful only for debugging
\bool_new:N \l_wa_shownodes_bool
\bool_set_false:N \l_wa_shownodes_bool

% to indiate if the elements of the \halign will be composed with \displaystyle
\bool_new:N \l_wa_displaystyle_bool
\bool_set_false:N \l_wa_displaystyle_bool

% will contains the Tikz options used to draw the arrow
\tl_clear_new:N \l_wa_options_tikz_tl
% At each possible level for the options ("global", "env" or "local : see below), the new values will be appended
% on the right of the token list \l_wa_options_tikz_tl

% will be raised when a key controling position of the arrow has been set in the same list of key
% useful in order to raise an error if 2 incompatibles keys are specified at the same level
\bool_new:N \l_wa_position_key_already_set_bool
\bool_set_false:N \l_wa_position_key_already_set_bool

% This Tikz style is for the nodes created automatically in the \halign.
\tikzstyle{wa_node_style}=[rectangle,
                           inner~sep = 0 pt,
                           minimum~height = 3 pt,
                           minimum~width = 0pt,
                           red,  % red... but will be drawn only if shownodes=true
                           \bool_if:NT \l_wa_shownodes_bool {draw}]



% There are 3 levels where keys can be set :
% with \WithArrowsOptions{...} : this level will be called "global" level ;
% with \begin{WithArrows}[...] : this level will be called "env" level ;
% with \Arrow[...] : this level will be called "local" level.

% That's why there is 3 groups of keys named "global", "env" and "local".

% Before each \keys_set_groups:nnn, we execute a \keys_set_filter:nnn with group "secondary"
% Thus, an unknown key will raise an error. Furthermore, the "primary" keys (those which are not in the 
% group "secondary") will be set first : it's useful to raise an error if, for example, keys "i" and "group"
% are set at the same level (which is incoherent).

% There is also the group named "jump" (with only one key : "jump") for technical reasons.
\keys_define:nn {WithArrows} 
    { % the key named "jump" will be extracted in the \Arrow command 
      jump     .groups:n          = {jump,secondary},
      jump     .code:n            = {\int_set:Nn \l_jump_int {#1}
                                     \int_compare:nNnF \l_jump_int > 0
                                         {\msg_error:nn {WithArrows} {The~option~"jump"~must~be~non~negative}}},
      jump     .value_required:n  = true,
      %
      % 0 = t = \vtop , 1 = c = \vcenter, 2 = b = \vbox
      pos         .groups:n         = {global,env,secondary},
      pos         .int_set:N        = \l_wa_pos_int,
      pos         .initial:n        = 0,
      pos         .value_required:n = true,
      %
      t           .groups:n         = {global,env,kown},
      t           .meta:n           = {pos=0},
      c           .groups:n         = {global,env,kown},
      c           .meta:n           = {pos=1},
      b           .groups:n         = {global,env,kown},
      b           .meta:n           = {pos=2},
      %
      % if the user wants to give a new name to the \Arrow command (and the name \Arrow remains free)
      CommandName .groups:n         = {global,env,secondary},
      CommandName .tl_set:N         = \l_wa_CommandName_tl,
      CommandName .initial:n        = {Arrow},
      CommandName .value_required:n = true,
      %
      displaystyle .groups:n          = {global,env,secondary},
      displaystyle .bool_set:N        = \l_wa_displaystyle_bool,
      displaystyle .value_forbidden:n = true,
      %
      %
      shownodes .groups:n          = {global,env,secondary},
      shownodes .bool_set:N        = \l_wa_shownodes_bool,
      shownodes .default:n         = true,
      shownodes .value_forbidden:n = true, 
      %
      jot       .groups:n          = {env,secondary},  % don't put "global"
      jot       .dim_set:N         = \jot, % \jot is a LaTeX parameter for the distance between 2 lines in displaymath
      jot       .value_required:n  = true,
      %
      xoffset  .groups:n          = {global,env,local,secondary},
      xoffset  .dim_set:N         = \l_wa_xoffset_dim,
      xoffset  .value_required:n  = true,           
      %
      tikz     .groups:n          = {global,env,local,secondary},
      tikz     .code:n            = {\tl_put_right:Nn \l_wa_options_tikz_tl {,#1}},
      tikz     .value_required:n  = true,
      %
      ll       .groups:n          = {global,env,local,secondary},
      ll       .value_forbidden:n = true, 
      ll       .default:n         = true,
      ll       .code:n            = {\bool_if:NT \l_wa_position_key_already_set_bool
                                       {\msg_error:nn {WithArrows} {Two~options~are~incompatible}}
                                     \bool_if:NT \l_wa_group_bool
                                       {\msg_error:nn {WithArrows} {Option~incompatible~with~group}}
                                     \bool_set_true:N \l_wa_position_key_already_set_bool
                                     \bool_set_false:N \l_wa_initial_r_bool
                                     \bool_set_false:N \l_wa_final_r_bool
                                     \bool_set_false:N \l_wa_i_bool },
      %
      lr       .groups:n          = {global,env,local,secondary},
      lr       .value_forbidden:n = true,
      lr       .default:n         = true, 
      lr       .code:n            = {\bool_if:NT \l_wa_position_key_already_set_bool
                                       {\msg_error:nn {WithArrows} {Two~options~are~incompatible}}
                                     \bool_if:NT \l_wa_group_bool
                                       {\msg_error:nn {WithArrows} {Option~incompatible~with~group}}
                                     \bool_set_true:N \l_wa_position_key_already_set_bool
                                     \bool_set_false:N \l_wa_initial_r_bool
                                     \bool_set_true:N  \l_wa_final_r_bool
                                     \bool_set_false:N \l_wa_i_bool},
      %
      rl       .groups:n          = {global,env,local,secondary},
      rl       .value_forbidden:n = true,
      rl       .default:n         = true, 
      rl       .code:n            = {\bool_if:NT \l_wa_position_key_already_set_bool
                                       {\msg_error:nn {WithArrows} {Two~options~are~incompatible}}
                                     \bool_if:NT \l_wa_group_bool
                                       {\msg_error:nn {WithArrows} {Option~incompatible~with~group}}
                                     \bool_set_true:N \l_wa_position_key_already_set_bool
                                     \bool_set_true:N  \l_wa_initial_r_bool
                                     \bool_set_false:N \l_wa_final_r_bool
                                     \bool_set_false:N \l_wa_i_bool},
      %
      rr       .groups:n          = {global,env,local,secondary},
      rr       .value_forbidden:n = true,
      rr       .default:n         = true, 
      rr       .code:n            = {\bool_if:NT \l_wa_position_key_already_set_bool
                                       {\msg_error:nn {WithArrows} {Two~options~are~incompatible}}
                                     \bool_if:NT \l_wa_group_bool
                                       {\msg_error:nn {WithArrows} {Option~incompatible~with~group}}
                                     \bool_set_true:N \l_wa_position_key_already_set_bool
                                     \bool_set_true:N  \l_wa_initial_r_bool
                                     \bool_set_true:N  \l_wa_final_r_bool
                                     \bool_set_false:N  \l_wa_i_bool},
      %
      i        .groups:n          = {global,env,local,secondary},
      i        .code:n            = {\bool_if:NT \l_wa_position_key_already_set_bool
                                       {\msg_error:nn {WithArrows} {Two~options~are~incompatible}}
                                     \bool_if:NT \l_wa_group_bool
                                       {\msg_error:nn {WithArrows} {Option~incompatible~with~group}}
                                     \bool_set_true:N \l_wa_position_key_already_set_bool
                                     \bool_set_true:N \l_wa_i_bool},
      i        .value_forbidden:n = true,
      i        .default:n         = true,
      %
      % indicate if all the arrows of an environment are grouped
      % volontary not in "secondary" group : thus, if will be set first during the \keys_set_filter:nnn
      group        .groups:n          = {global,env},   
      group        .bool_set:N        = \l_wa_group_bool,
      group        .value_forbidden:n = true
}





\ProcessKeysOptions {WithArrows}


% \WithArrowsOptions is the command of the WithArrows package to fix options at the document level
\NewDocumentCommand \WithArrowsOptions {m}
    {\bool_set_false:N \l_wa_position_key_already_set_bool
     \keys_set_filter:nnn {WithArrows} {secondary} {#1}   % in order to raise an error if a key is unknown
     \keys_set_groups:nnn {WithArrows} {global} {#1}}



\NewDocumentEnvironment {WithArrows} {O{}}
          {\reverse_if:N \if_mode_math:
                             \msg_error:nn {WithArrows} {WithArrows~used~outside~math~mode}
                         \fi
           \savenotes % to extract the footnotes of the environment {WithArrows}
           \m@th                
           \def\\{\cr}
           %
           \int_gzero_new:N \g_wa_line_int  % to count the lines in the \halign 
           %
           % to count the arrows created in the environment
           \int_gzero_new:N \g_wa_arrow_int 
           %
           % these 3 counters will be used later as variables
           \int_zero_new:N \l_initial_int
           \int_zero_new:N \l_final_int
           \int_zero_new:N \l_arrow_int
           % 
           \bool_set_false:N \l_wa_position_key_already_set_bool
           \keys_set_filter:nnn {WithArrows} {secondary} {#1} % in order to raise an error if a key is unknown
           \keys_set_groups:nnn {WithArrows} {env} {#1}
           %
           \cs_set:cpn \l_wa_CommandName_tl {\wa_Arrow} % new name for \Arrow command in {WithArrows} environments
           %
           \int_case:nnF \l_wa_pos_int % is fixed via the options t, c or b
                   {0 {\vtop}
                    1 {\vcenter}
                    2 {\vbox}}
                   {\msg_error:nn {WithArrows} {Bad~value~for~option~pos}}
           \bgroup
           \openup\jot
           \ialign\bgroup
           \int_gincr:N \g_wa_line_int % will be used for the names of the 2 nodes of the line
           \strut\hfil
           $\bool_if:NT \l_wa_displaystyle_bool \displaystyle {##}$
           & 
           $\bool_if:NT \l_wa_displaystyle_bool \displaystyle {{}##}$
           %
           % we create a first node (the "left node" of the line)
           \tikz[remember~picture] 
                  \node [wa_node_style] (wa-\int_use:N\g_wa_line_int-l) {} ;
           \hfil
           % we create a second node (the "right node" of the line)
           \tikz[remember~picture] 
                  \node [wa_node_style] (wa-\int_use:N\g_wa_line_int-r) {} ;
           \cr
          }
          {\crcr % 
           \egroup% end of halign
           \egroup% end of vtop (or \vcenter or \vbox)
           %
           \int_compare:nNnT \g_wa_arrow_int > 0   % If there is really arrows...
                    \wa_draw_arrows:               % ...we draw these arrows.
           \spewnotes
           }



% le following code is necessary because we whill have to expand un argument exactly 3 times
\cs_generate_variant:Nn \keys_set_groups:nnn {nno}
\cs_new:Nn \keys_set_groups_WithArrows_local: {\keys_set_groups:nno {WithArrows} {local}}

\cs_generate_variant:Nn \keys_set_filter:nnn {nno}
\cs_new:Nn \keys_set_filter_WithArrows_secondary: {\keys_set_filter:nno {WithArrows} {secondary}}

\cs_new:Nn \wa_draw_arrows:
  {%
   % if the option "group" is used in the current environment
   \bool_if:NT \l_wa_group_bool
        \wa_x_computation_for_option_group:   % will compute \g_wa_x_dim which is the abscissa of all the arrows 
   %
   % loop over the arrows created in the environment : the arrows will be drawn
   \int_set:Nn \l_arrow_int 1
   \int_until_do:nNnn \l_arrow_int > \g_wa_arrow_int   % g_wa_arrow_int is the total number of arrows
     {
      % \l_initial_int is the line number from which the arrow starts
      \int_set:Nn \l_initial_int {\int_use:c {wa_initial\int_use:N\l_arrow_int}}
      %
      % \l_final_int is the line number to which the arrow ends
      \int_set:Nn \l_final_int {\int_use:c {wa_final\int_use:N\l_arrow_int}}
      %
      \int_compare:nNnT \l_final_int > \g_wa_line_int
             {\msg_error:nn {WithArrows} {Too~few~lines~for~an~arrow}}
      %
      \group_begin: % for the options given in \Arrow[...] ("local level" of the options)
           \keys_define:nn {WithArrows} {group .undefine:}
           \bool_set_false:N \l_wa_position_key_already_set_bool
           % the third argument de \keys_set_groups:nnn must be expanded exactly three times
           % a x-expansion is not possible because there can be tokens like \bfseries in the option "font"
           \exp_args:NNo \exp_args:No \keys_set_filter_WithArrows_secondary: {\use:c {wa_options\int_use:N\l_arrow_int}}
           \exp_args:NNo \exp_args:No \keys_set_groups_WithArrows_local: {\use:c {wa_options\int_use:N\l_arrow_int}}
           %
           % In case of option "i", we have to compute the abscissa of the arrow (which is vertical).
           \bool_if:NT \l_wa_i_bool
               \wa_x_computation_for_option_i: % the abscissa is computed in \g_wa_x_dim
           %
           % \l_initial_tl contains the name of the Tikz node from which the arrow starts (in normal cases...
           % because with option "group" or option "i", the point will perhaps have an other x-value --- but always the
           % same y-value). 
           \tl_set:Nx \l_initial_tl {wa-\int_use:N\l_initial_int-\bool_if:NTF\l_wa_initial_r_bool rl} 
           %
           % \l_final_tl contains the name of the Tikz node to which the arrow ends (in normal cases : with option
           % "group" or option "i", the point will perhaps have an other x-value but always the same y-value)
           \tl_set:Nx \l_final_tl {wa-\int_use:N\l_final_int-\bool_if:NTF\l_wa_final_r_bool rl . north}
           % ". north" because we want a small gap between two consecutive arrows
           % 
           \begin{tikzpicture}[remember~picture,
                               overlay,
                               align=left,
                               auto=left,
                               font = {\small\itshape},
                               TipsOfWithArrows,
                               ->,
                               looseness=1,
                               bend~left=45]
           \draw \exp_after:wN [\l_wa_options_tikz_tl]
                 % \p1 and \p2 are the two ends of the arrow (in fact, not exactly in case of options "group" or "i")
                 let \p1 = (\tl_use:N \l_initial_tl),
                     \p2 = (\tl_use:N \l_final_tl) in 
                   (\bool_if:nTF {\l_wa_group_bool || \l_wa_i_bool}
                     {\dim_use:N \g_wa_x_dim + \dim_use:N \l_wa_xoffset_dim, \y1}
                     {\x1 + \dim_use:N \l_wa_xoffset_dim, \y1} )
                   to node (wa-a-\int_use:N\l_arrow_int) % in fact, this name will not be used by WithArrows
                           {\tl_use:c {wa_label\int_use:N\l_arrow_int}}
                   (\bool_if:nTF {\l_wa_group_bool || \l_wa_i_bool}
                     {\dim_use:N \g_wa_x_dim + \dim_use:N \l_wa_xoffset_dim, \y2}
                     {\x2 + \dim_use:N \l_wa_xoffset_dim, \y2} ) ; 
           \end{tikzpicture}
       \group_end: % for the options given to \Arrow[...] ("local level" of the options)
       \int_incr:N \l_arrow_int
      } % end of the loop over the arrows : all arrows are drawn
     }


% We want to compute the x-value for the current environment which has option "group" (and therefore all arrows are
% vertical at the same abscissa). This value will be computed in \g_wa_x_dim (which is global for technical reasons :
% we have to do assignments in a \tikz command).
\cs_new:Nn \wa_x_computation_for_option_group:
  {\dim_gzero_new:N \g_wa_x_dim
   %
   % first, we calculate the initial value for \g_wa_x_dim
   % in this \tikz command, nothing is drawn : we only want to read the abscissa of a Tikz node
   \tikz[remember~picture]
      \path let \p1 = (wa-\int_use:c{wa_initial1}-l)      % "let" is given by the Tikz library "calc"
            in \pgfextra {\dim_gset:Nn \g_wa_x_dim {\x1}} ;  % a global assignment is necessary because of \tikz
   %
   % Then, we loop to determine the maximal length of all the lines concerned by the arrows of the environment.
   \int_set:Nn \l_arrow_int 1
   \int_until_do:nNnn \l_arrow_int > \g_wa_arrow_int   % g_wa_arrow_int is the total number of arrows
      { % \l_initial_int is the line number from which the arrow starts
        \int_set:Nn \l_initial_int {\int_use:c {wa_initial\int_use:N\l_arrow_int}}
        %
        % \l_final_int is the line number to which the arrow ends
        \int_set:Nn \l_final_int {\int_use:c {wa_final\int_use:N\l_arrow_int}}
        %
        \wa_x_computation_analyze_lines_between: %... between lines \l_initial_int and \l_final_int
        %
        \int_incr:N \l_arrow_int
      } % end of the loop over the arrows of the environment
   }   


% We want to compute the x-value for the current arrow which has option "i" (and therefore is vertical).
% This value will be computed in \g_wa_x_dim (which is global for technical reasons : we have to do assignments in
% a \tikz command). 
\cs_new:Nn \wa_x_computation_for_option_i:
  {\dim_gzero_new:N \g_wa_x_dim
   % First, we calculate the initial value for \g_wa_x_dim.
   % In the next \tikz command, nothing is drawn : we only want to read a abscissa of a Tikz node.
   \tikz[remember~picture]
      \path let \p1 = (wa-\int_use:N\l_initial_int-l) % "let" is given by the Tikz library "calc"
            in \pgfextra {\dim_gset:Nn \g_wa_x_dim {\x1}} ;  % a global assignment is necessary because of \tikz
   %
   \wa_x_computation_analyze_lines_between:  %... between lines \l_initial_int and \l_final_int
   }



        
% We analyse lines between \l_initial_int and \l_final_int in order to modify \g_wa_x_dim in consequence.
% More precisely, we will increase \g_wa_x_dim if we find a line longer than the current value of \g_wa_x_dim.
\cs_new:Nn \wa_x_computation_analyze_lines_between:
    {\int_compare:nNnT \l_final_int > \g_wa_line_int
             {\msg_error:nn {WithArrows} {Too~few~lines~for~an~arrow}}
     %
     % \l_tmpa_int will be the index of the loop
     \int_set:Nn \l_tmpa_int \l_initial_int
     \int_until_do:nNnn \l_tmpa_int > \l_final_int
         {% in the next \tikz command, nothing is drawn : we only want to read the abscissa of a Tikz node
          \tikz[remember~picture]
             \path let \p1 = (wa-\int_use:N\l_tmpa_int-l) 
                   in \pgfextra {\dim_gset:Nn \g_wa_x_dim {\dim_max:nn \g_wa_x_dim {\x1}}} ; % \gset because of \tikz
          \int_incr:N \l_tmpa_int
         } % loop on the lines between \l_initial_int et \l_final_int
    }   



\cs_generate_variant:Nn \int_compare:nNnT {cNnT}

\cs_generate_variant:Nn \tl_if_eq:nnF {onF}


% The next command has a strange name, but, in fact, usually, we will have \let\Arrow\wa_Arrow ;
% The user can use the CommandName option if he wants an other name for the command called \Arrow by default.
% It's useful if the user already has a command named "\Arrow" he still wants to use.
\NewDocumentCommand \wa_Arrow {O{} m O{}}
         {\tl_if_eq:onF {\@currenvir} {WithArrows}
                  {\msg_fatal:nn {WithArrows} {Arrow~used~outside~{WithArrows}~environment}}
          %
          % \g_wa_arrow_int counts the arrows in the environment
          \int_gincr:N \g_wa_arrow_int
          %
          % we decide to extract immediatly the key "jump" in ordre to compute the end line ;
          \int_zero_new:N \l_jump_int
          \int_set:Nn \l_jump_int 1
          \keys_set_groups:nnn {WithArrows} {jump} {#1,#3} % "jump" is the only key of the group "jump"
          % 
          % to stock the informations of the considered arrow (in four variables) ;
          % these informations will be used when the arrow will be really drawn
          \int_gzero_new:c {wa_initial\int_use:N\g_wa_arrow_int}
          \int_gset_eq:cN  {wa_initial\int_use:N\g_wa_arrow_int} \g_wa_line_int
          %
          \int_gzero_new:c {wa_final\int_use:N\g_wa_arrow_int}
          \int_gset:cn     {wa_final\int_use:N\g_wa_arrow_int} {\g_wa_line_int + \l_jump_int}
          %
          \tl_gclear_new:c {wa_options\int_use:N\g_wa_arrow_int}
          \tl_gset:cn      {wa_options\int_use:N\g_wa_arrow_int} {#1,#3}
          %
          \tl_gclear_new:c {wa_label\int_use:N\g_wa_arrow_int}
          \tl_gset:cn      {wa_label\int_use:N\g_wa_arrow_int} {#2}
          %
          }



\msg_new:nnn {WithArrows}
             {Arrow~used~outside~{WithArrows}~environment}
             {Arrow~command~should~be~used~only~directly~in~\{WithArrows\}~environment\\
              and~not~in~a~subenvironment.}

\msg_new:nnn {WithArrows}
             {The~option~"jump"~must~be~non~negative}
             {You~can't~use~a~stricly~negative~value~for~the~option~"jump"~of~command~Arrow\\
              You~can~create~an~arrow~going~backwards~with~the~option~"<-"~of~Tikz.}

\msg_new:nnn {WithArrows}          
             {Too~few~lines~for~an~arrow}
             {There~is~at~least~an~arrow~that~can't~be~drawn~because~it~arrives~after~the\\
              last~line~of~the~environment.}

\msg_new:nnn {WithArrows}
             {Bad~value~for~option~pos}
             {The~option~"pos"~of~the~environment~\{WithArrows\}~must~be~equal~to\\        
              0,~1,~or~2.}

\msg_new:nnn {WithArrows}
             {WithArrows~used~outside~math~mode}
             {The~environment~WithArrows~must~be~used~only~in~math~mode.}

\msg_new:nnn {WithArrows}
             {Option~incompatible~with~group}
             {You~try~to~use~the~option~"\tl_use:N\l_keys_key_tl"~while~you~are~using~the~option~"group".\\
              It's~incompatible.~You~can~go~on~ignoring~this~option~"\tl_use:N\l_keys_key_tl"~and~"group"~will~be~used.}


\msg_new:nnn {WithArrows}
             {Two~options~are~incompatible}
             {You~try~to~use~the~option~"\tl_use:N\l_keys_key_tl"~but~this~option~is~incompatible\\
              with~an~option~previously~set.~If~you~go~on,~it~will~overwrite~the~previous~option.}


\endinput