# AVSLib :: 'string' package :: 'sprintf' module
# Copyright (c) 2005 George Zarkadas (gzarkadas@users.sourceforge.net)

# 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., 59 Temple Place, Suite 330, Boston, 
# MA  02111-1307  USA"

DefineModule("avslib", "string", "sprintf")

global __load__ = Eval("""
	LoadModule("avslib", "base", "core")
	LoadModule("avslib", "numeric", "core")
	""")
global __load__ = IsPackageLoaded("avslib", "string") ? NOP : Eval("""
	LoadModule("avslib", "string", "core")
	LoadModule("avslib", "string", "search")
	""")
global __load__ = IsPackageLoaded("avslib", "array") ? NOP : Eval("""
	LoadModule("avslib", "array", "core")
	LoadModule("avslib", "array", "slices")
	LoadModule("avslib", "array", "operators")
	LoadModule("avslib", "array", "transforms")
	""")

# A simplified avisynth script implementation of sprintf. Supports variable 
# substitution placeholders and assoc. variables pairs of the form:
#   %b -> bool
#	%c -> clip; print its framecount
#	%f -> float
# 	%g -> clip; store it as auto-global; print auto-global's name (1)
#	%i -> int
#	%q -> string; quote it before printing
#	%s -> string
#	%v -> any type; clips are treated as if %c was specified
# Total number of variables accepted is 50.
# In addition the following formating placeholders have a special meaning:
#	%% -> output a % character  
#	\n -> output a line feed character
#	\r -> output a carriage return character
#	\t -> output a tab character
# All placeholders' strings are case sensitive.
#
# (1) %g is useful for creating parameter strings to be passed at array 
# operator functions, in order to convert a local clip variable to a global

	global __STR_PRN_MSG = "StrPrint: missing (undefined) " \
		+ "positional argument; review the argument list."
	
	Function __str_prn_err(string s) {
		return Throw("StrPrint: wrong argument type; expected "	+ s + ".")
	}

	Function __str_prn_i(val "x") {
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsInt ? String(x) : __str_prn_err("int")
	}
	
	Function __str_prn_f(val "x") {
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsFloat ? ZStrip(String(x)) : __str_prn_err("float")
	}
	
	Function __str_prn_b(val "x") {
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsBool ? String(x) : __str_prn_err("bool")
	}
	
	Function __str_prn_s(val "x") {
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsString ? x : __str_prn_err("string")
	}
	
	Function __str_prn_q(val "x") {
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsString ? StrQuote(x) : __str_prn_err("string")
	}
	
	Function __str_prn_c(val "x") {
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsClip ? String(x.Framecount) : __str_prn_err("clip")
	}
	
	Function __str_prn_g(val "x") {
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsClip ? Apply(ArraySetValueHandler, x) : __str_prn_err("clip")
	}
	
	Function __str_prn_v(val "x")
	{ 
		Assert(Defined(x), __STR_PRN_MSG)
		return x.IsClip \
			? String(x.Framecount) \
			: (x.IsFloat ? ZStrip(String(x)) : String(x))
	}
	
	Function __str_prn_other(string x)
	{ 
		return \
			StrCompare(x, "%%", false) == 0 ? "%"     : ( \
			StrCompare(x, "\r", false) == 0 ? Chr(13) : ( \
			StrCompare(x, "\n", false) == 0 ? Chr(10) : ( \
			StrCompare(x, "\t", false) == 0 ? Chr(9)  : ( \
			Throw("StrPrint: Unsupported parameter in 'format' string (" + x + ").") ))))
	}

	Function __str_print_core(string format, string rpos, int nargs, val "p01", \
		val "p02", val "p03", val "p04", val "p05", val "p06", val "p07", val "p08", \
		val "p09", val "p10", val "p11", val "p12", val "p13", val "p14", val "p15", \
		val "p16", val "p17", val "p18", val "p19", val "p20", val "p21", val "p22", \
		val "p23", val "p24", val "p25", val "p26", val "p27", val "p28", val "p29", \
		val "p30", val "p31", val "p32", val "p33", val "p34", val "p35", val "p36", \
		val "p37", val "p38", val "p39", val "p40", val "p41", val "p42", val "p43", \
		val "p44", val "p45", val "p46", val "p47", val "p48", val "p49", val "p50")
	{
		p = rpos.ArrayGet(0)
		s = StrMid(format, p, 2)
		rep = \
			StrCompare(s, "%i", false) == 0 ? __str_prn_i(p01) : ( \
			StrCompare(s, "%f", false) == 0 ? __str_prn_f(p01) : ( \
			StrCompare(s, "%b", false) == 0 ? __str_prn_b(p01) : ( \
			StrCompare(s, "%s", false) == 0 ? __str_prn_s(p01) : ( \
			StrCompare(s, "%q", false) == 0 ? __str_prn_q(p01) : ( \
			StrCompare(s, "%c", false) == 0 ? __str_prn_c(p01) : ( \
			StrCompare(s, "%g", false) == 0 ? __str_prn_g(p01) : ( \
			StrCompare(s, "%v", false) == 0 ? __str_prn_v(p01) : \
			__str_prn_other(s) )))))))
		format = StrLeft(format, p - 1) + rep + StrMid(format, p + 2)
		# adjust all position counters for the difference between 
		# qualifier string (length=2) and replacement string
		rpos = rpos.ArrayOpValue(StrLen(rep) - 2, "+")
		# delete the first element aftwerwards, to guard against 
		# an 'index out of bounds' error at the last recursive call 
		rpos = rpos.ArrayDelete(0)
		# decrement nargs and shift parameters only if s {== %}ifbsqcgv
		# shifting parameters works, because everyone is supplied
		b_consumed_arg = FindStr("ifbsqcgv", MidStr(s, 2, 1)) > 0
		return rpos.ArrayLen > 0 \
			? (b_consumed_arg \
				? __str_print_core(format, rpos, nargs - 1, p02, p03, p04, p05, \
					p06, p07, p08, p09, p10, p11, p12, p13, p14, p15, p16, p17, \
					p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, \
					p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, \
					p42, p43, p44, p45, p46, p47, p48, p49, p50) \
				: __str_print_core(format, rpos, nargs, p01, p02, p03, p04, p05, \
					p06, p07, p08, p09, p10, p11, p12, p13, p14, p15, p16, p17, \
					p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, \
					p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, \
					p42, p43, p44, p45, p46, p47, p48, p49, p50)) \
			: format
	}

