%% This package is distributed under the terms of the GPLv3 or later, or the
%% LPPL 1.3c or later, which ever license fits your needs the best.
%%
%% Copyright (C) 2017 by Jonathan P. Spratte
\NeedsTeXFormat{LaTeX2e}
\RequirePackage{xparse,l3keys2e}

\def\ducksay@version{v1.1}
\def\ducksay@date{2017/09/25}

\ProvidesExplPackage
  {ducksay}           {\ducksay@date}
  {\ducksay@version}  {cowsay for LaTeX}

\ExplSyntaxOn

\int_new:N \l_ducksay_strlen_int
\int_new:N \l_ducksay_lines_int
\seq_new:N \l_ducksay_msg_lines_seq

\cs_new:Npn \ducksay_longest_line:n #1 {%>>>
  \int_incr:N \l_ducksay_lines_int
  \exp_args:NNx \tl_set:Nn \l_tmpa_tl { #1 }
  \regex_replace_all:nnN { \s } { \c{space} } \l_tmpa_tl
  \int_set:Nn \l_ducksay_strlen_int {
    \int_max:nn { \l_ducksay_strlen_int } { \tl_count:N \l_tmpa_tl } }
}%<<<

\cs_new:Npn \ducksay_open_bubble: {%>>>
  \begin{tabular}{@{}l@{}}
    \mbox{}\\
    \int_compare:nNnTF {\l_ducksay_lines_int} = {1} {
      (
    }{
      /
      \int_step_inline:nnnn {3}{1} {\l_ducksay_lines_int} {\\\kern-0.5ex|}
      \\\detokenize{\ }
    }
    \\[-1ex]\mbox{}
  \end{tabular}
  \begin{tabular}{@{}l@{}}
    _\\
    \int_step_inline:nnnn{2}{1}{\l_ducksay_lines_int}{\\}\\[-1ex]
    {-}
  \end{tabular}
}%<<<

\cs_new:Npn \ducksay_close_bubble: {%>>>
  \begin{tabular}{@{}l@{}}
    _\\
    \int_step_inline:nnnn{2}{1}{\l_ducksay_lines_int}{\\}\\[-1ex]
    {-}
  \end{tabular}
  \begin{tabular}{@{}r@{}}
    \mbox{}\\
    \int_compare:nNnTF {\l_ducksay_lines_int} = {1} {
      )
    }{
      \detokenize{\ }
      \int_step_inline:nnnn {3}{1}{\l_ducksay_lines_int} {\\|\kern-0.5ex}
      \\/
    }
    \\[-1ex]\mbox{}
  \end{tabular}
}%<<<

\cs_new:Npn \ducksay_print_msg:n #1 #2 {%>>>
  \begin{tabular}{@{} #2 @{}}
    \int_step_inline:nnnn{1}{1}{\l_ducksay_strlen_int}{_}\\
    #1\\[-1ex]
    \int_step_inline:nnnn{1}{1}{\l_ducksay_strlen_int}{{-}}
  \end{tabular}
}%<<<

\cs_new:Npn \ducksay_print:n #1 {%>>>
  \int_case:nnF { \l_ducksay_strlen_int }
    { { 0 } {
      \seq_set_split:Nnn \l_ducksay_msg_lines_seq {\\} { #1 }
      \seq_map_function:NN \l_ducksay_msg_lines_seq \ducksay_longest_line:n
    }}
    {
      \int_case:nn { \l_ducksay_lines_int }
        { { 0 } {
          \regex_count:nnN {\c{\\}} {#1} \l_ducksay_lines_int
          \int_incr:N \l_ducksay_lines_int
        }}
    }
  \frenchspacing
  \group_begin:
    \ttfamily
    \begin{tabular}[\l_ducksay_align_tl]{@{}l@{}}
      \ducksay_bubble:
      \begin{tabular}{@{}l@{}}
        \ducksay_open_bubble:
        \exp_args:Nno \ducksay_print_msg:n {#1} { \l_ducksay_msg_align_tl }
        \ducksay_close_bubble:
      \end{tabular}\\
      \ducksay_body:
      \begin{tabular}{@{}l@{}}
        \l_ducksay_animal_tl
      \end{tabular}
    \end{tabular}
  \group_end:
}%<<<

\NewDocumentCommand{\ducksay}{ O{} m }{%>>>
  \group_begin:
    \int_zero:N \l_ducksay_strlen_int
    \int_zero:N \l_ducksay_lines_int
    \keys_set:nn { ducksay } { default_animal,#1 }
    \ducksay_print:n { #2 }
  \group_end:
}%<<<

% regexes for \duckthink >>>
\regex_const:Nn \c_ducksay_first_regex  { \A(.\s*)\\ }
\regex_const:Nn \c_ducksay_second_regex { \A(.[^\c{null}]*\c{null}\s*)\\ }
\regex_const:Nn \c_ducksay_third_regex  {
  \A(.[^\c{null}]*\c{null}[^\c{null}]*\c{null}\s*)\\ }
%<<<

\NewDocumentCommand{\duckthink}{ O{} m }{%>>>
  \group_begin:
    \int_zero:N \l_ducksay_strlen_int
    \int_zero:N \l_ducksay_lines_int
    \keys_set:nn { ducksay } { default_animal,#1 }
    \regex_replace_once:NnN \c_ducksay_first_regex  { \1O } \l_ducksay_animal_tl
    \regex_replace_once:NnN \c_ducksay_second_regex { \1o } \l_ducksay_animal_tl
    \regex_replace_once:NnN \c_ducksay_third_regex  { \1o } \l_ducksay_animal_tl
    \ducksay_print:n { #2 }
  \group_end:
}%<<<

\keys_define:nn { ducksay } {%>>>
  ,bubble .code:n       = \cs_set:Nn \ducksay_bubble: {#1}
  ,bubble .initial:n    = \relax
  ,body   .code:n       = \cs_set:Nn \ducksay_body: {#1}
  ,body   .initial:n    = \relax
  ,align  .tl_set:N     = \l_ducksay_align_tl
  ,align  .initial:n    = {}
  ,align  .value_required:n = true
  ,wd     .int_set:N    = \l_ducksay_strlen_int
  ,wd     .value_required:n = true
  ,ht     .int_set:N    = \l_ducksay_lines_int
  ,ht     .value_required:n = true
  ,animal .code:n       = {
    \keys_define:nn { ducksay } { default_animal .meta:n = {#1} }}
  ,animal .initial:n    = duck
  ,msg-align .tl_set:N  = \l_ducksay_msg_align_tl
  ,msg-align .initial:n = l
  ,msg-align .value_required:n = true
}%<<<

\NewDocumentCommand{\DefaultAnimal}{ m }{%>>>
  \keys_define:nn { ducksay } { default_animal .meta:n = {#1} }}%<<<

\NewDocumentCommand{\AddAnimal}{ s m +v }{%>>>
  \tl_set:Nn \l_tmpa_tl { \ #3 }
  \regex_replace_all:nnN { \r } { \c{tabularnewline}\c{null} } \l_tmpa_tl
  \cs_gset_eq:cN { g_ducksay_animal_#2_tl } \l_tmpa_tl
  \keys_define:nn { ducksay } {
    #2 .code:n = \cs_set_eq:Nc \l_ducksay_animal_tl { g_ducksay_animal_#2_tl }
  }
  \IfBooleanT{#1}{
    \keys_define:nn { ducksay } {
      default_animal .meta:n = {#2} }}
}%<<<

\ExplSyntaxOff

% Animals:>>>
% some of the below are from http://ascii.co.uk/art/kangaroo
\AddAnimal{duck}%>>>
{  \
    \   __
      >(' )
        )/
       /(
      /  `----/
      \  ~=- /
    ~^~^~^~^~^~^~^}%<<<
\AddAnimal{small-duck}%>>>
{  \
    \
      >()_
       (__)__ _}%<<<
\AddAnimal{duck-family}%>>>
{  \
    \   __
      >(' )
        )/
       /(
      /  `----/  -()_  >()_
    __\__~=-_/__ _(__)__(__)__ _}%<<<
\AddAnimal{cow}%>>>
{  \  ^__^
    \ (oo)\_______
      (__)\       )\/\
          ||----w |
          ||     ||}%<<<
\AddAnimal{head-in}%>>>
{  \  
    \ ^__^         /
      (oo)\_______/  ________
      (__)\       )=(  ___|_ \____
          ||----w |  \ \    \____ |
          ||     ||   ||         ||}%<<<
\AddAnimal{sodomized}%>>>
{  \             _
    \           (_)
      ^__^       / \
      (oo)\_____/_\ \
      (__)\       ) /
          ||----w ((
          ||     ||>>}%<<<
\AddAnimal{tux}%>>>
{  \
    \  .--. 
      |o_o |
      |\_/ |
     //   \ \
    (|     | )
   /'\_   _/`\
   \___)=(___/}%<<<
\AddAnimal{pig}%>>>
+  \     _//| .-~~~-.
    \ _/oo  }        }-@
     ('')_  }        |
      `--'| { }--{  }
           //_/  /_/+%<<<
\AddAnimal{frog}%>>>
{   \
     \ (.)_(.)
    _ (   _   ) _
   / \/`-----'\/ \
 __\ ( (     ) ) /__
 )   /\ \._./ /\   (
  )_/ /|\   /|\ \_(}%<<<
\AddAnimal{snowman}%>>>
{  \
    \_[_]_
      (")
   >-( : )-<
    (__:__)}%<<<
\AddAnimal{hedgehog}%>>>
{  \    .\|//||\||.
    \  |/\/||/|//|/|
      /. `|/\\|/||/||
     o__,_|//|/||\||'}%<<<
\AddAnimal{kangaroo}%>>>
{  \
    \ _,'   ___
     <__\__/   \
        \_  /  _\
          \,\ / \\
            //   \\
          ,/'     `\_,}%<<<
\AddAnimal{rabbit}%>>> http://chris.com/ascii/index.php?art=animals/rabbits
{ \     / \`\         __
   \   |  \ `\      /`/ \
    \  \_/`\  \-"-/` /\  \
            |       |  \  |
            (d     b)   \_/
            /       \
        ,".|.'.\_/.'.|.",
       /   /\' _|_ '/\   \
       |  /  '-`"`-'  \  |
       | |             | |
       | \    \   /    / |
        \ \    \ /    / /
         `"`\   :   /'"`
             `""`""`}%<<<
\AddAnimal{bunny}%>>>
{ \
   \  \
       \ /\
       ( )
     .( o ).}%<<<
\AddAnimal{small-rabbit}%>>>
{  \
    \ _//
     (')---.
      _/-_( )o}%<<<
\AddAnimal{dragon}%>>>
{     \                    / \  //\
       \    |\___/|      /   \//  \\
        \   /0  0  \__  /    //  | \ \    
           /     /  \/_/    //   |  \  \  
           @_^_@'/   \/_   //    |   \   \ 
           //_^_/     \/_ //     |    \    \
        ( //) |        \///      |     \     \
      ( / /) _|_ /   )  //       |      \     _\
    ( // /) '/,_ _ _/  ( ; -.    |    _ _\.-~        .-~~~^-.
  (( / / )) ,-{        _      `-.|.-~-.           .~         `.
 (( // / ))  '/\      /                 ~-. _ .-~      .-~^-.  \
 (( /// ))      `.   {            }                   /      \  \
  (( / ))     .----~-.\        \-'                 .~         \  `. \^-.
             ///.----..>        \             _ -~             `.  ^-`  ^-_
               ///-._ _ _ _ _ _ _}^ - - - - ~                     ~-- ,.-~
                                                                  /.-~}%<<<
\AddAnimal{dog}%>>> http://www.ascii-art.de/ascii/def/dogs.txt
{  \     __
    \ .-'\/\
       "\   '------.
     ___/       (  .'_____
    '-----'"""'------"""""'}%<<<
\AddAnimal{squirrel}%>>> http://ascii.co.uk/art/squirrel
{  \           ,;:;;,
    \         ;;;;;
      .=',    ;:;;:,
     /_', "=. ';:;:;
     @=:__,  \,;:;:'
       _(\.=  ;:;;'
      `"_(  _/="`
       `"'``}%<<<
  
% from http://www.ascii-art.de/ascii/s/starwars.txt :
\AddAnimal{yoda}%>>>
{   \
     \             ____
      \         _.' :  `._
            .-.'`.  ;   .'`.-.
   __      / : ___\ ;  /___ ; \      __
 ,'_ ""--.:__;".-.";: :".-.":__;.--"" _`,
 :' `.t""--.. '<@.`;_  ',@>` ..--""j.' `;
      `:-.._J '-.-'L__ `-- ' L_..-;'
        "-.__ ;  .-"  "-.  : __.-"
            L ' /.------.\ ' J
             "-.   "--"   .-"
            __.l"-:_JL_;-";.__
         .-j/'.;  ;""""  / .'\"-.
       .' /:`. :  :     /.".'';  `.
    .-"  / ;`.".  :    ."."   :    "-.
 .+"-.  : :   ".".". ."."      ;-._   \
 ; \  `.; ; .   "."-"."        : : "+. ;
 :  ;   ; ;  .   ."."    ;     : ;  : \:
 ;  :   ; :     / /     /  ,   ;:   ;  :
: \  ;  :  ;   ; /     :  ,   : ;  /  ::
;  ; :   ; :  ; ;      ;      ;   :   ;:
:  :  ;  :  ;. ;      '      : :  ;  : ;
;\    :   ; : .          ,   ; ;     ; ;
: `."-;   :  ;      .   ;   :  ;    /  ;
 ;    -:   ; :      ,  ,    ;  : .-"   :
 :\     \  :  ;    ,       : \.-"      :
  ;`.    \  ; :   .   ,    ;.'_..--  / ;
  :  "-.  "-:  ;     ,    :/."      .'  :
   \         \ :    :     ;/  __        :
    \       .-`.\        /t-""  ":-+.   :
     `.  .-"    `l    __/ /`. :  ; ; \  ;
       \   .-" .-"-.-"  .' .'j \  /   ;/
        \ / .-"   /.     .'.' ;_:'    ;
         :-""-.`./-.'     /    `.___.'
               \ `t  ._  /
                "-.t-._:'}%<<<
\AddAnimal{yoda-head}%>>>
{   \
     \             ____
      \         _.' :  `._
            .-.'`.  ;   .'`.-.
   __      / : ___\ ;  /___ ; \      __
 ,'_ ""--.:__;".-.";: :".-.":__;.--"" _`,
 :' `.t""--.. '<@.`;_  ',@>` ..--""j.' `;
      `:-.._J '-.-'L__ `-- ' L_..-;'
        "-.__ ;  .-"  "-.  : __.-"
            L ' /.------.\ ' J
             "-.   "--"   .-"
            __.l"-:_JL_;-";.__
         .-j/'.;  ;""""  / .'\"-.
       .' /:`. :  :     /.".'';  `.
    .-"  / ;`.".  :    ."."   :    "-.
 .+"-.  : :   ".".". ."."      ;-._   \}%<<<
% from https://www.ascii-code.com/ascii-art/movies/star-wars.php
\AddAnimal{small-yoda}%>>>
{  \
    \
    __.-._
    '-._"7'
     /'.-c
     |  /T
    _)_/LI}%<<<
\AddAnimal{r2d2}%>>>
{  \
    \ ,-----.
    ,'_/_|_\_`.
   /<<::8[O]::>\
  _|-----------|_
 |  | ====-=- |  |
 |  | -=-==== |  |
 \  | ::::|()||  /
  | | ....|()|| |
  | |_________| |
  | |\_______/| |
 /   \ /   \ /   \
 `---' `---' `---'}%<<<
\AddAnimal{vader}%>>>
{  \     _.-'~~~~~~`-._
    \   /      ||      \
       /       ||       \
      |        ||        |
      | _______||_______ |
      |/ ----- \/ ----- \|
     /  (     )  (     )  \
    / \  ----- () -----  / \
   /   \      /||\      /   \
  /     \    /||||\    /     \
 /       \  /||||||\  /       \
/_        \O========O/        _\
  `--...__|`-._  _.-'|__...--'
          |    `'    |}%<<<
%<<<

\ExplSyntaxOn

\ProcessKeysOptions { ducksay }

\keys_define:nn { ducksay } {
  ,animal .undefine:
}

\ExplSyntaxOff

\endinput
% vim: fdm=marker foldmarker=>>>,<<<
