% ----------------------------------------------------------------------------
% the XSIM package - collections module
% 
%   eXercise Sheets IMproved
% 
% ----------------------------------------------------------------------------
% Clemens Niederberger
% Web:    https://github.com/cgnieder/xsim
% E-Mail: contact@mychemistry.eu
% ----------------------------------------------------------------------------
% Copyright 2017--2020 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{collections}{collect exercises and print collected exercises}

\xsim_load_modules:n {base}

\prop_new:N \g__xsim_collections_prop
\bool_new:N \l____xsim_active_bool

\msg_new:nnn {xsim} {collection-exists}
  {
    The~ collection~ `#1'~ you're~ trying~ to~ define~ \msg_line_context:
    \c_space_tl already~ exists.
  }

\msg_new:nnn {xsim} {collection-active}
  {
    You~ can't~ print~ collection~ `#1'~ while~ it~ is~ still~ active~
    \msg_line_context:
  }

\msg_new:nnn {xsim} {unknown-collection}
  {
    You're~ trying~ to~ use~ the~ collection~ `#1'~ \msg_line_context:
    \c_space_tl but~ no~ collection~ with~ that~ name~ exists.~ You~ need~ to~
    define~ it~ first!
  }

\msg_new:nnn {xsim} {print-collection-choice}
  {
    The~ choice~ `#1'~ for~ the~ option~ `print-collection/print'~ does~ not~
    exist.~ I'm~ using~ `exercises'~ instead~ \msg_line_context:
  }

% ----------------------------------------------------------------------------

