#! /usr/local/bin/bash
#
# flix: Convert ePiX file(s) to png/mng/gif
#
# Options: --help for usage, --version for version and license
#
# Compiler flags may be specified on the command line or in the config
# file ~/.epixrc, just as for epix.
#
# LaTeX packages may be specified on the command line or in the config
# file ~/.dvipsrc, just as for elaps
#
# Copyright (C) 2004, 2005, 2006, 2007
# Andrew D. Hwang   <For address, do "flix -V">
# 

. "/usr/local/share/epix/epix-lib.sh"

EPIX_CONFIG_FILE="$HOME/.epixrc"
FLIX_CONFIG_FILE="$HOME/.flixrc"

# Search these extensions in order of preference
FLIX_EXTENSIONS="flx xp cc c C cpp" # or NULL
FLIX_DOT_EXT=".${FLIX_EXTENSIONS// /|.}" # for usage message

# Path to standard header and library and compiler
HDR_PATH="-I/usr/local/include"
LIB_PATH="-L/usr/local/lib/epix"
COMPILER="c++"

# default values
FLIX_TMAX=1
FLIX_FRAME_COUNT=24
FLIX_DELAY=8

# filename variables
declare FLIX_FRAME    # $EPIX_INROOT-frameXX
declare FLIX_MOVIE    # actual output filename

FLIX_SUFFIX=mng

# flags
FLIX_PROGRESS="yes"   # Show progress bar?
FLIX_ONLY_EEPIC="no"  # Create eepic files only?
FLIX_ONLY_PNG="no"    # Create png and eepic files only?
FLIX_SAVE_EEPIC="no"  # Save eepic files after run?
FLIX_SAVE_PNG="no"    # Save png files after run?

FLIX_FRAME_PAD=""     # zero or more zeros, dep. on number of frames
FLIX_1ST_FRAME=       # filename, to test success of compile

trap '[ -n "$EPIX_TEMPDIR" ] && rm -rf "$EPIX_TEMPDIR"' 0 1 2 3 7 10 13 15

# options for external programs
declare -a ELAPS_OPTS

# compiler variables
declare GXX_INCLUDES
declare GXX_LIBDIRS
GXX_LIBS="-lm"
GXX_POSTLIBS="-lepix"
declare -a GXX_DEFINES

function flix_help 
{
cat <<HELP
Options:
    -h, --help
      Show this message and exit

    -V, --version, --gpl
      Show version and license

    -v, --verbose
      Show success/failure messages

    -d, --delay
      Set frame delay in 1/100 sec (default 8)

    --frames
      Set number of frames (default 24).

    --gif
      Create gif output instead of mng

    --no-defaults
      Do not use default include/library directories or libraries

    --pst
      Write frames using PSTricks macros (useful only with --save-eepic)

    --tikz
      Write frames using tikz macros (useful only with --save-eepic)

    --eepic
      Write frames using eepic macros (useful only with --save-eepic)

    --only-eepic
      Create only eepic files (default no); implies --save-eepic

    --only-png
      Create only png files (default no); implies --save-png

    -o, --output
      Specify output file explicitly (extension optional)

    --save-eepic
      Save eepic files (default no)

    --save-png
      Save png files (default no)

    -t, --tmax
      Set maximum time on animation interval (default 1.0).

    -H, --huge
      Use hugelatex (if available) to compile frames

    -D*
      Pass preprocessor definition to the compiler

    -I*, -L*, -l*, -W*, -i*, -u, -x
      Passed to g++ (q.v.)

    Other options are passed to elaps (q.v.)

HELP
ePiX_bugreport

} # End of flix_help


function flix_pad_init()
{
    let TMP_CT=$((FLIX_FRAME_COUNT/10))
    while [ $TMP_CT -ne 0 ]
        do 
            let TMP_CT=$((TMP_CT/10))
            FLIX_FRAME_PAD="0$FLIX_FRAME_PAD"
    done
}


# Parse command line/config file for compiler flags/options
function flix_parse_options 
{
while [ "$1" != "${1#-}" ]; do
    case "$1" in

        -v|--verbose)
            if [ -z "$2" ]; then 
                echo "Please use -V for version"
                exit 0
            fi
            EPIX_STDOUT="$SYSTEM_STDOUT"
            FLIX_PROGRESS="no"
            shift; continue
            ;;

        -vv)
            echo "Discontinued option -vv; please use -v for verbose output"
            exit 0
            ;;

        -h|--help)
	    ePiX_usage flix $FLIX_DOT_EXT
	    flix_help
	    exit 0
	    ;;

        -V|--version|--gpl)
            ePiX_version flix
            ePiX_license
            exit 0
	    ;;

        -d|--delay)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            FLIX_DELAY="$2"
            shift 2; continue
            ;;

        --frames)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            FLIX_FRAME_COUNT="$2"
            shift 2; continue
            ;;

        --gif)
            FLIX_SUFFIX=gif
            shift; continue
            ;;

        --no-defaults)
            GXX_POSTLIBS=
	    HDR_PATH=
	    LIB_PATH=
            ELAPS_OPTS=("${ELAPS_OPTS[@]}" "$1")
            shift; continue
            ;;

	--pst)
	    GXX_DEFINES=("${GXX_DEFINES[@]}" "-DEPIX_FMT_PSTRICKS")
	    shift; continue
	    ;;

	--tikz)
	    GXX_DEFINES=("${GXX_DEFINES[@]}" "-DEPIX_FMT_TIKZ")
	    shift; continue
	    ;;

	--eepic)
	    GXX_DEFINES=("${GXX_DEFINES[@]}" "-DEPIX_FMT_EEPIC")
	    shift; continue
	    ;;

        --only-eepic)
            FLIX_ONLY_EEPIC="yes"
            FLIX_SAVE_EEPIC="yes"
            shift; continue
            ;;

        --only-png)
            FLIX_ONLY_PNG="yes"
            FLIX_SAVE_PNG="yes"
            shift; continue
            ;;

        -o|--output)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            FLIX_OUTFILE="$2"
            if [ "$FLIX_OUTFILE" = "${FLIX_OUTFILE%.mng}.mng" ]; then
                FLIX_SUFFIX=mng
                EPIX_OUTROOT="${FLIX_OUTFILE%.mng}"

            elif [ "$FLIX_OUTFILE" = "${FLIX_OUTFILE%.gif}.gif" ]; then
                FLIX_SUFFIX=gif
                EPIX_OUTROOT="${FLIX_OUTFILE%.gif}"

            else
                EPIX_OUTROOT="$FLIX_OUTFILE"

            fi
            shift 2; continue
            ;;

        # Deliberately undocumented
        -q|--quiet)
            EPIX_STDERR="$SYSTEM_DEVNULL"
            shift; continue
            ;;

        --save-eepic)
            FLIX_SAVE_EEPIC="yes"
            shift; continue
            ;;

        --save-png)
            FLIX_SAVE_PNG="yes"
            shift; continue
            ;;

        -t|--tmax)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            FLIX_TMAX="$2"
            shift 2; continue
            ;;

        -H|--huge)
	    ELAPS_OPTS=("${ELAPS_OPTS[@]}" "$1")
	    shift 1; continue
            ;;

        -D)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            if [ "$2" != "${2#-}" ]; then
                GXX_DEFINES=("${GXX_DEFINES[@]}" "-D$2")
                shift 2; continue
            else
                ePiX_warn "Ignoring option \"$1\" followed by \"$2\""
                shift 1; continue
            fi
            ;;
 
        -D*)
            GXX_DEFINES=("${GXX_DEFINES[@]}" "$1")
            shift 1; continue
            ;;
 
        -I)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            GXX_INCLUDES="$GXX_INCLUDES -I$2"; shift 2; continue
            ;;

        -L)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            GXX_LIBDIRS="$GXX_LIBDIRS -L$2"; shift 2; continue
            ;;

        -l)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            GXX_LIBS="$GXX_LIBS -l$2"; shift 2; continue
            ;;

        -W)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            FLIX_WARNS="$FLIX_WARNS -W$2"; shift 2; continue
            ;;

        -I*)
            GXX_INCLUDES="$GXX_INCLUDES $1"; shift; continue
            ;;

        -L*)
            GXX_LIBDIRS="$GXX_LIBDIRS $1"; shift; continue
            ;;

        -l*)
            GXX_LIBS="$GXX_LIBS $1"; shift; continue
            ;;

        -W*)
            FLIX_WARNS="$FLIX_WARNS $1"; shift; continue
            ;;

            # Relatively obscure compiler options
        -i*|-u|-x)
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            FLIX_FLAGS="$FLIX_FLAGS $1 $2"; shift 2; continue
            ;;

        *)  # Pass other options to elaps
        if [ $# -lt 2 ]; then ePiX_die "Missing argument to \"$1\" option"; fi
            if [ "$2" != "${2#-}" ]; then
                ELAPS_OPTS=("${ELAPS_OPTS[@]}" "$1")
                shift 1; continue
            else
                ELAPS_OPTS=("${ELAPS_OPTS[@]}" "$1" "$2")
                shift 2; continue
            fi
            ;;
    esac
done

# Assume remaining parameters are input files
FLIX_FILE_LIST=("$@")
} # End of flix_parse_options


# print frame numbers from 1 to arg; Mac OS X has no "seq"
function flix_seq()
{
    FINISH=$1;
    COUNTER=1

    while [ $COUNTER -le $FINISH ]
      do echo -n "$COUNTER "
      let COUNTER=COUNTER+1
    done
}

# compile infile outfile.exe
function flix_compile_files()
{
    if [ -z "$1" ]; then
        ePiX_die "No input file specified"
    fi

    # See epix-lib.sh
    mkdir "$EPIX_TEMPDIR" || ePiX_die "Can't create \"${EPIX_TEMPDIR}\""

    # file counts
    local processed=0
    local success=0
    local failure=0

    # N.B. Global variables named EPIX_XX
    for EPIX_INFILE in "${FLIX_FILE_LIST[@]}"; do

        epix_parse_filename "$EPIX_INFILE" "$FLIX_EXTENSIONS"
        let processed=processed+1

        if [ "$EPIX_NOTFOUND" = "yes" ]; then ePiX_fail && continue ; fi

        : ${EPIX_OUTROOT=$EPIX_INROOT}
        FLIX_FRAME="$EPIX_OUTROOT"-frame

        # Temporary binary; use ".exe" for Cygwin
        { TEMP_BIN="$EPIX_INROOT".exe && touch "${EPIX_TEMPDIR}/$TEMP_BIN" ; } ||
        { ePiX_fail "No write permission in \"${EPIX_TEMPDIR}\"?!" && \
          continue ; }

        TEMPFILES=("{TEMPFILES[@]}" "${EPIX_TEMPDIR}/$TEMP_BIN")

        # Create symlink to input file with appropriate extension for compiler
        TEMP_INPUT="${TEMP_BIN}-tmp.cc"

        TEMPFILES=("{TEMPFILES[@]}" "${EPIX_TEMPDIR}/$TEMP_INPUT")

        { cd ${EPIX_TEMPDIR} && \
            cp -p "../$EPIX_INFILE" "$TEMP_INPUT" && cd .. ; } ||
          { ePiX_fail "Couldn't create \"${EPIX_TEMPDIR}/$TEMP_INPUT\"" && \
            continue ; }

        # Compile executable
        TMP_CMD=($COMPILER "${EPIX_TEMPDIR}/$TEMP_INPUT" $FLIX_WARNS \
            "${GXX_DEFINES[@]}" -o "${EPIX_TEMPDIR}/$TEMP_BIN" \
            $HDR_PATH $GXX_INCLUDES $LIB_PATH $GXX_LIBDIRS $GXX_LIBS \
            $GXX_POSTLIBS $FLIX_FLAGS)

        ePiX_command "${TMP_CMD[@]}"

        # Write animation frames
        if [ ! -x "${EPIX_TEMPDIR}/$TEMP_BIN" ]; then
            ePiX_fail "Couldn't compile executable \"${EPIX_TEMPDIR}/$TEMP_BIN\""
            continue
        fi

        # purge old files
        rm -f "$FLIX_FRAME"*.eepic  # eepics always overwritten

        if [ "$FLIX_ONLY_EEPIC" = "no" ]; then
            # some pngs clobbered => remove all
            rm -f "$FLIX_FRAME"*.png
        fi

        # and create new frames
        for i in $(flix_seq $FLIX_FRAME_COUNT); do
            flix_png "${EPIX_TEMPDIR}/$TEMP_BIN" $i

            # bomb out if we can't create first new frame
            if [ ! -f "${FLIX_1ST_FRAME}" ]; then
                ePiX_fail "Couldn't create frames"
                continue 2
            fi
        done

        if [ "$FLIX_ONLY_PNG" = "no" -a "$FLIX_ONLY_EEPIC" = "no" ]; then
          FLIX_MOVIE="${EPIX_OUTROOT}.$FLIX_SUFFIX"
          TMP_CMD=(convert -delay $FLIX_DELAY "$FLIX_FRAME"*.png "$FLIX_MOVIE")

            ePiX_command "${TMP_CMD[@]}"

            if [ -f "$FLIX_MOVIE" ]; then
                let success=success+1
            else
                ePiX_fail "Couldn't assemble frames"
                continue
            fi
        fi

        echo -e "\nTranscript written on $EPIX_LOGFILE" > "$EPIX_STDOUT"

        # clean up
        if [ "$EPIX_OUTROOT" = "$EPIX_INROOT" ]; then unset EPIX_OUTROOT; fi

        rm -f "$FLIX_FRAME"*.eps "$FLIX_FRAME"*.log

        if [ "$FLIX_SAVE_EEPIC" = "no" ]; then
            rm -f "$FLIX_FRAME"*.eepic
        fi

        if [ "$FLIX_SAVE_PNG"   = "no" ]; then
            rm -f "$FLIX_FRAME"*.png
        fi

        # Newline after progress bar?
        if [ "$FLIX_PROGRESS" = "yes" ]; then echo; fi

    done # for EPIX_INFILE in $FLIX_INFILE_LIST

    if [ $processed -gt 1 ]; then
        echo "$processed files processed: $success movie(s) written, $failure failed" > "$EPIX_STDERR"
    fi

    if [ -d "$EPIX_TEMPDIR" ]; then rm -Rf "$EPIX_TEMPDIR"; fi
} # end of flix_compile_files()


# Prints ....|....10....|....20
function progress_bar()
{
    if [ $(($1 % 10)) -eq 0 ]; then
        echo -n "$1"
    elif [ $(($1 % 5)) -eq 0 ]; then
        echo -n "|"
    else
        echo -n "."
    fi
}


# Create a single eepic, eps, and/or png file:
#   flix_png file.exe frame_number
function flix_png()
{
    local TEMP_BIN="$1"
    local INDEX="$2"

    TIME=$(echo ${FLIX_TMAX}*${INDEX} | bc)

    # Calculate number of 0s in output filename
    TMP_CT=$(( ${INDEX}/10 ))
    TMP_PAD="$FLIX_FRAME_PAD"

    # progress bar
    if [ "$FLIX_PROGRESS" = "yes" ]; then
        progress_bar $INDEX
    else
        ePiX_msg "Frame $INDEX.eps"
    fi

    # each time we're divisible by ten, remove a 0 from padding
    while [ $TMP_CT -ne 0 ]
    do
        TMP_CT=$(($TMP_CT/10))
        TMP_PAD="${TMP_PAD%%0}"
    done

    INDEX="$TMP_PAD$INDEX" # pad with 0s
    if [ $2 -eq 1 ]; then FLIX_1ST_FRAME="$FLIX_FRAME"${TMP_PAD}1.eepic; fi

    TMP_NAME="${FLIX_FRAME}${INDEX}"

    "./$TEMP_BIN" $TIME $FLIX_FRAME_COUNT > "$TMP_NAME".eepic

    if [ $? -ne 0 ]; then
        ePiX_warn "Could not create frames"
        return
    fi

    if [ "$FLIX_ONLY_EEPIC" = "no" ]; then # create eps
        TEMPFILES=("{TEMPFILES[@]}" "$TMP_NAME".eps)

        TMP_CMD=(elaps "${ELAPS_OPTS[@]}" -o "$TMP_NAME".eps "$TMP_NAME".eepic)
        ePiX_command "${TMP_CMD[@]}"

        TMP_CMD=(convert "$TMP_NAME".eps "$TMP_NAME".png)
        ePiX_command "${TMP_CMD[@]}"
    fi
} # end of flix_png()

## Script proper starts here ##

if [ $# -eq 0 ]; then 
    ePiX_usage flix $FLIX_DOT_EXT
    flix_help
    exit 0
fi

# Read options from config files, if any
if [ -f "$EPIX_CONFIG_FILE" ]; then
    flix_parse_options $(cat $EPIX_CONFIG_FILE | grep -v "#")
fi

if [ -f "$FLIX_CONFIG_FILE" ]; then
    flix_parse_options $(cat $FLIX_CONFIG_FILE | grep -v "#")
fi

flix_parse_options "$@"

flix_pad_init

# Suppress all warnings if none are requested
if [ "$FLIX_WARNS" = "" ]; then FLIX_WARNS="-w"; fi

flix_compile_files "${FLIX_FILE_LIST[@]}"

exit 0
