#!/bin/sh

FILE="/etc/X11/rgb.txt"

help() {
  cat <<EOHELP
colshift - list, manipulate and output X11 colors as text, GIF or HTML.
Usage:
  colshift [OPTION] [ARGUMENTS]
Options:
  -h         Show this help.
  --help     Show extensive help and program information.
  --html     Generate an HTML document containing all colors on the current
             system. The HTML code will be written to stdout.
  -m, --modify COLORNAME RED [GREEN] [BLUE]
             Look up COLORNAME among available X11 colors and print the
             resulting RGB values after applying each delta  respectively.
             E.G. if RED is -10, subtract 10 from the Red value in the
             original color. If RED alone is supplied, it will be used for
             all color values.
  -g, --gif COLORNAME RED [GREEN] [BLUE]
             Like -m, but write the resulting color as a GIF87a file to
             stdout.
  -c, --colorgif HEXPAIRS
             Generate a GIF87a colored with the given hex pairs and write
             it to stdout.
EOHELP
}

more_help() {
  help
  cat <<EOHELP

 Optionless Usage
==================
Colshift can be invoked without options:
  colshift [COLORNAME] [RED] [GREEN] [BLUE]

The color values RED, GREEN and BLUE are assumed to be decimal integers.
They can be positive, E.G. 100 or negative, E.G. -100.

If no options are given, colshift will attempt to look up COLORNAME
in the list of X11 colors. If no color values are given, any matching
colors will be listed together with their values in decimal and hexadecimal
notation.

If supplied, RED, GREEN and BLUE will be used as deltas to calculate a new
color value, E.G.
  colshift DarkSlateGray 10 -10 10
Will output
  DarkSlateGray
    #2f4f4f / 47, 79, 79
    #394559 / 57, 69, 89

If only one delta value is supplied, it will be used for all three color
values, E.G.
  colshift DarkSlateGray -15
Will output
  DarkSlateGray
    #2f4f4f / 47, 79, 79
    #204040 / 32, 64, 64

 Usage With -g or -m
=====================
If -g, --gif or -m, --mod are used, at least COLORNAME and RED must be
supplied, otherwise the behavior is the same as Optionless Usage.

 GIF87a Creation
=================
The GIF created is a 100x100 pixel GIF87a. It can be redirected to
a file or piped to a suitable image viewer.

 Examples
==========
List all X11 colors:
  colshift

Create an HTML file with all X11 colors:
  colshift --html > all_colors.html

List all X11 colors matching slate:
  colshift slate

Increase all values of DarkSlateGray by 15 and print the result:
  colshift DarkSlateGray 15

Increase blue value of SteelBlue4 by 30 and print the result:
  colshift SteelBlue4 0 0 15

Decrease the green value of DeepSkyBlue3 by 15 and print the result:
  colshift DeepSkyBlue3 0 -15 0

Decrease the green value of DeepSkyBlue3 by 15, generate a GIF of the
result and display the GIF in a 100x100 window using feh:
  colshift -g DeepSkyBlue3 0 -15 0 | feh - -Z --geometry 100x100

Generate a GIF with the color #BADFAD and display it in a 100x100 window
using feh:
  colshift -c BADFAD | feh - -Z --geometry 100x100

 Configuration
===============
By default, colshift assumes a list of X11 color names is available
in "/etc/X11/rgb.txt". This path can be changed by creating a file
in your home directory called ".colshiftrc", E.G.:
  echo "/path/to/colorlist/rgb.txt" > "$HOME/.colshiftrc"

 Dependencies
==============
Colshift is written in POSIX sh and assumes the following compliant
commands are available: awk, grep, printf, sed, tail.

 License
=========
colshift 1.1 (c) 2020 Carl Svensson.
Shellcheck SC2059 workaround by Bastian Bittorf.
Usage of the works is permitted provided that this instrument is
retained with the works, so that any entity that uses the works is
notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
EOHELP
}

errdie() {
  echo "$1" 1>&2
  exit 1
}

read_config() {
  if [ -f "$HOME/.colshiftrc" ]; then
    UCFG_FILE=$(grep -vx "" "$HOME/.colshiftrc" | sed "s/^ //" | sed "s/ *$//")
  fi
  if [ -f "$UCFG_FILE" ]; then
    FILE="$UCFG_FILE"
  fi
}

presentcolor() {
  awk '{ CN = "";
         for(i=4; i<=NF; i++) CN = CN$i" ";
         printf "%s\n  #%02x%02x%02x / %d, %d, %d\n",
                 CN, $1, $2, $3, $1, $2, $3 }'
}

