% ----------------------------------------------------------------------------
% the XSIM package - random module
% 
%   eXercise Sheets IMproved
% 
% ----------------------------------------------------------------------------
% Clemens Niederberger
% Web:    https://github.com/cgnieder/xsim
% E-Mail: contact@mychemistry.eu
% ----------------------------------------------------------------------------
% Copyright 2017 Clemens Niederberger
% 
% This work may be distributed and/or 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://www.latex-project.org/lppl.txt
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
% 
% This work has the LPPL maintenance status `maintained'.
% 
% The Current Maintainer of this work is Clemens Niederberger.
% ----------------------------------------------------------------------------
% If you have any ideas, questions, suggestions or bugs to report, please
% feel free to contact me.
% ----------------------------------------------------------------------------
\XSIMmodule{random}{randomly select exercises from collections}

\xsim_load_modules:n {collections}

\tl_new:N    \l_xsim_random_collection_tl
\tl_new:N    \l_xsim_random_exclude_list_tl
\tl_new:N    \l__xsim_print_random_choice_tl
\tl_const:Nn \c__xsim_print_random_both_tl {both}
\tl_const:Nn \c__xsim_print_random_exercise_tl {exercise}
\tl_const:Nn \c__xsim_print_random_solution_tl {solution}
\seq_new:N   \g__xsim_random_list_seq
\seq_new:N   \l__xsim_allowed_exercise_ids_seq
\int_new:N   \g__xsim_random_list_id_int
\bool_new:N  \l__xsim_sort_random_bool

% #1: random list id
% #2: csv list of integers
\xsim_new_aux_cs:cpn {random} #1#2
  {
    \xsim_random_list_if_exist:nF {#1}
      { \xsim_new_random_list:n {#1} }
    \xsim_set_random_list:nn {#1} {#2}
  }

\cs_new_protected:Npn \xsim_new_random_list:n #1
  {
    \xsim_verbose:n {Defining~ new~ random~ list~ `#1'}
    \seq_new:c {g__xsim_random_#1 _seq}
  }

