#!/bin/sh
# vim: ft=sh:sts=4:sw=4
set -f
_locale=$(locale)
LC_ALL=C

# Refuse to run as root.
# This doesn't reliably prevent running with privileges, but it's a start.
if command -v id >/dev/null 2>&1 && [ $(id -u) -eq 0 ]; then
    echo >&2 showansi will not run as root.
    exit 1
fi

# Determine our name.
if [ -n "$0" ]; then
    if [ "$(command -v showansi 2>/dev/null)" = "$0" ]; then
	_name=showansi
    else
	_name=$0
    fi
else
    _name=showansi
fi

# Check whether a variable is an integer.
# Return values are "exit codes" (0 means true).
# Accept non-emtpy strings containing only digits.
is_integer()
{
    case "${1#[+-]}" in
	*[!0123456789]*)    return 1;;
	'')		    return 1;;
	*)		    return 0;;
    esac
}

# Quote a string to remain uninterpreted by the shell
shquote()
{
    _unquoted="${1}"
    _quoted=""
    while [ -n "${_unquoted}" ]; do
	_char="$(printf %c "${_unquoted}")"
	_unquoted="${_unquoted#"${_char}"}"
	if [ "${_char}" = "'" ]; then
	    _quoted="${_quoted}'\\''"
	else
	    _quoted="${_quoted}${_char}"
	fi
    done
    printf "'%s'" "${_quoted}"
}

# Quote all backslashes in a string
escquote()
{
    _unquoted="${1}"
    _quoted=""
    while [ -n "${_unquoted}" ]; do
	_char="$(printf %c "${_unquoted}")"
	_unquoted="${_unquoted#"${_char}"}"
	test "${_char}" = '\' && _quoted="${_quoted}\\"
	_quoted="${_quoted}${_char}"
    done
    printf %s "${_quoted}"
}

# Exit handler to clean up temp dir
rmtmpdir()
{
    rm -fr "${_tmpdir}"
}

# Create a private temp dir and install handler to clean it.
# Prefer mktemp if available, fall back to constructing a name including
# our pid.
mktmpdir()
{
    trap rmtmpdir EXIT HUP INT TERM
    if command -v mktemp >/dev/null 2>&1; then
	_tmpdir="$(mktemp -d)"
    else
	_tmpdir="${TMPDIR:-/tmp}/tmp.showansi.$$"
	mkdir -m 700 "${_tmpdir}" >/dev/null 2>&1
    fi
}

# Version output
version()
{
    echo
    echo "This is showansi from dos2ansi v2.0"
    echo
    echo "WWW:      https://github.com/Zirias/dos2ansi"
    echo "Author:   Felix Palmen <felix@palmen-it.de>"
    echo "License:  BSD 2-clause (all rights reserved)"
    echo
}

usage() {
  echo "\
Usage: $1 -V
       $1 -h
       $1 [-Sensw] [-c columns] [-d dos2ansi] [-r rows]
            [--] [args]"
}

