# AVSLib :: 'filters' package :: 'stack' 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"

__lib__ = "avslib" #$$ [__lib__ = "avslib-debug"]

DefineModule(__lib__, "filters", "stack")

global __load__ = IsPackageLoaded(__lib__, "base") ? NOP : Eval("""
	LoadModule(__lib__, "base", "core")
	LoadModule(__lib__, "base", "conversion")
	""")
global __load__ = IsPackageLoaded(__lib__, "array") ? NOP : Eval("""
	LoadModule(__lib__, "array", "core")
	LoadModule(__lib__, "array", "slices")
	LoadModule(__lib__, "array", "operators")
	LoadModule(__lib__, "array", "transforms")
	LoadModule(__lib__, "array", "properties")
	""")
global __load__ = IsPackageLoaded(__lib__, "string") ? NOP : Eval("""
	LoadModule(__lib__, "string", "core")
	LoadModule(__lib__, "string", "sprintf")
	""")
global __load__ = IsPackageLoaded(__lib__, "clip") ? NOP : Eval("""
	LoadModule(__lib__, "clip", "core")
	LoadModule(__lib__, "clip", "arrays")
	""")
global __load__ = Eval("""
	LoadModule(__lib__, "filters", "resize")
	""")

# Stacks clips onto a matrix with 'nrows' rows and 'ncols' columns.
# Assumes clips have same properties (dimensions, fps, etc.).
# If 'nrows'*'ncols' > number of clips adds blank ones.
# If 'nrows'*'ncols' < number of clips, discards last clips.

	Function __stack_stackrow(int row, int ncols)
	{
		row_clips = __stack_clips.ArrayGetRange(row, row + ncols)
		
		#$$ DebugLog(DBG_FILT_EXT, "\trow=%i, cols=%i, clips=%i", row, ncols, row_clips.ArrayLen())
		
		return row_clips.ArraySum(sum_func="StackHorizontal")
	}

	Function __stack_add_blank(string clips, int nadds)
	{
		dlm = ArrayDelimiterGet()
		s = StrFill("__stack_blank" + dlm, nadds)
		s = StrLeft(s, StrLen(s) - StrLen(dlm))
		return ArrayJoin(clips, s)
	}
	
Function Stack(string clips, int nrows, int ncols)
{
	arrlen = clips.ArrayLen()
	Assert(nrows > 0, "Stack: 'nrows' must be greater than zero")
	Assert(ncols > 0, "Stack: 'ncols' must be greater than zero")

	#$$ DebugLog(DBG_FILT_STD, "Stack(clips[%i], %i, %i)", arrlen, nrows, ncols)

	st_elms = nrows * ncols
	global __stack_blank = BlankClip(clips.ArrayGet(0))
	clips = \
		st_elms < arrlen ? \
			clips.ArrayGetRange(0, st_elms) : ( \
		st_elms > arrlen ? \
			__stack_add_blank(clips, st_elms - arrlen) \
		: clips )
	global __stack_clips = clips
	# stack in the matrix
	row_nums = ArrayRange(0, st_elms - 1, step=ncols)
	rows = row_nums.ArrayOpFunc("__stack_stackrow", String(ncols))
	global __stack_blank = Null(__stack_blank)
	global __stack_clips = ""

	#$$ DebugLog(DBG_FILT_STD, "<End> Stack")

	return rows.ArraySum(sum_func="StackVertical")
}

# Auto-stacks clips onto a matrix with specified dimensions, enlarging or 
# shrinking as necessary.
# Clips are converted to the dominant colorspace, unless a specific setting 
# is enforced by the 'pixel_type' argument.
# The output clip has the audio of the first clip, unless a clip is passed
# in by the 'audio' argument.
# Clips must have the same fps, else an error occures.

	# creates a row clip
	Function __stack_row(int row, int ncols, string rsz_args)
	{
		row_clips = __stack_clips.ArrayGetRange(row * ncols, (row + 1) * ncols)
		return row_clips.ArraySum("ResizeToFit", rsz_args, "StackHorizontal")
	}

Function StackToFit(string clips, int target_w, int target_h, int "mode", \
	string "pixel_type", clip "audio", string "resizer", val "rsz_param1", \
	val "rsz_param2", bool "interlaced")
{	
	_FID = "StackToFit: "
	clen = clips.ArrayLen
	Assert(clen > 0, _FID + "Total number of input clips must be greater than zero.")
	n_clips = clips.ArrayOpFunc("IsClip").ArrayOpFunc("CInt").ArraySum()
	Assert(n_clips == clen, _FID + "There are non-clip elements in input array.")
	b_same_fps = clips.ArrayOpFunc("Framerate").ArrayDistinct().ArrayLen == 1
	Assert(b_same_fps, _FID + "All input array's clips must have the same framerate")
	Assert(target_w > 0, _FID + "'target_w' must be a positive number")
	Assert(target_h > 0, _FID + "'target_h' must be a positive number")
	mode = Default(mode, RTF_ZOOM)
	Assert(mode >= RTF_ZOOM && mode <= RTF_STRETCH, _FID + \
		"invalid value for 'mode' (should be 0, 1, or 2)")
	# check colorspace of input clips and auto-convert to the 
	# dominant one unless it is specified by the caller
	px_type = Defined(pixel_type) \
		? (pixel_type.IsPixelType \
			? UCase(pixel_type) \
			: Throw( _FID + "'pixel_type' accepts only RGB32, RGB24, YUY2, or YV12") \
		) : clips.JointPixelType
	audio = Defined(audio) ? audio : clips.ArrayGet(0)
	resizer = LCase(Default(resizer, GetDefaultResizer(false)))
	nrows = Round(Sqrt(clen))    # output matrix rows
	ncols = Ceil(Sqrt(clen))     # output matrix columns
	csfunc = "ConvertTo" + px_type  # will be used when __stack_clips will be assigned
	# fmax is needed because we may overlay *on* black
	fmax = clips.ArrayOpFunc("Framecount").ArrayMax()
	black = clips.ArrayGet(0).BlankClip(width=target_w, height=target_h, \
		pixel_type=px_type, length=fmax).KillAudio()  # template is needed for fps
	interlaced = Default(interlaced, false)
	# Check that dimensions are in accordance with px_type
	# (the ResizeToFit filter will throw an error if not).
	# Instead of aborting, reduce them in order to conform.
	cellw = Int(target_w / ncols)
	cellh = Int(target_h / nrows)
	cel_ws = black.SafeWidth(cellw)
	cel_hs = black.SafeHeight(cellh, interlaced)
	subw = black.IsYV12 ? 4 : (black.IsYUY2 ? 2 : 1)
	subh = (black.IsYV12 ? 2 : 1) * (interlaced ? 2 : 1)
	cellw = cellw != cel_ws \
		? (cellw > cel_ws ? cel_ws : cel_ws - subw) \
		: cellw
	cellh = cellh != cel_hs \
		? (cellh > cel_hs ? cel_hs : cel_hs - subh) \
		: cellh
	# If any of cellw, cellh has gone down to zero, abort.
	Assert(cellw > 0 && cellh > 0, _FID + \
		"calculated a zero cell width or height;" + Chr(10) + \
		"increase the values of 'target_w' and/or 'target_h'")
	exc_w = target_w - ncols * cellw
	exc_h = target_h - nrows * cellh
	# set default resizer to simplify coding of __stack_row
	old_def = SetDefaultResizer(resizer, rsz_param1, rsz_param2)
	# create global array for __stack_row; fill it with blank clips if needed
	global __stack_clips = clips.ArrayOpFunc(csfunc).ArrayOpFunc("KillAudio")
	st_elms = nrows * ncols
	global __stack_clips = st_elms < n_clips ? \
		Throw( _FID + "matrix elements are less than clips; this is a bug") : ( \
	st_elms > n_clips ? \
		ArrayJoin(__stack_clips, ArrayFill(__stack_clips.ArrayGet(0).BlankClip, st_elms - n_clips)) \
	: \
		__stack_clips )
	# set argument list and create the matrix
	row_args = StrPrint( "%i, %q", ncols, StrPrint("%i, %i, %i, interlaced=%b", \
		cellw, cellh, mode, interlaced) )
	rows = ArrayRange(0, nrows - 1).ArrayOpFunc("__stack_row", row_args)
    matrix = rows.ArraySum(sum_func="StackVertical")
	# append black bars if exc_w/exc_h are > zero
	matrix = exc_w > 0 || exc_h > 0 \
		? Overlay(black, matrix, x=Int(exc_w/2), y=Int(exc_h/2), \
			mode="blend", opacity=1.0) \
		: matrix
	# restore default resizer before returning
	dlen = old_def.ArrayLen
	dummy = SetDefaultResizer(old_def.ArrayGet(0), \
		(dlen > 1 ? old_def.ArrayGet(1) : Undef()), \
		(dlen > 2 ? old_def.ArrayGet(2) : Undef()) )
	# don't forget to (re)add audio
	return AudioDub(matrix, audio)
}
