#!/usr/bin/bash
# ==========================================================================
#         ____            _                     _____           _
#        / ___| _   _ ___| |_ ___ _ __ ___     |_   _|__   ___ | |___
#        \___ \| | | / __| __/ _ \ '_ ` _ \ _____| |/ _ \ / _ \| / __|
#         ___) | |_| \__ \ ||  __/ | | | | |_____| | (_) | (_) | \__ \
#        |____/ \__, |___/\__\___|_| |_| |_|     |_|\___/ \___/|_|___/
#               |___/
#                             --- System-Tools ---
#                  https://www.nntb.no/~dreibh/system-tools/
# ==========================================================================
#
# GIMP Scripts
# Copyright (C) 2013-2026 by Thomas Dreibholz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact: thomas.dreibholz@gmail.com

# Bash options:
set -euo pipefail


# ###### Usage ##############################################################
usage () {
   local exitCode="$1"
   echo >&2 "Usage: $0 input_filename output_filename [-C|--compensate-darkening on|off] [-I|--invert on|off] [-T|--tiled on|off] [-A|--azimuth bearing] [-E|--elevation bearing] [-D|--depth depth] [-X|--offset-x value] [-Y|--offset-y value] [-W|--waterlevel level] [-B|--ambient factor] [-H|--hue-range all|red|yellow|green|cyan|blue|magenta] [-O|--hue-offset degrees] [-L|--lightness adjustment] [-S|--saturation adjustment] [-V|--overlap overlap] [-w|--verbose] [-q|--quiet] [-h|--help] [-v|--version]"
   exit "${exitCode}"
}


# ###### Version ############################################################
version () {
   echo "gs-bumpmap 2.7.0"
   exit 0
}



# ###### Main program #######################################################

# ====== Handle arguments ===================================================
if (( BASH_VERSINFO[0] < 4 )) || (( (BASH_VERSINFO[0] == 4) && (BASH_VERSINFO[1] < 2) )) ; then
   echo "ERROR: Bash 4.2 or higher is required, but Bash ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]} is installed!"
   exit 1
fi
GETOPT="$(PATH="/usr/local/bin:${PATH}" command -v getopt)"
if "${GETOPT}" -T >/dev/null 2>&1 || [[ $? -ne 4 ]]; then
   echo >&2 "ERROR: Enhanced/GNU getopt is required!"
   exit 1
fi
options="$(${GETOPT} -o C:I:T:A:E:D:X:Y:W:B:H:O:L:S:V:wqhv --long compensate-darkening:,invert:,tiled:,azimuth:,elevation:,depth:,offset-x:,offset-y:,waterlevel:,ambient:,hue-range:,hue-offset:,lightness:,saturation:,overlap:,verbose,quiet,help,version -a -- "$@")"
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
   usage 1
fi
eval set -- "${options}"

declare -A hueRanges=(
 ["all"]=0
 ["red"]=1
 ["yellow"]=2
 ["green"]=3
 ["cyan"]=4
 ["blue"]=5
 ["magenta"]=6
)

COMPENSATE="TRUE"
INVERT="FALSE"
TILED="FALSE"
AZIMUTH=135.0
ELEVATION=45.0
DEPTH=20
OFFSET_X=0
OFFSET_Y=0
WATERLEVEL=0.0
AMBIENT=0.0
HUE_RANGE=0   # 0=all
HUE_OFFSET=0.0
LIGHTNESS=0.0
SATURATION=0.0
OVERLAP=0.0
VERBOSE=0
while [ $# -gt 0 ] ; do
   case "$1" in
      -C | --compensate-darkening)
         COMPENSATE="$2"
         shift
         if [[ "${COMPENSATE}" =~ ^(on|ON|true|TRUE|yes|YES|1)$ ]] ; then
            COMPENSATE="TRUE"
         elif [[ "${COMPENSATE}" =~ ^(off|OFF|false|FALSE|no|NO|0)$ ]] ; then
            COMPENSATE="FALSE"
         else
            echo >&2 "ERROR: Invalid value ${COMPENSATE} for -C|--compensate-darkening!"
            exit 1
         fi
         ;;
      -I | --invert)
         INVERT="$2"
         shift
         if [[ "${INVERT}" =~ ^(on|ON|true|TRUE|yes|YES|1)$ ]] ; then
            INVERT="TRUE"
         elif [[ "${INVERT}" =~ ^(off|OFF|false|FALSE|no|NO|0)$ ]] ; then
            INVERT="FALSE"
         else
            echo >&2 "ERROR: Invalid value ${INVERT} for -I|--invert!"
            exit 1
         fi
         ;;
      -T | --tiled)
         TILED="$2"
         shift
         if [[ "${TILED}" =~ ^(on|ON|true|TRUE|yes|YES|1)$ ]] ; then
            TILED="TRUE"
         elif [[ "${TILED}" =~ ^(off|OFF|false|FALSE|no|NO|0)$ ]] ; then
            TILED="FALSE"
         else
            echo >&2 "ERROR: Invalid value ${TILED} for -T|--tiled!"
            exit 1
         fi
         ;;
      -A | --azimuth)
         AZIMUTH="$2"
         shift
         if [[ ! "${AZIMUTH}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$ ]] || \
            awk "BEGIN { exit ( ($AZIMUTH < 0.0) || ($AZIMUTH > 360.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${AZIMUTH} for -A|--azimuth!"
            exit 1
         fi
         ;;
      -E | --elevation)
         ELEVATION="$2"
         shift
         if [[ ! "${ELEVATION}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($ELEVATION < 0.5) || ($ELEVATION > 90.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${ELEVATION} for -E|--elevation!"
            exit 1
         fi
         ;;
      -D | --depth)
         DEPTH="$2"
         shift
         if [[ ! "${DEPTH}" =~ ^[0-9]+$ ]] || [ "${DEPTH}" -lt 1 ] || [ "${DEPTH}" -gt 65 ] ; then
            echo >&2 "ERROR: Invalid value ${DEPTH} for -D|--depth!"
            exit 1
         fi
         ;;
      -X | --offset-x)
         OFFSET_X="$2"
         shift
         if [[ ! "${OFFSET_X}" =~ ^[0-9]+$ ]] || [ "${OFFSET_X}" -lt -20000 ] || [ "${OFFSET_X}" -gt 20000 ] ; then
            echo >&2 "ERROR: Invalid value ${OFFSET_X} for -X|--offset-x!"
            exit 1
         fi
         ;;
      -Y | --offset-y)
         OFFSET_Y="$2"
         shift
         if [[ ! "${OFFSET_Y}" =~ ^[0-9]+$ ]] || [ "${OFFSET_Y}" -lt -20000 ] || [ "${OFFSET_Y}" -gt 20000 ] ; then
            echo >&2 "ERROR: Invalid value ${OFFSET_Y} for -Y|--offset-y!"
            exit 1
         fi
         ;;
      -W | --waterlevel)
         WATERLEVEL="$2"
         shift
         if [[ ! "${WATERLEVEL}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($WATERLEVEL < 0.0) || ($WATERLEVEL > 1.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${WATERLEVEL} for -W|--waterlevel!"
            exit 1
         fi
         ;;
      -B | --ambient)
         AMBIENT="$2"
         shift
         if [[ ! "${AMBIENT}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($AMBIENT < 0.0) || ($AMBIENT > 1.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${AMBIENT} for -B|--ambient!"
            exit 1
         fi
         ;;

      -H | --hue-range)
         HUE_RANGE="$2"
         shift
         if [[ ! -v hueRanges["${HUE_RANGE}"] ]] ; then
            echo >&2 "ERROR: Invalid value ${HUE_RANGE} for -H|--hue-range!"
            exit 1
         fi
         HUE_RANGE="${hueRanges["${HUE_RANGE}"]}"
         ;;
      -O | --hue-offset)
         HUE_OFFSET="$2"
         shift
         if [[ ! "${HUE_OFFSET}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($HUE_OFFSET < -180.0) || ($HUE_OFFSET > 180.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${HUE_OFFSET} for -O|--hue-offset!"
            exit 1
         fi
         ;;
      -L | --lightness)
         LIGHTNESS="$2"
         shift
         if [[ ! "${LIGHTNESS}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($LIGHTNESS < -100.0) || ($LIGHTNESS > 100.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${LIGHTNESS} for -L|--lightness!"
            exit 1
         fi
         ;;
      -S | --saturation)
         SATURATION="$2"
         shift
         if [[ ! "${SATURATION}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($SATURATION < -100.0) || ($SATURATION > 100.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${SATURATION} for -S|--saturation!"
            exit 1
         fi
         ;;
      -V | --overlap)
         OVERLAP="$2"
         shift
         if [[ ! "${OVERLAP}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($OVERLAP < 0.0) || ($OVERLAP > 100.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${OVERLAP} for -V|--overlap!"
            exit 1
         fi
         ;;

      -w | --verbose)
         VERBOSE=1
         ;;
      -q | --quiet)
         VERBOSE=0
         ;;
      -h | --help)
         usage 0
         ;;
      -v | --version)
         version
         ;;
      --)
         shift
         break
         ;;
      *)
         # This should not happen: wrong getopt parameters, or missing case?
         echo >&2 "INTERNAL ERROR: Unhandled option $1!"
         exit 1
         ;;
  esac
  shift
done

if [ $# -ne 2 ] ; then
   usage 1
fi
INPUT_FILENAME="$1"
OUTPUT_FILENAME="$2"

if [ ! -f "${INPUT_FILENAME}" ] ; then
   echo >&2 "ERROR: Input file ${INPUT_FILENAME} does not exist!"
   exit 1
fi


# ====== Detect GIMP, and determine version-specific call options ===========
GIMP_CONSOLE="$(command -v gimp-console || true)"
if [ "${GIMP_CONSOLE}" == "" ] ; then
   echo >&2 "ERROR: GIMP is not installed!"
   echo >&2 "* Ubuntu:  sudo apt install -y gimp"
   echo >&2 "* Fedora:  sudo dnf install -y gimp"
   echo >&2 "* SuSE     sudo zypper install -y gimp"
   echo >&2 "* Alpine:  sudo apk add gimp"
   echo >&2 "* FreeBSD: sudo pkg install -y gimp3-app"
   exit 1
fi
GIMP_VERSION="$(LC_ALL=C.UTF-8 "${GIMP_CONSOLE}" --version | sed -e 's/GNU Image Manipulation Program version //')"
if [[ "${GIMP_VERSION}" =~ ^[012] ]] ; then
   # GIMP 2.x:
   GIMP_OPTIONS="--new-instance --no-interface --no-splash --batch-interpreter plug-in-script-fu-eval --batch -"
else
   # GIMP 3.x:
   GIMP_OPTIONS="--quit --new-instance --no-interface --no-splash --batch-interpreter plug-in-script-fu-eval --batch -"
fi

# This script does not use fonts, or gradients/palettes/brushes:
GIMP_OPTIONS="${GIMP_OPTIONS} --no-fonts --no-data"


# ====== Call GIMP ==========================================================
rm -f "${OUTPUT_FILENAME}"
# shellcheck disable=SC2086
( cat <<EOF
(let*
   ; ------ Initialise and load image ---------------------------------------
   ((inputFileName  "${INPUT_FILENAME}")
    (outputFileName "${OUTPUT_FILENAME}")
    (azimuth        ${AZIMUTH})
    (elevation      ${ELEVATION})
    (depth          ${DEPTH})
    (offsetX        ${OFFSET_X})
    (offsetY        ${OFFSET_Y})
    (waterlevel     ${WATERLEVEL})
    (ambient        ${AMBIENT})
    (compensate     ${COMPENSATE})
    (invert         ${INVERT})
    (tiled          ${TILED})
    (hueRange       ${HUE_RANGE})
    (hueOffset      ${HUE_OFFSET})
    (lightness      ${LIGHTNESS})
    (saturation     ${SATURATION})
    (overlap        ${OVERLAP})
    (image          (car (gimp-file-load RUN-NONINTERACTIVE inputFileName inputFileName)))
    (imageType      (if (not (defined? 'gimp-image-get-active-layer))
                       ; New GIMP 3.0 API:
                       (car (gimp-image-get-base-type image))
                       ; Old GIMP 2.x API:
                       (car (gimp-image-base-type image)))
                    )
    (layer          (if (not (defined? 'gimp-image-get-active-layer))
                       ; New GIMP 3.0 API:
                       (car (list (vector-ref (car (gimp-image-get-selected-layers image)) 0)))
                       ; Old GIMP 2.x API:
                       (car (gimp-image-get-active-layer image)))
                    )
   )

   ; ------ Apply filter ----------------------------------------------------
   ; Step 1: Adjust hue
   (if (defined? 'gimp-drawable-hue-saturation)
      (begin  ; New GIMP > 2.8 API:
         ; New GIMP > 2.8 API:
         (gimp-drawable-hue-saturation layer hueRange hueOffset 0.0 0.0 0.0)
      )
      (begin  ; Old GIMP <= 2.8 API:
         (gimp-hue-saturation layer hueRange hueOffset 0.0 0.0 0.0)
      )
   )

   ; Step 2: Apply Bump Map effect
   (if (defined? 'gimp-drawable-merge-new-filter)
      (begin  ; New GIMP >= 3.0 API:
        (gimp-drawable-merge-new-filter layer "gegl:bump-map" 0 LAYER-MODE-REPLACE 1.0
            "azimuth"    azimuth
            "elevation"  elevation
            "depth"      depth
            "offset-x"   offsetX
            "offset-y"   offsetY
            "waterlevel" waterlevel
            "ambient"    ambient
            "compensate" compensate
            "invert"     invert
            "tiled"      tiled
            "type"       "linear"
        )
      )
      (begin  ; Old GIMP < 3.0 API:
         (plug-in-bump-map RUN-NONINTERACTIVE image layer layer
           azimuth elevation depth
           offsetX offsetY waterlevel ambient
           compensate invert tiled)
      )
   )

   ; Step 3: Adjust saturation, lightness, and overlap
   (if (defined? 'gimp-drawable-hue-saturation)
      (begin  ; New GIMP > 2.8 API:
         ; New GIMP > 2.8 API:
         (gimp-drawable-hue-saturation layer hueRange 0.0 lightness saturation overlap)
      )
      (begin  ; Old GIMP <= 2.8 API:
         (gimp-hue-saturation layer hueRange 0.0 lightness saturation overlap)
      )
   )


   ; ------ Save result -----------------------------------------------------
   (if (defined? 'gimp-drawable-merge-new-filter)
      (begin  ; New GIMP >= 3.0 API:
         ; FIXME: This does not work when running in GIMP 2.x!
         ; (file-png-export
         ;    #:run-mode         RUN-NONINTERACTIVE
         ;    #:image            image
         ;    #:file             outputFileName
         ;    #:options          -1
         ;    #:interlaced       TRUE
         ;    #:compression      6
         ;    #:bkgd             TRUE
         ;    #:offs             FALSE
         ;    #:phys             TRUE
         ;    #:time             TRUE
         ;    #:save-transparent FALSE
         ;    #:optimize-palette TRUE
         ; )
         (file-png-export RUN-NONINTERACTIVE image
            outputFileName -1
            TRUE 6 TRUE FALSE TRUE TRUE FALSE TRUE)
      )
      (begin  ; Old GIMP < 3.0 API:
         (file-png-save2 RUN-NONINTERACTIVE image
            (car (gimp-image-get-active-layer image))
            outputFileName outputFileName
            TRUE 6 TRUE TRUE FALSE TRUE TRUE FALSE TRUE)
      )
   )

   ; ------ Clean up --------------------------------------------------------
   (gimp-image-delete image)
)
(gimp-quit TRUE)
EOF
) | env LC_ALL=C.UTF-8 HOME=/tmp "${GIMP_CONSOLE}" ${GIMP_OPTIONS} 2>&1 | \
(
   if [ ${VERBOSE} -ne 0 ] ; then
      cat
   else
      grep -vE "^ts>|(#t)|^Copyright|Welcome to (GIMP|TinyScheme)|^using gegl copy|^gimp_color_transform_new: using babl|scriptfu-WARNING|-WARNING|^Please use named arguments:|^script quit with code:|^$" || true
   fi
)


# ====== Check result =======================================================
if [ ! -e "${OUTPUT_FILENAME}" ] ; then
   echo >&2 "ERROR: ${OUTPUT_FILENAME} has not been produced. Something went wrong!"
   exit 1
fi