Function StrPrint(string format, val "p01", val "p02", val "p03", val "p04", \
	val "p05", val "p06", val "p07", val "p08", val "p09", val "p10", val "p11", \
	val "p12", val "p13", val "p14", val "p15", val "p16", val "p17", val "p18", \
	val "p19", val "p20", val "p21", val "p22", val "p23", val "p24", val "p25", \
	val "p26", val "p27", val "p28", val "p29", val "p30", val "p31", val "p32", \
	val "p33", val "p34", val "p35", val "p36", val "p37", val "p38", val "p39", \
	val "p40", val "p41", val "p42", val "p43", val "p44", val "p45", val "p46", \
	val "p47", val "p48", val "p49", val "p50")
{
	nargs = Count(p01, p02, p03, p04, p05, p06, p07, p08, \
			p09, p10, p11, p12, p13, p14, p15, p16) + \
		Count(p17, p18, p19, p20, p21, p22, p23, p24, p25, \
			p26, p27, p28, p29, p30, p31, p32, p33) + \
		Count(p34, p35, p36, p37, p38, p39, p40, p41, p42, \
			p43, p44, p45, p46, p47, p48, p49, p50)
	# first scan for variable substitution placeholders, then test for equality 
	# with arguments and finally scan for formatting placeholders
	vpos = ArrayJoin( \
		StrFind(format, "%i"), StrFind(format, "%f"), StrFind(format, "%b"), \
		StrFind(format, "%s"), StrFind(format, "%q"), StrFind(format, "%c"), \
		StrFind(format, "%g"), StrFind(format, "%v") \
		)
	Assert(vpos.ArrayLen() == nargs, \
		"StrPrint: format string and number of parameters do not match.")
	fpos = ArrayJoin( \
		StrFind(format, "%%"), StrFind(format, "\r"), StrFind(format, "\n"), \
		StrFind(format, "\t") \
		)
	nfmt = fpos.ArrayLen()
	# to perform a substitution either nargs or nfmt must be > 0
	return (nargs > 0) || (nfmt > 0) \
		? __str_print_core(format, ArrayJoin(vpos, fpos).ArraySort(), nargs, \
			p01, p02, p03, p04, p05, p06, p07, p08, p09, p10, p11, p12, p13, \
			p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, \
			p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, \
			p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50) \
		: format
}