\cs_new_protected:Npn \xsim_set_random_list:nn #1#2
  { \seq_gset_from_clist:cn {g__xsim_random_#1_seq} {#2} }

\cs_new_protected:Npn \xsim_set_random_list_from_seq:nN #1#2
  { \seq_gset_eq:cN {g__xsim_random_#1_seq} #2 }

\cs_new:Npn \xsim_use_random_list:nn #1#2
  { \seq_use:cn {g__xsim_random_#1_seq} {#2} }

\cs_new_protected:Npn \xsim_map_random_list:nn #1#2
  { \seq_map_inline:cn {g__xsim_random_#1_seq} {#2} }

\prg_new_conditional:Npnn \xsim_random_list_if_exist:n #1 {p,T,F,TF}
  {
    \seq_if_exist:cTF {g__xsim_random_#1_seq}
      { \prg_return_true: }
      { \prg_return_false: }
  }

\prg_new_conditional:Npnn \xsim_random_list_if_empty:n #1 {p,T,F,TF}
  {
    \seq_if_empty:cTF {g__xsim_random_#1_seq}
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: random list id
% #2: sequence variable to set list from
\cs_new_protected:Npn \xsim_save_random_list:nN #1#2
  {
    \xsim_random_list_if_exist:nF {#1}
      { \xsim_new_random_list:n {#1} }
    \xsim_random_list_if_empty:nT {#1}
      { \xsim_set_random_list_from_seq:nN {#1} #2 }
    \xsim_verbose:n {Saving~ random~ list~ `#1'}
    \xsim_add_cs_to_aux:nn {random}
      { {#1} { \xsim_use_random_list:nn {#1} {,} } }
  }

% #1: collection
% #2: number of items
% #3: csv list of excluded ids
\cs_new_protected:Npn \xsim_print_random_list:nnn #1#2#3
  {
    \xsim_if_collection_exists:nTF {#1}
      {
        \int_gincr:N \g__xsim_random_list_id_int
        \int_compare:nNnTF { \xsim_collection_count:n {#1} } = {0}
          { \xsim_rerun: }
          {
            % generate seq of allowed ids from collection:
            \xsim_foreach_exercise_id:n
              {
                \tl_set:Nx \l__xsim_tmpa_tl
                  { \xsim_get_type_for_property:nn {id} {##1} }
                \xsim_if_in_collection:nVnT {#1} \l__xsim_tmpa_tl {##1}
                  {
                    \clist_if_in:nnF {#3} {##1}
                      { \seq_put_right:Nn \l__xsim_allowed_exercise_ids_seq {##1} }
                  }
              }
            \int_compare:nNnTF
              {#2}
               >
              { \seq_count:N \l__xsim_allowed_exercise_ids_seq }
              {
                \int_set:Nn \l__xsim_tmpa_int
                  { \seq_count:N \l__xsim_allowed_exercise_ids_seq }
              }
              { \int_set:Nn \l__xsim_tmpa_int {#2} }
            % generate random list:
            \xsim_generate_random_list:nnnN
              { \int_to_alph:n { \g__xsim_random_list_id_int } }
              { \seq_count:N \l__xsim_allowed_exercise_ids_seq }
              { \l__xsim_tmpa_int }
              \g__xsim_random_list_seq
          }
        \xsim_save_random_list:nN
          { \int_to_alph:n { \g__xsim_random_list_id_int } }
          \g__xsim_random_list_seq
        \xsim_verbose:x
          {
            Printing~ exercises~ of~ random~ list~
            `\int_to_alph:n { \g__xsim_random_list_id_int }'~ of~
            collection~ `#1'
          }
        \xsim_print_random_exercises:Nn
          \l__xsim_allowed_exercise_ids_seq
          { \int_to_alph:n { \g__xsim_random_list_id_int } }
      }
      { \msg_error:nnn {xsim} {unknown-collection} {#1} }
  }
\cs_generate_variant:Nn \xsim_print_random_list:nnn {VnV}

% #1: random list id
% #2: max number to choose from
% #3: number of items
% #4: seq variable
\cs_new_protected:Npn \xsim_generate_random_list:nnnN #1#2#3#4
  {
    \bool_if:nT
      {
        !\xsim_random_list_if_exist_p:n {#1}
        ||
        \xsim_random_list_if_empty_p:n {#1}
      }
      {
        \xsim_verbose:n
          {Generating~ random~ list~ `#1'~ with~ #3~ out~ of~ #2~ items}
        \seq_gclear:N #4
        \int_do_until:nNnn { \seq_count:N #4 } = {#3}
          {
            \tl_set:Nx \l__xsim_tmpa_tl
              { \fp_eval:n { randint(#2) } }
            \seq_if_in:NVF #4
              \l__xsim_tmpa_tl
              { \seq_gput_right:NV #4 \l__xsim_tmpa_tl }
          }
        \bool_if:NT \l__xsim_sort_random_bool
          {
            \seq_sort:Nn #4
              {
                \int_compare:nNnTF {##1} > {##2}
                  { \sort_return_swapped: }
                  { \sort_return_same: }
              }
          }
      }
  }

% #1: seq variable of ids
% #2: random list id
\cs_new_protected:Npn \xsim_print_random_exercises:Nn #1#2
  {
    \bool_if:nT
      {
        \xsim_random_list_if_exist_p:n {#2}
        &&
        !\xsim_random_list_if_empty_p:n {#2}
      }
      {
        \xsim_verbose:x
          {
            Printing~
            \l__xsim_print_random_choice_tl \c_space_tl
            \str_if_eq:onT { \l__xsim_print_random_choice_tl } {both}
              { exercises~ and~ solutions~ }
            of~ random~ list~ `#2'
          }
        \xsim_map_random_list:nn {#2}
          {
            \tl_set:Nx \l__xsim_tmpa_tl
              { \seq_item:Nn #1 {##1} }
            \tl_set:Nx \l__xsim_tmpb_tl
              { \xsim_get_type_for_property:nV {id} \l__xsim_tmpa_tl }
            \bool_if:nT
              {
                \tl_if_eq_p:NN
                  \l__xsim_print_random_choice_tl
                  \c__xsim_print_random_both_tl
                ||
                \tl_if_eq_p:NN
                  \l__xsim_print_random_choice_tl
                  \c__xsim_print_random_exercise_tl
              }
              {
                \xsim_if_exercise_exists:VVT
                  \l__xsim_tmpb_tl
                  \l__xsim_tmpa_tl
                  {
                    \xsim_insert:VVnn
                      \l__xsim_tmpb_tl
                      \l__xsim_tmpa_tl
                      {}
                      {exercise}
                  }
              }
            \bool_if:nT
              {
                \tl_if_eq_p:NN
                  \l__xsim_print_random_choice_tl
                  \c__xsim_print_random_both_tl
                ||
                \tl_if_eq_p:NN
                  \l__xsim_print_random_choice_tl
                  \c__xsim_print_random_solution_tl
              }
              {
                \xsim_if_solution_exists:VVT
                  \l__xsim_tmpb_tl
                  \l__xsim_tmpa_tl
                  {
                    \xsim_insert:VVnn
                      \l__xsim_tmpb_tl
                      \l__xsim_tmpa_tl
                      {}
                      {solution}
                  }
              }
          }
      }
  }

\keys_define:nn {xsim/random}
  {
    sort       .bool_set:N = \l__xsim_sort_random_bool ,
    sort       .initial:n  = true ,
    collection .tl_set:N   = \l_xsim_random_collection_tl ,
    collection .initial:n  = all~exercises ,
    exclude    .code:n     = \__xsim_exclude_ids:n {#1} ,
    print             .choice: ,
    print/exercises   .code:n =
      \tl_set:Nn \l__xsim_print_random_choice_tl {exercise} ,
    print/solutions   .code:n =
      \tl_set:Nn \l__xsim_print_random_choice_tl {solution} ,
    print/both        .code:n =
      \tl_set:Nn \l__xsim_print_random_choice_tl {both} ,
    print/unknown     .code:n =
      \msg_warning:nnn {xsim} {print-collection-choice} {#1}
      \tl_set:Nn \l__xsim_print_random_choice_tl {exercise} ,
    print             .initial:n = exercises
  }

\cs_new_protected:Npn \__xsim_exclude_ids:n #1
  {
    \seq_clear:N \l__xsim_tmpa_seq
    \clist_map_inline:nn {#1}
      {
        \tl_set:Nx \l__xsim_tmpa_tl
          { \xsim_get_id_for_property:nn {ID} {##1} }
        \tl_if_blank:NTF \l__xsim_tmpa_tl
          { \seq_put_right:Nn \l__xsim_tmpa_seq {##1} }
          {
            \seq_put_right:Nx \l__xsim_tmpa_seq
              { \xsim_get_id_for_property:nn {ID} {##1} }
          }
      }
    \seq_remove_duplicates:N \l__xsim_tmpa_seq
    \tl_set:Nx \l_xsim_random_exclude_list_tl
      { \seq_use:Nn \l__xsim_tmpa_seq {,} }
  }

% use this in the exclude list:
% \xsim_get_type_for_property:nn {id} {}
% or a combination of:
% \xsim_get_type_for_property:nn {ID} {}
% \xsim_get_id_for_property:nn {ID} {}

% ----------------------------------------------------------------------------
\tex_endinput:D
