# AVSLib :: 'array' package :: 'core' 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", "array", "core")

global __load__ = Eval("""
	LoadModule("avslib", "base", "core")
	LoadModule("avslib", "string", "core")
	""")

# Constants, handlers and helper functions 

# __ARRAY_LEN is the length of __ARRAY_DLM; 
# Use the helper functions ArrayDelimiter... to change these values; 
# else you risk a serious mesh-up.
#
global __ARRAY_DLM = ","
global __ARRAY_LEN = 1

# array clip variables namespace (note the 2 trailing _); use a short prefix 
# to allow more clips in an array (for Avisynth versions up to 2.55)
#
global __ARRAY_CLIP_NS = "__acp__"	

# total number of created global clip variables (handlers must increase it 
# by one after each clip assignment to an array element)
#
global __array_clip_vars = 0

# Array element setting handlers. NOTE that all set value handlers 
# are REQUIRED to return only string values.

# Variable to hold the names of currently active get/set value handlers. 
# Changes affect all array get/set operations; so be careful what you are doing.
#
global ArrayGetValueHandler = "__array_getvalue_handler_default"
global ArraySetValueHandler = "__array_setvalue_handler_default"

# NOTE:
# When Eval(x) fails, x may under certain circumstances be Undefed (!) 
# Thus we must keep a *global* copy of it in order to achieve our purpose

	Function __array_eval_elm(string elm)
	{
		global __array_eval_temp = elm
		try {
			new_val = Eval(elm) 
			# if elm is a comment or an empty string, new_val will be undefined; 
			# in that case return elm (it was a string)
			new_val = Defined(new_val) ? new_val : __array_eval_temp 
		}
		catch(err_msg) { 
			new_val = __array_eval_temp 
		}
		return new_val
	}

# if elm is quoted then return it as is, 
# since it will evaluate to an unquoted string
#
Function __array_getvalue_handler_default(string elm)
{
	return IsQuoted(elm) \
		? elm \
		: (elm != "" ? __array_eval_elm(elm) : elm)
}

# Create a new global clip var in array clip variables namespace 
# and assign it the passed argument. Return the new global's name.
#
Function __array_setvalue_handler_clips(clip new_value)
{
	# integer overflow check
	Assert( __array_clip_vars < MAX_INT, \
		"Array: Maximum number of array clips reached. Please recode your script.")
	# advance counter and get a new global 'slot' for the incoming clip
	global __array_clip_vars = __array_clip_vars + 1
	strvar = __ARRAY_CLIP_NS + String(__array_clip_vars)
	dummy = Eval("global " + strvar + "= new_value") # Eval is called in local context
	return strvar
}

# Handle all other variable types except clips. If new_value is 
# an integer or a float, pack the resulting string (cut redundant zeros).
#
Function __array_setvalue_handler_other(val new_value)
{ 
	return IsFloat(new_value) ? ZStrip(String(new_value)) : String(new_value)
}

# Default handler for returning a string representation of a variable 
# to be inserted in an array (handles all possible types).
#
Function __array_setvalue_handler_default(val new_value)
{
	return IsClip(new_value) \
		? __array_setvalue_handler_clips(new_value) \
		: __array_setvalue_handler_other(new_value)
}

# Helper functions to set  and restore array delimiter constants.

Function ArrayDelimiterGet() { return __ARRAY_DLM }

Function ArrayDelimiterSet(string delimiter)
{
	Assert(StrLen(delimiter) > 0, \
		"ArrayDelimiterSet: Null-string delimiter is not allowed")
	old = __ARRAY_DLM
	global __ARRAY_DLM = delimiter
	global __ARRAY_LEN = StrLen(delimiter)
	return old
}

Function ArrayDelimiterReset() { return ArrayDelimiterSet(",") }

# Helper functions to register and restore array get_value handlers .

Function ArrayGetValueRegisterNewHandler(string new_handler)
{
	_ret = ArrayGetValueHandler
	# check that new_handler is callable and only then assign global var.
	global ArrayGetValueHandler = IsCallable(new_handler) \
		? new_handler \
		: ArrayGetValueHandler
	return _ret
}

Function ArrayGetValueResetDefaultHandler()
{
	_ret = ArrayGetValueHandler
	global ArrayGetValueHandler = "__array_getvalue_handler_default"
	return _ret
}

# Helper functions to register and restore array set_value handlers .

