% ----------------------------------------------------------------------------
% the XSIM package - properties 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{properties}{properties of exercises}

\xsim_load_modules:n {base,auxfile}

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

\msg_new:nnn {xsim} {property-unique}
  {
    You~ tried~ to~ set~ the~ property~ `#1'~ \msg_line_context: . \\
    This~ property~ has~ to~ have~ a~ unique~ value~ and~ thus~ \\
    cannot~ be~ set~ to~ `#2'~ since~ this~ value~ is~ already~ talen.
  }

\msg_new:nnn {xsim} {no-boolean-property}
  {
    You~ tried~ to~ check~ the~ boolean~ value~ of~ property~ `#1'~
    \msg_line_context: . \\
    However,~ property~ `#1'~ is~ not~ a~ boolean~ property!
  }
  
\seq_new:N \l__xsim_properties_seq
\seq_new:N \l__xsim_boolean_properties_seq
\seq_new:N \l__xsim_unique_properties_seq
\seq_new:N \l__xsim_noupdate_properties_seq
\seq_new:N \l__xsim_given_properties_seq
\prop_new:N \g__xsim_properties_prop

% new property:
% #1: boolean if unique
% #2: boolean if boolean
% #3: boolean if noupdate
% #4: property name
\cs_new_protected:Npn \xsim_declare_property:nnnn #1#2#3#4
  {
    \xsim_if_property_exist:nF {#4}
      {
        \seq_put_right:Nn \l__xsim_properties_seq {#4}
        \bool_if:nTF {#3}
          {
            \seq_if_in:NnF \l__xsim_noupdate_properties_seq {#4}
              { \seq_put_right:Nn \l__xsim_noupdate_properties_seq {#4} }
          }
          { \xsim_new_list:n {#4} }
        \bool_if:nTF {#2}
          {
            \xsim_verbose:n
              { Declaring~ new~ boolean~ exercise~ property~ `#4'. }
            \seq_if_in:NnF \l__xsim_boolean_properties_seq {#4}
              { \seq_put_right:Nn \l__xsim_boolean_properties_seq {#4} }
          }
          {
            \bool_if:nTF {#1}
              {
                \xsim_verbose:n
                  { Declaring~ new~ unique~ exercise~ property~ `#4'. }
                \seq_if_in:NnF \l__xsim_unique_properties_seq {#4}
                  { \seq_put_right:Nn \l__xsim_unique_properties_seq {#4} }
                \seq_new:c {l__xsim_unique_property_#4_values_seq}
                \prop_new:c {g__xsim_property_#4_type_prop}
                \prop_new:c {g__xsim_property_#4_id_prop}
              }
              { \xsim_verbose:n { Declaring~ new~ exercise~ property~ `#4'. } }
          }
        \bool_if:nF {#3}
          {
            \xsim_at_end_document:n { \xsim_update_list:n {#4} }
            \xsim_at_begin_document:n { \__xsim_set_property_from_list:n {#4} }
          }
      }
  }

% check if property exists:
\prg_new_conditional:Npnn \xsim_if_property_exist:n #1 {p,T,F,TF}
  {
    \seq_if_in:NnTF \l__xsim_properties_seq {#1}
      { \prg_return_true: }
      { \prg_return_false: }
  }
\cs_generate_variant:Nn \xsim_if_property_exist:nTF {x}

\prg_new_conditional:Npnn \xsim_if_property_noupdate:n #1 {p,T,F,TF}
  {
    \seq_if_in:NnTF \l__xsim_noupdate_properties_seq {#1}
      { \prg_return_true: }
      { \prg_return_false: }
  }
\cs_generate_variant:Nn \xsim_if_property_noupdate:nF {V}

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

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

% true if property #1 has been set in the /last/ call of
% \xsim_set_properties:nnn
\prg_new_conditional:Npnn \xsim_if_property_given:n #1 {p,T,F,TF}
  {
    \seq_if_in:NnTF \l__xsim_given_properties_seq {#1}
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: property
% #2: value
\prg_new_conditional:Npnn \xsim_if_property_unique_value:nn #1#2 {p,T,F,TF}
  {
    \xsim_if_property_unique:nTF {#1}
      {
        \seq_if_in:cnTF {l__xsim_unique_property_#1_values_seq} {#2}
          { \prg_return_true: }
          { \prg_return_false: }
      }
      { \prg_return_false: }
  }

% #1: type
% #2: id
% #3: property
% #4: value
\cs_new_protected:Npn \xsim_set_property:nnnn #1#2#3#4
  {
    \xsim_if_property_exist:nTF {#3}
      {
        \xsim_if_property_set:nnnTF {#1} {#2} {#3}
          {
            \xsim_if_property_unique_value:nnTF {#3} {#4}
              { \msg_warning:nnnn {xsim} {property-unique} {#3} {#4} }
              {
                \xsim_if_property_boolean:nTF {#3}
                  { \__xsim_set_boolean_property:nnnn {#1} {#2} {#3} {#4} }
                  {
                    \xsim_if_property_unique:nTF {#3}
                      { \__xsim_set_unique_property:nnnn {#1} {#2} {#3} {#4} }
                      { \__xsim_set_property:nnnn {#1} {#2} {#3} {#4} }
                  }
              }
          }
          {
            \xsim_if_property_boolean:nTF {#3}
              { \__xsim_set_boolean_property:nnnn {#1} {#2} {#3} {#4} }
              {
                \xsim_if_property_unique:nTF {#3}
                  { \__xsim_set_unique_property:nnnn {#1} {#2} {#3} {#4} }
                  { \__xsim_set_property:nnnn {#1} {#2} {#3} {#4} }
              }
          }
      }
      { \msg_error:nnn {xsim} {unknown-property} {#3} }  
  }
\cs_generate_variant:Nn \xsim_set_property:nnnn {nnxx,nnnV,nV,nVnx,nnnx,xx,xxnx,nxnx}

% #1: type
% #2: id
% #3: property
% #4: value
\cs_new_protected:Npn \__xsim_set_boolean_property:nnnn #1#2#3#4
  {
    \str_case_e:nnTF { \str_lowercase:n {#4} }
      {
        {} {}
        {true} {}
      }
      { \__xsim_set_property:nnnn {#1} {#2} {#3} {true} }
      { \__xsim_set_property:nnnn {#1} {#2} {#3} {false} }
  }

% #1: type
% #2: id
% #3: property
% #4: value
\cs_new_protected:Npn \__xsim_set_unique_property:nnnn #1#2#3#4
  {
    \__xsim_set_property:nnnn {#1} {#2} {#3} {#4}
    \__xsim_set_property_reverse:nnnn {#1} {#2} {#3} {#4}
  }

% #1: type
% #2: id
% #3: property
\cs_new_protected:Npn \xsim_unset_property:nnn #1#2#3
  {
    \xsim_if_property_exist:nTF {#3}
      { \__xsim_unset_property:nnn {#1} {#2} {#3} }
      { \msg_error:nnn {xsim} {unknown-property} {#3} }  
  }
\cs_generate_variant:Nn \xsim_unset_property:nnn {nx,nnx,nV}

% #1: type
% #2: id
% #3: property
% #4: value
\cs_new_protected:Npn \__xsim_set_property:nnnn #1#2#3#4
  {
    \xsim_verbose:n
      {
        Setting~ property~ `#3'~ of~ exercise~ type~ `#1'~ id~ `#2'~ to~
        value~ `#4'.
      }
   \__xsim_remove_property_from_list:nnn {#1} {#2} {#3}
    % now we can safely add the property to the list:
    \xsim_add_to_list:nn {#3} {#1-#2=={#4}}
    \prop_gput:Nnn \g__xsim_properties_prop {#2!#1!#3} {#4}
  }
\cs_generate_variant:Nn \__xsim_set_property:nnnn {VVnV}

% #1: type
% #2: id
% #3: property
% #4: value
\cs_new_protected:Npn \__xsim_set_property_reverse:nnnn #1#2#3#4
  {
    \prop_gput:cnn {g__xsim_property_#3_type_prop} {#4} {#1}
    \prop_gput:cnn {g__xsim_property_#3_id_prop} {#4} {#2}
  }

% #1: type
% #2: id
% #3: property
\cs_new_protected:Npn \__xsim_unset_property:nnn #1#2#3
  {
    \xsim_verbose:n
      { Un-setting~ property~ `#3'~ of~ exercise~ type~ `#1'~ id~ `#2'. }
    \__xsim_remove_property_from_list:nnn {#1} {#2} {#3}
    \prop_gremove:Nn \g__xsim_properties_prop {#2!#1!#3}
  }

\tl_set:Nx \l__xsim_tmpa_tl { \cs_to_str:N \{ }
\tl_set:Nx \l__xsim_tmpb_tl { \cs_to_str:N \} }
\use:x
  {
    \cs_new_protected:Npn
    \exp_not:N \__xsim_extract_property_list_entry:NNNwww
    ##1##2##3##4-##5== \l__xsim_tmpa_tl ##6 \l__xsim_tmpb_tl
    \exp_not:N \q_stop
  }
  {
    \str_set:Nn #1 {#4}
    \str_set:Nn #2 {#5}
    \str_set:Nn #3 {#6}
  }

\cs_new_protected:Npn \__xsim_remove_property_from_list:nnn #1#2#3
  {
    % make sure we change the entry if it has been recorded previously:
    \xsim_foreach_list_entry:nn {#3}
      {
        \__xsim_extract_property_list_entry:NNNwww
          \l__xsim_tmpa_str \l__xsim_tmpb_str \l__xsim_tmpc_str ##1 \q_stop
        \str_if_eq:xnT { \l__xsim_tmpa_str - \l__xsim_tmpb_str } {#1-#2}
          {
            \xsim_remove_from_list:nx {#3}
              { #1-#2 == {\l__xsim_tmpc_str} }
          }
      }
    % make sure we change the entry if it has been set during the current run:
    \xsim_foreach_new_list_entry:nn {#3}
      {
        \__xsim_extract_property_list_entry:NNNwww
          \l__xsim_tmpa_str \l__xsim_tmpb_str \l__xsim_tmpc_str ##1 \q_stop
        \str_if_eq:xnT { \l__xsim_tmpa_str - \l__xsim_tmpb_str } {#1-#2}
          {
            \xsim_remove_from_list:nx {#3}
              { #1-#2 == {\l__xsim_tmpc_str} }
          }
      }
  }

\cs_new_protected:Npn \__xsim_set_property_from_list:n #1
  {
    \xsim_foreach_list_entry:nn {#1}
      {
        \__xsim_extract_property_list_entry:NNNwww
          \l__xsim_tmpa_str \l__xsim_tmpb_str \l__xsim_tmpc_str ##1 \q_stop
        \tl_set_rescan:Nnx \l__xsim_tmpc_tl {} { \l__xsim_tmpc_str }
        \__xsim_set_property:VVnV
          \l__xsim_tmpa_str
          \l__xsim_tmpb_str
          {#1}
          \l__xsim_tmpc_tl
      }
  }

% set properties:
% #1: type
% #2: id
% #3: csv list of properties
\cs_new_protected:Npn \xsim_set_properties:nnn #1#2#3
  {
    \seq_clear:N \l__xsim_given_properties_seq
    \clist_map_inline:nn {#3}
      {
        \seq_set_split:Nnx \l__xsim_tmpa_seq {=} { \tl_trim_spaces:n {##1} }
        \xsim_set_property:nnxx
          {#1}
          {#2}
          { \seq_item:Nn \l__xsim_tmpa_seq {1} }
          { \seq_item:Nn \l__xsim_tmpa_seq {2} }
        \seq_put_right:Nx \l__xsim_given_properties_seq
          { \seq_item:Nn \l__xsim_tmpa_seq {1} }
      }
    \xsim_property_set_aliases:nn {#1} {#2}
  }
\cs_generate_variant:Nn \xsim_set_properties:nnn {nnx,nxx,nV,nVx}

% check if property is set:
% #1: type
% #2: id
% #3: property
\prg_new_conditional:Npnn \xsim_if_property_set:nnn #1#2#3 {T,F,TF}
  {
    \prop_if_in:NnTF \g__xsim_properties_prop {#2!#1!#3}
      { \prg_return_true: }
      { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \xsim_if_property_set:nnn
  {nx,nnx,xx,nV}
  {T,F,TF}

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

\prop_new:N \g__xsim_property_aliases_prop
\seq_new:N \g__xsim_property_aliases_seq

% #1: property
\prg_new_conditional:Npnn \xsim_if_property_alias:n #1 {p,T,F,TF}
  {
    \prop_if_in:NnTF \g__xsim_property_aliases_prop {#1}
      { \prg_return_true: }
      { \prg_return_false: }
  }

% #1: property
\cs_new:Npn \xsim_property_alias:n #1
  { \prop_item:Nn \g__xsim_property_aliases_prop {#1} }

% #1: property
% #2: alias of
\cs_new_protected:Npn \xsim_make_property_alias:nn #1#2
  {
    \xsim_if_property_alias:nF {#1}
      {
        \xsim_verbose:n
          { Making~ property~ `#1'~ an~ alias~ of~ property~ `#2'. }
        \prop_new:c {g__xsim_property_#1_alias_prop}
        \prop_gput:Nnn \g__xsim_property_aliases_prop {#1} {#2}
        \seq_gput_right:Nn \g__xsim_property_aliases_seq {#1}
      }
  }

% #1: type
% #2: id
% #3: property
\cs_new_protected:Npn \xsim_property_set_alias:nnn #1#2#3
  {
    \xsim_if_property_set:nnnF {#1} {#2} {#3}
      {
        \xsim_if_property_set:nnxT {#1} {#2}
          { \xsim_property_alias:n {#3} }
          { 
            \xsim_set_property:nnnx {#1} {#2} {#3}
              {
                \xsim_get_property:nnf {#1} {#2}
                  { \xsim_property_alias:n {#3} }
              }
          }
      }
    \xsim_if_property_set:nnxT {#1} {#2}
      { \xsim_property_alias:n {#3} }
      {
        \prop_gput:cxx {g__xsim_property_#3_alias_prop}
          { \xsim_get_property:nnn {#1} {#2} {#3} }
          {
            \xsim_get_property:nnf {#1} {#2}
              { \xsim_property_alias:n {#3} }
          }
      }
  }
\cs_generate_variant:Nn \xsim_property_set_alias:nnn {nV}

% #1: type
% #2: id
\cs_new_protected:Npn \xsim_property_set_aliases:nn #1#2
  {
    \seq_map_inline:Nn \g__xsim_property_aliases_seq
      { \xsim_property_set_alias:nnn {#1} {#2} {##1} }
  }

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

% retrieve properties:
% #1: type
% #2: id
% #3: property
\cs_new:Npn \xsim_get_property:nnn #1#2#3
  { \prop_item:Nn \g__xsim_properties_prop { #2 ! #1 ! #3 } }
\cs_generate_variant:Nn \xsim_get_property:nnn {nx,ne,xx,oo,no,nnf,nf,oof,nV}

% #1: property
% #2: value
\cs_new:Npn \xsim_get_id_for_property:nn #1#2
  { \prop_item:cn {g__xsim_property_#1_id_prop} {#2} }

% #1: property
% #2: value
\cs_new:Npn \xsim_get_type_for_property:nn #1#2
  { \prop_item:cn {g__xsim_property_#1_type_prop} {#2} }
\cs_generate_variant:Nn \xsim_get_type_for_property:nn {nV}

% #1: id or ID
\cs_new:Npn \xsim_normalize_id:n #1
  {
    \tl_if_blank:eTF
      { \xsim_get_id_for_property:nn {ID} {#1} }
      {#1}
      { \xsim_get_id_for_property:nn {ID} {#1} }
  }
\cs_generate_variant:Nn \xsim_normalize_id:n {e}

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

% #1: type
% #2: id
% #3: property
\cs_new_protected:Npn \xsim_get_property_if_set:nnnTF #1#2#3#4#5
  {
    \group_begin:
      \xsim_if_property_exist:nTF {#3}
        {
          \xsim_if_property_set:nnnTF {#1} {#2} {#3}
            {    
              \tl_set:Nx \PropertyValue
                { \xsim_get_property:nnn {#1} {#2} {#3} }
              \cs_set:Npn \__xsim_get_property_tmp:n ##1 {#4}
              \__xsim_get_property_tmp:V \PropertyValue
            }
            {#5}
        }
        { \msg_error:nnn {xsim} {unknown-property} {#3} }
    \group_end:
  }
\cs_new_protected:Npn \xsim_get_property_if_set:nnnT #1#2#3#4
  { \xsim_get_property_if_set:nnnTF {#1} {#2} {#3} {#4} {} }
\cs_new_protected:Npn \xsim_get_property_if_set:nnnF #1#2#3#4
  { \xsim_get_property_if_set:nnnTF {#1} {#2} {#3} {} {#4} }
\cs_generate_variant:Nn \xsim_get_property_if_set:nnnTF {oo}

% #1: type
% #2: id
% #3: property
\prg_new_conditional:Npnn \xsim_if_boolean_property:nnn #1#2#3 {T,F,TF}
  {
    \xsim_if_property_exist:nTF {#3}
      {
        \xsim_if_property_boolean:nTF {#3}
          {
            \xsim_if_property_set:nnnTF {#1} {#2} {#3}
              {
                \bool_if:cTF
                  {c_ \xsim_get_property:nnn {#1} {#2} {#3} _bool}
                  { \prg_return_true: }
                  { \prg_return_false: }
              }
              { \prg_return_false: }
          }
          { \msg_error:nnn {xsim} {no-boolean-property} {#3} }
      }
      { \msg_error:nnn {xsim} {unknown-property} {#3} }
  }
\cs_generate_variant:Nn \xsim_if_boolean_property:nnnTF {oo}
\cs_generate_variant:Nn \xsim_if_boolean_property:nnnF  {nV}

\cs_new_protected:Npn \xsim_save_property:nnnN #1#2#3#4
  { \prop_get:NnN \g__xsim_properties_prop {#2!#1!#3} #4 }
\cs_generate_variant:Nn \xsim_save_property:nnnN {nx,xx}

\cs_new_protected:Npn \xsim_gsave_property:nnnN #1#2#3#4
  {
    \xsim_save_property:nnnN {#1} {#2} {#3} #4
    \tl_gset:NV #4 #4
  }
\cs_generate_variant:Nn \xsim_gsave_property:nnnN {nx,xx,nV}

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