#!/bin/bash
set -e

MYVERSION=@VERSION@

# parse invocation name and dir

SCRIPTNAME=$(basename "$0")
ORIGDIR=$(pwd)
SCRIPTDIR=$(dirname "$0")
case ${SCRIPTDIR} in
/*) : ;;
*) SCRIPTDIR=${ORIGDIR}/${SCRIPTDIR} ;;
esac

# First, try "docclass-2-target", then obsolete "docclass2target"
SINGLEDOCCLASS=${SCRIPTNAME%-2-*}
if [ "x$SINGLEDOCCLASS" != "x$SCRIPTNAME" ]
then
    TARGETFORMAT=${SCRIPTNAME#${SINGLEDOCCLASS}-2-}
else
    SINGLEDOCCLASS=${SCRIPTNAME%2*}
    TARGETFORMAT=${SCRIPTNAME#${SINGLEDOCCLASS}2}
fi
# if called as sgml2xxx, don't assume a single docclass
#[ "${SINGLEDOCCLASS}" != sgml ] || SINGLEDOCCLASS=


# stdlib

error()
{
    echo >&2 "ERROR: ${SCRIPTNAME}: $*"
    exit 1
}

warn()
{
    [ $VERBOSITY -lt 1 ] || echo >&2 "Warning: ${SCRIPTNAME}: $*"
}

notice()
{
    [ $VERBOSITY -lt 2 ] || echo >&2 "Notice:  ${SCRIPTNAME}: $*"
}

trace()
{
    [ $VERBOSITY -lt 3 ] || echo >&2 "Trace:   ${SCRIPTNAME}: $*"
}

debug()
{
    [ $VERBOSITY -lt 4 ] || echo >&2 "Debug:   ${SCRIPTNAME}: $*"
}

translate_verbosity()
{
    case "$1" in
    quiet)	echo 0 ;;
    default)	echo 1 ;;
    verbose)	echo 2 ;;
    trace)	echo 3 ;;
    debug)	echo 4 ;;
    *)		error "unknown verbosity \`$1'" ;;
    esac
}

version()
{
    echo "sgml2x version ${MYVERSION}"
    exit 0
}

usage()
{
    # ensure the conf has been read
    if [ -z "${BACKENDS}" ]
    then
	readconf
    fi

    local stylefmt='    %15s : %s\n'

    # usage message
    cat <<EOF
Usage: ${SCRIPTNAME} [options] <sgmlfile> [<sgmlfile> ...]
	Options:
    -c|--catalog <catalog>
			use specified SGML catalog
    -C|--confdirs	use (whitespace-separated) list of confdirs
			(cumulative)
			default: "${DEFAULTCONFDIRS}"
    -r|--remarks	include remarks in document
    -D|--dssslproc <dsssl-processor>
			select the dsssl processor to use
    -j|--jade <dsssl-processor>
			[DEPRECATED] synonym for --dssslproc
    -o|--openjade	[DEPRECATED] use openjade to format (now the default)
    -O|--jadeopts <jade-options>
			pass additionnal options to jade (cumulative)
    --jadetexfilter <perl-filter>
			a perl filter, with enough quotes (use -n to check)

    -n|--no-act		print commands instead of running them
    -q|--quiet		set verbosity to 0
    -v|--verbose	increase verbosity (cumulative)
    -V|--version	print the version of this program
    --verbosity <level>	set verbosity to <level> (default: "default")

    -s|--style <style>	select output style

	Standard styles:
EOF
    printf "${stylefmt}" 'local' 'Stylesheet from ./local.dsl'
    printf "${stylefmt}" 'local=<file>' 'Stylesheet from ./<file> or ./<file>.dsl'
    echo

    for backend in print html
    do
	local styles default style desc
	eval styles=\"\${STYLES_${SINGLEDOCCLASS}_${backend}}\"
	eval default=\"\${DEFAULTSTYLE_${SINGLEDOCCLASS}_${backend}}\"
	echo "Styles for ${SINGLEDOCCLASS} ${backend}:"
	for style in $styles
	do
	    eval desc=\${DESC_${SINGLEDOCCLASS}_${backend}_${style}}
	    printf "${stylefmt}" $style "$desc"
	done
	printf "    Default: ${default}\n\n"
    done

    exit 1
}


# default values for params

DEFAULTCONFDIRS="/etc/sgml/sgml2x ~/.sgml2x ./sgml2x ./sgml2x-aliases"
declare -i VERBOSITY=$(translate_verbosity default)

# local lib

id2file()
{
    local file=$(ospcat ${CATALOG:+-c $CATALOG} -P "$1" | grep '^<OSFILE' | 
	    sed -e s/'^<OSFILE>'// \
		-e "s%<OSFILE SOIBASE='\([^']*\)/[^/]*'>%\1/%" \
		-e "s%<OSFILE SOIBASE='\([^'/]*\)[^/]*'>%%" )
    [ -r "$file" ] || error "style not found: \`$1'"
    echo "$file"
}

# emulate "mktemp -d" if not present
if rmdir $(mktemp -d 2>/dev/null) 2>/dev/null
then
    MKDIR_D_WORKS=YES
else
    MKDIR_D_WORKS=NO
fi
mymktemp()
{
    if [ NO = "${MKDIR_D_WORKS}" -a "x$1" = "x-d" ]
    then
	# emulate -d by using -u and creating a dir instead
	shift
	local name=$(mktemp -u "$@")
	mkdir ${name}
	echo ${name}
    else
	mktemp "$@"
    fi
}

# Maybe run the given command, according to $NOACT
# and (non-)presence of the --always flag as $1, with
# support for tracing the commands run.
runcmd()
{
    local ALWAYS=
    if [ "x$1" = "x--always" ]
    then
	ALWAYS=YES
	shift
    fi

    if [ -n "$NOACT" ]
    then
	if [ ${VERBOSITY} -ge $(translate_verbosity trace) ]
	then
	    # This is a ugly hack to ensure we'll get the line prefix
	    # as expected.  Breaks encapsulation of VERBOSITY.  Sigh.  FIXME.
	    trace "$@"
	else
	    echo >&2 "$@"
	fi
	[ -z "${ALWAYS}" ] || "$@"
    else
	trace "$@"
	"$@"
    fi
}

# transform $1 as a path as seen from parent dir
fromparent()
{
    case "$1" in
    "") echo "" ;;
    /*) echo "$1" ;;
    *) echo "../$1" ;;
    esac
}

# list dir entries of type $1 in dir $2
files()
{
    cd "$2"
    find -type "$1" -maxdepth 1 ! -name '.*' -printf '%f\n'
}

## insert $1 in set represented by the array named $2
# prints the command that inserts $1 in set represented by the string named $2
# returns true (0) if insertion was done, 1 if already there
insert()
{
    if ! eval echo \" \${$2} \" | grep " $1 " >/dev/null
    then
	# register backend
	echo $2=\"\${$2} $1\"
    else
	return 1
    fi
}

conffield()
{
    local field="$1"
    grep "^${field}:" "${styledir}/${docclass}/${backend}/${style}" | sed "s/^${field}: *//"
}

# find aliases
readconf()
{
    local confdir styledir backend style id cmd

#    declare -a BACKENDS=()
    BACKENDS=

    for confdir in ${CONFDIRS:-${DEFAULTCONFDIRS}}
    do

	# Get style configuration

	case "${confdir}" in
	*/sgml2x-aliases) styledir=${confdir} ;; # sort of compatibility hack
	*) styledir=${confdir}/styles ;;
	esac

	if [ -d "${styledir}" ]
	then
	    notice "reading style configuration from dir \`${styledir}'"
	    for docclass in $(files d "${styledir}")
	    do
		[ "x$docclass" != xCVS ] || continue

		# skip any unwanted docclass
		if [ -n "${SINGLEDOCCLASS}" -a "x$docclass" != "x${SINGLEDOCCLASS}" ]
		then
		    continue
		fi

		for backend in $(files d "${styledir}/${docclass}")
		do
		    [ "x$backend" != xCVS ] || continue

		    if cmd=$(insert "${backend}" BACKENDS)
		    then
			eval $cmd
			# ensure its styles list is empty on first occurence of a backend
			eval STYLES_${docclass}_${backend}=
		    fi
		    debug BACKENDS=$BACKENDS
		    eval DEFAULTSTYLE_${docclass}_${backend}=
		    eval local DEFAULTSTYLEPRIO_${docclass}_${backend}=

		    for style in $(files f "${styledir}/${docclass}/${backend}")
		    do
			[ "x$style" != xCVS ] || continue

			# ignore files with non-alphanum chars
			echo "$style" | grep -qv '[^a-zA-Z0-9]' || continue

			## Id

			local id=$(conffield Id)

			# ignore styles for which we don't find the stylesheets
			if ! ( set +e; id2file "$id" >/dev/null 2>&1)
			then
			    notice "ignoring style \`$style': file not found"
			    continue
			fi
			eval PUBID_${docclass}_${backend}_${style}=\"${id}\"

			## Desc

			local desc=$(conffield Desc)
			if ! cmd=$(insert ${style} STYLES_${docclass}_${backend})
			then
			    warn "overring definition for ${style}, ${styledir} prevails"
			fi
			eval $cmd
			eval DESC_${docclass}_${backend}_${style}=\"${desc}\"

			## Priority

			local prio=$(conffield Priority)
			local bestprio=$(eval echo \${DEFAULTSTYLEPRIO_${docclass}_${backend}})
			if [ -z "$bestprio" ] || [ "$prio" -gt "$bestprio" ]
			then
			    eval DEFAULTSTYLE_${docclass}_${backend}=${style}
			    eval DEFAULTSTYLEPRIO_${docclass}_${backend}=$prio
			fi

			## Stylesheet inheritance: get parent

			eval INHERITS_${docclass}_${backend}_${style}=\"$(conffield Inherits)\"

			## Overrides

			# prepend '-V' to each Override line relevant to ${TARGETFORMAT}
			local overrides=$(conffield ${TARGETFORMAT}Override |
			    paste -d'-V' /dev/null /dev/null -)
			eval ${TARGETFORMAT}OVERRIDES_${docclass}_${backend}_${style}=\"${overrides}\"
		    done
		done
	    done
	fi

	# Get dssslproc configuration

	if [ -r ${confdir}/dssslproc ]
	then
	    DSSSLPROCS="$(grep -v ^# ${confdir}/dssslproc) ${DSSSLPROCS}"
	fi
    done

    ## Stylesheet inheritance: get parent's overrides in addition to ours

    for backend in ${BACKENDS}
    do
	eval local styles=\${STYLES_${SINGLEDOCCLASS}_${backend}}

	# First we must order styles with a topological sort, so that ancestors
	# get processed first.

	styles=$(
	    for style in ${styles}
	    do
		eval echo \${INHERITS_${SINGLEDOCCLASS}_${backend}_${style}} ${style}
	    done | tsort
	)

	# Then we can proceed

	for style in ${styles}
	do
	    eval local parent=\${INHERITS_${SINGLEDOCCLASS}_${backend}_${style}}
	    eval ${TARGETFORMAT}OVERRIDES_${SINGLEDOCCLASS}_${backend}_${style}=\"\${${TARGETFORMAT}OVERRIDES_${SINGLEDOCCLASS}_${backend}_${parent}} \${${TARGETFORMAT}OVERRIDES_${SINGLEDOCCLASS}_${backend}_${style}}\"
	done
    done
}