Function ArraySetValueRegisterNewHandler(string new_handler)
{
	_ret = ArraySetValueHandler
	# check that new_handler is callable and only then assign global var.
	global ArraySetValueHandler = IsCallable(new_handler) \
		? new_handler \
		: ArraySetValueHandler
	return _ret
}

Function ArraySetValueResetDefaultHandler()
{
	_ret = ArraySetValueHandler	
	global ArraySetValueHandler = "__array_setvalue_handler_default"
	return _ret
}

# Array creation functions

# ArrayCreate: Creates an array from the argument list passed in. 
# If none argument is passed, returns an empty array

	Function __acreate_elm(val "elm")
	{ 
		return Defined(elm) \
			? Apply(ArraySetValueHandler, elm) + __ARRAY_DLM \
			: ""
	}

Function ArrayCreate(val "elm01", val "elm02", val "elm03", val "elm04", \
	val "elm05", val "elm06", val "elm07", val "elm08", val "elm09", val "elm10", \
	val "elm11", val "elm12", val "elm13", val "elm14", val "elm15", val "elm16", \
	val "elm17", val "elm18", val "elm19", val "elm20", val "elm21", val "elm22", \
	val "elm23", val "elm24", val "elm25", val "elm26", val "elm27", val "elm28", \
	val "elm29", val "elm30", val "elm31", val "elm32", val "elm33", val "elm34", \
	val "elm35", val "elm36", val "elm37", val "elm38", val "elm39", val "elm40", \
	val "elm41", val "elm42", val "elm43", val "elm44", val "elm45", val "elm46", \
	val "elm47", val "elm48", val "elm49", val "elm50", val "elm51", val "elm52", \
	val "elm53", val "elm54", val "elm55", val "elm56", val "elm57", val "elm58", \
	val "elm59", val "elm60") 
{
	arr = __acreate_elm(elm01) + __acreate_elm(elm02) \
		+ __acreate_elm(elm03) + __acreate_elm(elm04) \
		+ __acreate_elm(elm05) + __acreate_elm(elm06) \
		+ __acreate_elm(elm07) + __acreate_elm(elm08) \
		+ __acreate_elm(elm09) + __acreate_elm(elm10) \
		+ __acreate_elm(elm11) + __acreate_elm(elm12) \
		+ __acreate_elm(elm13) + __acreate_elm(elm14) \
		+ __acreate_elm(elm15) + __acreate_elm(elm16) \
		+ __acreate_elm(elm17) + __acreate_elm(elm18) \
		+ __acreate_elm(elm19) + __acreate_elm(elm20)
	arr = arr \
		+ __acreate_elm(elm21) + __acreate_elm(elm22) \
		+ __acreate_elm(elm23) + __acreate_elm(elm24) \
		+ __acreate_elm(elm25) + __acreate_elm(elm26) \
		+ __acreate_elm(elm27) + __acreate_elm(elm28) \
		+ __acreate_elm(elm29) + __acreate_elm(elm30) \
		+ __acreate_elm(elm31) + __acreate_elm(elm32) \
		+ __acreate_elm(elm33) + __acreate_elm(elm34) \
		+ __acreate_elm(elm35) + __acreate_elm(elm36) \
		+ __acreate_elm(elm37) + __acreate_elm(elm38) \
		+ __acreate_elm(elm39) + __acreate_elm(elm40)
	arr = arr \
		+ __acreate_elm(elm41) + __acreate_elm(elm42) \
		+ __acreate_elm(elm43) + __acreate_elm(elm44) \
		+ __acreate_elm(elm45) + __acreate_elm(elm46) \
		+ __acreate_elm(elm47) + __acreate_elm(elm48) \
		+ __acreate_elm(elm49) + __acreate_elm(elm50) \
		+ __acreate_elm(elm51) + __acreate_elm(elm52) \
		+ __acreate_elm(elm53) + __acreate_elm(elm54) \
		+ __acreate_elm(elm55) + __acreate_elm(elm56) \
		+ __acreate_elm(elm57) + __acreate_elm(elm58) \
		+ __acreate_elm(elm59) + __acreate_elm(elm60)
	slen = StrLen(arr)
	return (slen > 0) ? __left_str(arr, slen - __ARRAY_LEN) : ""
}

# Returns an array containing 'count' copies of 'base'
# If count < 0 the function defaults to throwing an error; 
# set 'strict' to false to return an empty array instead.
#
Function ArrayFill(val base, int count, bool "strict")
{
	strict = Default(strict, true)
	Assert((strict ? count >= 0 : true), "ArrayFill: 'count' cannot be negative")
	return count > 0 \
		? Apply(ArraySetValueHandler, base) + (count - 1 > 0 ? __ARRAY_DLM : "") \
			+ ArrayFill(base, count - 1) \
		: ""	
}

