#!/usr/bin/env 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 [-T|--tile-type triangles|squares|hexagons|octagons] [-S|--tile-size size] [-H|--tile-height height] [-N|--tile-neatness neatness] [-F|--tile-surface rough|smooth] [-X|--tile-spacing spacing] [-I|--tile-allow-split on|off] [-C|--color-variation variation] [-G|--color-averaging on|off] [-A|--antialiasing on|off] [-L|--light-direction degrees] [-w|--verbose] [-q|--quiet] [-h|--help] [-v|--version]"
   exit "${exitCode}"
}


# ###### Version ############################################################
version () {
   echo "gs-mosaic @BUILD_MAJOR@.@BUILD_MINOR@.@BUILD_PATCH@"
   exit 0
}



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

# ====== Handle arguments ===================================================
GETOPT="$(PATH=/usr/local/bin:$PATH which getopt)"
# shellcheck disable=SC2068
options="$(${GETOPT} -o T:S:H:N:F:X:I:C:G:A:L:wqhv --long tile-type:,tile-size:,tile-height:,tile-neatness:,tile-surface:,tile-spacing:,tile-allow-split:,color-variation:,color-averaging:,antialiasing:,light-direction:,verbose,quiet,help,version -a -- "$@")"
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
   usage 1
fi
eval set -- "${options}"

