\ProvidesExplPackage{magicwatermark}{2024/06/11}{1.2B}{magic watermark, author}


\msg_new:nnn {magicwatermark}{ Unable to parse } 
{
  Unable~to~parse~this~list~parameter.^^J
  #1
}

\msg_new:nnn {magicwatermark}{ Enable TikZ } 
{
  tikz~option~has~been~disabled,^^J
  style~will~be~ignored.
}

\msg_new:nnn {magicwatermark}{ Load TikZ } 
{
  Please~load~tikz~package~before~magicwatermark.
}

\seq_new:N \l__mw_tmpa_seq 
\seq_new:N \l__mw_tmpb_seq 
\seq_new:N \l__mw_tmpc_seq 
\seq_new:N \l__mw_tmpd_seq 


\int_new:N \l__mw_tmpa_int 
\int_new:N \l__mw_tmpb_int 
\int_new:N \l__mw_tmpc_int 
\int_new:N \l__mw_tmpd_int 
\int_new:N \l__mw_tmpe_int 

\clist_new:N \l__mw_list_arabic_number_clist 
\clist_new:N \l__mw_list_roman_number_clist 
\clist_new:N \l__mw_list_range_clist 
\clist_new:N \l__mw_list_expression_clist




\clist_new:N \l__mw_tmpa_clist
\clist_new:N \l__mw_tmpb_clist
\clist_new:N \l__mw_tmpc_clist


\tl_new:N \l__mw_tmpa_tl
\tl_new:N \l__mw_tmpb_tl
\tl_new:N \l__mw_tmpc_tl
\tl_new:N \g__mw_list_tl

\tl_new:N \g__mw_style_tl 
\tl_new:N \g__mw_content_tl

\bool_new:N \g__mw_is_append_bool
\bool_set_false:N \g__mw_is_append_bool
\bool_new:N \g__mw_tikz_bool
\bool_set_true:N \g__mw_tikz_bool

\regex_const:Nn \c__mw_arabic_numbers_regex { ^\d+$ }
\regex_const:Nn \c__mw_roman_numbers_regex { ^[ivxlcdm]+$ }



\int_new:N \g__mw_last_page_int 

\AtBeginDocument{
  \int_compare:nTF { \@abspage@last = \number\maxdimen }
  {
    \int_gset:Nn \g__mw_last_page_int { 10 }
  }
  {
    \int_gset:Nn \g__mw_last_page_int { \@abspage@last }
  }
}

