# -*- mode: Perl -*-
# /=====================================================================\ #
# |  enumitem                                                           | #
# | Implementation for LaTeXML                                          | #
# |=====================================================================| #
# | Part of LaTeXML:                                                    | #
# |  Public domain software, produced as part of work done by the       | #
# |  United States Government & not subject to copyright in the US.     | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov>                        #_#     | #
# | http://dlmf.nist.gov/LaTeXML/                              (o o)    | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# TODO  Deal with Scads of options!
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Package Options:
DeclareOption('shortlabels', sub { AssignValue('enumitem@shortlabels' => 1); return; });
DeclareOption('inline',      sub { AssignValue('enumitem@inline'      => 1); return; });
DeclareOption('loadonly',    sub { AssignValue('enumitem@loadonly'    => 1); return; });
# sizes requires a peculiar dimension parser...
# ignoredisplayed
# series=override
# ignoredisplayed, includedisplayed; affects redefinition of trivlist, which we haven't done.
ProcessOptions();

#======================================================================
# Labelling:
DefKeyVal('enumitem', 'label',  'UndigestedKey');
DefKeyVal('enumitem', 'label*', 'UndigestedKey');
DefKeyVal('enumitem', 'ref',    'UndigestedKey');
DefKeyVal('enumitem', 'font',   'UndigestedKey');
DefKeyVal('enumitem', 'format', 'UndigestedKey');

# Numbering
DefKeyVal('enumitem', 'start',   'Number');
DefKeyVal('enumitem', 'series',  'UndigestedKey');
DefKeyVal('enumitem', 'resume',  '', 'noseries');
DefKeyVal('enumitem', 'resume*', '', 'noseries');    # NOTE: not complete; we don't reuse options

# Description styles
#   style=(standard|unboxed|nextline|sameline|multiline)
DefKeyVal('enumitem', 'style', 'UndigestedKey');    # Not done

# Inline lists
DefKeyVal('enumitem', 'itemjoin',   'UndigestedKey');    # Not done
DefKeyVal('enumitem', 'itemjoin*',  'UndigestedKey');    # Not done
DefKeyVal('enumitem', 'afterlabel', 'UndigestedKey');    # Not done [only applies to inline!]
DefKeyVal('enumitem', 'mode',       'UndigestedKey');    # =(boxed|unboxed) ignored

# IGNORED: Alignment, Positioning, penalties
DefKeyVal('enumitem', 'align',        'UndigestedKey');   # =(left|right|parleft|SetLabelAlign-name)
DefKeyVal('enumitem', 'labelindent',  'Dimension');
DefKeyVal('enumitem', 'left',         'Dimension');
DefKeyVal('enumitem', 'leftmargin',   'UndigestedKey');
DefKeyVal('enumitem', 'itemindent',   'Dimension');
DefKeyVal('enumitem', 'labelsep',     'Dimension');
DefKeyVal('enumitem', 'labelwidth',   'Dimension');
DefKeyVal('enumitem', 'labelindent',  'Dimension');
DefKeyVal('enumitem', 'widest',       'UndigestedKey');
DefKeyVal('enumitem', 'beginpenalty', 'Number');
DefKeyVal('enumitem', 'midpenalty',   'Number');
DefKeyVal('enumitem', 'endpenalty',   'Number');
DefKeyVal('enumitem', 'noitemsep',    '', 'true');
DefKeyVal('enumitem', 'nolistsep',    '', 'true');

# Code
#   before=code, after=code
DefKeyVal('enumitem', 'before', 'UndigestedKey');    # TODO ?
DefKeyVal('enumitem', 'after',  'UndigestedKey');    # TODO ?

# \EnumitemId
# \SetEnumerateShortLabel{key}{replacement}

# Extends LaTeX's beginItemize, with modifications...
sub beginEnumItemize {
  my ($type, $counter, $keys) = @_;
  $counter = '@item' unless $counter;
  my $level      = (LookupValue($counter . 'level') || 0) + 1;
  my $hash       = merged_enumitem_keyvals($type, $level, $keys);
  my $postfix    = ToString(Tokens(roman($level)));
  my $usecounter = $counter . $postfix;

  # Deal with label formatting [Seems this could be cleaned up & standardized a bit more]
  my ($firstkey, $firstvalue) = $keys && $keys->getPairs;
  if ($firstkey && !$firstvalue    # 1st key has no value
    && LookupValue('enumitem@shortlabels')                         # and shortlabels enabled
    && !LookupDefinition(T_CS('\KV@enumitem@' . $firstkey))) {     # AND unknown!!!
    setEnumerationStyle(TokenizeInternal($firstkey), $level); }    # alas already turned to string
  if (my $label = $$hash{label} || $$hash{'label*'}) {
    my $llabel = replace_star($label, T_OTHER($usecounter));
    if ($$hash{'label*'} && ($level > 1)) {
      $llabel = Tokens(T_CS('\label' . $counter . ToString(Tokens(roman($level - 1)))), $llabel); }
    DefMacroI('\the' . $usecounter,   undef, $llabel);
    DefMacroI('\label' . $usecounter, undef, $llabel);
    DefMacroI('\fnum@' . $usecounter, undef,
      Tokens(T_BEGIN, T_CS('\makelabel'), T_BEGIN, T_CS('\label' . $usecounter), T_END, T_END)); }
  if (my $label = $$hash{ref}) {
    my $llabel = replace_star($label, T_OTHER($usecounter));
    # This is a Hotfix.
    # DG: what is the correct general implementation here?
    # if $llabel contains "\the$usecounter", expand it, to avoid the inf. loop.
    if (ToString($llabel) =~ "\Q\\the$usecounter\E") {
      $llabel = Expand($llabel); }
    DefMacroI('\the' . $usecounter, undef, $llabel); }
  if (my $font = $$hash{font} || $$hash{format}) {
    DefMacroI('\fnum@font@' . $usecounter, undef, $font); }
  return beginItemize($type, $counter, %$hash); }

sub replace_star {
  my ($tokens, $replacement) = @_;
  my @t = ();
  foreach my $t ($tokens->unlist) {
    push(@t, ($t->equals(T_OTHER('*')) ? $replacement : $t)); }
  return Tokens(@t); }

sub endEnumItemize {
  my ($whatsit) = @_;
  if (my $series = $whatsit->getProperty('series')) {
    if (my $ctr = $whatsit->getProperty('counter')) {
      AssignValue('enumitem_series_' . $series . '_last' => CounterValue($ctr), 'global'); } }
  return; }

DefMacro('\restartlist{}', sub {
    my ($gullet, $listname) = @_;
    $listname = ToString($listname);
    my $counter = ($listname eq 'enumerate' ? 'enum'
      : ($listname eq 'itemize' ? '@item'
        : ($listname eq 'description' ? '@desc'
          : $listname)));
    # Assme we reset all levels? Assume no more than 6 levels?
    for (my $i = 0 ; $i < 6 ; $i++) {
      my $r = ToString(Tokens(roman($i)));
      if (LookupDefinition('\c@' . $counter . $r)) {
        SetCounter($counter . $r, Number(0)); } }
    return; });

#======================================================================
if (!LookupValue('enumitem@loadonly')) {
  DefEnvironment('{itemize} OptionalKeyVals:enumitem',
    "<ltx:itemize xml:id='#id'>#body</ltx:itemize>",
    properties      => sub { beginEnumItemize('itemize', '@item', $_[1]); },
    beforeDigestEnd => sub { Digest('\par'); },
    afterDigestBody => sub { endEnumItemize($_[1]); },
    locked          => 1, mode => 'text');

  DefEnvironment('{enumerate} OptionalKeyVals:enumitem',
    "<ltx:enumerate  xml:id='#id'>#body</ltx:enumerate>",
    properties      => sub { beginEnumItemize('enumerate', 'enum', $_[1]); },
    beforeDigestEnd => sub { Digest('\par'); },
    afterDigestBody => sub { endEnumItemize($_[1]); },
    locked          => 1, mode => 'text');
  DefEnvironment('{description} OptionalKeyVals:enumitem',
    "<ltx:description  xml:id='#id'>#body</ltx:description>",
    beforeDigest    => sub { Let('\makelabel', '\descriptionlabel'); },
    properties      => sub { beginEnumItemize('description', '@desc', $_[1]); },
    beforeDigestEnd => sub { Digest('\par'); },
    afterDigestBody => sub { endEnumItemize($_[1]); },
    locked          => 1, mode => 'text');
}
if (LookupValue('enumitem@inline')) {
  DefEnvironment('{itemize*} OptionalKeyVals:enumitem',
    "<ltx:inline-itemize xml:id='#id'>#body</ltx:inline-itemize>",
    properties      => sub { beginEnumItemize('inline@itemize', '@item', $_[1]); },
    afterDigestBody => sub { endEnumItemize($_[1]); },);
  DefEnvironment('{enumerate*} OptionalKeyVals:enumitem',
    "<ltx:inline-enumerate xml:id='#id'>#body</ltx:inline-enumerate>",
    properties      => sub { beginEnumItemize('inline@enumerate', 'enum', $_[1]); },
    afterDigestBody => sub { endEnumItemize($_[1]); },);
  DefEnvironment('{description*} OptionalKeyVals:enumitem',
    "<ltx:inline-description xml:id='#id'>#body</ltx:inline-description>",
    properties      => sub { beginEnumItemize('inline@description', '@desc', $_[1]); },
    afterDigestBody => sub { endEnumItemize($_[1]); },);
}
DefPrimitive('\newlist{}{}{}', sub {
    my ($stomach, $listname, $listtype, $maxdepth) = @_;
    $listname = ToString($listname);
    $listtype = ToString($listtype);
    $maxdepth = ToString($maxdepth);    # Or should it be a Number?
    my ($basetype, $inline) = ($listtype, '');
    if ($listtype =~ /^(.*)\*$/) { $basetype = $1; $inline = 1; }
    my $elementname = ($inline ? 'inline-' . $basetype : $basetype);
    # Officially only supposed to define counters for enumerate, but we need them (no prefix?)
    for (my $d = 1 ; $d <= $maxdepth ; $d++) {
      NewCounter($listname . ToString(Tokens(roman($d)))); }
    # Need to hook-up to \itemize@item, inline, etc
    Let(T_CS('\\' . $listname . '@item'),
      T_CS('\\' . ($inline ? 'inline@' : '') . $basetype . '@item'),
      'global');
    DefEnvironmentI($listname, "OptionalKeyVals:enumitem",
      "<ltx:$elementname xml:id='#id'>#body</ltx:$elementname>",
      properties      => sub { beginEnumItemize($listname, $listname, $_[1]); },
      beforeDigestEnd => sub { Digest('\par'); },
      afterDigestBody => sub { endEnumItemize($_[1]); },
      locked          => 1, mode => 'text');
    return; });
Let('\renewlist', '\newlist');

# Convoluted: optionally applies to all lists, all levels of one list or a single level
DefPrimitive('\setlist Optional RequiredKeyVals:enumitem', sub {
    my ($stomach, $names, $kv) = @_;
    if (!$names) {    # No names? applies to ALL lists
      store_enumitem_defaults('enumitem_defaults', $kv); }
    else {
      my ($name, @levels) = split(/\s*,\s*/, ToString($names));
      if (!@levels) {    # No levels? applies to ALL levels of this list
        store_enumitem_defaults('enumitem_' . $name . '_defaults', $kv); }
      else {
        foreach my $level (@levels) {
          store_enumitem_defaults('enumitem_' . $name . $level . '_defaults', $kv); } } }
    return; });

# These are obsolete
DefMacro('\setitemize Optional {}',     '\setlist[itemize,#1]{#2}');
DefMacro('\setenumerate Optional {}',   '\setlist[enumerate,#1]{#2}');
DefMacro('\setdescription Optional {}', '\setlist[description,#1]{#2}');

sub store_enumitem_defaults {
  my ($name, $kv) = @_;
  my $kvdef = LookupValue($name);
  if (!$kvdef) {
    $kvdef = LaTeXML::Core::KeyVals->new('KV', 'enumitem');
    AssignValue($name, $kvdef, 'global'); }
  my $hash = $kv->getKeyVals;
  foreach my $key (keys %$hash) {
    $kvdef->setValue($key, $$hash{$key}); }
  return; }

sub merged_enumitem_keyvals {
  my ($name, $level, $argkv) = @_;
  my @defaults = map { LookupValue($_); }
'enumitem_defaults', 'enumitem_' . $name . '_defaults', 'enumitem_' . $name . $level . '_defaults';
  my $hash = {};
  foreach my $kv (@defaults, $argkv) {
    if ($kv) {
      my $defhash = $kv->getKeyVals;
      foreach my $key (keys %$defhash) {
        $$hash{$key} = $$defhash{$key}; } } }
  return $hash; }

#======================================================================
# Not yet handled bits
DefMacro('\SetLabelAlign{}{}',          '');
DefMacro('\EnumitemId',                 '');
DefMacro('\SetEnumitemKey{}{}',         '');
DefMacro('\SetEnumerateShortLabel{}{}', '');
DefMacro('\SetEnumitemValue{}{}{}',     '');
DefMacro('\SetEnumitemSize{}{}',        '');
DefMacro('\AddEnumerateCounter{}{}{}',  '');
#======================================================================

1;