presentcolor_html() {
  awk '{ CN = "";
        for(i=4; i<=NF; i++) CN = CN$i" ";
        printf "<tr><td>%s</td><td bgcolor=\"#%02x%02x%02x\">",
                CN, $1, $2, $3
        printf "%s</td><td>&nbsp;&nbsp;#%02x%02x%02x</td></tr>\n",
                "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;",
                $1, $2, $3 }'
}

mk_html() {
  HOST=$(hostname)
  echo "<html><head><title>Colshift HTML</title></head><body>"
  echo "<h3>Colshift HTML from $FILE on $HOST</h3>"
  echo "<table>"
  grep -v "^!" "$FILE" | presentcolor_html
  echo "</table>"
  echo "Generated by colshift $(date -R)"
  echo "</body></html>"
}

findsingle() {
  grep -i "[[:space:]][[:space:]]$1$" "$FILE"
}

findmany() {
  grep -i "$1" "$FILE"
}

modify() {
  COLOR=$(findsingle "$1")
  if [ -z "$COLOR" ]; then
    errdie "No match for color name '$1'"
  fi

  MODRED="$2"

  if [ -z "$MODRED" ]; then
    errdie "No modification values supplied"
  fi

  MODGREEN="$3"
  if [ -z "$MODGREEN" ]; then
    MODGREEN="$MODRED"
    MODBLUE="$MODRED"
  else
    MODBLUE="$4"
  fi

  ADDED=$(echo "$MODRED $MODGREEN $MODBLUE $COLOR" |
          awk '{ NR = $1 + $4; NR = NR > 255 ? 255 : (NR < 0 ? 0 : NR);
                 NG = $2 + $5; NG = NG > 255 ? 255 : (NG < 0 ? 0 : NG);
                 NB = $3 + $6; NB = NB > 255 ? 255 : (NB < 0 ? 0 : NB);
                 printf "#%02x%02x%02x / %d, %d, %d\n",
                 NR, NG, NB, NR, NG, NB }')


  echo "$COLOR" | presentcolor
  echo "  $ADDED"
}

split_hexpairs() {
  sed "s/#//" |
  awk '{ print substr($1,1,2)" "substr($1,3,2)" "substr($1,5,2) }'
}

resulting_hexpairs() {
  grep "#" | awk '{print $1}' | tail -n 1 | split_hexpairs
}

mk_gif() {
  HEXA="47 49 46 38 37 61 64 00 64 00 90 00 00 $1" # N.B. $1!
  HEXA="$HEXA 00 00 00 2c 00 00 00 00 64 00 64 00 00 02 73 84"
  HEXA="$HEXA 8f a9 cb ed 0f a3 9c b4 da 8b b3 de bc fb 0f 86"
  HEXA="$HEXA e2 48 96 e6 89 a6 ea ca b6 ee 0b c7 f2 4c d7 f6"
  HEXA="$HEXA 8d e7 fa ce f7 fe 0f 0c 0a 87 c4 a2 f1 88 4c 2a"
  HEXA="$HEXA 97 cc a6 f3 09 8d 4a a7 d4 aa f5 8a cd 6a b7 dc"
  HEXA="$HEXA ae f7 0b 0e 8b c7 e4 b2 f9 8c 4e ab d7 ec b6 fb"
  HEXA="$HEXA 0d 8f cb e7 f4 ba fd 8e cf eb f7 fc be ff 0f 18"
  HEXA="$HEXA 28 38 48 58 68 78 88 98 a8 b8 c8 d8 e8 f8 08 69"
  HEXA="$HEXA 58 00 00 3b"
  for V in $HEXA; do
    BYTE=$(printf "%o" "0x$V")
    eval printf "\\\\$BYTE"
  done
}

# First, read the config file
read_config

# Then, handle those args!
case $1 in
  "-h")
    help
  ;;
  "--help")
    more_help
  ;;
  "--html")
    mk_html
  ;;
  "-g"|"--gif")
    HEXA=$(modify "$2" "$3" "$4" "$5" | resulting_hexpairs)
    mk_gif "$HEXA"
  ;;
  "-m"|"--modify")
    modify "$2" "$3" "$4" "$5"
  ;;
  "-c"|"--colorgif")
    HEXA=$(echo "$2" | split_hexpairs)
    mk_gif "$HEXA"
  ;;
  "")
    # List all colors
    grep -v "^!" "$FILE" | presentcolor
  ;;
  *)
    if [ -n "$2" ]; then
      # At least one modification value given, call modify
      modify "$1" "$2" "$3" "$4"
    elif [ -n "$1" ]; then
      # Single color name given, present any matches
      findmany "$1" | presentcolor
    fi
  ;;
esac