\cs_new_protected:Npn \__mw_clist_deduplicate:N #1
{
  \group_begin:
  \clist_clear:N \l_tmpa_clist
  \clist_map_inline:Nn #1
  {
    \clist_if_in:NnF \l_tmpa_clist { ##1 }
      { \clist_put_right:Nn \l_tmpa_clist { ##1 } }
  }
  \clist_gset_eq:NN #1 \l_tmpa_clist
  \group_end:
}


\cs_new:Npn \__mw_list_parser_aux:n #1 {

  \clist_clear:N \l__mw_list_arabic_number_clist 
  \clist_clear:N \l__mw_list_roman_number_clist 
  \clist_clear:N \l__mw_list_range_clist 
  \clist_clear:N \l__mw_list_expression_clist

  % split by commas
  \group_begin:
  \seq_set_split:Nnn \l__mw_tmpa_seq { , } { #1 }
  \seq_map_inline:Nn \l__mw_tmpa_seq
  {
    \tl_if_eq:nnTF { ##1 } { odd } % odd, push 2X + 1
    {
      \clist_gput_right:Nn \l__mw_list_expression_clist  { 2X + 1 }
    }
    {
      \tl_if_eq:nnTF { ##1 } { even } % even, push 2X
      {
        \clist_gput_right:Nn \l__mw_list_expression_clist  { 2X }
      }
      {
        \tl_if_in:nnTF { ##1 } { X } % expression, push ##1
        {
          \clist_gput_right:Nn \l__mw_list_expression_clist { ##1 }
        }
        {
          \tl_if_in:nnTF { ##1 } { - } % range, push ##1
          {
            \clist_gput_right:Nn \l__mw_list_range_clist { ##1 }
          }
          {
            \__mw_if_arabic_number:nTF { ##1 }
            {
              \clist_gput_right:Nn \l__mw_list_arabic_number_clist { ##1 }
            }
            {
              \__mw_if_roman_number:nTF { ##1 }
              {
                \clist_gput_right:Nn \l__mw_list_roman_number_clist { ##1 }
              }
              {
                % error
                \msg_error:nnn {magicwatermark}{ Unable to parse }
                {
                  Error~list~parameter~``##1''.
                }
              }
            }
          }
        }
      }
    }
  }
  \group_end:
}

\prg_new_conditional:Npnn \__mw_if_arabic_number:n #1 { p, T, F, TF }
{
  \regex_match:NnTF \c__mw_arabic_numbers_regex { #1 }
  {
    \prg_return_true:
  }
  {
    \prg_return_false:
  }
}

\prg_new_conditional:Npnn \__mw_if_roman_number:n #1 { p, T, F, TF }
{
  \regex_match:NnTF \c__mw_roman_numbers_regex { #1 }
  {
    \prg_return_true:
  }
  {
    \prg_return_false:
  }
}

\prg_generate_conditional_variant:Nnn \__mw_if_arabic_number:n { x } {p, T, F, TF}
\prg_generate_conditional_variant:Nnn \__mw_if_roman_number:n { x } {p, T, F, TF}


\cs_new:Npn \__mw_list_parser_for_arabic_number:n #1 {
  % arabic number
  \fp_compare:nTF { #1 <= \g__mw_last_page_int && #1 > 0 }
  {
    \clist_put_right:Nn \l__mw_tmpa_clist { #1 }
  }
  {
    % error
    \msg_error:nnn {magicwatermark}{ Unable to parse }
    {
      Error~list~parameter~``#1''.^^J
      Error~number~``#1''.
    }
  }
}



\cs_new:Npn \__mw_list_parser_for_roman_number:n #1 {
  % roman number
  \int_set:Nn \l__mw_tmpa_int { \g__mw_last_page_int - \int_from_roman:n { #1 } + 1 }
  \fp_compare:nTF 
  {
     \l__mw_tmpa_int <= \g__mw_last_page_int && \l__mw_tmpa_int > 0 
  }
  {
    \clist_put_right:NV \l__mw_tmpa_clist \l__mw_tmpa_int
  }
  {
    % error
    \msg_error:nnn {magicwatermark}{ Unable to parse }
    {
      Error~list~parameter~``#1''.^^J
      Error~roman~number~``#1''.
    }
  }
}


\cs_new:Npn \__mw_list_parser_for_range:n #1 {
  % range, 1-5, 3-i
  \seq_set_split:Nnn \l__mw_tmpb_seq { - } { #1 }

  \__mw_if_arabic_number:xTF { \seq_item:Nn \l__mw_tmpb_seq { 1 } } 
  {
    \int_set:Nn \l__mw_tmpb_int { \seq_item:Nn \l__mw_tmpb_seq { 1 } }
  }
  {
    \__mw_if_roman_number:xTF { \seq_item:Nn \l__mw_tmpb_seq { 1 } } 
    {
      \exp_args:NNx \int_set:Nn \l__mw_tmpb_int 
      {
        \g__mw_last_page_int - \exp_not:N \int_from_roman:n { \seq_item:Nn \l__mw_tmpb_seq { 1 } } + 1
      }
    }
    {
      \int_set:Nn \l__mw_tmpb_int { -1 }
      % error
      \msg_error:nnn {magicwatermark}{ Unable to parse }
      {
        Error~list~parameter~``#1''.^^J
        Error~range~left~``#1''.
      }
    }
  }

  \__mw_if_arabic_number:xTF { \seq_item:Nn \l__mw_tmpb_seq { 2 } } 
  {
    \int_set:Nn \l__mw_tmpc_int { \seq_item:Nn \l__mw_tmpb_seq { 2 } }
  }
  {
    \__mw_if_roman_number:xTF { \seq_item:Nn \l__mw_tmpb_seq { 2 } } 
    {
      \exp_args:NNx \int_set:Nn \l__mw_tmpc_int 
      {
        \g__mw_last_page_int - \exp_not:N \int_from_roman:n { \seq_item:Nn \l__mw_tmpb_seq { 2 } } + 1
      }
    }
    {
      \int_set:Nn \l__mw_tmpc_int { -1 }
      % error
      \msg_error:nnn {magicwatermark}{ Unable to parse }
      {
        Error~list~parameter~``#1''.^^J
        Error~range~right~``#1''.
      }
    }
  }

  \fp_compare:nT { \l__mw_tmpb_int > 0 && \l__mw_tmpc_int > 0} 
  {
    \int_step_inline:nnn 
    { \int_min:nn { \l__mw_tmpb_int } { \l__mw_tmpc_int } }
    { \int_max:nn { \l__mw_tmpb_int } { \l__mw_tmpc_int } }
    {
      \clist_put_right:Nn \l__mw_tmpa_clist { ##1 }
    }
  }
}

\cs_new:Npn \__mw_list_parser_for_expression:n #1 {
  \int_zero:N \l__mw_tmpd_int
  \int_zero:N \l__mw_tmpe_int
  \group_begin:
  \tl_set:Nn \l__mw_tmpa_tl { #1 }
  \regex_replace_all:nnN { (\d+)X } { \1*\c{l__mw_tmpe_int} } \l__mw_tmpa_tl
  \tl_replace_all:Nnn \l__mw_tmpa_tl { X } { \l__mw_tmpe_int } 
  % \tl_show:N \l__mw_tmpa_tl
    \int_while_do:nn { \l__mw_tmpd_int < \g__mw_last_page_int } 
    {
      \int_set:Nn \l__mw_tmpd_int 
      { 
        \fp_eval:n { \l__mw_tmpa_tl }
      }
      \clist_gput_right:NV \l__mw_tmpa_clist \l__mw_tmpd_int 
      \int_incr:N \l__mw_tmpe_int
    }
  \group_end:
}


\cs_new_nopar:Npn \__mw_list_parser:nN #1#2 {

  % case 1 -> odd, even
  % case 2 -> number, 1
  % case 3 -> roman, i
  % case 4 -> range, 1-5, 3-ii
  % case 5 -> expression, 3X + 1

  \__mw_list_parser_aux:n { #1 }

  \clist_clear:N \l__mw_tmpa_clist
  \clist_map_inline:Nn \l__mw_list_arabic_number_clist
  {
    \__mw_list_parser_for_arabic_number:n { ##1 }
  }

  \clist_map_inline:Nn \l__mw_list_roman_number_clist 
  {
    \__mw_list_parser_for_roman_number:n { ##1 }
  }


  \clist_map_inline:Nn \l__mw_list_range_clist 
  {
    \__mw_list_parser_for_range:n { ##1 }
  }
  

  \clist_map_inline:Nn \l__mw_list_expression_clist 
  {
    \__mw_list_parser_for_expression:n { ##1 }
  }


  \clist_sort:Nn \l__mw_tmpa_clist {
    \int_compare:nNnTF { ##1 } > { ##2 }
    { \sort_return_swapped: }
    { \sort_return_same: }
  }
  \__mw_clist_deduplicate:N \l__mw_tmpa_clist
  \clist_set_eq:NN #2 \l__mw_tmpa_clist
}


\cs_new:Npn \__mw_parser:nN #1#2 {
  \clist_clear:N #2
  \tl_if_eq:nnTF { #1 } { * } 
  {
    \int_step_inline:nnn { 1 } { \g__mw_last_page_int }
    {
      \clist_put_right:Nn #2 { ##1 }
    }
  }
  {
    \__mw_list_parser:nN { #1 } #2
  }
}




\keys_define:nn { mw / new }
{
  pages .tl_set:N = \g__mw_list_tl,
  style .tl_set:N = \g__mw_style_tl,
  % style .code:n = \__mw_parse_style:n { #1 },
  content .tl_set:N = \g__mw_content_tl,
  is~append .bool_set:N = \g__mw_is_append_bool,
  tikz .bool_set:N = \g__mw_tikz_bool
}

\cs_new:Npn \__mw_parse_style: {
  \tl_if_empty:NF \g__mw_style_tl 
  {
    \bool_if:NTF \g__mw_tikz_bool 
    {
      \IfPackageLoadedTF{tikz}
      {
        \tl_set_eq:NN \l__mw_tmpc_tl \g__mw_style_tl 
      }
      {
        \msg_error:nn {magicwatermark}{ Load TikZ } 
      }
    }
    {
      \msg_warning:nn {magicwatermark}{ Enable TikZ }
    }
  }
}

\keys_define:nn { mw }
{
  setup .code:n = \__mw_new_watermark:n {#1}
}


\cs_new:Npn \__mw_new_watermark:n #1 {
  \group_begin:
  \keys_set:nn { mw / new }{#1}

  % parse style
  \__mw_parse_style:

  % parse pages
  \exp_args:NV \__mw_parser:nN \g__mw_list_tl \l__mw_tmpc_clist

  \clist_map_inline:Nn \l__mw_tmpc_clist {

    \bool_if:NTF \g__mw_tikz_bool
    {
      \IfPackageLoadedTF{tikz}
      {
        \tl_set:Ne \l__mw_tmpb_tl 
        {
          \exp_not:N \tikz[remember~picture, overlay] 
          {
            \exp_not:N \node[inner~sep = 0pt, outer~sep = 0pt, opacity = .5, align = left, \exp_not:V \g__mw_style_tl] at (current~page.center) 
            {
              \exp_not:V \g__mw_content_tl
            };
          }
        }
      }
      {
        \msg_error:nn {magicwatermark}{ Load TikZ } 
      }
    }
    {
      \tl_set:NV \l__mw_tmpb_tl \g__mw_content_tl
    }

    \bool_if:nTF { \g__mw_is_append_bool && \tl_if_exist_p:c { g__mw_content_ \int_to_roman:n { ##1 } _tl } }
    {
      \tl_gput_right:cV { g__mw_content_ \int_to_roman:n { ##1 } _tl } \l__mw_tmpb_tl 
    }
    {
      \tl_gset:cV { g__mw_content_ \int_to_roman:n { ##1 } _tl } \l__mw_tmpb_tl 
    }
  }
  \group_end:
}


\NewDocumentCommand{\MagicWatermark}{m}{
  \group_begin:
    \AtBeginDocument{
      \keys_set:nn { mw } { #1 }
      \AddToHook{shipout/background}{
        \put(.5\paperwidth,-.5\paperheight){
          \tl_if_exist:cT { g__mw_content_ \int_to_roman:n { \value{page} }_tl }
          {
            \tl_use:c { g__mw_content_ \int_to_roman:n { \value{page} } _tl }
          }
        }
      }
    }
  \group_end:
}

\@onlypreamble\MagicWatermark