% \iffalse meta-comment
% dtx/mahjong-tiles-04-parser.dtx -- parser for mahjong-tiles
%
% Copyright (c) 2021 Daniel Schmitz
% Copyright (c) 2026 Lilia Chen May Wither
%
% This file is distributed under the MIT Licence; see LICENCE.
% \fi
%
% \subsection{Parser}
%
%    \begin{macrocode}
%<*package>
% -----------------------------------------------------------------------------
% Parser
% -----------------------------------------------------------------------------
\quark_new:N \q__mahjongtiles_parse_stop
\quark_new:N \q__mahjongtiles_parse_done

\cs_new_protected:Npn \__mahjongtiles_parse:n #1
  {
    \seq_clear:N \l__mahjongtiles_tiles_seq
    \seq_clear:N \l__mahjongtiles_pending_seq
    \bool_set_false:N \l__mahjongtiles_last_concealed_kong_tail_bool
    \__mahjongtiles_parse_loop:w #1 \q__mahjongtiles_parse_stop ] \q__mahjongtiles_parse_done
    \seq_if_empty:NF \l__mahjongtiles_pending_seq
      {
        \tl_clear:N \l__mahjongtiles_pending_digits_tl
        \seq_map_inline:Nn \l__mahjongtiles_pending_seq
          { \__mahjongtiles_collect_pending_digits:nnn ##1 }
        \msg_error:nnV { mahjong-tiles } { missing-suit } \l__mahjongtiles_pending_digits_tl
      }
  }

\cs_new_protected:Npn \__mahjongtiles_parse_loop:w #1
  {
    \tl_if_eq:nnTF {#1} { \q__mahjongtiles_parse_stop }
      { \__mahjongtiles_parse_finish:w }
      {
        \token_if_eq_meaning:NNTF #1 \mj
          {
            \int_compare:nNnTF { \l__mahjongtiles_mj_depth_int } > {0}
              { \__mahjongtiles_parse_nested_mj:n }
              { \__mahjongtiles_parse_inline_mj:n }
          }
          {
            \str_if_eq:nnTF {#1} { [ }
              { \__mahjongtiles_parse_overlay:w }
              {
                \__mahjongtiles_parse_token:n {#1}
                \__mahjongtiles_parse_loop:w
              }
          }
      }
  }

\cs_new_protected:Npn \__mahjongtiles_parse_nested_mj:n #1
  {
    \msg_error:nnn { mahjong-tiles } { nested-mj } {#1}
    \__mahjongtiles_parse_loop:w
  }

\cs_new_protected:Npn \__mahjongtiles_parse_inline_mj:n #1
  {
    \seq_if_empty:NF \l__mahjongtiles_pending_seq
      {
        \tl_clear:N \l__mahjongtiles_pending_digits_tl
        \seq_map_inline:Nn \l__mahjongtiles_pending_seq
          { \__mahjongtiles_collect_pending_digits:nnn ##1 }
        \msg_error:nnV { mahjong-tiles } { missing-suit } \l__mahjongtiles_pending_digits_tl
        \seq_clear:N \l__mahjongtiles_pending_seq
      }
    \__mahjongtiles_draw_put_from_notation:n {#1}
    \__mahjongtiles_parse_loop:w
  }

\cs_new_protected:Npn \__mahjongtiles_draw_put_from_notation:n #1
  {
    \bool_gset_false:N \g__mahjongtiles_inline_mj_valid_bool
    \tl_gclear:N \g__mahjongtiles_inline_mj_face_tl
    \tl_gclear:N \g__mahjongtiles_inline_mj_suit_tl
    \group_begin:
      \int_incr:N \l__mahjongtiles_mj_depth_int
      \__mahjongtiles_parse:n {#1}
      \int_compare:nNnTF { \seq_count:N \l__mahjongtiles_tiles_seq } = {1}
        {
          \seq_get_left:NN \l__mahjongtiles_tiles_seq \l__mahjongtiles_tmp_tl
          \exp_last_unbraced:NV \__mahjongtiles_extract_inline_mj_tile:nnnnnn
            \l__mahjongtiles_tmp_tl {#1}
        }
        { \msg_error:nnn { mahjong-tiles } { inline-mj-single-tile } {#1} }
    \group_end:
    \bool_if:NT \g__mahjongtiles_inline_mj_valid_bool
      { \__mahjongtiles_draw_put:VV \g__mahjongtiles_inline_mj_face_tl \g__mahjongtiles_inline_mj_suit_tl }
  }

\cs_new_protected:Npn \__mahjongtiles_extract_inline_mj_tile:nnnnnn #1#2#3#4#5#6
  {
    \str_if_eq:nnTF {#1} { tile }
      {
        \tl_gset:Nn \g__mahjongtiles_inline_mj_face_tl {#2}
        \tl_gset:Nn \g__mahjongtiles_inline_mj_suit_tl {#3}
        \bool_gset_true:N \g__mahjongtiles_inline_mj_valid_bool
      }
      { \msg_error:nnn { mahjong-tiles } { inline-mj-tile-only } {#6} }
  }

\cs_new_protected:Npn \__mahjongtiles_parse_finish:w #1 \q__mahjongtiles_parse_done { }

\cs_new_protected:Npn \__mahjongtiles_parse_overlay:w #1 ]
  {
    \tl_if_in:nnTF {#1} { \q__mahjongtiles_parse_stop }
      {
        \msg_error:nn { mahjong-tiles } { unclosed-overlay }
        \__mahjongtiles_parse_finish:w
      }
      {
        \__mahjongtiles_set_last_overlay:n {#1}
        \__mahjongtiles_parse_loop:w
      }
  }

\cs_new_protected:Npn \__mahjongtiles_collect_pending_digits:nnn #1#2#3
  { \tl_put_right:Nn \l__mahjongtiles_pending_digits_tl {#1} }

\cs_new_protected:Npn \__mahjongtiles_parse_token:n #1
  {
    \tl_if_blank:nTF {#1}
      { }
      {
        \tl_set:Nx \l__mahjongtiles_tmp_tl { \text_lowercase:n {#1} }
        \exp_args:NV \__mahjongtiles_parse_normalized_token:n \l__mahjongtiles_tmp_tl
      }
  }

\cs_new_protected:Npn \__mahjongtiles_parse_normalized_token:n #1
  {
    \tl_if_in:nnTF { 0123456789 } {#1}
      { \__mahjongtiles_pending_put:nn {#1} {0} }
      {
        \tl_if_in:nnTF { mpszf } {#1}
          { \__mahjongtiles_flush_suit:n {#1} }
          {
            \str_case:nnF {#1}
              {
                { - } { \__mahjongtiles_handle_dash: }
                { x } { \__mahjongtiles_tile_put:nnn { x } { } {0} }
                { ? } { \__mahjongtiles_tile_put:nnn { ? } { } {0} }
                { ' } { \__mahjongtiles_set_last_orientation:n {1} }
                { * } { \__mahjongtiles_set_last_orientation:n {1} }
                { " } { \__mahjongtiles_set_last_stack_marker:n { " } }
                { + } { \__mahjongtiles_set_last_stack_marker:n { + } }
              }
              { \msg_error:nnn { mahjong-tiles } { invalid-token } {#1} }
          }
      }
  }

\cs_new_protected:Npn \__mahjongtiles_handle_dash:
  {
    \seq_if_empty:NTF \l__mahjongtiles_pending_seq
      { \__mahjongtiles_gap_put:n {7} }
      {
        \tl_clear:N \l__mahjongtiles_pending_digits_tl
        \seq_map_inline:Nn \l__mahjongtiles_pending_seq
          { \__mahjongtiles_collect_pending_digits:nnn ##1 }
        \__mahjongtiles_gap_put:V \l__mahjongtiles_pending_digits_tl
        \seq_clear:N \l__mahjongtiles_pending_seq
      }
  }
\cs_generate_variant:Nn \__mahjongtiles_gap_put:n { V }

\cs_new_protected:Npn \__mahjongtiles_flush_suit:n #1
  {
    \seq_if_empty:NF \l__mahjongtiles_pending_seq
      {
        \__mahjongtiles_detect_concealed_kong:
        \bool_if:NTF \l__mahjongtiles_concealed_kong_bool
          {
            \tl_if_in:nnTF { mpsz } {#1}
              { \__mahjongtiles_flush_concealed_kong:n {#1} }
              { \__mahjongtiles_flush_pending_suit:n {#1} }
          }
          { \__mahjongtiles_flush_pending_suit:n {#1} }
        \seq_clear:N \l__mahjongtiles_pending_seq
      }
  }

\cs_new_protected:Npn \__mahjongtiles_flush_pending_suit:n #1
  {
    \seq_map_inline:Nn \l__mahjongtiles_pending_seq
      { \__mahjongtiles_flush_pending_item:nnnn ##1 {#1} }
  }

\cs_new_protected:Npn \__mahjongtiles_flush_pending_item:nnnn #1#2#3#4
  {
    \str_if_eq:nnTF {#4} {f}
      { \__mahjongtiles_flush_flower_item:nnn {#1} {#2} {#3} }
      {
        \str_if_eq:nnTF {#1} {0}
          {
            \tl_if_in:nnTF { mps } {#4}
              {
                \int_case:nnF {#2}
                  {
                    {3} { \__mahjongtiles_akastack_put:nnn {#4} {1} {#3} }
                    {4} { \__mahjongtiles_akastack_put:nnn {#4} {2} {#3} }
                    {5} { \__mahjongtiles_akastack_put:nnn {#4} {3} {#3} }
                  }
                  { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {#4} {#2} {#3} }
              }
              { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {#4} {#2} {#3} }
          }
          { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {#4} {#2} {#3} }
      }
  }

\cs_new_protected:Npn \__mahjongtiles_flush_flower_item:nnn #1#2#3
  {
    \tl_if_in:nnTF { 012345678 } {#1}
      { \__mahjongtiles_tile_put_aka_aware:nnnn {#1} {f} {#2} {#3} }
      { \msg_error:nnn { mahjong-tiles } { invalid-flower } {#1} }
  }

\cs_new_protected:Npn \__mahjongtiles_detect_concealed_kong:
  {
    \bool_set_false:N \l__mahjongtiles_concealed_kong_bool
    \int_compare:nNnT { \seq_count:N \l__mahjongtiles_pending_seq } = {5}
      {
        \seq_get_left:NN \l__mahjongtiles_pending_seq \l__mahjongtiles_tmp_tl
        \exp_last_unbraced:NV \__mahjongtiles_remember_first_pending:nnn \l__mahjongtiles_tmp_tl
        \bool_set_true:N \l__mahjongtiles_concealed_kong_bool
        \seq_map_inline:Nn \l__mahjongtiles_pending_seq
          { \__mahjongtiles_check_concealed_kong_item:nnn ##1 }
      }
  }

\cs_new_protected:Npn \__mahjongtiles_remember_first_pending:nnn #1#2#3
  { \tl_set:Nn \l__mahjongtiles_first_face_tl {#1} }

\cs_new_protected:Npn \__mahjongtiles_check_concealed_kong_item:nnn #1#2#3
  {
    \str_if_eq:VnF \l__mahjongtiles_first_face_tl {#1}
      { \bool_set_false:N \l__mahjongtiles_concealed_kong_bool }
    \int_compare:nNnF {#2} = {0}
      { \bool_set_false:N \l__mahjongtiles_concealed_kong_bool }
  }

\cs_new_protected:Npn \__mahjongtiles_remember_pending_overlay:nnn #1#2#3
  {
    \tl_if_blank:nF {#3}
      { \tl_set:Nn \l__mahjongtiles_tmpb_tl {#3} }
  }

\cs_new_protected:Npn \__mahjongtiles_put_aka_concealed_five_pair:n #1
  {
    \int_compare:nNnTF { \g__mahjongtiles_aka_int } = {0}
      {
        \__mahjongtiles_tile_put:nnn {5} {#1} {0}
        \__mahjongtiles_tile_put:nnn {5} {#1} {0}
      }
      {
        \str_if_eq:nnTF {#1} {p}
          {
            \int_compare:nNnTF { \g__mahjongtiles_aka_int } = {2}
              {
                \__mahjongtiles_tile_put:nnn {0} {p} {0}
                \__mahjongtiles_tile_put:nnn {0} {p} {0}
              }
              {
                \__mahjongtiles_tile_put:nnn {0} {p} {0}
                \__mahjongtiles_tile_put:nnn {5} {p} {0}
              }
          }
          {
            \__mahjongtiles_tile_put:nnn {0} {#1} {0}
            \__mahjongtiles_tile_put:nnn {5} {#1} {0}
          }
      }
  }

\cs_new_protected:Npn \__mahjongtiles_flush_concealed_kong:n #1
  {
    % Five repeated digits followed by a suit are rendered as a concealed kong:
    % back, two visible tiles, back.  The aka setting controls the visible pair
    % for 55555m/p/s: aka=0 uses two regular fives, aka=1 uses red plus regular,
    % and aka=2 gives 55555p an extra red 5p.
    \tl_clear:N \l__mahjongtiles_tmpb_tl
    \seq_map_inline:Nn \l__mahjongtiles_pending_seq
      { \__mahjongtiles_remember_pending_overlay:nnn ##1 }
    \__mahjongtiles_tile_put:nnn { x } { } {0}
    \str_if_eq:VnTF \l__mahjongtiles_first_face_tl {5}
      {
        \tl_if_in:nnTF { mps } {#1}
          {
            \__mahjongtiles_put_aka_concealed_five_pair:n {#1}
          }
          {
            \__mahjongtiles_tile_put:nnn {5} {#1} {0}
            \__mahjongtiles_tile_put:nnn {5} {#1} {0}
          }
      }
      {
        \__mahjongtiles_tile_put_aka_aware:Vnn \l__mahjongtiles_first_face_tl {#1} {0}
        \__mahjongtiles_tile_put_aka_aware:Vnn \l__mahjongtiles_first_face_tl {#1} {0}
      }
    \__mahjongtiles_tile_put:nnn { x } { } {0}
    \bool_set_true:N \l__mahjongtiles_last_concealed_kong_tail_bool
    \tl_if_blank:VF \l__mahjongtiles_tmpb_tl
      { \__mahjongtiles_output_set_last_overlay:V \l__mahjongtiles_tmpb_tl }
  }

%</package>
%    \end{macrocode}
\endinput
