#!/usr/bin/env bash

set -euo pipefail

: <<"=cut"

=encoding utf-8

=head1 NAME

cmap - Term::ANSIColor::Concise demo/test script

=head1 SYNOPSIS

cmap [ options ]

=head1 DESCRIPTION

Print a table of color samples.  The three parameters of a color space
are taken from the values of B<-X>, B<-Y> and B<-Z>: one table for each
Z, one column for each X and one row for each Y.  Each sample has the
color as its background, and the same color changed by a modifier as
its foreground.

The samples are made by L<ansiecho|App::ansiecho> and laid out by
L<ansicolumn|App::ansicolumn>.  L<getoptlong.sh|Getopt::Long::Bash> is
taken from PATH.

=head1 OPTIONS

=over 4

=item B<-f> I<format>, B<--format>=I<format>

Color format, which gives the default values and order of X, Y and Z:

    hsl        X: hue         Y: lightness  Z: saturation  (default)
    rgb        X: red         Y: green      Z: blue
    lch        X: hue         Y: luminance  Z: chroma
    rgb-chart  X: red         Y: green      Z: blue

C<rgb-chart> is C<rgb> with a smaller step of X and without text, to be
looked at as a chart.

=item B<-X> I<values>, B<-Y> I<values>, B<-Z> I<values>

Values of the parameters instead of the default of the format.  Values
are separated by commas, and the option can be repeated.

=item B<-o> I<order>, B<--xyz-order>=I<order>

Which of X, Y and Z goes to each parameter of the color, as in C<yzx>,
C<y,z,x> or C<"y z x">.  The default depends on the format.

=item B<-m> I<modifier>, B<--mods>=I<modifier>

Color modifier of the foreground.  A table is printed for each modifier.

=item B<-D> I<modifier>, B<--default-mod>=I<modifier>

Modifier used when no B<-m> is given.  Default is C<+r90%y50>.

=item B<-r>, B<--reverse>

Apply the modifier to the background instead of the foreground.

=item B<-L> I<string>, B<--leader>=I<string>

String put before the text of each sample.

=item B<-l>, B<--label>[=I<string>]

Text of the samples, instead of the color spec.  Without a value, a
fixed sample text is used.

=item B<-C> I<number>, B<--column>=I<number>

Number of columns.  Default is the number of X values.

=item B<-t>, B<--terse>

Do not print the header of each column.

=item B<--message> B<BEGIN>=I<string>, B<--message> B<END>=I<string>

String printed before and after the tables.

=item B<-M> I<package>, B<--package>=I<package>

Package for color handling under C<Term::ANSIColor::Concise::>:
C<ColorObject> or C<Color>.

=item B<-I> I<path>, B<--include>=I<path>

Perl module path.  Default is the F<lib> directory of the distribution
that this script belongs to.

=item B<-n>, B<--dryrun>

Print the B<ansiecho> commands and do not run them.

=item B<-d>, B<--debug>

Print the option values.  Repeat to print the internal ones too.

=item B<-x>, B<--trace>

Trace the script with C<set -x>.

=item B<-h>, B<--help>

Show the list of options.

=item B<--man>

Show this document.

=back

=head1 EXAMPLES

    cmap
    cmap -f rgb
    cmap -f lch -m '+r90%y50' -m '+l20'
    cmap -t --label=Sample -X 0,120,240 -Z 100
    cmap -f rgb-chart

=head1 SEE ALSO

L<Term::ANSIColor::Concise>

=cut

die() { echo "$@" >&2 ; exit 1 ; }
man() { perldoc "$0" ; exit ; }

USAGE='cmap - Term::ANSIColor::Concise demo/test script

cmap [ options ]
'