# command-line

JADEARGS=
NOACT=
CATALOG=
CONFDIRS=
JADETEXPERLCMD=

while [ $# -gt 0 ] 
do
    case "$1" in
    -n|--no-act) NOACT=noact ;;
    -o|--openjade) JADE=openjade
	warn "-o and --openjade are strongly deprecated.  See --dssslproc."
	;;
    -D|--dssslproc) [ $# -ge 2 ] || usage
	JADE="$2" ; shift
	;;
    -j|--jade) [ $# -ge 2 ] || usage
	JADE="$2" ; shift
	warn "-j and --jade are deprecated.  See --dssslproc."
	;;
    -r|--remarks) JADEARGS="$JADEARGS -V%show-comments%" ;;
    -c|--catalog) [ $# -ge 2 ] || usage; CATALOG="$2" ; shift ;;
    -s|--style) [ $# -ge 2 ] || usage; CMDLN_STYLE="$2" ; shift ;;
    -O|--jadeopts) [ $# -ge 2 ] || usage; JADEARGS="$JADEARGS $2" ; shift ;;
    -C|--confdirs) [ $# -ge 2 ] || usage; CONFDIRS="$CONFDIRS $2" ; shift ;;
    --jadetexfilter) [ $# -ge 2 ] || usage; JADETEXPERLCMD="$2"; shift ;;
    -q|--quiet) VERBOSITY=0 ;;
    -v|--verbose) VERBOSITY=${VERBOSITY}+1 ;;
    -V|--version) version ;;
    --verbosity) [ $# -ge 2 ] || usage; VERBOSITY=$(translate_verbosity $2) ; shift ;;
    -*) usage ;;
    *) break ;;
    esac
    shift
done

# Check that user did not use something "-s path/to/my.dsl"
# Additionally, style names are currently restricted to the shell notion of a "name"
# (aka "identifier")
case "$CMDLN_STYLE" in
local=*) : ;;
*[^A-Za-z_0-9]*) error "Unexpected characters in style name - did you forget \`local=' ?" ;;
esac

