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

\xsim_load_modules:n
  {
    base, properties, environments, templates, solutions, tags, goals,
    collections
  }

\seq_new:N  \l__xsim_parameters_seq
\seq_new:N  \l__xsim_fixed_parameters_seq
\seq_new:N  \l__xsim_mandatory_parameters_seq
\prop_new:N \g__xsim_exercises_prop
\int_gzero_new:N  \g_xsim_id_int
\int_gzero_new:N  \g_xsim_max_id_int
\tl_new:N   \l_xsim_current_id_tl
\tl_new:N   \g_xsim_exercise_id_tl
\tl_new:N   \ExerciseID
\bool_new:N \l____xsim_print_bool

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

\msg_new:nnn {xsim} {parameter-fixed}
  {
    You~ tried~ to~ set~ parameter~ `#2'~ for~ type~ `#1'~ \msg_line_context:
    . ~ However,~ parameter~ `#2'~ is~ a~ fixed~ parameter~ that~ already~
    has~ been~ set~ for~ type~ `#1'.~ Its~ value~ can't~ be~ changed!
  }

\msg_new:nnn {xsim} {type-exists}
  {
    You~ try~ to~ define~ exercise~ type~ `#1'~ \msg_line_context: .~
    However,~ the~ exercise~ type~ `#1'~ already~ exists.
  }

\msg_new:nnn {xsim} {mandatory-parameter}
  {
    You~ forget~ to~ set~ the~ mandatory~ parameter~ `#1'~ when~ defining~
    exercise~ type~ `#2'.
  }

\msg_new:nnn {xsim} {unknown-parameter}
  {
    You~ tried~ to~ retrieve~ the~ parameter~ `#1'~ \msg_line_context: . \\
    This~ parameter~ does~ not~ exist.~ Check~ for~ a~ typo~ or~ \\
    define~ the~ parameter~ yourself.
  }

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

\xsim_new_list:n {types}

% #1: true|false (fixed parameter)
% #2: true|false (mandatory parameter)
% #3: parameter name
\cs_new_protected:Npn \xsim_declare_parameter:nnn #1#2#3
  {
    \xsim_verbose:n { Declaring~ parameter~ `#3'. }
    \xsim_if_parameter_exist:nF {#3}
      {
        \seq_put_right:Nn \l__xsim_parameters_seq {#3}
        \bool_if:cT {c_#1_bool}
          { \seq_put_right:Nn \l__xsim_fixed_parameters_seq {#3} }
        \bool_if:cT {c_#2_bool}
          { \seq_put_right:Nn \l__xsim_mandatory_parameters_seq {#3} }
      }
  }
  
\cs_new_protected:Npn \xsim_remove_parameter:n #1
  {
    \xsim_verbose:n { Removing~ parameter~ `#1'. }
    \xsim_if_parameter_exist:nT {#1}
      { \seq_remove_all:Nn \l__xsim_parameters_seq {#1} }
  }

\prg_new_conditional:Npnn \xsim_if_parameter_exist:n #1 {p,T,F,TF}
  {
    \seq_if_in:NnTF \l__xsim_parameters_seq {#1}
      { \prg_return_true: }
      { \prg_return_false: }
  }
\cs_generate_variant:Nn \xsim_if_parameter_exist:nT {x}

\prg_new_conditional:Npnn \xsim_if_parameter_fixed:n #1 {p,T,F,TF}
  {
    \seq_if_in:NnTF \l__xsim_fixed_parameters_seq {#1}
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: type
% #2: csv list of parameters
\cs_new_protected:Npn \xsim_set_parameters:nn #1#2
  {
    \clist_map_inline:nn {#2}
      {
        \seq_set_split:Nnx \l__xsim_tmpa_seq {=} { \tl_trim_spaces:n {##1} }
        \xsim_set_parameter:nxx {#1}
          { \seq_item:Nn \l__xsim_tmpa_seq {1} }
          { \seq_item:Nn \l__xsim_tmpa_seq {2} }
      }
  }
\cs_generate_variant:Nn \xsim_set_parameters:nn {nx}

% #1: type
\cs_new_protected:Npn \xsim_check_mandatory_parameters:n #1
  {
    \seq_map_inline:Nn \l__xsim_mandatory_parameters_seq
      {
        \xsim_if_parameter_set:nnF {#1} {##1}
          { \msg_error:nnnn {xsim} {mandatory-parameter} {##1} {#1} }
      }
  }

% #1: type
\prg_new_conditional:Npnn \xsim_if_type:n #1 {p,T,F,TF}
  {
    \prop_if_exist:cTF {l__xsim_#1_type_parameters_prop}
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: type
% #2: parameter
% #3: value
\cs_new_protected:Npn \xsim_set_parameter:nnn #1#2#3
  {
    \xsim_if_parameter_exist:nT {#2}
      {
        \xsim_if_parameter_fixed:nTF {#1}
          {
            \xsim_if_parameter_set:nnTF {#1} {#2}
              {
                \msg_warning:nnnn {xsim} {parameter-fixed} {#1} {#2}
                \xsim_verbose:n
                  {
                    Not~ setting~ already~ set~ and~ fixed~ parameter~ `#2'~
                    for~ type~ `#1'.
                  }
              }
              {
                \xsim_verbose:n
                  { Setting~ parameter~ `#2'~ for~ type~ `#1'~ to~ `#3'. }
                \prop_put:cnn {l__xsim_#1_type_parameters_prop} {#2} {#3}
              }
          }
          {
            \xsim_verbose:n
              { Setting~ parameter~ `#2'~ for~ type~ `#1'~ to~ `#3'. }
            \prop_put:cnn {l__xsim_#1_type_parameters_prop} {#2} {#3}
          }
      }
  }
\cs_generate_variant:Nn \xsim_set_parameter:nnn {nnx,nxx}

% #1: type
% #2: parameter
\cs_new:Npn \xsim_get_parameter:nn #1#2
  { \prop_item:cn {l__xsim_#1_type_parameters_prop} {#2} }
\cs_generate_variant:Nn \xsim_get_parameter:nn {o}

\prg_new_conditional:Npnn \xsim_if_parameter_set:nn #1#2 {p,T,F,TF}
  {
    \prop_if_in:cnTF {l__xsim_#1_type_parameters_prop} {#2}
      { \prg_return_true: }
      { \prg_return_false: }
  }

\cs_new:Npn \__xsim_get_parameter_tmp:n #1 {}
\cs_generate_variant:Nn \__xsim_get_parameter_tmp:n {V}

% #1: type
% #2: parameter
\cs_new_protected:Npn \xsim_get_parameter_if_set:nnTF #1#2#3#4
  {
    \group_begin:
      \xsim_if_parameter_exist:nTF {#2}
        {
          \xsim_if_parameter_set:nnTF {#1} {#2}
            {    
              \tl_set:Nx \ParameterValue
                { \xsim_get_parameter:nn {#1} {#2} }
              \cs_set:Npn \__xsim_get_parameter_tmp:n ##1 {#3}
              \__xsim_get_parameter_tmp:V \ParameterValue
            }
            {#4}
        }
        { \msg_error:nnn {xsim} {unknown-parameter} {#2} }
    \group_end:
  }
\cs_new_protected:Npn \xsim_get_parameter_if_set:nnT #1#2#3
  { \xsim_get_parameter_if_set:nnTF {#1} {#2} {#3} {} }
\cs_new_protected:Npn \xsim_get_parameter_if_set:nnF #1#2#3
  { \xsim_get_parameter_if_set:nnTF {#1} {#2} {} {#3} }
\cs_generate_variant:Nn \xsim_get_parameter_if_set:nnTF {o}

% ----------------------------------------------------------------------------
% inside the argument `#1' refers to the type
\cs_new_protected:Npn \xsim_foreach_exercise_type:n #1
  { \xsim_foreach_new_list_entry:nn {types} {#1} }

\cs_new_protected:Npn \xsim_foreach_exercise_id:n #1
  { \int_step_inline:nnnn {1} {1} { \g_xsim_max_id_int } {#1} }

\cs_new:Npn \__xsim_loop_item:nnnnnn #1#2#3#4#5#6 {}
\cs_generate_variant:Nn \__xsim_loop_item:nnnnnn {VVxxxx}

% #1: type
% #2: id
% #3: boolean property
\cs_new:Npn \__xsim_deliver_exercise_details:nnn #1#2#3
  {
    \xsim_if_in_list:nnT {#3} {#1-#2=={true}}
      {
        \tl_set_rescan:Nnx \l__xsim_tmpa_tl {} {#1}
        \tl_set_rescan:Nnx \l__xsim_tmpb_tl {} {#2}
        \__xsim_loop_item:VVxxxx
          \l__xsim_tmpa_tl                                    % #1
          \l__xsim_tmpb_tl                                    % #2
          { \xsim_get_property:nnn {#1} {#2} {counter} }      % #3
          { \xsim_get_property:nnn {#1} {#2} {subtitle} }     % #4
          { \xsim_get_property:nnn {#1} {#2} {points} }       % #5
          { \xsim_get_property:nnn {#1} {#2} {bonus-points} } % #6
       }
  }

% #1: boolean property
% #2: loop code
\xsim_define_loop_macro:Nnn \xsim_foreach_exercise_type_id:nn
  {nnnnnn}
  {
    \xsim_foreach_exercise_type:n
      {
        \xsim_foreach_exercise_id:n
          { \__xsim_deliver_exercise_details:nnn {##1} {####1} {#1} }
      }
  }

\xsim_define_loop_macro:Nnn \xsim_foreach_exercise_id_type:nn
  {nnnnnn}
  {
    \xsim_foreach_exercise_id:n
      {
        \xsim_foreach_exercise_type:n
          { \__xsim_deliver_exercise_details:nnn {####1} {##1} {#1} }
      }
  }

\cs_new_protected:Npn \xsim_define_exercise_type_variables:n #1
  {
    \prop_new:c {l__xsim_#1_type_parameters_prop}
    \bool_new:c {l__xsim_#1_solution_print_bool}
    \bool_new:c {l__xsim_#1_exercise_print_bool}
    \bool_new:c {l__xsim_#1_exercise_use_bool}
  }

\cs_new_protected:Npn \xsim_define_counters:n #1
  {
    \xsim_verbose:n { Defining~ counters~ for~ new~ exercise~ type~ `#1' }
    \cs_if_exist:cF { c@ \xsim_get_parameter:nn {#1} {counter} }
      { \newcounter { \xsim_get_parameter:nn {#1} {counter} } }
    \cs_if_exist:cF { c@ \xsim_get_parameter:nn {#1} {solution-counter} }
      { \newcounter { \xsim_get_parameter:nn {#1} {solution-counter} } }
    \cs_if_exist:cF { c@ number of \xsim_get_parameter:nn {#1} {exercise-env} s }
      { \newcounter { number of \xsim_get_parameter:nn {#1} {exercise-env} s } }
  }

% #1: type
% #2: id
\cs_new_protected:Npn \xsim_step_exercise_counter:nn #1#2
  {
    \refstepcounter { \xsim_get_parameter:nn {#1} {counter} }
    \xsim_set_properties:nnx {#1} {#2}
      {
        counter = \use:c { the \xsim_get_parameter:nn {#1} {counter} } ,
        counter-value = \arabic { \xsim_get_parameter:nn {#1} {counter} }
      }
  }
\cs_generate_variant:Nn \xsim_step_exercise_counter:nn {nV}

% #1: type
\cs_new_protected:Npn \xsim_declare_exercise_type:nn #1#2
  {
    \xsim_if_type:nTF {#1}
      { \msg_error:nnn {xsim} {type-exists} {#1} }
      {
        \xsim_verbose:n { Declaring~ new~ exercise~ type~ `#1'. }
        \xsim_define_exercise_type_variables:n {#1}
        \xsim_add_to_list:nn {types} {#1}
        \xsim_set_parameters:nn {#1} {#2}
        \xsim_set_parameter:nnx {#1} {number}
          { number of \xsim_get_parameter:nn {#1} {exercise-env} s }
        \xsim_check_mandatory_parameters:n {#1}
        \xsim_if_parameter_set:nnF {#1} {counter}
          {
            \xsim_set_parameters:nx {#1}
              { counter =  \xsim_get_parameter:nn {#1} {exercise-env} }
          }
        \xsim_if_parameter_set:nnF {#1} {solution-counter}
          {
            \xsim_set_parameters:nx {#1}
              { solution-counter =  \xsim_get_parameter:nn {#1} {solution-env} }
          }
        \xsim_define_counters:n {#1}
        \xsim_verbose:n { Defining~ options~ for~ new~ exercise~ type~ `#1' }
        \keys_define:nx {xsim}
          {
            \xsim_get_parameter:nn {#1} {solution-env} / print
              .bool_set:N = \exp_not:c {l__xsim_#1_solution_print_bool} ,
            \xsim_get_parameter:nn {#1} {solution-env} / print
              .initial:n  = false ,
            \xsim_get_parameter:nn {#1} {exercise-env} / print
              .bool_set:N = \exp_not:c {l__xsim_#1_exercise_print_bool} ,
            \xsim_get_parameter:nn {#1} {exercise-env} / print
              .initial:n  = true ,
            \xsim_get_parameter:nn {#1} {exercise-env} / use
              .bool_set:N = \exp_not:c {l__xsim_#1_exercise_use_bool} ,
            \xsim_get_parameter:nn {#1} {exercise-env} / use
              .initial:n  = true ,
            \xsim_get_parameter:nn {#1} {exercise-env} / within .code:n =
              \exp_not:N \@addtoreset { \xsim_get_parameter:nn {#1} {counter} } {####1},
            \xsim_get_parameter:nn {#1} {exercise-env} / the-counter .code:n =
              \cs_set:cpn {the \xsim_get_parameter:nn {#1} {counter} } {####1} ,
            \xsim_get_parameter:nn {#1} {exercise-env} / template
              .code:n = \xsim_set_parameter:nnn {#1} {exercise-template} {####1} ,
            \xsim_get_parameter:nn {#1} {solution-env} / template
              .code:n = \xsim_set_parameter:nnn {#1} {solution-template} {####1} ,
            \xsim_get_parameter:nn {#1} {exercise-env} / name
              .code:n = \xsim_set_parameter:nnn {#1} {exercise-name} {####1} ,
            \xsim_get_parameter:nn {#1} {solution-env} / name
              .code:n = \xsim_set_parameter:nnn {#1} {solution-name} {####1}
          }
        \xsim_new_environment:nn {#1} {exercise}
        \xsim_new_environment:nn {#1} {solution}
      }
  }
  
% #1: type
% #2: id
\prg_new_conditional:Npnn \xsim_if_exercise_exists:nn #1#2 {T,F,TF}
  {
    \__xsim_set_file_signature:nnn {#1} {#2} {exercise}
    \file_if_exist:VTF \l__xsim_file_path_and_name_tl
      { \prg_return_true: }
      { \prg_return_false: }
  }
\cs_generate_variant:Nn \xsim_if_exercise_exists:nnT {VV}

% #1: type
% #2: options
\cs_new_protected:Npn \xsim_start_exercise:nn #1#2
  {
    \xsim_if_insert_mode:F
      {
        \int_gincr:N \g_xsim_id_int
        \int_compare:nF { \g_xsim_max_id_int > \g_xsim_id_int }
          { \int_gset_eq:NN \g_xsim_max_id_int \g_xsim_id_int }
        \xsim_set_properties:nxx {#1} { \int_use:N \g_xsim_id_int }
          {
            print = true ,
            use = true ,
            \exp_not:n {#2} ,
            id = \int_use:N \g_xsim_id_int ,
            section-value = \arabic{section} ,
            section = \thesection ,
            page-value = \arabic{page} ,
            sectioning =
              { \cs_if_exist:NTF \chapter { \arabic{chapter} } {0} }
              { \arabic {section} }
              { \arabic {subsection} }
              { \arabic {subsubsection} }
              { \arabic {paragraph} } ,
            page = \thepage ,
            used = false
          }
        \xsim_if_property_exist:nT {chapter}
          {
            \xsim_set_properties:nxx {#1} { \int_use:N \g_xsim_id_int }
              {
                chapter-value = \arabic{chapter} ,
                chapter = \thechapter
              }
          }
        \tl_gclear:N \g_xsim_exercise_id_tl
        \xsim_gsave_property:nxnN {#1} { \int_use:N \g_xsim_id_int } {id}
          \g_xsim_exercise_id_tl
        \tl_gset_eq:NN \ExerciseID \g_xsim_exercise_id_tl
        \tl_gset:Nn \ExerciseType {#1}
        \xsim_verbose:x
          {
            Starting~ exercise~ type~ `#1' with~ id~
            ` \g_xsim_exercise_id_tl '.
          }
        \xsim_exercise_if_use:nVTF {#1} \g_xsim_exercise_id_tl
          {
            \stepcounter { \xsim_get_parameter:nn {#1} {number} }
            % don't step if in active collection:
            \xsim_if_collections_active:TF
              {
                \xsim_set_property:nVnn {#1} \g_xsim_exercise_id_tl
                  {print}
                  {false}
              }
              {
                \xsim_step_exercise_counter:nV {#1} \g_xsim_exercise_id_tl
                \xsim_set_property:nVnn {#1} \g_xsim_exercise_id_tl
                  {used}
                  {true}
              }
            \xsim_set_property:nVnn {#1} \g_xsim_exercise_id_tl
              {use}
              {true}
            \xsim_update_goals:nV {#1} \g_xsim_exercise_id_tl
          }
          {
            \xsim_set_properties:nVn {#1} \g_xsim_exercise_id_tl
              { use = false , print = false }
          }
        \xsim_update_collections:nV {#1} \g_xsim_exercise_id_tl
      }
  }

\cs_new_protected:Npn \xsim_stop_exercise:n #1 {}

% ----------------------------------------------------------------------------
% #1: type
% #2: id
\prg_new_protected_conditional:Npnn \xsim_exercise_if_print:nn #1#2 {T,F,TF}
  {
    \xsim_if_boolean_property:nnnTF {#1} {#2} {print!}
      { \prg_return_true: }
      {
        \bool_set_true:N \l____xsim_print_bool
        \bool_if:cF {l__xsim_#1_exercise_print_bool}
          { \bool_set_false:N \l____xsim_print_bool }
        \xsim_if_tagged:nnF {#1} {#2}
          { \bool_set_false:N \l____xsim_print_bool }
        \xsim_if_boolean_property:nnnF {#1} {#2} {print}
          { \bool_set_false:N \l____xsim_print_bool }
        \bool_if:NTF \l____xsim_print_bool
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }
\cs_generate_variant:Nn \xsim_exercise_if_print:nnT  {nV}
\cs_generate_variant:Nn \xsim_exercise_if_print:nnTF {nV}

% #1: type
% #2: id
\prg_new_protected_conditional:Npnn \xsim_exercise_if_use:nn #1#2 {T,F,TF}
  {
    \xsim_if_boolean_property:nnnTF {#1} {#2} {use!}
      { \prg_return_true: }
      {
        \xsim_exercise_if_print:nnTF {#1} {#2}
          { \prg_return_true: }
          {
            \bool_if:cTF {l__xsim_#1_exercise_use_bool}
              {
                \xsim_if_boolean_property:nnnTF {#1} {#2} {use}
                  {
                    \xsim_if_tagged:nnTF {#1} {#2}
                      { \prg_return_true: }
                      { \prg_return_false: }
                  }
                  { \prg_return_false: }
              }
              { \prg_return_false: }
          }
          
      }
  }
\cs_generate_variant:Nn \xsim_exercise_if_use:nnT  {nV}
\cs_generate_variant:Nn \xsim_exercise_if_use:nnTF {nV}

% #1: type
% #2: id
% #3: exercise|solution
\prg_new_protected_conditional:Npnn \xsim_if_print:nnn #1#2#3 {T,F,TF}
  {
    \use:c {xsim_#3_if_print:nnTF} {#1} {#2}
      { \prg_return_true: }
      { \prg_return_false: }
  }

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

\xsim_new_aux_cs:cpn {total-number} #1
  { \int_gset:Nn \g_xsim_max_id_int {#1} }

\RequirePackage {etoolbox}

\AtEndPreamble
  {
    \xsim_foreach_exercise_type:n
      {
        \tl_new:c { number of #1 s }
        \tl_set:cn { number of #1 s } {0}
        \xsim_new_aux_cs:cpn {#1} ##1
          { \tl_gset:cn { number of #1 s } {##1} }
      }
  }

\AtBeginDocument
  {
    \xsim_foreach_exercise_type:n
      {
        \xsim_write_to_aux:x
          { \token_to_str:N \providecommand \token_to_str:N \numberof #1 s {} }
      }
  }

\AtEndDocument
  {
    \xsim_if_clear_aux:T
      {
        \int_compare:nNnF { \g_xsim_max_id_int } = { \g_xsim_id_int }
          { \xsim_empty_lists: }
      }
    \xsim_add_cs_to_aux:nn {total-number} { { \int_use:N \g_xsim_id_int } }
    \xsim_foreach_exercise_type:n
      {
        \xsim_add_cs_to_aux:nn {#1}
          { { \arabic { \xsim_get_parameter:nn {#1} {number} } } }
      }
    \xsim_update_list:n {types}
  }

% ----------------------------------------------------------------------------  
\file_input_stop:
