#! /bin/bash

# Whizzy4Vim - WhizzyTeX for Vim
# Version 0.0.2
#
# This script is invoked by Vim and shows a preview of editing LaTeX source file (.tex).
# Copyright (C) 2007 Yusuke Nakano <pakuchan@pakunet.jp>
#
# 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 2
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#set -o xtrace

trap "Cleanup; exit 0" INT
trap "Cleanup; exit 0" HUP
trap "echo Initializing failed; Cleanup; exit 1" TERM

FILENAME=$1
WDIR=$2
LINE=$3
COLUMN=$4
COMMAND=$5

HELPER=Helper
WHIZZYTEX_PID=
USING_BEAMER=unknown

function Log() #{{{
{
	# In subshell, the $$ variable returns the same value of $$ of its parent.
	# Therefore I can't use $$ variable.
	local pid=$(exec sh -c 'echo $PPID')
	echo "$pid: $@" >> "$WHIZZY4VIM_LOG"
}
#}}}
function VarLog() #{{{
{
	Log "Variable: $@"
}
#}}}
function SetVariables() #{{{
{
	SLIDE_KEY="slide"
	ROOT_DOC="$FILENAME"

	ReadModeLine

	NAME=`basename "$ROOT_DOC" ".tex"`

	WHIZZY="_whizzy_${NAME}"
	WHIZZY_DIR="${WHIZZY}_d"
	SLICE="${WDIR}/${WHIZZY_DIR}/input/${WHIZZY}.new"

	WHIZZY4VIM_PREFIX="${WDIR}/_whizzy4vim_${NAME}"
	WHIZZY4VIM_FIFOFILE="${WHIZZY4VIM_PREFIX}.whizzytex.fifo"
	WHIZZY4VIM_PIDFILE="${WHIZZY4VIM_PREFIX}.whizzytex.pid"
	WHIZZY4VIM_WHIZZYTEX_LOG="${WHIZZY4VIM_PREFIX}.whizzytex.log"
	WHIZZY4VIM_LOG="${WDIR}/_whizzy4vim_${NAME}.log"
	WHIZZY4VIM_CURRENT_PREAMBLE="${WHIZZY4VIM_PREFIX}.preamble.current"
	WHIZZY4VIM_PREV_PREAMBLE="${WHIZZY4VIM_PREFIX}.preamble.previous"
	WHIZZY4VIM_EDITING_TEMP="$WHIZZY4VIM_PREFIX.editing.tex"
}
#}}}
function LogVariables() #{{{
{
	Log "-----(Log of shell variables)-----"
	VarLog "COMMAND=$COMMAND"

	VarLog "CMDLINE=$CMDLINE"
	VarLog "TEXINPUTS=$TEXINPUTS"
	VarLog "SLIDE_KEY=$SLIDE_KEY"
	VarLog "ROOT_DOC=$ROOT_DOC"

	VarLog "NAME=$NAME"

	VarLog "WHIZZY=$WHIZZY"
	VarLog "WHIZZY_DIR=$WHIZZY_DIR"
	VarLog "SLICE=$SLICE"

	VarLog "WHIZZY4VIM_PREFIX=$WHIZZY4VIM_PREFIX"
	VarLog "WHIZZY4VIM_FIFOFILE=$WHIZZY4VIM_FIFOFILE"
	VarLog "WHIZZY4VIM_PIDFILE=$WHIZZY4VIM_PIDFILE"
	VarLog "WHIZZY4VIM_WHIZZYTEX_LOG=$WHIZZY4VIM_WHIZZYTEX_LOG"
	VarLog "WHIZZY4VIM_LOG=$WHIZZY4VIM_LOG"
	VarLog "WHIZZY4VIM_CURRENT_PREAMBLE=$WHIZZY4VIM_CURRENT_PREAMBLE"
	VarLog "WHIZZY4VIM_PREV_PREAMBLE=$WHIZZY4VIM_PREV_PREAMBLE"
	Log "-----(The end of log)-----"
}
#}}}
function ExtractParameter() #{{{
{
	if echo "$1" | grep '^.*[[:space:]]*'"$2"'=[^[:space:]][^[:space:]]*.*$' > /dev/null 2>&1; then
		echo "$1" | sed 's/^.*[[:space:]]*'"$2"'=\([^[:space:]][^[:space:]]*\).*$/\1/g'
	fi
}
#}}}
function ReadWhizzyTeXModeLine() #{{{
{
	CMDLINE=`grep "^[[:space:]]*%;[[:space:]]*whizzy[[:space:]]" "$FILENAME" | sed 's/^[[:space:]]*%;[[:space:]]*whizzy[[:space:]]//'`
	if [ -z "$CMDLINE" ]; then
		CMDLINE=`grep "^[[:space:]]*%;[[:space:]]*whizzy[[:space:]]" "$ROOT_DOC" | sed 's/^[[:space:]]*%;[[:space:]]*whizzy[[:space:]]//'`
	fi
} #}}}
function ReadWhizzy4VimModeLine() #{{{
{
	local modeline=`grep "^[[:space:]]*% whizzy4vim:" "$1" | sed 's/^[^:]*:\(.*\)$/\1/'`

	texinputs=`ExtractParameter "$modeline" "TEXINPUTS"`
	slide_key=`ExtractParameter "$modeline" "KEY"`
	root_doc=`ExtractParameter "$modeline" "ROOT"`

	[ -n "$texinputs" ] && eval "export TEXINPUTS=$texinputs"
	[ -n "$slide_key" ] && eval "SLIDE_KEY=$slide_key"
	[ -n "$root_doc" ]  && eval "ROOT_DOC=$root_doc"

	if [ "`expr '.*\.tex' : "$ROOT_DOC" 2> /dev/null`" -eq "0" ]; then
		ROOT_DOC="${ROOT_DOC}.tex"
	fi
} #}}}
function ReadModeLine() #{{{
{
	ReadWhizzy4VimModeLine "$FILENAME"
	ReadWhizzy4VimModeLine "$ROOT_DOC"
	ReadWhizzyTeXModeLine
} #}}}
function IsInitialized() #{{{
{
	[ -f "$WHIZZY4VIM_PIDFILE" ] && return 0;
	return 1;
} #}}}
function Initialize() #{{{
{
	if IsUsingBeamer; then
		Log "Using LaTeX-Beamer, switching to Beamer mode"
		CMDLINE=`echo $CMDLINE | sed 's/-latex[[:space:]][[:space:]]*\([^[:space:]][^[:space:]]*\)/-latex "whizzy4vim_fakelatex \1"/g'`
		Log "New CMDLINE=$CMDLINE"
	fi

	eval set -- $CMDLINE

	Log "Invoke: $HELPER $ROOT_DOC $WDIR $@ &"
	( $HELPER "$ROOT_DOC" "$WDIR" "$@" ) &
	echo $! > "$WHIZZY4VIM_PIDFILE"
	Log "PID of the helper process: $!"
} #}}}
function Update() #{{{
{
	Log "Updating..."
	echo "reslice" >&5
} #}}}
function Whole() #{{{
{
	Log "Typesetting whole document..."
	echo "whole" >&5
} #}}}
function Helper() #{{{
{
	Log "This is the helper process."

	trap "Update" USR1
	trap "Whole"  USR2
	trap "KillWhizzyTex; exit 0"  HUP
	trap "KillWhizzyTex; exit 0"  TERM
	trap "KillWhizzyTex; exit 0"  INT

	filename=$1
	wdir=$2
	shift 2

	cd "$wdir"
	mkfifo -m 0600 "$WHIZZY4VIM_FIFOFILE"
	# WhizzyTeX must be invoked in different process group (i.e. different tty)
	Log "Invoke: setsid whizzytex $@ $filename < $WHIZZY4VIM_FIFOFILE > $WHIZZY4VIM_WHIZZYTEX_LOG 2>&1 &"
	setsid whizzytex "$@" "$filename" < "$WHIZZY4VIM_FIFOFILE" > "$WHIZZY4VIM_WHIZZYTEX_LOG" 2>&1 &
	WHIZZYTEX_PID=$!
	Log "WhizzyTeX pid = `GetWhizzyTeXPID`"
	exec 5>"$WHIZZY4VIM_FIFOFILE"
	rm -f "$WHIZZY4VIM_FIFOFILE"

	Log "Waiting for WhizzyTeX will be started..."
	WaitForWhizzyTeXStarted || return 1

	Log "WhizzyTeX started. Entering loop."
	while true; do
		# Monitor whether the WhizzyTeX process is running.
		IsWhizzyTeXRunning || return 1
		sleep 1
	done
} #}}}
function GetWhizzyTeXPID() #{{{
{
	echo "$WHIZZYTEX_PID"
} #}}}
function WaitForWhizzyTeXStarted() #{{{
{
	local deadline=30
	while [ "$deadline" -gt 0 ]; do
		Log "Deadline=$deadline. Checking..."
		Log "WDIR=${WDIR}/${WHIZZY_DIR}"
		[ -d "${WDIR}/${WHIZZY_DIR}" ] && return 0
		sleep 0.1
		let deadline=${deadline}-1		# Wait for 0.1 [sec] * 30 = 3 [sec].
	done
	return 1
} #}}}
function IsWhizzyTeXRunning() #{{{
{
	[ -d "/proc/`GetWhizzyTeXPID`" ] && return 0
	return 1
} #}}}
function KillWhizzyTex() #{{{
{
	kill -HUP `GetWhizzyTeXPID` > /dev/null 2>&1
	WaitForStopping
} #}}}
function WaitForStopping() #{{{
{
	echo "Waiting for quitting WhizzyTeX..." > /dev/tty
	while true; do
		[ -d "/proc/`GetWhizzyTeXPID`" ] || break
		sleep 0.1
	done
} #}}}
function WaitForHelperStopped() #{{{
{
	echo "Waiting for quitting helper process..." > /dev/tty
	while true; do
		[ -d "/proc/`GetHelperPID`" ] || break
		sleep 0.1
	done
} #}}}
function IsHelperRunning() #{{{
{
	[ -d "/proc/`GetHelperPID`" ] && return 0
	return 1
} #}}}
function GetHelperPID() #{{{
{
	cat "$WHIZZY4VIM_PIDFILE"
} #}}}
function Cleanup() #{{{
{
	kill -HUP `GetHelperPID` > /dev/null 2>&1
	WaitForHelperStopped
	rm -rf "$WHIZZY4VIM_PIDFILE" "$WHIZZY_DIR" "$WHIZZY4VIM_WHIZZYTEX_LOG" "$WHIZZY4VIM_LOG"
} #}}}
function IsUsingBeamer() #{{{
{
	[ "$USING_BEAMER" = "yes" ] && return 0
	[ "$USING_BEAMER" = "no"  ] && return 1
	if grep -n '^[^%]*\\documentclass.*{beamer}' "$ROOT_DOC" > /dev/null 2>&1; then
		USING_BEAMER=yes
		return 0
	else
		USING_BEAMER=no
		return 1
	fi
} #}}}
function ExtractPreamble() #{{{
{
	local PREAMBLE_END_LINE=${DOCUMENT_START_LINE}-1
	sed -n "1,${PREAMBLE_END_LINE}p" "$ROOT_DOC" > "$WHIZZY4VIM_CURRENT_PREAMBLE"
} #}}}
function SavePreamble() #{{{
{
	cp "$WHIZZY4VIM_CURRENT_PREAMBLE" "$WHIZZY4VIM_PREV_PREAMBLE"
} #}}}
function IsPreambleChanged() #{{{
{
	[ -f "$WHIZZY4VIM_PREV_PREAMBLE" ] || return 0
	ExtractPreamble
	diff "$WHIZZY4VIM_CURRENT_PREAMBLE" "$WHIZZY4VIM_PREV_PREAMBLE" > /dev/null 2>&1 && return 1
	return 0
} #}}}
function InsertFileAtLine() #{{{
{
	Log "Inserting $3 at line $2 of file $1"

	let before_line=$2-1
	let after_line=$2+1
	sed -n "1,${before_line}p" "$1" > "$1.tmp"
	cat "$3" >> "$1.tmp"
	sed -n "${after_line},\$p" "$1" >> "$1.tmp"
	mv "$1.tmp" "$1"
}
#}}}
function InsertString() #{{{
{
	local filename=$1
	local line=$2
	local column=$3
	local string=$4

	let column=$column-1
	let before_line=$line-1
	let after_line=$line+1

	sed -n "1,${before_line}p" "$filename"
	local temp_string=`sed -n "${line}p" "$filename"`
	echo "$temp_string" | dd bs=1 count=$column 2> /dev/null
	echo -ne "$string"
	echo "$temp_string" | dd skip=$column bs=1 2> /dev/null
	sed -n "${after_line},\$p" "$filename"
}
#}}}
function GenerateWhizzy() #{{{
{
	DOCUMENT_START_LINE=`grep -n '^[^%]*\\\\begin{document}' "$ROOT_DOC" | cut -d':' -f1`
	sed -n "$DOCUMENT_START_LINE"',$p' "$ROOT_DOC" > "$WHIZZY4VIM_PREFIX.tmp"

	local EDITING_ROOT=no
	if [ "$FILENAME" = "$ROOT_DOC" ]; then
		EDITING_ROOT=yes
		let LINE=${LINE}-${DOCUMENT_START_LINE}+1
	fi

	# If using \input in the source file. expand them all!
	local VIRTUAL_LINE=$LINE
	local INPUT_LINES=`grep -n '^[^%]*\\\\input{[^}][^}]*}' "$WHIZZY4VIM_PREFIX.tmp" | sed 's/^\([0-9][0-9]*\):.*\\input{\([^}][^}]*\)}.*$/\1%\2/' | sort -r | tr '\n' ' '`
	for i in $INPUT_LINES; do
		line=`echo $i | cut -d'%' -f1`
		filename=`echo $i | cut -d'%' -f2`
		file_lines=`wc -l < "$filename"`

		InsertFileAtLine "$WHIZZY4VIM_PREFIX.tmp" "$line" "$filename"

		if [ "$EDITING_FILE_INCLUDED" = "yes" ] || [ "$EDITING_ROOT" = "yes" ] && [ "$VIRTUAL_LINE" -gt "$line" ]; then
			let VIRTUAL_LINE=${VIRTUAL_LINE}+${file_lines}-1
		fi
		if [ "$filename" = "$FILENAME" ]; then
			let VIRTUAL_LINE=${VIRTUAL_LINE}+$line-1
			EDITING_FILE_INCLUDED=yes
		fi
	done

	if [ "$FILENAME" = "$ROOT_DOC" ] && [ "$LINE" -lt "1" ]; then
		cp "$WHIZZY4VIM_PREFIX.tmp" "$WHIZZY4VIM_EDITING_TEMP"
	else
		if [ -z "`sed -n "${VIRTUAL_LINE}p" "$WHIZZY4VIM_PREFIX.tmp"`" ]; then
			mark="\n"
		else
			mark=""
		fi
		InsertString "$WHIZZY4VIM_PREFIX.tmp" $VIRTUAL_LINE $COLUMN $mark > "$WHIZZY4VIM_EDITING_TEMP"
	fi

	sed 's:^\([^%]*\\input{\)'"$FILENAME"'}:\1'"$WHIZZY4VIM_EDITING_TEMP"'}:g' "$WHIZZY4VIM_EDITING_TEMP" | sed 's:^[^%]*\\begin{document}:\\begin{document}\\WhizzySkip\\SourceFile{'"$ROOT_DOC"'}\\WhizzyStart{}\\WhizzyLinePoint{'"$VIRTUAL_LINE"'}\\relax:g' > "$SLICE"

	rm -f "$WHIZZY4VIM_PREFIX.tmp" "$WHIZZY4VIM_EDITING_TEMP"
} #}}}
function GetLineOfSlideOnCursor() #{{{
{
	local slide_start_lines=`egrep -n '^[^%]*\\\\(begin{('"$SLIDE_KEY"')}|maketitle)' "$FILENAME" | cut -d':' -f1 | tr '\n' ' '`
} #}}}
function ExtractOneSlideOnCursor() #{{{
{
	local doc_start
	let doc_start=${DOCUMENT_START_LINE}+1
	
	local slide_delimiter=(`GetLineOfSlideOnCursor`)
	SLIDE_START=${slide_delimiter[0]}
	SLIDE_END=${slide_delimiter[1]}

	sed -n "${doc_start}"',$s/^[[:space:]][[:space:]]*[^%].*$//g' "$FILENAME"
} #}}}

# Entry point

SetVariables
LogVariables

if [ "$COMMAND" = "quit" ]; then
	if IsInitialized; then
		Cleanup
	fi
	exit 0
fi

if ! IsInitialized || ! IsHelperRunning; then
	Initialize
	if ! IsWhizzyTeXRunning; then
		# Initialization error
		Log "Failed in initialization"
		exit 1
	fi
fi

GenerateWhizzy

if IsPreambleChanged || IsUsingBeamer; then
	SavePreamble
	kill -USR2 `GetHelperPID` > /dev/null 2>&1
else
	kill -USR1 `GetHelperPID` > /dev/null 2>&1
fi

exit 0