# Returns an array with values [start, start + step, start + 2*step,..end], 
# ie 'end' IS included IF it can be reached by adding 'step' to 'start' (else 
# the maximum allowed value < 'end' is included)
# Note that 'step' and 'npoints' arguments are mutually exclusive; you must 
# supply only one of them. 
# The function can only accept int/float arguments.

	Function __array_range_step(val start, val end, val step)
	{
		# this is to ensure we will never go past end
		do_recurse = (step > 0) \
			? (start + step <= end) \
			: (start + step >= end)
		return Apply(ArraySetValueHandler, start) + \
			(do_recurse \
				? __ARRAY_DLM + __array_range_step(start + step, end, step) \
				: "")
	}
	
	Function __array_range_points(val start, val end, int npoints)
	{
		# subtract 1 from npoints (if defined) to calculate 
		# the step interval
		# force conversion to float to avoid integer arithmetic 
		# if both 'start', 'end' are ints
		step = Float(end - start) / Float(npoints - 1)
		# this is to ensure we will never go past end
		do_recurse = (step > 0) \
			? (start + step <= end) \
			: (start + step >= end)
		return Apply(ArraySetValueHandler, start) + \
			(do_recurse \
				? __ARRAY_DLM + __array_range_step(start + step, end, step) \
				: "")
	}

Function ArrayRange(val start, val end, val "step", int "npoints")
{
	onedefmax = !(Defined(step) && Defined(npoints)) 
	Assert(onedefmax, \
		"ArrayRange: you must supply either 'step' or 'npoints' but not both")
	Assert(start <= end, \
		"ArrayRange: 'start' must be <= 'end'")
	Assert((Defined(step) ? step != 0 : true), \
		"ArrayRange: 'step' must not be zero")
	Assert((Defined(npoints) ? npoints > 0 : true), \
		"ArrayRange: 'npoints' must be greater than zero")
	# We can only check with Defined(npoints) to decide where to branch, 
	# since step may not be defined also.
	# The start==end case needs check only for __array_range_points; 
	# the ...step variant will behave correctly.
	arr = (!Defined(npoints)) ? \
			__array_range_step(start, end, Default(step, 1)) : \
		((npoints > 1) && (start < end) ? \
			__array_range_points(start, end, npoints) : \
		ArrayCreate(start))
	# if npoints is specified and float values are used the final point 
	# may be missing due to rounding errors
	# if this is the case add 'end' to the returned array; else do nothing 
	# since when step is used we don't know how many points the user wants 
	# to be returned.
	arr = Defined(npoints) \
		? ((start < end) && (arr.ArrayLen() < npoints) ? arr.ArrayInsert(end) : arr) \
		: arr
	return arr
}

# Basic operations and properties

# Returns the number of elements of the array-string array (ie the number of 
# __ARRAY_DLM inside array + 1). A null string array has zero length.
#
Function ArrayLen(string array)
{
	match = FindStr(array, __ARRAY_DLM)
	return (match > 0) \
		? 1 + ArrayLen(__mid_str(array, match + __ARRAY_LEN)) \
		: (StrLen(array) > 0) ? 1 : 0
}

# Handles and bounds-checks 'index' values in a standardised way, 
# based on the folowing rules:
# R1:  arrays are 0-based; ie valid indexes always are in the range 
#      [0..ArrayLen(array)-1].
# R2:  indexes < 0 mean 'from end of the array' ie (-1 == last element 
#      of array).
# R3:  if index is negative, add ArrayLen(array) to it, but only once, 
#      ie the interpretation of R2 is not recursive.
# R4:  bounds-check is always performed after the application of R3.
#
Function __bound_array_index(string array, int "index", bool "tail", bool "extend")
{
	tail = Default(tail, false)
	extend = Default(extend, false)
	arrlen = ArrayLen(array)
	ind = Default(index, tail ? (extend ? arrlen : arrlen - 1) : 0)
	ind = (ind < 0) ? arrlen + ind : ind
	# this assertion also handles the null array ("") case
	Assert(ind >= 0 && ind <= (extend ? arrlen : arrlen - 1), \
		"Arrays: array index out of range") 
	return ind
}

# Returns the starting position of the array[index] array element in the 
# string variable holding the array. Intended to be used internally by 
# other Array... functions
#
Function __array_index_pos(string array, int index)
{
	index = __bound_array_index(array, index)
	# match == 0 should happen only for the last element of the array 
	# (due to the recursion <=> the array contains 1 element)
	match = FindStr(array, __ARRAY_DLM)
	arr = match > 0 ? __mid_str(array, match + __ARRAY_LEN) : ""
	# subtract 1 because each FindStr adds 1 (since it returns a 1-based counter)
	return (index == 0) \
		? 1 \
		: match + __ARRAY_LEN - 1 + __array_index_pos(arr, index - 1)
}

# Returns the index element of array (ie array[index]). Index must be in 
# the range [1...ArrayLen(array)]
# Note also that since the function returns the result of Eval on each element 
# it is possible to create an array with global variable names and retrieve 
# their current values by getting the associated element of the array
#
Function ArrayGet(string array, int index)
{
	index = __bound_array_index(array, index)
	# match == 0 should happen only for the last element of the array 
	# where index == 0 is always true (due to the recursion <=> the array 
	# contains 1 element).
	match = FindStr(array, __ARRAY_DLM)
	elm = match > 0 ? __left_str(array, match - 1) : array
	return (index == 0) \
		? Apply(ArrayGetValueHandler, elm) \
		: ArrayGet(match > 0 \
			? __mid_str(array, match + __ARRAY_LEN) \
			: "", index - 1)
}

# intended to be called by array manipulation functions 
# when only the string representation of array[i] is needed
#
Function ArrayGetString(string array, int index)
{
	index = __bound_array_index(array, index)
	match = FindStr(array, __ARRAY_DLM)
	return (index == 0) \
		? (match > 0 ? __left_str(array, match - 1) : array) \
		: ArrayGetString(match > 0 \
			? __mid_str(array, match + __ARRAY_LEN) \
			: "", index - 1)
}

# Sets the index element of array (ie array[index]) to new_value 
# and returns the resulting array.
#
Function ArraySet(string array, int index, val new_value)
{ 
	arrlen = ArrayLen(array)
	index = __bound_array_index(array, index)
	# end delimiter is included or zero string if index_pos = 1
	part1 = __left_str(array, __array_index_pos(array, index) - 1)	
	part2 = (index < arrlen - 1) \
		? __mid_str(array, __array_index_pos(array, index + 1) - __ARRAY_LEN) \
		: ""
	return part1 + Apply(ArraySetValueHandler, new_value) + part2
}

# Inserts new_value before array[index] and returns the resulting array. 
# As a result the array is expanded, the new value is positioned at array[index] 
# and all subsequent elements's indexes are increased by one.
# If index is ommited the new_value is appended to the end of the array 
# (same as providing an index of ArrayLen(array)).
#
Function ArrayInsert(string array, val new_value, int "index")
{ 
	arrlen = ArrayLen(array)
	index = __bound_array_index(array, index, tail=true, extend=true)
	return (index == 0) \
		? Apply(ArraySetValueHandler, new_value) + \
			(arrlen > 0 ? __ARRAY_DLM + array : "") \
	: (index == ArrayLen(array) \
		? array + __ARRAY_DLM + Apply(ArraySetValueHandler, new_value) \
	: __left_str(array, __array_index_pos(array, index) - 1) + \
		Apply(ArraySetValueHandler, new_value) + \
		__ARRAY_DLM + __mid_str(array, __array_index_pos(array, index)))
}

# Deletes the element array[index] and returns the resulting array. 
# As a result the array is reduced and all subsequent elements's indexes are 
# decreased by one.
# If index is ommited the last element of the array is deleted 
# (same as providing an index of ArrayLen(array)-1).
#
Function ArrayDelete(string array, int "index")
{
	arrlen = ArrayLen(array)
	index = __bound_array_index(array, index, tail=true)
	# end delimiter is included or zero string if index_pos = 1
	part1 = __left_str(array, __array_index_pos(array, index) - 1) 
	part2 = (index < arrlen - 1) \
		? __mid_str(array, __array_index_pos(array, index + 1)) \
		: ""
	# If index == ArrayLen - 1 then part2 is a null string and part1 
	# contains an exess delimiter at the end, unless we are deleting 
	# the only element of a one element array in which case both are 
	# null strings (which is fine; we get a null string).
	# Thus only if index == ArrayLen - 1 && ArrayLen > 1 (since array 
	# has not been modified yet) we must trim part1 from the excess delimiter.
	return (index == arrlen - 1) && (arrlen > 1) \
		? __left_str(part1, StrLen(part1) - __ARRAY_LEN) \
		: part1 + part2
}