declare -A OPTS=(
    [&REQUIRE]=1.1.0 [&USAGE]="$USAGE"
    [      format | f :  # color FORMAT: hsl, rgb, lch, rgb-chart ]=hsl
    [ default-mod | D :  # MODIFIER used without --mods           ]="+r90%y50"
    [        mods | m @  # color MODIFIER (ex. +r90%y50)          ]=
    [     package | M :  # color PACKAGE: ColorObject, Color      ]=
    [      leader | L :  # STRING before the text of a sample     ]="██  "
    [               X @  # values of X                            ]=
    [               Y @  # values of Y                            ]=
    [               Z @  # values of Z                            ]=
    [   xyz-order | o :  # order of X, Y and Z (ex. yzx)          ]=
    [       terse | t    # no column headers                      ]=
    [       label | l ?! # text of the samples                    ]=
    [     reverse | r    # modify the background                  ]=
    [      column | C :  # number of columns                      ]=
    [      dryrun | n    # print commands, do not run             ]=
    [       debug | d +  # print option values                    ]=0
    [       trace | x !  # trace the script                       ]=
    [         man     !  # show the manual                        ]=
    [     include | I :  # Perl module PATH                       ]="${0%/*}/../lib"
    [     message     %  # BEGIN=STRING, END=STRING               ]=
)

. getoptlong.sh -
getoptlong init OPTS

label() { [[ ${2:-} ]] || label='hasta la vista  ' ; }
trace() { [[ ${2:-} ]] && set -x || set +x ; }

getoptlong parse "$@" && eval "$(getoptlong set)"

(( $# == 0 )) || die "$*: unexpected argument"

# Set the values that the options did not give.
format_defaults() {
    local x y z order
    case $format in
    hsl)
	order="x z y"
	x=$(seq 0 60 359)        # Hue
	y=$(seq 0 5 99)          # Lightness
	z=20,80,100              # Saturation
	;;
    rgb)
	order="x y z"
	x="0 51 102 153 204 255" # Red
	y=$(seq 0 15 255)        # Green
	z=0,128,255              # Blue
	;;
    rgb-chart)
	format=rgb
	order="x y z"
	x=$(seq 0 2 255)         # Red
	y=$(seq 0 15 255)        # Green
	z=0,128,255              # Blue
	label=" "
	terse=yes
	leader=
	;;
    lch)
	order="y z x"
	x=$(seq 0 60 359)        # Hue
	y=$(seq 0 5 99)          # Luminance
	z=20,60,100              # Chroma
	;;
    *)
	die "$format: unknown format"
    esac
    (( ${#X[@]} )) || X=($x)
    (( ${#Y[@]} )) || Y=($y)
    (( ${#Z[@]} )) || Z=($z)
    [[ $xyz_order ]] || xyz_order=$order
}
format_defaults

xyz_order=${xyz_order//[ ,]/}
[[ $xyz_order =~ ^[xyz]{3}$ ]] || die "$xyz_order: invalid order"

[[ $package ]] && export TAC_COLOR_PACKAGE=${package}
[[ $include ]] && export PERL5LIB=${include}${PERL5LIB:+:$PERL5LIB}
(( debug > 1 )) && dump_opt=-a
(( debug > 0 )) && {
    gol_dump ${dump_opt-} | column
    declare -p mods
    declare -p message
}

declare -A xyz=([x]=0 [y]=1 [z]=2)
reorder() {
    local orig=("$@") ans=() i
    for ((i = 0; i < ${#xyz_order}; i++)) ; do
	ans+=("${orig[${xyz[${xyz_order:i:1}]}]}")
    done
    echo "${ans[@]}"
}

table() {
    local mod=$1
    local IFS=$' \t\n,'
    for z in ${Z[@]} ; do
	local option=(--separate $'\n')
	for x in ${X[@]} ; do
	    local ys=${Y[0]} ye=${Y[$(( ${#Y[@]} - 1 ))]}
	    [[ $terse ]] || option+=("x=$x,y=$ys-$ye,z=$z")
	    for y in ${Y[@]} ; do
		col=$(printf "%s(%03d,%03d,%03d)" ${format} $(reorder $x $y $z))
		[[ $reverse ]] && arg="$col/$col$mod" \
		               || arg="$col$mod/$col"
		text="${leader}${label:-$col$mod}"
		option+=(-c "$arg" "$text")
	    done
	done
	if [[ $dryrun ]] ; then
	    echo ansiecho "${option[@]}"
	else
	    ansiecho "${option[@]}" | ansicolumn -C ${column:-${#X[@]}} --cu=1 --margin=0
	fi
    done
}

(( ${#mods[@]} )) || mods=("$default_mod")

if [[ -v message[BEGIN] ]] ; then
    echo "${message[BEGIN]}"
fi

for mod in "${mods[@]}" ; do
    table "$mod"
done

if [[ -v message[END] ]] ; then
    echo "${message[END]}"
fi