% #1: collection name
\cs_new_protected:Npn \xsim_new_collection:n #1
  {
    \xsim_if_collection_exist:nTF {#1}
      { \msg_error:nnn {xsim} {collection-exists} {#1} }
      {
        \xsim_verbose:n { Declaring~ new~ collection~ `#1'. }
        \prop_gput:Nnn \g__xsim_collections_prop {#1} { \c_false_bool }
        \xsim_new_list:n {collection:#1}
        \seq_new:c {g__xsim_collection_#1_seq}
      }
  }

% #1: collection name
\prg_new_conditional:Npnn \xsim_if_collection_exist:n #1 {T,F,TF}
  {
    \prop_if_in:NnTF \g__xsim_collections_prop {#1}
      { \prg_return_true: }
      { \prg_return_false: }
  }

\xsim_new_collection:n {all~ exercises}

\cs_new_protected:Npn \xsim_foreach_collection:n #1
  { \prop_map_inline:Nn \g__xsim_collections_prop {#1} }

% ----------------------------------------------------------------------------

% #1: collection name
% #2: type
\cs_new_protected:Npn \xsim_activate_collection:nn #1#2
  {
    \xsim_if_collection_active:nTF {#1}
      { % TODO: error
      }
      {
        \xsim_verbose:n { Activating~ collection~ `#1'. }
        \prop_gput:Nnn \g__xsim_collections_prop {#1} { \c_true_bool }
        \tl_if_blank:nF {#2}
          { \seq_gput_right:cn {g__xsim_collection_#1_seq} {#2} }
      }
  }

\cs_new_protected:Npn \xsim_activate_collection:n #1
  { \xsim_activate_collection:nn {#1} {} }

% #1: collection name
\cs_new_protected:Npn \xsim_deactivate_collection:n #1
  {
    \xsim_if_collection_active:nTF {#1}
      {
        \xsim_verbose:n { Deactivating~ collection~ `#1'. }
        \prop_gput:Nnn \g__xsim_collections_prop {#1} { \c_false_bool }
        \seq_gclear:c {g__xsim_collection_#1_seq}
      }
      { % TODO: warning
      }
  }

% start collection for an exercise type:
% #1: collection name
% #2: type
\cs_new_protected:Npn \xsim_start_collection:nn #1#2
  {
    \group_begin:
    \xsim_if_collection_exist:nF {#1}
      { \msg_error:nnn {xsim} {unknown-collection} {#1} }
    \xsim_activate_collection:nn {#1} {#2}
    \xsim_setup:x { \xsim_get_parameter:nn {#2} {exercise-env} /print = false }
  }
\cs_generate_variant:Nn \xsim_setup:n {x}

% start collection for all exercise types:
% #1: collection name
\cs_new_protected:Npn \xsim_start_collection:n #1
  {
    \group_begin:
    \xsim_if_collection_exist:nF {#1}
      { \msg_error:nnn {xsim} {unknown-collection} {#1} }
    \xsim_activate_collection:n {#1}
    \xsim_foreach_exercise_type:n
      { \xsim_setup:x { \xsim_get_parameter:nn {##1} {exercise-env} /print = false } }
  }

% stop collection:
% #1: collection name
\cs_new_protected:Npn \xsim_stop_collection:n #1
  {
    \xsim_deactivate_collection:n {#1}
    \group_end:
  }

% ----------------------------------------------------------------------------

% #1: collection name
% #2: type
% #3: id
\cs_new_protected:Npn \xsim_add_to_collection:nnn #1#2#3
  {
    \seq_if_empty:cTF {g__xsim_collection_#1_seq}
      { \__xsim_add_to_collection:nnn {#1} {#2} {#3} }
      {
        \seq_if_in:cnT {g__xsim_collection_#1_seq} {#2}
          { \__xsim_add_to_collection:nnn {#1} {#2} {#3} }
      }
  }

% #1: collection name
% #2: type
% #3: id
\cs_new_protected:Npn \__xsim_add_to_collection:nnn #1#2#3
  {
    \xsim_verbose:n
      { Adding~ exercise~ type~ `#2'~ id~ `#3'~ to~ collection~ `#1'. } 
    \xsim_add_to_list:nn {collection:#1} {#2-#3}
  }

\cs_new:Npn \xsim_collection_count:n #1
  { \xsim_list_count:n {collection:#1} }

% #1: type
% #2: id
\cs_new_protected:Npn \xsim_update_collections:nn #1#2
  {
    \xsim_if_insert_mode:F
      {
        \xsim_exercise_if_use:nnT {#1} {#2}
          { \xsim_add_to_collection:nnn {all~ exercises} {#1} {#2} }
        \xsim_foreach_collection:n
          {
            \xsim_exercise_if_use:nnTF {#1} {#2}
              {
                \xsim_if_collection_active:nT {##1}
                  { \xsim_add_to_collection:nnn {##1} {#1} {#2} }
              }
              { \xsim_remove_from_list:nn {collection:##1} {#1-#2} }
          }
      }
  }
\cs_generate_variant:Nn \xsim_update_collections:nn {nV}

% #1: collection name
\cs_new_protected:Npn \xsim_get_collections:
  {
    \xsim_foreach_collection:n
      {
        \xsim_foreach_list_entry:nn {collection:##1}
          { \__xsim_list_to_collection:nww {##1} ####1 \q_stop }
      }
  }

\cs_new_protected:Npn \__xsim_list_to_collection:nww #1#2-#3 \q_stop
  { \xsim_add_to_collection:nnn {#1} {#2} {#3} }

% ----------------------------------------------------------------------------

% #1: collection name
% #2: type
% #3: id
\prg_new_conditional:Npnn \xsim_if_in_collection:nnn #1#2#3 {T,F,TF}
  {
    \xsim_if_in_list:nnTF {collection:#1} {#2-#3}
      { \prg_return_true: }
      { \prg_return_false: }
  }
\cs_generate_variant:Nn \xsim_if_in_collection:nnnTF {V}
\cs_generate_variant:Nn \xsim_if_in_collection:nnnT  {x,nV}

% ----------------------------------------------------------------------------

\bool_new:N \l__xsim_printcollection_headings_bool
\tl_new:N   \l__xsim_printcollection_headings_template_tl
\tl_new:N   \l__xsim_print_collection_choice_tl

\keys_define:nn {xsim/print-collection}
  {
    headings          .bool_set:N = \l__xsim_printcollection_headings_bool ,
    headings          .initial:n  = false ,
    headings-template .tl_set:N  = \l__xsim_printcollection_headings_template_tl ,
    headings-template .initial:n = collection ,
    print             .choice: ,
    print/exercises   .code:n =
      \tl_set:Nn \l__xsim_print_collection_choice_tl {exercise} ,
    print/solutions   .code:n =
      \tl_set:Nn \l__xsim_print_collection_choice_tl {solution} ,
    print/both        .code:n =
      \tl_set:Nn \l__xsim_print_collection_choice_tl {both} ,
    print/unknown     .code:n =
      \msg_warning:nnn {xsim} {print-collection-choice} {#1}
      \tl_set:Nn \l__xsim_print_collection_choice_tl {exercise} ,
    print             .initial:n = exercises
  }

% #1: options
% #2: collection name
\cs_new_protected:Npn \xsim_print_collection:nn #1#2
  {
    \xsim_if_collection_active:nTF {#2}
      { \msg_error:nnn {xsim} {collection-active} {#2} }
      {
        \xsim_verbose:x
          {
            Printing~
            \l__xsim_print_collection_choice_tl \c_space_tl
            \str_if_eq:onT { \l__xsim_print_collection_choice_tl } {both}
              { exercises~ and~ solutions~ }
            of~ collection~ `#2'
          }
        \group_begin:
          \keys_set:nn {xsim/print-collection} {#1} 
          \tl_set:Nn \ExerciseCollection {#2}
          \xsim_foreach_exercise_type:n
            {
              \tl_set:Nn \ExerciseType {##1}
              \bool_if:NT \l__xsim_printcollection_headings_bool
                {
                  \bool_set_false:N \l__xsim_tmpa_bool
                  \xsim_foreach_exercise_id:n
                    {
                      \xsim_if_in_collection:nnnT {#2} {##1} {####1}
                        {
                          \tl_if_eq:VnTF
                            \l__xsim_print_collection_choice_tl
                            {both}
                            {
                              \xsim_if_exercise_exist:nnT {##1} {####1}
                                { \bool_set_true:N \l__xsim_tmpa_bool }
                            }
                            {
                              \use:c
                                {
                                  xsim_if_
                                  \l__xsim_print_collection_choice_tl
                                  _exist:nnT
                                }
                                {##1}
                                {####1}
                                { \bool_set_true:N \l__xsim_tmpa_bool }
                            }
                        }
                    }
                  \bool_if:NT \l__xsim_tmpa_bool
                    {
                      \xsim_use_template:nV
                      {heading}
                      \l__xsim_printcollection_headings_template_tl
                    }
                }
              \xsim_foreach_exercise_id:n
                {
                  \xsim_if_in_collection:nnnT {#2} {##1} {####1}
                    {
                      \tl_if_eq:VnTF
                        \l__xsim_print_collection_choice_tl
                        {both}
                        {
                          \xsim_if_exercise_exist:nnT {##1} {####1}
                            { \xsim_insert:nnnn {##1} {####1} {} {exercise} }
                          \xsim_if_solution_exist:nnT {##1} {####1}
                            { \xsim_insert:nnnn {##1} {####1} {} {solution} }
                        }
                        {
                          \use:c
                            {
                              xsim_if_
                              \l__xsim_print_collection_choice_tl
                              _exist:nnT
                            }
                            {##1}
                            {####1}
                            {
                              \xsim_insert:nnnV
                                {##1}
                                {####1}
                                {}
                                \l__xsim_print_collection_choice_tl
                            }
                        }
                    }
                }
            }
        \group_end:
      }
  }

% ----------------------------------------------------------------------------

% #1: collection name
\prg_new_conditional:Npnn \xsim_if_collection_active:n #1 {T,F,TF}
  {
    \prop_if_in:NnTF \g__xsim_collections_prop {#1}
      {
        \bool_lazy_and:nnTF
          { !\str_if_eq_p:en {#1} {all~ exercises} }
          { \prop_item:Nn \g__xsim_collections_prop {#1} }
          { \prg_return_true: }
          { \prg_return_false: }
      }
      { \prg_return_false: }
  }

\prg_new_protected_conditional:Npnn \xsim_if_collections_active: {T,F,TF}
  {
    \bool_set_false:N \l____xsim_active_bool
    \prop_map_inline:Nn \g__xsim_collections_prop
      {
        \xsim_if_collection_active:nT {##1}
          {
            \bool_set_true:N \l____xsim_active_bool
            \prop_map_break:
          }
      }
    \bool_if:NTF \l____xsim_active_bool
      { \prg_return_true: }
      { \prg_return_false: }
  }

% ----------------------------------------------------------------------------

\xsim_at_begin_document:n
  {
    \xsim_get_collections:
    \xsim_activate_collection:n   {all~ exercises}
  }

\xsim_at_end_document:n
  {
    \xsim_deactivate_collection:n {all~ exercises}
    \xsim_foreach_collection:n { \xsim_update_list:n {collection:#1} }
  }
  
% ----------------------------------------------------------------------------
\file_input_stop:
