%% *********************************************************************
%%          Copyright 2024 by M.Y. XIA <xiamyphys@gmail.com>           *
%%                                                                     *
%%    This work may be distributed and/or modified under the           *
%%    conditions of the LaTeX Project Public License                   *
%%                                                                     *
%%               http://www.latex-project.org/lppl.txt                 *
%%                                                                     *
%%    either version 1.3c of this license or any later version.        *
%%                                                                     *
%%    This work has the LPPL maintenance status `maintained'.          *
%%                                                                     *
%%    The Current Maintainers of this work are M.Y. XIA & L.J. Guo     *
%%                                                                     *
%%    This work consists of the files litetable.cls,                   *
%%                               and README.md.                        *
%%    available at https://github.com/xiamyphys/litetable              *
%% *********************************************************************
\RequirePackage{xparse}% For TeX Live 2019 - 2020 Compatibility
\ProvidesExplClass {litetable} {2024/10/13} {3.1D} {Course Schedule}

\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
\ProcessOptions\relax
\LoadClass{article}

\pagestyle{empty}
\RequirePackage{tikz}

% Module for texlive 2021 and later (by @ljguo)
\cs_if_exist:NF \clist_put_right:Ne 
  { \cs_generate_variant:Nn \clist_put_right:Nn { Ne } }
\cs_if_exist:NF \clist_set:Ne 
  { \cs_generate_variant:Nn \clist_set:Nn { Ne } }
\cs_if_exist:NT \clist_count:e 
  { \cs_generate_variant:Nn \clist_count:n { e } }
\cs_if_exist:NF \exp_args:NNNe 
  {
    \cs_new:Npn \exp_args:NNNe #1#2#3#4
      {
        \exp_after:wN #1
        \exp_after:wN #2
        \exp_after:wN #3
        \tex_expanded:D { {#4} }
      }
  }

% Module for left -> right data structure (by @ljguo)
\cs_new_protected_nopar:Npn \__litetable_get_left:nN #1#2
  {
    \group_begin:
    \seq_set_split:Nnn \l__litetable_tmpa_seq { -> } {#1}
    \exp_args:NNNe \group_end: \tl_set:Nn #2
      { \seq_item:Nn \l__litetable_tmpa_seq { 1 } }
  }
\cs_new_protected_nopar:Npn \__litetable_get_right:nN #1#2
  {
    \group_begin:
    \seq_set_split:Nnn \l__litetable_tmpa_seq { -> } {#1}
    \exp_args:NNNe \group_end: \tl_set:Nn #2 
      { \seq_item:Nn \l__litetable_tmpa_seq { 2 } }
  }
\cs_generate_variant:Nn \__litetable_get_left:nN { e }
\cs_generate_variant:Nn \__litetable_get_right:nN { e }

\int_new:N \l__time_num_int
\dim_new:N \l__time_vunit_dim
\cs_new_protected:Npn \litetable_msg_new:nn #1#2 
  { \msg_new:nnn { litetable } {#1} {#2} }
\cs_new_protected:Npn \litetable_msg_warning:n #1 
  { \msg_warning:nn { litetable } {#1} }
\litetable_msg_new:nn { timelist }
  { \exp_not:N \timelist~extra~time~group(s)~were~ignored }
\cs_new_protected_nopar:Npn \litetable_timelist:nn #1#2
  {
    \clist_set:Nn \l__litetable_timelist_clist {#2}
    \tl_if_empty:nTF {#1}
      {
        \int_set:Nn \l__time_num_int
          { \clist_count:N \l__litetable_timelist_clist }
      }
      {
        \int_set:Nn \l__time_num_int {#1}
        \int_compare:nNnTF {#1} <
          { \clist_count:N \l__litetable_timelist_clist }
          {
            \int_set:Nn \l__time_num_int
              { \clist_count:N \l__litetable_timelist_clist }
            \litetable_msg_warning:n { timelist }
          }
          { \int_set:Nn \l__time_num_int {#1} }
      }
    \dim_set:Nn \l__time_vunit_dim
      { \fp_eval:n { 1/( 2\l__time_num_int + 3.5 ) } \paperheight }
  }
\NewDocumentCommand \timelist { O{} m O{} } 
  { \litetable_timelist:nn { #1 #3 } {#2} }

\clist_new:N \l__week_ratio_clist
\clist_new:N \l__week_accum_clist
\int_new:N \l__week_num_int
\dim_new:N \l__week_hunit_dim
\cs_new_protected_nopar:Npn \litetable_weeklist:nn #1#2
  {
    \tl_set:Nn \l__default_weeks_tl {#1}
    \clist_set:Nn \l__litetable_weeklist_clist {#2}
    \int_step_inline:nn 
      { \clist_count:N \l__litetable_weeklist_clist }
      {
        \__litetable_get_right:eN
          {
            \clist_item:Nn \l__litetable_weeklist_clist {##1}
          } \l__litetable_tmpb_tl
        \clist_put_right:Ne \l__week_ratio_clist
          { \l__litetable_tmpb_tl }
      }
    \int_step_inline:nn 
      { \clist_count:N \l__litetable_weeklist_clist }
      {
        \clist_clear:N \l__week_accumtmp_clist
        \int_step_inline:nn {##1}
          {
            \clist_put_right:Ne \l__week_accumtmp_clist
              { \clist_item:Nn \l__week_ratio_clist {####1} }
          }
        \clist_put_right:Ne \l__week_accum_clist
          { \int_eval:n { \clist_use:Nn \l__week_accumtmp_clist { + } } }
      }
    \int_set:Nn \l__week_num_int
      {
        \clist_item:Nn \l__week_accum_clist
          { \clist_count:N \l__litetable_weeklist_clist }
      }
    \dim_set:Nn \l__week_hunit_dim
      { \fp_eval:n { 1/\l__week_num_int * 14/15 } \paperwidth }
  }
\NewDocumentCommand \weeklist { O{} m O{} } 
  { \litetable_weeklist:nn { #1 #3 } {#2} }

\NewDocumentCommand \more { m }
  { \tl_set:Nn \l__litetable_comment_tl {#1} }

\int_new:N \l__litetable_week_day_int
\int_set:Nn \l__litetable_week_day_int { 1 }
\NewDocumentCommand \newday { O{1} }
  { \int_add:Nn \l__litetable_week_day_int {#1} }

\keys_define:nn { litetable / frame }
  {
    sem.tl_set:N    = \l__bg_sem_tl,
    color.tl_set:N  = \l__bg_color_tl,
    color.initial:n = gray
  }
\cs_new_protected_nopar:Npn \litetable_maketable:nn #1
  {
    \fill [ \l__bg_color_tl!5 ]
          (current~page.north~west) rectangle +
          (\paperwidth, -1.5\l__time_vunit_dim)
     node [ midway, black, font = \huge\bfseries ] {#1};
    \tl_if_empty:NF { \l__bg_sem_tl }
      {
        \node [ shift = {(-.02\paperwidth, -.75\l__time_vunit_dim)},
                left, rectangle, fill = DarkBlue!10, text = DarkBlue!60,
                inner~sep = 2ex, rounded~corners = 8pt, font = \large
              ] at (current~page.north~east)
          { \ensuremath\rightleftharpoons\ \l__bg_sem_tl };
      }
    % Darker Blocks
    \int_step_inline:nnnn { 0 } { 2 } { \l__time_num_int }
      {
        \filldraw
          [ fill = \l__bg_color_tl!5, draw = gray, 
            thick, densely~dashed, line~cap = round
          ]
          ([shift = {(-.4pt, \fp_eval:n { - 2 * ##1 - 2.5 } \l__time_vunit_dim)}]
            current~page.north~west
          ) rectangle + (\paperwidth + .8pt, -2\l__time_vunit_dim);
      }
    % Classes numbering
    \clist_if_empty:NTF { \l__litetable_timelist_clist }
      {
        \int_step_inline:nn { \l__time_num_int }
          {
            \node
              [ shift =
                  {(
                    \paperwidth/30,
                    \fp_eval:n { - 2 * ##1 - 1.5 } \l__time_vunit_dim
                  )}, darkgray!80, font = \large\ttfamily\bfseries
              ] at (current~page.north~west) {##1};
          }
      }
      {
        \int_step_inline:nn { \l__time_num_int }
          {
            \node
              [ shift =
                  {(
                    \paperwidth/30,
                    \fp_eval:n { - 2 * ##1 - 1 } \l__time_vunit_dim
                  )}, darkgray!80, font = \large\ttfamily\bfseries
              ] at (current~page.north~west) {##1};
          }
        % Classes time
        \int_step_inline:nn 
          { \clist_count:N \l__litetable_timelist_clist }
          {
            \__litetable_get_left:eN
              {
                \clist_item:Nn \l__litetable_timelist_clist {##1}
              } \l__litetable_tmpa_tl
            \__litetable_get_right:eN
              {
                \clist_item:Nn \l__litetable_timelist_clist {##1}
              } \l__litetable_tmpb_tl
            \node 
              [ shift =
                  {(
                    \paperwidth/30,
                    \fp_eval:n { - 1.9 - 2 * ##1 } \l__time_vunit_dim
                  )}, gray, font = \ttfamily, align = center
              ] at (current~page.north~west)
              { \l__litetable_tmpa_tl\\\l__litetable_tmpb_tl };
          }
      }
    % Weekdays
    \int_step_inline:nn
      { \clist_count:N \l__litetable_weeklist_clist }
      {
        \__litetable_get_left:eN
          {
            \clist_item:Nn \l__litetable_weeklist_clist {##1}
          } \l__litetable_tmpa_tl
          \node 
            [ shift =
                {(\fp_eval:n
                    {(
                      \clist_item:Nn \l__week_accum_clist {##1} -
                      \clist_item:Nn \l__week_ratio_clist {##1}/2
                    )/\l__week_num_int * 14/15 + 1/15
                    } \paperwidth, -2\l__time_vunit_dim
                )},
              font = \large\bfseries
            ] at (current~page.north~west) { \l__litetable_tmpa_tl };
      }
    % Comment
    \tl_if_empty:NF \l__litetable_comment_tl
      {
        \node [ yshift = .5\l__time_vunit_dim, left = 1ex,
                darkgray, font = \small\bfseries
              ] at (current~page.south~east)
          { \l__litetable_comment_tl };
      }
    \tl_gclear:N \l__litetable_comment_tl
  }
\NewDocumentCommand \maketable { O{} m O{} } 
  {
    \group_begin:
    \keys_set:nn { litetable / frame } { #1 #3 }
    \litetable_maketable:nn {#2}
    \group_end:
  }

\keys_define:nn { litetable / course }
  {
    color.tl_set:N    = \l__course_color_tl,
    color.initial:n   = teal,
    subject.tl_set:N  = \l__course_subject_tl,
    teacher.tl_set:N  = \l__course_teacher_tl,
    location.tl_set:N = \l__course_location_tl,
    weeks.tl_set:N    = \l__default_weeks_tl,
    weeks.initial:n   = \l__default_weeks_tl,
  }
\dim_new:N \l__course_infoshift_dim
\cs_new_protected_nopar:Npn \__litetable_course_box_aux:nn #1#2
  {
    % Course box fg
    \begin{scope}
    \clip [ preaction = { draw, ultra~thick, \l__course_color_tl!60 },
            preaction = { fill, \l__course_color_tl!10 },
            rounded~corners = 8pt
          ] ([shift = 
              {(
                \fp_eval:n
                  {
                    \clist_item:Nn \l__week_accum_clist
                      { \l__litetable_week_day_int } -
                    \clist_item:Nn \l__week_ratio_clist
                      { \l__litetable_week_day_int }
                  } \l__week_hunit_dim + \paperwidth/15 + 1.2pt,
                \fp_eval:n { - .5 - 2 * #1 } \l__time_vunit_dim - 1.2pt
              )}
             ]current~page.north~west) rectangle +
            (
              \clist_item:Nn \l__week_ratio_clist
                { \l__litetable_week_day_int } \l__week_hunit_dim - 2.4pt,
              \fp_eval:n { 2 * ( #1 - #2 - 1 ) } \l__time_vunit_dim + 2.4pt
            );
    % Course box bg
    \fill [ \l__course_color_tl!60 ]
          ([shift =
            {(
              \fp_eval:n
                {
                  \clist_item:Nn \l__week_accum_clist
                    { \l__litetable_week_day_int } -
                  \clist_item:Nn \l__week_ratio_clist
                    { \l__litetable_week_day_int }
                } \l__week_hunit_dim + \paperwidth/15,
              \fp_eval:n { - .5 - 2 * #1 } \l__time_vunit_dim
            )}
           ]current~page.north~west) rectangle +
          (
            \clist_item:Nn \l__week_ratio_clist
              { \l__litetable_week_day_int } \l__week_hunit_dim,
            -\l__time_vunit_dim/2
          );
    \end{scope}
    % Course info
    \tl_if_eq:NNTF {#1} {#2}% Single-unit height course box
      {
        \bool_if:nTF
          {
            \tl_if_empty_p:N \l__course_location_tl &&
            \tl_if_empty_p:N \l__course_teacher_tl
          }
          { \tl_set:Nn \l_shortcourse_anchor_tl { } }
          { \tl_set:Nn \l_shortcourse_anchor_tl { above } }
        \cs_if_exist:NT { \l__course_subject_tl }
          {
            \node
              [ shift =
                {(
                  \fp_eval:n
                    {
                      \clist_item:Nn \l__week_accum_clist
                        { \l__litetable_week_day_int } -
                      \clist_item:Nn \l__week_ratio_clist
                        { \l__litetable_week_day_int }/2
                    } \l__week_hunit_dim + \paperwidth/15,
                    \fp_eval:n { - 1.75 - #1 - #2 } \l__time_vunit_dim
                )}, align = center, font = \large\bfseries,
                \l_shortcourse_anchor_tl, \l__course_color_tl!60
              ] at (current~page.north~west) { \l__course_subject_tl };
          }
        \bool_if:nTF
          {
            !\tl_if_empty_p:N \l__course_location_tl &&
            !\tl_if_empty_p:N \l__course_teacher_tl
          }
          { \tl_set:Nn \l_shortcourse_sep_tl { ,~ } }
          { \tl_set:Nn \l_shortcourse_sep_tl { } }
        \node
          [ shift =
            {(
              \fp_eval:n
                {
                  \clist_item:Nn \l__week_accum_clist
                    { \l__litetable_week_day_int } -
                  \clist_item:Nn \l__week_ratio_clist
                    { \l__litetable_week_day_int }/2
                } \l__week_hunit_dim + \paperwidth/15,
              \fp_eval:n { - 1.75 - #1 - #2 } \l__time_vunit_dim
            )}, below, \l__course_color_tl!60, align = center
          ] at (current~page.north~west)
          {
            \tl_if_exist:NT { \l__course_location_tl }
                            { \l__course_location_tl }
            \l_shortcourse_sep_tl
            \tl_if_exist:NT { \l__course_teacher_tl }
                            { \l__course_teacher_tl }
          };
      }
      {
        \bool_if:nTF% Multiply-unit height course box
          {
            \tl_if_empty_p:N \l__course_location_tl &&
            \tl_if_empty_p:N \l__course_teacher_tl
          }
          {
            \tl_set:Nn \l_course_anchor_tl { }
            \dim_set:Nn \l__course_infoshift_dim { 0pt }
          }
          {
            \tl_set:Nn \l_course_anchor_tl { above }
            \dim_set:Nn \l__course_infoshift_dim { \l__time_vunit_dim/8 }
          }
        \cs_if_exist:NT { \l__course_subject_tl }
          {
            \node 
              [ shift =
                {(
                  \fp_eval:n
                    {
                      \clist_item:Nn \l__week_accum_clist
                        { \l__litetable_week_day_int } -
                      \clist_item:Nn \l__week_ratio_clist
                        { \l__litetable_week_day_int }/2
                    } \l__week_hunit_dim + \paperwidth/15,
                  \fp_eval:n { - 1.5 - #1 - #2 } \l__time_vunit_dim +
                  \l__course_infoshift_dim
                )}, font = \large\bfseries, \l_course_anchor_tl,
                \l__course_color_tl!60, align = center
              ] at (current~page.north~west) { \l__course_subject_tl };
          }
        \bool_if:nTF
          {
            !\tl_if_empty_p:N \l__course_location_tl && 
            !\tl_if_empty_p:N \l__course_teacher_tl
          }
          { \tl_set:Nn \l_courseinfo_sep_tl { \\ } }
          { \tl_set:Nn \l_courseinfo_sep_tl { } }
        \node
          [ shift =
            {(
              \fp_eval:n
                {
                  \clist_item:Nn \l__week_accum_clist
                    { \l__litetable_week_day_int } -
                  \clist_item:Nn \l__week_ratio_clist
                    { \l__litetable_week_day_int }/2
                } \l__week_hunit_dim + \paperwidth/15,
                \fp_eval:n { - 1.625 - #1 - #2 } \l__time_vunit_dim
            )}, below, \l__course_color_tl!60, align = center
          ] at (current~page.north~west)
          {
            \tl_if_exist:NT { \l__course_location_tl }
                            { \l__course_location_tl }
            \l_courseinfo_sep_tl
            \tl_if_exist:NT { \l__course_teacher_tl }
                            { \l__course_teacher_tl }
          };
        \node
          [ shift =
            {(\clist_item:Nn \l__week_accum_clist
                { \l__litetable_week_day_int }
              \l__week_hunit_dim + \paperwidth/15,
              \fp_eval:n { - 2.5 - 2 * #2 } \l__time_vunit_dim
            )}, above~left, \l__course_color_tl!60, outer~sep = .5ex
          ] at (current~page.north~west) { \l__default_weeks_tl };
      }
  }
\litetable_msg_new:nn { course }
  { \exp_not:N \course~box(s)~exceed~workdays~were~ignored }
\NewDocumentCommand \course { O{} m O{} m O{} }
  {
    \group_begin:
    \int_compare:nNnTF { \l__litetable_week_day_int - 1 } <
      { \clist_count:N \l__litetable_weeklist_clist }
      {
        \keys_set:nn { litetable / course } { #1 #3 #5 }
        \__litetable_course_box_aux:nn {#2} {#4}
      }
      { \litetable_msg_warning:n { course } }
    \group_end:
  }

\endinput

% End of file litetable.cls
