#!/bin/bash

GIMP=/Applications/GIMP.app/Contents/MacOS/gimp

function usage {
    echo "Usage: tronify [-n <threads>] [-t] [-d <frames>] <dir-with-frames>"
    echo
    echo "Frames must be named as frame-nnn.png (or jpg, ...) where 'nnn' is a zero-padded numerical index of arbitrary length."
    echo "-n <threads>: number of gimp threads to run"
    echo "-t: test mode; will erase all output and only do a few frames."
    echo "-d <frames>: delete output <frames> for reprocessing, given as 5,8,42-55,63,..."
    exit 2
}

if [ ! -f "tronify_dir.py" ]; then
    echo "Missing tronify_dir.py in this directory."
    exit 1
fi

# Copy tronify_dir.py into the GIMP plug-ins directory.
/bin/cp -f tronify_dir.py ~/Library/Application\ Support/GIMP/2.10/plug-ins/.
chmod 700 ~/Library/Application\ Support/GIMP/2.10/plug-ins/tronify_dir.py

dir=""
numThreads=2
testMode=0
frames=""
while [[ $# -gt 0 ]]; do
    case $1 in
        -n)
        numThreads=$2
        shift
        shift
        ;;
        -t|--test)
        testMode=1
        shift
        ;;
        -d)
        frames=$2
        shift
        shift
        ;;
        *) # unknown option; needs to be a path
        if [[ ${1:0:1} == "-" ]]; then usage; fi
        dir=$1
        shift
        break
        ;;
    esac
done
if [ "X$dir" == "X" ]; then usage; fi
if [ "X$1" != "X" ]; then usage; fi
if [[ "X$numThreads" != "X" ]] && ! [[ "$numThreads" =~ ^[1-9][0-9]?$ ]]; then
    echo "n must be 1-99 threads"
    usage
fi
if [ ! -d "$dir" ]; then
    echo "invalid directory: $dir"
    exit 2
fi

# Convert dir into absolute dir.
here=`pwd`
cd "$dir"
dir=`pwd`
cd "$here"

# Remove any done frames based on "-d".
if [ "X$frames" != "X" ]; then
    cd "$dir"
    for f in $(echo $frames | sed "s/,/ /g"); do
        if [[ "$f" =~ ^[0-9]+$ ]]; then
            rm -f `ls | egrep "frame-0*$f\.done\."`
        else if [[ "$f" =~ ^[0-9]+-[0-9]+$ ]]; then
            f1=`echo $f | awk -F"-" '{print $1}'`
            f2=`echo $f | awk -F"-" '{print $2}'`
            for (( i=$f1; i<=$f2; i++ )); do
                rm -f `ls | egrep "frame-0*$i\.done\."`
            done
        fi
        fi
    done
    cd "$here"
fi

function cleanLogs {
    if [ ! "$(ls gimp-err-[[:digit:]].txt 2>/dev/null)" ]; then
        return
    fi
    cat `ls gimp-err-*.txt 2>/dev/null | egrep "err-\d{1,2}.txt"` | \
        grep -v "deprecated" | \
        grep -iv "gegl" | \
        grep -v "gimp.main(None" | \
        grep -v "gimp_wire_read" | \
        grep -v "not loaded" | \
        grep -v "Reason:" | \
        grep -v "Referenced from" | \
        grep -v "terminated" | \
        grep -v "successfully" > gimp-err.txt
    /bin/rm -f gimp*-[[:digit:]].txt
    /bin/rm -f gimp*-[[:digit:]][[:digit:]].txt
    if [ ! -s gimp-err.txt ]; then
        /bin/rm -f gimp-err.txt
    fi
}

function displayTime {
    local T=$1
    local D=$((T/60/60/24))
    local H=$((T/60/60%24))
    local M=$((T/60%60))
    local S=$((T%60))
    (( $D > 0 )) && printf '%dd:' $D
    (( $H >= 0 )) && printf '%02d:' $H
    (( $M >= 0 )) && printf '%02d:' $M
    printf '%02d\n' $S
}

function getFileCounts {
    local numOut=`ls "$dir" | egrep "frame-\d{1,9}\.done\..{3}$" | wc -l | tr -d '[:space:]'`
    local numLeft=$(($numIn - $numOut))
    echo "$numOut $numLeft"
}

function removeEmptyFiles {
    # Remove any zero-byte results from GIMP not finishing.
    find "$dir" -maxdepth 1 -name "*.done.???" -size 0 -delete
}

function showStats {
    local t1=`date +%s`
    local n1=`ls "$dir" | egrep "frame-\d{1,9}\.done\..{3}$" | wc -l | tr -d '[:space:]'`
    local t=$(($t1 - $t0))
    local n=$(($n1 - $n0))
    (( $t > 0 )) && a=`bc <<< "scale=4; $n/$t"` || a='0'
    local dt=`displayTime $t`
    (( $n == 1 )) && frame='frame' || frame='frames'
    getFileCounts | awk '{split($0, a, " "); printf "Done: %s, Remaining: %s", a[1], a[2]}'
    echo "; processed $n $frame in $dt ($a fps)"
}

function showStatsLoop {
    while ls /tmp/tronify_dir.* >/dev/null 2>&1; do
        line=`showStats`
        echo -ne "\r\033[2K$line." # \r is carriage return, \033[2K erases line
        # Show less expensive dots every second for 5 seconds.
        counter=0
        while ls /tmp/tronify_dir.* >/dev/null 2>&1; do
            sleep 1
            echo -ne "."
            counter=$[$counter+1]
            (( $counter == 5)) && break
        done
    done
}

function stopTronify {
    # Kill the tronify threads first to prevent them from while-looping up more GIMPs.
    for i in $(seq 1 $numThreads); do
        kill -9 ${tpid[$i]} >/dev/null 2>&1
    done
    # Now kill the existing GIMPs and greps.
    for i in $(seq 1 $numThreads); do
        kill -9 `cat /tmp/tronify_dir.$i | awk -F" " '{print $1}'` >/dev/null 2>&1
        kill -9 `cat /tmp/tronify_dir.$i | awk -F" " '{print $2}'` >/dev/null 2>&1
    done
    # Remove temp thread ID and pipe files.
    /bin/rm -f /tmp/tronify_dir.*
    /bin/rm -f /tmp/tronify_dir_outpipe.*
    # Remove last output file because it might be a crashed buffer dump.
    # Avoid argument list too long issues.
    /bin/rm -f `ls -art "$dir" | egrep ".+\.done\..{3}$" | tail -n 1`
    STOP_TRONIFY=1
}

function tronThread {
    threadId=$1
    # In order to prune out "GIMP is started as MacOS application", use named pipes and grep -v line-buffered.
    /bin/rm -f /tmp/tronify_dir_outpipe.$threadId
    mkfifo /tmp/tronify_dir_outpipe.$threadId
    $GIMP -i -b "(python-fu-tronify-dir RUN-NONINTERACTIVE \"/tmp/tronify_dir_outpipe.$threadId\" $threadId $numThreads \"$dir\" $testMode)" -b "(gimp-quit 0)" >/tmp/tronify_dir_outpipe.$threadId 2>gimp-err-$threadId.txt & pid1=$!
    grep -v --line-buffered "GIMP is started as MacOS application\|GIMP was built with MacPorts" /tmp/tronify_dir_outpipe.$threadId & pid2=$!
    echo $pid1 $pid2 >/tmp/tronify_dir.$threadId
    wait $pid2 # the grep won't stop until its gimp has stopped; no need for 'wait $pid1'
    removeEmptyFiles
    /bin/rm -f /tmp/tronify_dir_outpipe.$threadId
    /bin/rm -f /tmp/tronify_dir.$threadId
}

trap "stopTronify" SIGINT

removeEmptyFiles
# If testMode, delete all done files.
if [ $testMode == 1 ]; then
    # Avoid argument list too long issues.
    find "$dir" -maxdepth 1 -name "*.done.???" -delete
fi
t0=`date +%s`
n0=`ls "$dir" | egrep "frame-\d{1,9}\.done\..{3}$" | wc -l | tr -d '[:space:]'`
numIn=`ls "$dir" | egrep "frame-\d{1,9}\..{3}$" | wc -l | tr -d '[:space:]'`

# Quit only when all frames are processed.
numLeft=`getFileCounts | awk '{split($0, a, " "); print a[2]}'`
STOP_TRONIFY=0
while [[ $numLeft -gt 0 && STOP_TRONIFY -ne 1 ]]; do
    # Process with multiple threads.
    for i in $(seq 1 $numThreads); do
        tronThread $i & tpid[$i]=$!
    done
    sleep 2
    showStatsLoop &
    wait
    removeEmptyFiles
    (( $testMode == 1 )) && break
    numLeft=`getFileCounts | awk '{split($0, a, " "); print a[2]}'`
done

cleanLogs
line=`showStats`
echo -e "\r\033[2K$line"