TILE_TYPE=6
TILE_TYPESTRING="hexagons"
TILE_SIZE=15.0
TILE_HEIGHT=4.0
TILE_NEATNESS=0.65
TILE_SURFACE="TRUE"
TILE_SPACING=4.0
TILE_ALLOW_SPLIT="TRUE"
COLOR_VARIATION=0.2
COLOR_AVERAGING="TRUE"
ANTIALIASING="TRUE"
LIGHT_DIRECTION=135.0
VERBOSE=0
while [ $# -gt 0 ] ; do
   case "$1" in
      -T | --tile-type)
         TILE_TYPESTRING="$2"
         if [ "${TILE_TYPESTRING}" = "squares" ] ; then
            TILE_TYPE=0
         elif [ "${TILE_TYPESTRING}" = "hexagons" ] ; then
            TILE_TYPE=1
         elif [ "${TILE_TYPESTRING}" = "octagons" ] ; then
            TILE_TYPE=2
         elif [ "${TILE_TYPESTRING}" = "triangles" ] ; then
            TILE_TYPE=3
         else
            echo >&2 "ERROR: Invalid value ${TILE_TYPESTRING} for -T|--tile-type!"
            exit 1
         fi
         shift 2
         ;;
      -S | --tile-size)
         TILE_SIZE="$2"
         if [[ ! "${TILE_SIZE}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($TILE_SIZE < 1.0) || ($TILE_SIZE > 1000.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${TILE_SIZE} for -S|--tile-size!"
            exit 1
         fi
         shift 2
         ;;
      -H | --tile-height)
         TILE_HEIGHT="$2"
         if [[ ! "${TILE_HEIGHT}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($TILE_HEIGHT < 1.0) || ($TILE_HEIGHT > 1000.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${TILE_HEIGHT} for -H|--tile-height!"
            exit 1
         fi
         shift 2
         ;;
      -N | --tile-neatness)
         TILE_NEATNESS="$2"
         if [[ ! "${TILE_NEATNESS}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($TILE_NEATNESS < 0.0) || ($TILE_NEATNESS > 1.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${TILE_NEATNESS} for -N|--tile-neatness!"
            exit 1
         fi
         shift 2
         ;;
      -F | --tile-surface)
         if [ "$2" == "smooth" ] ; then
            TILE_SURFACE="FALSE"
         elif [ "$2" == "rough" ] ; then
            TILE_SURFACE="TRUE"
         else
            echo >&2 "ERROR: Invalid value ${TILE_SURFACE} for -F|--tile-surface!"
            exit 1
         fi
         shift 2
         ;;
      -I | --tile-allow-split)
         if [[ "$2" =~ ^(on|ON|true|TRUE|yes|YES|1)$ ]] ; then
            TILE_ALLOW_SPLIT="TRUE"
         elif [[ "$2" =~ ^(off|OFF|false|FALSE|no|NO|0)$ ]] ; then
            TILE_ALLOW_SPLIT="FALSE"
         else
            echo >&2 "ERROR: Invalid value $2 for -I|--tile-allow-split!"
            exit 1
         fi
         shift 2
         ;;
      -X | --tile-spacing)
         TILE_SPACING="$2"
         if [[ ! "${TILE_SPACING}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($TILE_SPACING < 0.0) || ($TILE_SPACING > 1000.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${TILE_SPACING} for -X|--tile-spacing!"
            exit 1
         fi
         shift 2
         ;;
      -C | --color-variation)
         COLOR_VARIATION="$2"
         if [[ ! "${COLOR_VARIATION}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($COLOR_VARIATION < 0.0) || ($COLOR_VARIATION > 1.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${COLOR_VARIATION} for -C|--color-variation!"
            exit 1
         fi
         shift 2
         ;;
      -G | --color-averaging)
         if [[ "$2" =~ ^(on|ON|true|TRUE|yes|YES|1)$ ]] ; then
            COLOR_AVERAGING="TRUE"
         elif [[ "$2" =~ ^(off|OFF|false|FALSE|no|NO|0)$ ]] ; then
            COLOR_AVERAGING="FALSE"
         else
            echo >&2 "ERROR: Invalid value $2 for -g|--color-averaging!"
            exit 1
         fi
         shift 2
         ;;
      -A | --antialiasing)
         if [[ "$2" =~ ^(on|ON|true|TRUE|yes|YES|1)$ ]] ; then
            ANTIALIASING="TRUE"
         elif [[ "$2" =~ ^(off|OFF|false|FALSE|no|NO|0)$ ]] ; then
            ANTIALIASING="FALSE"
         else
            echo >&2 "ERROR: Invalid value $2 for -A|--antialiasing!"
            exit 1
         fi
         shift 2
         ;;
      -L | --light-direction)
         LIGHT_DIRECTION="$2"
         if [[ ! "${LIGHT_DIRECTION}" =~ ^([-]{0,1})([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)$  ]] || \
            awk "BEGIN { exit ( ($LIGHT_DIRECTION < 0.0) || ($LIGHT_DIRECTION > 360.0) ? 0 : 1) }" ; then
            echo >&2 "ERROR: Invalid value ${LIGHT_DIRECTION} for -C|--light-direction!"
            exit 1
         fi
         shift 2
         ;;
      -w | --verbose)
         VERBOSE=1
         shift
         ;;
      -q | --quiet)
         VERBOSE=0
         shift
         ;;
      -h | --help)
         usage 0
         ;;
      -v | --version)
         version
         # shift
         ;;
      --)
         shift
         break
         ;;
      *)
         usage 1
         ;;
  esac
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="$(which gimp-console 2>/dev/null || 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}")
    (tileType         ${TILE_TYPE})
    (tileTypeString   "${TILE_TYPESTRING}")
    (tileSize         ${TILE_SIZE})
    (tileHeight       ${TILE_HEIGHT})
    (tileNeatness     ${TILE_NEATNESS})
    (colorVariation   ${COLOR_VARIATION})
    (colorAveraging   ${COLOR_AVERAGING})
    (tileSurface      ${TILE_SURFACE})
    (tileAllowSplit   ${TILE_ALLOW_SPLIT})
    (tileSpacing      ${TILE_SPACING})
    (antialiasing     ${ANTIALIASING})
    (lightDirection   ${LIGHT_DIRECTION})
    (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 ----------------------------------------------------
   ; First, make sure that the image is RGB, not indexed color mode!
   (if (not (= RGB imageType))
      (gimp-image-convert-rgb image))

   ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   ; GEGL Mosaic options: https://gegl.org/operations/gegl-mosaic.html
   ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   (cond( (not(defined? 'plug-in-mosaic))
      ; New GIMP 3.0 API:
      (gimp-drawable-merge-new-filter layer "gegl:mosaic" 0 LAYER-MODE-REPLACE 1.0
          "tile-type"        tileTypeString
          "tile-size"        tileSize
          "tile-height"      tileHeight
          "tile-neatness"    tileNeatness
          "color-variation"  colorVariation
          "color-averaging"  colorAveraging
          "tile-surface"     tileSurface
          "tile-allow-split" tileAllowSplit
          "tile-spacing"     tileSpacing
          "antialiasing"     TRUE
          "light-dir"        lightDirection
          "seed"             0
      )
   )
   (else
      ; Old GIMP 2.x API:
      (plug-in-mosaic RUN-NONINTERACTIVE image layer
         tileSize tileHeight tileSpacing
         tileNeatness TRUE lightDirection colorVariation TRUE TRUE
         tileType tileSurface FALSE
   )))

   ; ------ 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