help() {
  usage "$1"
  echo "
    -S           Ignore SAUCE and other metadata and use all defaults or
                 values passed on the commandline.
                 Also passes ignoring SAUCE to \`dos2ansi\`.
    -V           Print version information and exit.
    -c columns   The number of columns for display, IOW, the width of the
                 terminal window.
                 Also passed to \`dos2ansi\` as the default canvas width (flag
                 \`-w\`).
    -d dos2ansi  Run dos2ansi to render the input file for the configured
                 pager to display.
                 default: \`/usr/local/bin/dos2ansi\`
    -e           Use an EGA font. This is ignored if the font type can be
                 deduced from SAUCE metadata unless \`-S\` is given.
                 default: from metadata or VGA
    -h           Print a help text and exit.
    -n           No letter spacing, which means to pick an 8px wide font.
                 This is ignored if spacing information is available from
                 SAUCE unless \`-S\` is given.
                 default: from metadata or spaced (9px)
    -r rows      The number of rows for display, IOW, the height of the
                 terminal window.
    -s           Assume square pixels, so, pick a font without aspect
                 correction. This is ignored if aspect information is
                 available from SAUCE unless \`-S\` is given.
                 default: from metadata or rectangular pixels
    -w           Use a wide font (for 40-columns or VGA50 or EGA43 modes),
                 which means a 9x8/8x8 or 18x16/16x16 font. This is ignored
                 if the font type can be deduced from SAUCE metadata unless
                 \`-S\` is given.
                 default: from metadata or regular width
    args         Arguments for \`dos2ansi\`, including the file to display.
                 Must be preceded by \`--\` if it contains flags (starting with
                 \`-\`). If it doesn't contain a file to display, the standard
                 input will be read and fed to \`dos2ansi\`."
}

# Print usage plus optional error and exit
err()
{
    usage "${_name}" >&2
    if [ -n "$1" ]; then
	echo >&2
	echo >&2 Error: $1
    fi
    exit 1
}

# Try to get an argument for a commandline flag
fetcharg()
{
    case "${_needarg}" in
	c*) is_integer "${1}" || err "Invalid number of columns"
	    _def_width="${1}"; _needarg="${_needarg#c}";;
	d*) _dos2ansi="${1}"; _needarg="${_needarg#d}";;
	r*) is_integer "${1}" || err "Invalid number of rows"
	    _def_height="${1}"; _needarg="${_needarg#r}";;
	*)  err "Unknown flag"
    esac
}

# Default settings before parsing commandline
_dos2ansi="/usr/local/bin/dos2ansi"
_d2a_args=""
_def_width=""
_def_height=""
_def_spacing=1
_def_aspect=0
_def_fontbase=vga
_def_fontvariant=""
_file=""
_nosauce=0
_nometa=0
_noinput=0
_needarg=""
_exec=exec

# Parse commandline part one: showansi flags
while true; do
    case "${1}" in
	--) shift; break;;
	-*) _flags="${1#-}"
	    while true; do
		case "${_flags}" in
		    V*) version; exit 0;;
		    S*) _nosauce=1; _nometa=1; _flags="${_flags#S}";;
		    c*) _needarg="${_needarg}c"; _flags="${_flags#c}";;
		    d*) _needarg="${_needarg}d"; _flags="${_flags#d}";;
		    e*) _def_fontbase=ega; _flags="${_flags#e}";;
		    h*) help "${_name}"; exit 0;;
		    n*) _def_spacing=0; _flags="${_flags#n}";;
		    r*) _needarg="${_needarg}r"; _flags="${_flags#r}";;
		    s*) _def_aspect=1; _flags="${_flags#s}";;
		    w*) _def_fontvariant=w; _flags="${_flags#w}";;
		    '')	break;;
		    *) fetcharg "${_flags}"; break;;
		esac
	    done
	    shift;;
	*)  test -z "${_needarg}" && break
	    fetcharg "${1}"
	    shift;;
    esac
done

# Parse commandline part two: dos2ansi flags and optional input file
_d2a_endflags=0
while test -n "${1}"; do
    test "${1}" = "--" || _file="${1}"
    _d2a_args="${_d2a_args} $(shquote "${1}")"
    case "${1}" in
	--) _d2a_endflags=1;;
	-*T*) test ${_d2a_endflags} -eq 0 && _noinput=1 && _nosauce=1;;
	-*s*) test ${_d2a_endflags} -eq 0 && _nosauce=1;;
    esac
    shift
done
test -n "${_needarg}" && err "Missing arguments for given flags"
if [ ${_noinput} -eq 1 ]; then
    _file=""
fi

# Default configuration values
TERMINAL=xterm
TERM_ARGS="-tn xterm-256color -bg black -fg lightgray -sl 0"
D2A_ARGS=-X
MAXWIDTH=200
MAXHEIGHT=60
PAGER="less --mouse --wheel-lines 3 -~QRScPs"
ADDLINES=1
SETTITLE=!
SETGEOM=-geometry
EXECUTE=-e
SHELL=/bin/sh
FONTSET=
FONT_VGA9X16=
FONT_VGA8X16=
FONT_VGA9X8=
FONT_VGA8X8=
FONT_EGA9X14=
FONT_EGA8X14=
FONT_EGA9X8=
FONT_EGA8X8=
FONT_AC_VGA9X16=
FONT_AC_VGA8X16=
FONT_AC_VGA9X8=
FONT_AC_VGA8X8=
FONT_AC_EGA9X14=
FONT_AC_EGA8X14=
FONT_AC_EGA9X8=
FONT_AC_EGA8X8=
MAXHEIGHT_VGA9X16=
MAXHEIGHT_VGA8X16=
MAXHEIGHT_VGA9X8=
MAXHEIGHT_VGA8X8=
MAXHEIGHT_EGA9X14=
MAXHEIGHT_EGA8X14=
MAXHEIGHT_EGA9X8=
MAXHEIGHT_EGA8X8=
MAXHEIGHT_AC_VGA9X16=
MAXHEIGHT_AC_VGA8X16=
MAXHEIGHT_AC_VGA9X8=
MAXHEIGHT_AC_VGA8X8=
MAXHEIGHT_AC_EGA9X14=
MAXHEIGHT_AC_EGA8X14=
MAXHEIGHT_AC_EGA9X8=
MAXHEIGHT_AC_EGA8X8=
USERRC=
USERFONTS=

# Read system-wide configuration
test -r "/usr/local/etc/dos2ansi/showansirc" && . "/usr/local/etc/dos2ansi/showansirc"

# Read per-user configuration (if enabled in system-wide configuration)
test -n "${USERRC}" && test -r "${USERRC}" && . "${USERRC}"

# Read configured fontset
if [ -n "${FONTSET}" ]; then
    if [ -r "/usr/local/share/dos2ansi/showansi/fonts.${FONTSET}" ]; then
	. "/usr/local/share/dos2ansi/showansi/fonts.${FONTSET}"
    fi
    if [ -r "/usr/local/etc/dos2ansi/showansi/fonts.${FONTSET}" ]; then
	. "/usr/local/etc/dos2ansi/showansi/fonts.${FONTSET}"
    fi
    if [ -n "${USERFONTS}" ] && [ -r "${USERFONTS}/fonts.${FONTSET}" ]; then
	. "${USERFONTS}/fonts.${FONTSET}"
    fi
fi

# Sanitize numeric values
is_integer "${MAXWIDTH}"    || MAXWIDTH=200
is_integer "${MAXHEIGHT}"   || MAXHEIGHT=60
is_integer "${ADDLINES}"    || ADDLINES=1

# Prepend dos2ansi flags from configuration
test -n "${D2A_ARGS}" && _d2a_args="${D2A_ARGS} ${_d2a_args}"

# Create "mapping table" to map SAUCE metadata to fonts and per font
# max window height easily
_font_vga_1_1=${FONT_VGA9X16}
_font_vga_0_1=${FONT_VGA8X16}
_font_vgaw_1_1=${FONT_VGA9X8}
_font_vgaw_0_1=${FONT_VGA8X8}
_font_ega_1_1=${FONT_EGA9X14}
_font_ega_0_1=${FONT_EGA8X14}
_font_egaw_1_1=${FONT_EGA9X8}
_font_egaw_0_1=${FONT_EGA8X8}
_font_vga_1_0=${FONT_AC_VGA9X16}
_font_vga_0_0=${FONT_AC_VGA8X16}
_font_vgaw_1_0=${FONT_AC_VGA9X8}
_font_vgaw_0_0=${FONT_AC_VGA8X8}
_font_ega_1_0=${FONT_AC_EGA9X14}
_font_ega_0_0=${FONT_AC_EGA8X14}
_font_egaw_1_0=${FONT_AC_EGA9X8}
_font_egaw_0_0=${FONT_AC_EGA8X8}
_height_vga_1_1=${MAXHEIGHT_VGA9X16}
_height_vga_0_1=${MAXHEIGHT_VGA8X16}
_height_vgaw_1_1=${MAXHEIGHT_VGA9X8}
_height_vgaw_0_1=${MAXHEIGHT_VGA8X8}
_height_ega_1_1=${MAXHEIGHT_EGA9X14}
_height_ega_0_1=${MAXHEIGHT_EGA8X14}
_height_egaw_1_1=${MAXHEIGHT_EGA9X8}
_height_egaw_0_1=${MAXHEIGHT_EGA8X8}
_height_vga_1_0=${MAXHEIGHT_AC_VGA9X16}
_height_vga_0_0=${MAXHEIGHT_AC_VGA8X16}
_height_vgaw_1_0=${MAXHEIGHT_AC_VGA9X8}
_height_vgaw_0_0=${MAXHEIGHT_AC_VGA8X8}
_height_ega_1_0=${MAXHEIGHT_AC_EGA9X14}
_height_ega_0_0=${MAXHEIGHT_AC_EGA8X14}
_height_egaw_1_0=${MAXHEIGHT_AC_EGA9X8}
_height_egaw_0_0=${MAXHEIGHT_AC_EGA8X8}

# Validate dos2ansi works with given flags, try to feed stdin if input is
# missing and collect medatada.
m_setwidth=""
m_setheight=""
m_resetwidth=""
m_resetheight=""
m_height=""
if ! _d2a_stderr=$(eval \
    "${_dos2ansi} -m ${_d2a_args} </dev/null 2>&1 >/dev/null"); then
    # When dos2ansi fails for missing input with -m, there will be no
    # output. So, error out if there *is* output.
    if [ -n "${_d2a_stderr}" ]; then
	printf >&2 "${_d2a_stderr}\n"
	exit 1;
    fi
    # Otherwise retry after capturing stdin to a temporary file.
    if ! mktmpdir; then
	echo >&2 "Error creating temporary directory"
	test -n "${TMPDIR}" && echo >&2 "Check TMPDIR variable."
	exit 1
    fi
    _file="${_tmpdir}/dosinput"
    cat >"${_file}"
    _d2a_args="${_d2a_args} $(shquote "${_file}")"
    _exec=""
    if ! _d2a_stderr=$(eval "${_dos2ansi} -m ${_d2a_args} 2>&1 >/dev/null");
    then
	echo >&2 "No input found."
	exit 1
    fi
fi
while read -r _m; do eval ${_m}; done <<META
${_d2a_stderr}
META

# Read SAUCE metadata
if [ ${_noinput} -eq 0 ]; then {
    read -r _f; eval s_title=${_f}
    read -r _f; eval s_author=${_f}
    read -r _f; eval s_group=${_f}
    read -r _f; eval s_spacing=${_f}
    read -r _f; eval s_aspect=${_f}
    read -r _f; eval s_font=${_f}
} <<SAUCE
$(${_dos2ansi} -q :TAGsaf "${_file}" 2>/dev/null)
SAUCE
fi
if [ ${_nosauce} -eq 1 ]; then
    if [ ${_noinput} -eq 1 ]; then
	s_title="dos2ansi test mode"
	s_author=""
	s_group=""
    fi
    s_spacing=""
    s_aspect=""
    s_font=""
    _d2a_args="-S ${_d2a_args}"
fi

# Sanitize SAUCE metadata, construct full title and set defaults
test -z "${s_title}" && s_title="showansi: <Unnamed>"
if [ -n "${s_author}" ]; then
    s_title="${s_title} | by ${s_author}"
    test -n "${s_group}" && s_title="${s_title}/${s_group}"
fi
is_integer ${s_spacing}	|| s_spacing=${_def_spacing}
is_integer ${s_aspect}	|| s_aspect=${_def_aspect}

# Map metadata to font to use and optional max height
case "${s_font}" in
    "IBM VGA")	    _fontbase=vga;;
    "IBM VGA50")    _fontbase=vgaw;;
    "IBM EGA")	    _fontbase=ega;;
    "IBM EGA43")    _fontbase=egaw;;
    *)
	_fontbase=${_def_fontbase}${_def_fontvariant}
	if [ ${_nometa} -eq 0 ]; then
	    if is_integer ${m_resetheight}; then
		if [ ${m_resetheight} -eq 25 ]; then
		    if is_integer ${m_resetwidth} && [ ${m_resetwidth} -eq 40 ]
		    then
			_fontbase=${_def_fontbase}w
		    else
			_fontbase=${_def_fontbase}
		    fi
		fi
	    elif is_integer ${m_height}; then
		test ${m_height} -eq 43 && _fontbase=egaw
		test ${m_height} -eq 50 && _fontbase=vgaw
	    fi
	fi
	;;
esac
_fontname=${_fontbase}_${s_spacing}_${s_aspect}
eval _font=\${_font_${_fontname}}
eval _maxheight=\${_height_${_fontname}}
is_integer "${_maxheight}" || _maxheight=${MAXHEIGHT}

# Calculate terminal window geometry
if is_integer ${_def_width}; then
    _d2a_args="-w${_def_width} ${_d2a_args}"
    _width=${_def_width}
fi
is_integer ${_width} || _width=${m_resetwidth}
is_integer ${_width} || _width=${m_setwidth}
is_integer ${_width} || _width=80
_height=${_def_height}
is_integer ${_height} || _height=${m_height}
is_integer ${_height} || _height=25
_height=$((${_height} + ${ADDLINES}))
test ${_width} -gt ${MAXWIDTH} && _width=${MAXWIDTH}
test ${_width} -lt 20 && _width=20
test ${_height} -gt ${_maxheight} && _height=${_maxheight}
test ${_height} -lt $((${ADDLINES} + 2)) && _height=$((${ADDLINES} + 2))

# Construct commandline to execute
_dos2ansiesc=""
_dos2ansicmd="${_dos2ansi} ${_d2a_args} | ${PAGER}"
if [ "${SETGEOM}" = "!" ]; then
    _dos2ansiesc="${_dos2ansiesc}\\033[8;${_height};${_width}t"
    SETGEOM=""
fi
test -n "${SETGEOM}" && \
    TERM_ARGS="${TERM_ARGS} $(shquote "${SETGEOM}") ${_width}x${_height}"
test -n "${_font}" && TERM_ARGS="${TERM_ARGS} ${_font}"
if [ "${SETTITLE}" = "!" ]; then
    _dos2ansiesc="${_dos2ansiesc}\\033]0;$(escquote "${s_title}")\\07"
    SETTITLE=""
fi
test -n "${SETTITLE}" && \
    TERM_ARGS="${TERM_ARGS} $(shquote "${SETTITLE}") $(shquote "${s_title}")"
test -n "${_dos2ansiesc}" && \
    _dos2ansicmd="printf %b $(shquote "${_dos2ansiesc}"); ${_dos2ansicmd}"
TERM_ARGS="${TERM_ARGS} $(shquote "${EXECUTE}") $(shquote "${_dos2ansicmd}")"

# Debugging output
if [ -n "${SHOWANSI_DEBUG}" ]; then
    is_integer ${m_resetwidth} && m_setwidth=${m_resetwidth}
    is_integer ${m_resetheight} && m_setheight=${m_resetheight}
    is_integer ${m_setwidth} || m_setwidth=${_def_width}
    is_integer ${m_setheight} || m_setheight=${_def_height}
    echo screen size: ${m_setwidth}x${m_setheight}
    is_integer ${m_height} && echo canvas size: ${m_setwidth}x${m_height}
    echo font: ${s_font}
    echo letter spacing: ${s_spacing}
    echo square pixels: ${s_aspect}
    echo
    echo Executing: ${TERMINAL} ${TERM_ARGS}
fi

# Execute
export SHELL
unset LESS
eval ${_locale} ${_exec} ${TERMINAL} ${TERM_ARGS}