# FIXME: drop this when feature gets implemented
[ "x${SINGLEDOCCLASS}" != xsgml ] ||
    error "can't be used with that name yet, use one of the symlinks"

# try to find runjade
if [ -x "$(dirname $0)/runjade" ]
then
    RUNJADE="$(dirname $0)/runjade"
    case "${RUNJADE}" in
    /*) : ;;
    *) RUNJADE=${ORIGDIR}/${RUNJADE} ;;
    esac
elif command -v runjade >/dev/null
then
    RUNJADE=runjade
else
    warn "runjade not found, (open)jade will be called directly"
    RUNJADE=
fi

# locate our jadetex.cfg
JADETEXCFG=
if [ -r ${SCRIPTDIR}/../lib/jadetex.cfg ]
then
    JADETEXCFG=${SCRIPTDIR}/../lib/jadetex.cfg
elif [ -r ${SCRIPTDIR}/../share/sgml/sgml2x/jadetex.cfg ]
then
    JADETEXCFG=${SCRIPTDIR}/../share/sgml/sgml2x/jadetex.cfg
fi

if [ -n "${JADETEXCFG}" ]
then
    notice "using ${JADETEXCFG}"
else
    warn "no jadetex.cfg found."
fi

# read the conffile
readconf

# maybe guess which jade clone to use
if [ -z "$JADE" ]
then
    for JADE in ${DSSSLPROCS} false ;
    do
	if command -v "${JADE}" >/dev/null 2>&1
	then
	    notice "Using ${JADE} for DSSSL processing"
	    break
	fi
    done

    if [ "x${JADE}" = xfalse ]
    then
	error "can't find a dsssl processor, and none specified using --jade"
    fi
else
    command -v "$JADE" >/dev/null
fi

# configure jade step

CHANGEDIR=
BACKEND=print
ISDIR=
FINALTARGETSUFFIX=.${TARGETFORMAT}
case ${TARGETFORMAT} in
pdf)
    TARGET=tex
    TARGETSUFFIX=.jtex
    ;;
ps)
    TARGET=tex
    TARGETSUFFIX=.jtex
    JADEARGS="$JADEARGS -V use-eps"
    ;;
fot)
    TARGET=fot 
    TARGETSUFFIX=.fot
   ;;
html)
    TARGET=sgml
    TARGETSUFFIX=-html
    FINALTARGETSUFFIX=-html
    BACKEND=html
    ISDIR=YES
    ;;
rtf)
    TARGET=rtf
    TARGETSUFFIX=.rtf
    ;;
mif)
    TARGET=mif
    TARGETSUFFIX=.mif
    ;;
*)
    error "unsupported target format \`${TARGETFORMAT}'"
    ;;
esac

# convert files

[ -n "$*" ] || error "nothing to convert"

for SRC in "$@"
do
    [ -r "$SRC" ] || error "no such file \`$SRC'"

    SRCDIR=$(dirname $SRC)
    case "$SRCDIR" in
    /*) : ;;
    *) SRCDIR=${ORIGDIR}/${SRCDIR} ;;
    esac

    FILEJADEARGS="-D $SRCDIR $JADEARGS"
    PREFIXSRC=

    case "${SRC}" in
    /*) ;;
    *) SRC="${ORIGDIR}/${SRC}" ;;
    esac

    case "${SRC}" in
    *.sgml)
	SRCFORMAT=sgml
	NAMEROOT="${SRC%.sgml}"
	;;
    *.xml)
	SRCFORMAT=xml
	# XML files must be prefixed with the XML declaration to be parsed 
	XMLDECL=/usr/lib/sgml/declaration/xml.decl
	[ -r $XMLDECL ] || error "cannot find XML declaration $XMLDECL"
	NAMEROOT="${SRC%.xml}"
	PREFIXSRC=$XMLDECL
	# get XML-specific warnings
	# er, no, finally not - why is XML so dumb ?!
#	FILEJADEARGS="$FILEJADEARGS -wxml"
	;;
    *)
	error "can't guess what to do without .sgml or .xml suffix: \`$SRC'"
	;;
    esac

    #
    # select stylesheet
    #

    # Look in document
    DOCCLASS_DSL=$(id2file "-//Alcove//DOCUMENT DocBook DocumentClass Stylesheet//EN")
    DOC_STYLE=$(runcmd --always ${RUNJADE} ${JADE} ${FILEJADEARGS} ${CATALOG:+-c $CATALOG} \
		    -t xml -d "${DOCCLASS_DSL}" \
		    ${PREFIXSRC} "${SRC}" | tr 'A-Z' 'a-z')

    # Use default style for document type only if no style given on cmd line
    # FIXME: make this logic stuff readable
    if [ -n "$DOC_STYLE" -a -z "$CMDLN_STYLE" ]
    then
	STYLE="$DOC_STYLE"
    else
	[ -n "$DOC_STYLE" -a "x$DOC_STYLE" != "x$CMDLN_STYLE" ] &&
	    notice "overriding doctype-derived style from command-line"
	STYLE=${CMDLN_STYLE:-${DEFAULTSTYLE}}
    fi

    # Map STYLE id to SHEET filename
    case $STYLE in
    local)
	SHEET="./local.dsl" ;;
    local=*)
	SHEET="${STYLE#local=}" 
	SHEET="${SHEET%.dsl}.dsl"	# Force .dsl suffix if not given
	STYLE=local
	;;
    *)
	eval SHEETID=\"\${PUBID_${SINGLEDOCCLASS}_${BACKEND}_${STYLE}}\"
    esac

    # Take default style if needed
    if [ -z "$SHEETID" -a -z "$SHEET" ]
    then
	ADDMSG=
	if [ "x$STYLE" = "x$DOC_STYLE" ]
	then
	    eval DEFSTYLE=\"\${DEFAULTSTYLE_${SINGLEDOCCLASS}_${BACKEND}}\"
	    warn "unknown style \`$STYLE' - using default ${DEFSTYLE}"
	    STYLE=${DEFSTYLE}
	else
	    error "unknown style \`$STYLE'"
	fi
    fi

    # Final values for SHEET and SHEETID
    notice "converting ${SRCFORMAT} to ${TARGET} using style \`${STYLE}'... "
    [ "x${STYLE}" != xlocal ] || notice " using local stylesheet \`${SHEET}'"

    eval SHEETID=\"\${PUBID_${SINGLEDOCCLASS}_${BACKEND}_${STYLE}}\"

    SHEET=${SHEET:-$(id2file "$SHEETID")}
    debug "using stylesheet file \`${SHEET}'"

    # Use specified overrides
    eval FILEJADEARGS=\"\${FILEJADEARGS} \${${TARGETFORMAT}OVERRIDES_${SINGLEDOCCLASS}_${BACKEND}_${STYLE}}\"

    #
    # Stylesheet application itself
    #

    NAMEROOT="${NAMEROOT%.${SINGLEDOCCLASS}}"
    FINALDEST="${NAMEROOT}${FINALTARGETSUFFIX}"
    DEST="$(basename "${NAMEROOT}${TARGETSUFFIX}")"

    # Bring a jadetex.cfg if meaningful
    # FIXME: should be more clever, merge provided one if any, etc.
    if [ tex = "$TARGET" -a -n "${JADETEXCFG}" -a ! -r ./jadetex.cfg ]; then
	runcmd cp ${JADETEXCFG} .
    fi
    (
	set -e

	# Run jade in a temp subdir in the "html" case
	if [ -n "$ISDIR" ]
	then
	    runcmd rm -rf "${FINALDEST}"
	    runcmd mkdir "${FINALDEST}"
	    runcmd --always cd "${FINALDEST}"

	    CATALOG=$(fromparent ${CATALOG})
	    SHEET=$(fromparent "${SHEET}")
	fi

	runcmd ${RUNJADE} ${JADE} ${FILEJADEARGS} ${CATALOG:+-c $CATALOG} \
	    -t ${TARGET} -d "${SHEET}" \
	    -o "${DEST}" ${PREFIXSRC} "${SRC}" || exit $?

	# run arbitrary additional perl post-processing filters
	if [ "x$TARGET" = xtex -a -n "${JADETEXPERLCMD}" ]
	then
	    runcmd perl -pi -e "${JADETEXPERLCMD}" "${DEST}"
	fi

	notice "done"

	# postprocessing as needed

	case ${TARGETFORMAT} in
	ps)
	    runcmd env LATEXPRG=jadetex rlatex "${DEST}"
	    runcmd dvips "$(basename ${NAMEROOT})" -o
	    ;;
	pdf)
	    runcmd env LATEXPRG=pdfjadetex rlatex "${DEST}"
	    ;;
	esac

    ) || exit $?

    if [ -n "$ISDIR" ]
    then
	trace "exiting subshell, restoring cwd to $PWD"
    fi

done

# Local Variables:
# imenu-generic-expression: ((nil "^\\(\\w+\\) *()" 1) ("vars" "^ *\\(declare +-. +\\)?\\(\\w+\\)=" 2))
# imenu-sort-function: imenu--sort-by-name
# End:
