% ----------------------------------------------------------------------------
% the XSIM package - goals 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{goals}{counting of points and bonus points}

\xsim_load_modules:n {properties,exercises}

\msg_new:nnn {xsim} {goal-unknown}
  { The~ goal~ `#1'~ has~ never~ been~ declared~ \msg_line_context: }

\seq_new:N \l__xsim_goals_seq
\tl_new:N  \l_xism_grading_table_template_tl
\tl_new:N  \l_xism_grading_table_exercise_type_tl

% #1: name
\cs_new_protected:Npn \xsim_declare_exercise_goal:n #1
  {
    \xsim_if_goal_exist:nF {#1}
      {
        \xsim_verbose:n { Declaring~ new~ goal~ `#1'. }
        \xsim_declare_property:nnnn
          { \c_false_bool }
          { \c_false_bool }
          { \c_false_bool }
          {#1}
        \seq_put_right:Nn \l__xsim_goals_seq {#1}
        \fp_gzero_new:c {g__xsim_goal_#1_fp}
        \bool_new:c {l__xsim_goal_#1_given_bool}
        \fp_gzero_new:c {l__xsim_current_goal_#1_fp}
        \fp_if_exist:cF {g__xsim_total_goal_#1_fp}
          { \fp_new:c {g__xsim_total_goal_#1_fp} }
        \fp_if_exist:cF {g__xsim_total_goal_recorded_#1_fp}
          { \fp_new:c {g__xsim_total_goal_recorded_#1_fp} }
        \xsim_at_begin_document:n
          {
            \xsim_foreach_exercise_type:n
              {
                \fp_gzero_new:c {g__xsim_##1_goal_#1_fp}
                \fp_if_exist:cF {g__xsim_total_##1_goal_#1_fp}
                  { \fp_new:c {g__xsim_total_##1_goal_#1_fp} }
              }
          }
      }
  }

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

\prg_new_conditional:Npnn \xsim_if_goal_given:n #1 {p,T,F,TF}
  {
    \bool_if:cTF {l__xsim_goal_#1_given_bool}
      { \prg_return_true: }
      { \prg_return_false: }
  }

\cs_new_protected:Npn \xsim_goal_given:n #1
  { \bool_set_true:c {l__xsim_goal_#1_given_bool} }

% #1: exercise type
% #2: id
% #3: goal
\cs_new_protected:Npn \xsim_update_goal:nnn #1#2#3
  {
    \xsim_if_goal_exist:nT {#3}
      {
        \fp_gadd:cn {g__xsim_goal_#3_fp}
          { \xsim_get_property:nnn {#1} {#2} {#3} + 0 }
        \fp_gadd:cn {g__xsim_#1_goal_#3_fp}
          { \xsim_get_property:nnn {#1} {#2} {#3} + 0 }
      }
  }

% #1: exercise type
% #2: id
\cs_new_protected:Npn \xsim_update_goals:nn #1#2
  {
    \xsim_foreach_goal:n
      { \xsim_update_goal:nnn {#1} {#2} {##1} }
  }
\cs_generate_variant:Nn \xsim_update_goals:nn {nV}

\cs_new_protected:Npn \xsim_foreach_goal:n #1
  { \seq_map_inline:Nn \l__xsim_goals_seq {#1} }

% #1: type
% #2: id
\cs_new_protected:Npn \xsim_synchronize_goals:nn #1#2
  {
    \xsim_foreach_goal:n
      {
        \xsim_if_property_set:nnnTF {#1} {#2} {##1}
          {
            \xsim_set_property:nnnx {#1} {#2} {##1}
              { \xsim_temporary_goal_get:nnn {#1} {#2} {##1} }
          }
          {
            \fp_compare:nNnT
              { \xsim_temporary_goal_get:nnn {#1} {#2} {##1} } > {0}
              {
                \xsim_set_property:nnnx {#1} {#2} {##1}
                  { \xsim_temporary_goal_get:nnn {#1} {#2} {##1} }
              }
          }
      }
  }

% #1: exercise type
% #2: id
% #3: goal
% #4: value
\cs_new_protected:Npn \xsim_addto_goal:nnnn #1#2#3#4
  {
    \xsim_if_goal_exist:nT {#3}
      {
        \xsim_verbose:n { Adding~ `#4'~ to~ `#3'~ goal~ of~ #1~ #2 }
        % \fp_gadd:cn {g__xsim_goal_#3_fp} {#4}
        % \fp_gadd:cn {g__xsim_#1_goal_#3_fp} {#4}
        % \fp_add:cn {l__xsim_current_goal_#3_fp} {#4}
        \xsim_temporary_goal_add:nnnn {#1} {#2} {#3} {#4}
      }
  }
\cs_generate_variant:Nn \xsim_addto_goal:nnnn {xx,nVnx}

% #1: exercise type
% #2: id
% #3: goal
% #4: relation and value
\prg_new_conditional:Npnn \xsim_if_goal_value:nnnn #1#2#3#4 {p,T,F,TF}
  {
    \fp_compare:nTF
      { (0 \xsim_get_property:nnn {#1} {#2} {#3}) #4 }
      { \prg_return_true: }
      { \prg_return_false: }
  }
\cs_generate_variant:Nn \xsim_if_goal_value:nnnnTF {xx}

% ----------------------------------------------------------------------------
% we need some kind of temporary variable to be able to set goals both as
% option to exercises and within the exercise body

% #1: type
% #2: id
% #3: goal
\cs_new_protected:Npn \xsim_temporary_goal_new:nnn #1#2#3
  { \fp_new:c {g__xsim_goal_#3_#1_#2_fp} }
\cs_generate_variant:Nn \xsim_temporary_goal_new:nnn {nV}

% #1: type
% #2: id
% #3: goal
% #4: value
\cs_new_protected:Npn \xsim_temporary_goal_set:nnnn #1#2#3#4
  { \fp_gset:cn {g__xsim_goal_#3_#1_#2_fp} {#4} }
\cs_generate_variant:Nn \xsim_temporary_goal_set:nnnn {nV}

% #1: type
% #2: id
% #3: goal
% #4: value
\cs_new_protected:Npn \xsim_temporary_goal_add:nnnn #1#2#3#4
  { \fp_gadd:cn {g__xsim_goal_#3_#1_#2_fp} {#4} }
\cs_generate_variant:Nn \xsim_temporary_goal_add:nnnn {nV}

% #1: type
% #2: id
% #3: goal
\cs_new:Npn \xsim_temporary_goal_get:nnn #1#2#3
  { \fp_to_decimal:c {g__xsim_goal_#3_#1_#2_fp} }
\cs_generate_variant:Nn \xsim_temporary_goal_get:nnn {nV}

% #1: type
% #2: id
% #3: goal
\cs_new_protected:Npn \__xsim_temporary_goal_show:nnn #1#2#3
  { \fp_show:c {g__xsim_goal_#3_#1_#2_fp} }

% ----------------------------------------------------------------------------
% a function which can be used to modify how the numbers of a goal are
% printed:
\cs_new_protected:Npn \__xsim_print_goal:n #1 {#1}
\cs_generate_variant:Nn \__xsim_print_goal:n {x}

% #1: value
% #2: value == 1
% #3: value != 1
\cs_new_protected:Npn \xsim_print_goal:nnn #1#2#3
  {
    \__xsim_print_goal:x { \fp_to_decimal:n {#1} }
    \fp_compare:nTF { (#1) = 1 } {#2} {#3}
  }

% #1: exercise type
% #2: goal
% #3: sum == 1
% #4: sum != 1
\cs_new_protected:Npn \xsim_print_goal_sum:nnnn #1#2#3#4
  { \xsim_print_goal:nnn { \use:c {g__xsim_total_#1_goal_#2_fp} } {#3} {#4} }

% #1: goal
% #2: sum == 1
% #3: sum != 1
\cs_new_protected:Npn \xsim_print_total_goal_sum:nnn #1#2#3
  { \xsim_print_goal:nnn { \use:c {g__xsim_total_goal_#1_fp} } {#2} {#3} }

% ----------------------------------------------------------------------------
% the same as the last to functions but for a list of goals:

% #1: fp variable to be set
% #2: list of goals (separated with +)
% #3: function to be applied to goal names
\cs_new_protected:Npn \__xsim_exercise_goals_sum_aux:Nnn #1#2#3
  {
    \seq_set_split:Nnn \l__xsim_tmpa_seq {+} {#2}
    \seq_set_map:NNn \l__xsim_tmpb_seq \l__xsim_tmpa_seq {#3}
    \fp_set:Nn #1 { \seq_use:Nn \l__xsim_tmpb_seq {+} }
  }

% #1: fp variable
% #2: exercise type
% #3: list of goals (separated with +)
\cs_new_protected:Npn \__xsim_exercise_goals_sum:Nnn #1#2#3
  {
    \__xsim_exercise_goals_sum_aux:Nnn #1 {#3}
      { \exp_not:c {g__xsim_total_#2_goal_ \tl_trim_spaces:n {##1} _fp} }
  }

% #1: fp variable
% #2: list of goals (separated with +)
\cs_new_protected:Npn \__xsim_exercise_goals_sum:Nn #1#2
  {
    \__xsim_exercise_goals_sum_aux:Nnn #1 {#2}
      { \exp_not:c {g__xsim_total_goal_ \tl_trim_spaces:n {##1} _fp} }
  }

% #1: exercise type
% #2: list of goals (separated with +)
% #3: sum == 1
% #4: sum != 1
\cs_new_protected:Npn \xsim_print_goals_sum:nnnn #1#2#3#4
  {
    \__xsim_exercise_goals_sum:Nnn \l__xsim_tmpa_fp {#1} {#2}
    \xsim_print_goal:nnn { \l__xsim_tmpa_fp } {#3} {#4}
  }

% #1: list of goals (separated with +)
% #2: sum == 1
% #3: sum != 1
\cs_new_protected:Npn \xsim_print_total_goals_sum:nnn #1#2#3
  {
    \__xsim_exercise_goals_sum:Nn \l__xsim_tmpa_fp {#1}
    \xsim_print_goal:nnn { \l__xsim_tmpa_fp } {#2} {#3}
  }

% ----------------------------------------------------------------------------
% #1: exercise type
% #2: goal
% #3: relation and value
\prg_new_conditional:Npnn \xsim_if_goal_sum:nnn #1#2#3 {p,T,F,TF}
  {
    \fp_compare:nTF
      { ( \use:c {g__xsim_total_#1_goal_#2_fp} ) #3 }
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: exercise type
% #2: list of goals (separated with +)
% #3: relation and value
\prg_new_protected_conditional:Npnn \xsim_if_goals_sum:nnn #1#2#3 {T,F,TF}
  {
    \__xsim_exercise_goals_sum:Nnn \l__xsim_tmpa_fp {#1} {#2}
    \fp_compare:nTF
      { ( \l__xsim_tmpa_fp ) #3 }
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: goal
% #2: relation and value
\prg_new_conditional:Npnn \xsim_if_total_goal_sum:nn #1#2 {p,T,F,TF}
  {
    \fp_compare:nTF
      { ( \use:c {g__xsim_total_goal_#1_fp} ) #2 }
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: list of goals (separated with +)
% #2: relation and value
\prg_new_protected_conditional:Npnn \xsim_if_total_goals_sum:nn #1#2 {T,F,TF}
  {
    \__xsim_exercise_goals_sum:Nn \l__xsim_tmpa_fp {#1}
    \fp_compare:nTF
      { ( \l__xsim_tmpa_fp ) #2 }
      { \prg_return_true: }
      { \prg_return_false: }
  }

% ----------------------------------------------------------------------------
% #1: exercise type
% #2: goal
% #3: value
\xsim_new_aux_property:cpn {goal} #1#2#3
  { \fp_gset:cn {g__xsim_total_#1_goal_#2_fp} {#3} }

% #1: goal
% #2: value
\xsim_new_aux_property:cpn {totalgoal} #1#2
  { \fp_gset:cn {g__xsim_total_goal_#1_fp} {#2} }

\xsim_at_begin_document:n
  {
    \seq_map_inline:Nn \l__xsim_goals_seq
      {
        \fp_gset_eq:cc
          {g__xsim_total_goal_recorded_#1_fp}
          {g__xsim_total_goal_#1_fp}
      }
  }
  
\xsim_at_end_document:n
  {
    \seq_map_inline:Nn \l__xsim_goals_seq
      {
        \xsim_foreach_exercise_type:n
          {
            \xsim_add_property_to_aux:nn {goal}
              { {##1} {#1} { \fp_to_decimal:c {g__xsim_##1_goal_#1_fp} } }
          }
        \tl_set:Nx \l__xsim_tmpa_tl
          { \fp_to_decimal:c {g__xsim_total_goal_#1_fp} }
        \tl_set:Nx \l__xsim_tmpb_tl
          { \fp_to_decimal:c {g__xsim_total_goal_recorded_#1_fp} }
        \tl_if_eq:NNF \l__xsim_tmpa_tl \l__xsim_tmpb_tl { \xsim_rerun: }
        \xsim_add_property_to_aux:nn {totalgoal}
          { {#1} { \fp_to_decimal:c {g__xsim_goal_#1_fp} } }
      }
  }

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

\keys_define:nn {xsim}
  {
    goal-print .code:n =
      \cs_set_protected:Npn \__xsim_print_goal:n ##1 {#1} ,
    grading-table/template .tl_set:N  = \l_xism_grading_table_template_tl ,
    grading-table/template .initial:n = default ,
    grading-table/type     .tl_set:N  = \l_xism_grading_table_exercise_type_tl ,
    grading-table/type     .initial:n =
  }

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

2019/01/30 - bug fix (cf. https://texwelt.de/wissen/fragen/24101/)
2020/02/20 - add temporary variables for the whole subpoint thing
             (cf. issue #2)
