
/* Lots of little arg checks. Global for convenience.
 */

check_any = [(const true), _ "any"];
check_bool = [is_bool, _ "boolean"];
check_real = [is_real, _ "real"];
check_ureal = [is_ureal, _ "unsigned real"];
check_preal = [is_preal, _ "positive real"];
check_list = [is_list, _ "list"];
check_real_list = [is_real_list, _ "list of real"];
check_string = [is_string, _ "string"];
check_string_list = [is_string_list, _ "list of string"];
check_int = [is_int, _ "integer"];
check_uint = [is_uint, _ "unsigned integer"];
check_pint = [is_pint, _ "positive integer"];
check_matrix = [is_matrix, _ "rectangular array of real"];
check_matrix_display = [Matrix_display.is_display, _ "0, 1, 2 or 3"];
check_image = [is_image, _ "image"];
check_xy_list = [is_xy_list, _ "list of form [[1, 2], [3, 4], [5, 6], ...]"];
check_instance name = [is_instanceof name, name];
check_Image = check_instance "Image";
check_Matrix = [is_Matrix, _ "Matrix"];
check_colour_space = [is_colour_space, "colour_space"];
check_rectangular = [is_rectangular, _ "rectangular [[*]]"];
check_Guide = [is_Guide, _ "HGuide or VGuide"];
check_Colour = check_instance (_ "Colour");
check_Mark = check_instance (_ "Mark");

/* Check a set of args to a class. Two members to look at: _check_args and
 * _check_all.
 *
 * - each line in _check_args is [arg, "arg name", [test_fn, "arg type"]]
 *   same number of lines as there are args
 *
 *   stuff like "arg 2 must be real"
 *
 * - each line in _check_all is [test, "description"]
 *   any number of lines 
 *
 *   stuff like "to must be greater than from"
 *
 * generate an error dialog with a helpful message on failure.
 *
 * Have as a separate function to try to keep the size of _Object down a bit.
 */
check_args x
	= error message, badargs != [] || badalls != []
 	= check_args x.super, x.super != []
	= x
{
	argcheck = x._check_args;
	allcheck = x._check_all;

	// join two strings up with a separator string
	join_sep j a b = a ++ j ++ b;

	// indent string
	indent = "    ";

	// test for a condition in a check line fails
	test_fail x = ! x?0;

	// set of failed argcheck indexes
	badargs = map (extract 1) 
		(filter test_fail (zip2 (map testarg argcheck) [0..]))
	{
		testarg x = x?2?0 x?0;
	}

	// set of failed allcheck indexes
	badalls = map (extract 1) 
		(filter test_fail (zip2 (map hd allcheck) [0..]));

	// the error message
	message = _ "bad arguments to " ++ "\"" ++ x.name ++ "\"\n" ++
		argmsg ++ allmsg ++ "\n" ++
		_ "usage" ++ "\n" ++ indent ++ usage ++ "\n" ++ _ "where" ++ "\n" ++ 
		arg_types ++ extra;

	// make the failed argcheck messages ... eg.  ""value" should be 
	// real, you passed <function>" etc.
	argmsg = concat (map fmt badargs)
	{
		fmt n = indent ++ "\"" ++ argcheck?n?1 ++ "\"" ++
			_ " should be of type " ++ argcheck?n?2?1 ++ ", " ++
			_ "you passed" ++ ":\n" ++ 
			indent ++ indent ++ print argcheck?n?0 ++ "\n";
	}

	// make the failed allcheck messages ... eg "condition failed:
	// x < y" ... don't make a message if any typechecks have 
	// failed, as we'll probably error horribly
	allmsg 
		= [], badargs != []
		= concat (map fmt badalls) ++ 
			_ "you passed" ++ "\n" ++
			concat (map fmt_arg argcheck)
	{
		fmt n = _ "condition failed" ++ ": " ++ allcheck?n?1 ++ "\n";
		fmt_arg l = indent ++ l?1 ++ " = " ++ print l?0 ++ "\n";
	}

	// make usage note
	usage = x.name ++ " " ++ 
		foldr (join_sep " ") [] (map (extract 1) argcheck);

	// make arg type notes
	arg_types = foldr (join_sep "\n") [] (map fmt argcheck)
	{
		fmt l = indent ++ l?1 ++ " is of type " ++ l?2?1;
	}

	// extra bit at the bottom, if we have any conditions
	extra 
		= [], allcheck == []
		= _ "and" ++ "\n" ++ all_desc;

	// make a list of all the allcheck descriptions, with a few 
	// spaces in front
	all_desc_list = map (join indent @ extract 1) allcheck;

	// join em up to make a set of condition notes
	all_desc = foldr (join_sep "\n") [] all_desc_list;
}

/* Operator overloading stuff.
 */

Operator_type = class {
	ARITHMETIC = 1;			// eg. add
	RELATIONAL = 2;			// eg. less
	COMPOUND = 3;			// eg. max/mean/etc.
	COMPOUND_REWRAP = 4;	// eg. transpose
}

Operator op_name fn type symmetric = class {
}

/* Form the converse of an Operator.
 */
oo_converse op 
	= Operator (converse_name op.op_name) 
		(converse op.fn) op.type op.symmetric
{
	converse_name x
		= init x, last x == last "'"
		= x ++ "'";
}

/* Given an operator name, look up the definition.
 */
oo_binary_lookup op_name
	= matches?0, matches != []
	= error (_ "unknown binary operator" ++ ": " ++ print op_name)
{
	operator_table = [
		Operator "add" add 
			Operator_type.ARITHMETIC true,
		Operator "subtract" subtract 
			Operator_type.ARITHMETIC false,
		Operator "remainder" remainder 
			Operator_type.ARITHMETIC false,
		Operator "power" power 
			Operator_type.ARITHMETIC false,
		Operator "subscript" subscript 
			Operator_type.ARITHMETIC false,
		Operator "left_shift" left_shift 
			Operator_type.ARITHMETIC false,
		Operator "right_shift" right_shift 
			Operator_type.ARITHMETIC false,
		Operator "divide" divide 
			Operator_type.ARITHMETIC false,
		Operator "join" join 
			Operator_type.ARITHMETIC false,
		Operator "multiply" multiply 
			Operator_type.ARITHMETIC true,
		Operator "logical_and" logical_and 
			Operator_type.ARITHMETIC true,
		Operator "logical_or" logical_or 
			Operator_type.ARITHMETIC true,
		Operator "bitwise_and" bitwise_and 
			Operator_type.ARITHMETIC true,
		Operator "bitwise_or" bitwise_or 
			Operator_type.ARITHMETIC true,
		Operator "eor" eor 
			Operator_type.ARITHMETIC true,
		Operator "comma" comma 
			Operator_type.ARITHMETIC false,
		Operator "if_then_else" if_then_else 
			Operator_type.ARITHMETIC false,
		Operator "equal" equal 
			Operator_type.RELATIONAL true,
		Operator "not_equal" not_equal 
			Operator_type.RELATIONAL true,
		Operator "less" less 
			Operator_type.RELATIONAL false,
		Operator "less_equal" less_equal 
			Operator_type.RELATIONAL false
	];

	matches = filter test_name operator_table;
	test_name x = x.op_name == op_name;
}

/* Given an operator name, look up a function that implements that
 * operator.
 */
oo_unary_lookup op_name
	= matches?0, matches != []
	= error (_ "unknown unary operator" ++ ": " ++ print op_name)
{
	operator_table = [
		/* Operators.
		 */
		Operator "cast_signed_char" cast_signed_char 
		Operator_type.ARITHMETIC false,
		Operator "cast_unsigned_char" cast_unsigned_char 
		Operator_type.ARITHMETIC false,
		Operator "cast_signed_short" cast_signed_short 
		Operator_type.ARITHMETIC false,
		Operator "cast_unsigned_short" cast_unsigned_short 
		Operator_type.ARITHMETIC false,
		Operator "cast_signed_int" cast_signed_int 
		Operator_type.ARITHMETIC false,
		Operator "cast_unsigned_int" cast_unsigned_int 
		Operator_type.ARITHMETIC false,
		Operator "cast_float" cast_float 
		Operator_type.ARITHMETIC false,
		Operator "cast_double" cast_double 
		Operator_type.ARITHMETIC false,
		Operator "cast_complex" cast_complex 
		Operator_type.ARITHMETIC false,
		Operator "cast_double_complex" cast_double_complex 
		Operator_type.ARITHMETIC false,
		Operator "unary_minus" unary_minus 
		Operator_type.ARITHMETIC false,
		Operator "negate" negate 
		Operator_type.RELATIONAL false,
		Operator "complement" complement 
		Operator_type.ARITHMETIC false,
		Operator "unary_plus" unary_plus 
		Operator_type.ARITHMETIC false,

		/* Built in projections.
 		 */
		Operator "re" re Operator_type.ARITHMETIC false,
		Operator "im" im Operator_type.ARITHMETIC false,
		Operator "hd" hd Operator_type.ARITHMETIC false,
		Operator "tl" tl Operator_type.ARITHMETIC false,

		/* Maths builtins.
		 */
		Operator "sin" sin Operator_type.ARITHMETIC false,
		Operator "cos" cos Operator_type.ARITHMETIC false,
		Operator "tan" tan Operator_type.ARITHMETIC false,
		Operator "asin" asin Operator_type.ARITHMETIC false,
		Operator "acos" acos Operator_type.ARITHMETIC false,
		Operator "atan" atan Operator_type.ARITHMETIC false,
		Operator "log" log Operator_type.ARITHMETIC false,
		Operator "log10" log10 Operator_type.ARITHMETIC false,
		Operator "exp" exp Operator_type.ARITHMETIC false,
		Operator "exp10" exp10 Operator_type.ARITHMETIC false,
		Operator "ceil" ceil Operator_type.ARITHMETIC false,
		Operator "floor" floor Operator_type.ARITHMETIC false
	];

	matches = filter test_name operator_table;
	test_name x = x.op_name == op_name;
}

/* Find the matching methods in a method table.
 */
oo_method_lookup table = map (extract 0) (filter (extract 1) table);

/* A binary op: a is a class, b may be a class ... eg. "add" a b

   two obvious ways to find a method:

   - a.oo_binary_search "add" (+) b
   - b.oo_binary_search "add'" (converse (+)) a, is_class b

   if these fail but op is a symmetric operator (eg. a + b == b + a), we can
   also try reversing the args

   - a.oo_binary_search "add'" (converse (+)) b
   - b.oo_binary_search "add" (+) a, is_class b

 */
oo_binary_function op a b
	= matches1?0, 
		matches1 != []
	= matches2?0, 
		is_class b && matches2 != []
	= matches3?0, 
		op.symmetric && matches3 != []
	= matches4?0, 
		op.symmetric && is_class b && matches4 != []
	= error (_ "No method found for binary operator." ++ "\n" ++
		_ "left" ++ " = " ++ print a ++ "\n" ++
		_ "operator" ++ " = " ++ op.op_name ++ "\n" ++
		_ "right" ++ " = " ++ print b)
{
	matches1 = oo_method_lookup (a.oo_binary_table op b);
	matches2 = oo_method_lookup (b.oo_binary_table (oo_converse op) a);
	matches3 = oo_method_lookup (a.oo_binary_table (oo_converse op) b);
	matches4 = oo_method_lookup (b.oo_binary_table op a);
}

/* A binary op: a is not a class, b is a class ... eg. "subtract" a b

   only one way to find a method:

   - b.oo_binary_search "subtract'" (converse (-)) a

   if this fails but op is a symmetric operator (eg. a + b == b + a), we can
   try reversing the args

   - b.oo_binary_search "add" (+) a, is_class b

 */
oo_binary'_function op a b
	= matches1?0, 
		matches1 != []
	= matches2?0, 
		op.symmetric && matches2 != []
	= error (_ "No method found for binary operator." ++ "\n" ++
		_ "left" ++ " = " ++ print a ++ "\n" ++
		_ "operator" ++ " = " ++ op.op_name ++ "\n" ++
		_ "right" ++ " = " ++ print b)
{
	matches1 = oo_method_lookup (b.oo_binary_table (oo_converse op) a);
	matches2 = oo_method_lookup (b.oo_binary_table op a);
}

oo_unary_function op x
	= matches?0,
		matches != []
	= error (_ "No method found for unary operator." ++ "\n" ++ 
		_ "operator" ++ " = " ++ op.op_name ++ "\n" ++
		_ "argument" ++ " = " ++ print x)
{
	matches = oo_method_lookup (x.oo_unary_table op);
}

/* Base class for nip's built-in classes ... base check function, base
 * operator overload functions.
 */
_Object = class {
	check = check_args this;

	_check_args = [];
	_check_all = [];

	/* Operator overloading stuff.
	 */
	oo_binary op x 
		= oo_binary_function (oo_binary_lookup op) this x;
	oo_binary' op x 
		= oo_binary'_function (oo_binary_lookup op) x this;
	oo_unary op 
		= oo_unary_function (oo_unary_lookup op) this;

	/* Provide a fallback for class == thing ... just use pointer
	 * equality.
	 */
	oo_binary_table op x = [
		[pointer_equal this x,
			op.op_name == "equal" || op.op_name == "equal'"],
		[not_pointer_equal this x,
			op.op_name == "not_equal" || 
				op.op_name == "not_equal'"]
	];
	oo_unary_table op = [];
}

/* A list of things. Do automatic iteration of unary and binary operators on
 * us. 
 *	List [1, 2] + [2, 3] -> List [3, 5]
 *	hd (List [2, 3]) -> 2
 *	List [] == [] -> true
 */
List value = class
	_Object {
	_check_args = [
		[value, "value", check_list]
	];

	// methods
    oo_binary_table op x = [
		[apply2 op value x',
			op.op_name == "subscript" || op.op_name == "subscript'" ||
			op.op_name == "equal" || op.op_name == "equal'"],
		[this.List (apply2 op value x'),
			op.op_name == "join" || op.op_name == "join'"],
		[this.List (map2 (apply2 op) value x'),
			is_list x'],
		[this.List (map (apply2 op' x) value),
			true]
	]
	{
		op' = oo_converse op;

		// strip the List wrapper, if any
		x'
			= x.value, is_List x
			= x;

		apply2 op x1 x2
			= oo_binary_function op x1 x2, is_class x1 
			= oo_binary'_function op x1 x2, is_class x2
			= op.fn x1 x2;
	};

	oo_unary_table op = [
		[apply value,
			op.op_name == "hd" || op.op_name == "tl"],
		[this.List (map apply value),
			true]
	]
	{
		apply x
			= oo_unary_function op x, is_class x
			= op.fn x;
	}
}

/* A group of things. Loop the operation over the group.
 */
Group value = class 
	_Object {
	_check_args = [
		[value, "value", check_list]
	];

	// methods
    oo_binary_table op x = [
		// if_then_else is really a trinary operator
		[map_trinary ite this x?0 x?1,
			op.op_name == "if_then_else"],
		[map_binary op.fn this x,
			is_Group x],
		[map_unary ((converse op.fn) x) this,
			true]
	]
	{
		// need ite as a true trinary
		ite a b c = if a then b else c;

		// we can't call map_trinary directly, since it uses Group and we
		// don't support mutually recursive top-level functions :-(
		// copy-paste it here, keep in sync with the version in _stdenv
		map_nary fn args
			= fn args, groups == []
			= Group (map process [0, 1 .. shortest - 1])
		{
			groups = filter is_Group args;

			shortest = foldr1 min_pair (map (len @ get_value) groups);

			process n
				= [], lor (map (is_noval n) args)
				= map_nary fn (map (extract n) args)
			{
				extract n arg
					= arg.value?n, is_Group arg
					= arg;

				is_noval n arg = is_Group arg && arg.value?n == [];
			}
		}

		map_unary fn a = map_nary (list_1ary fn) [a];
		map_binary fn a b = map_nary (list_2ary fn) [a, b];
		map_trinary fn a b c = map_nary (list_3ary fn) [a, b, c];
	}

	oo_unary_table op = [
		[map_unary op.fn this,
			true]
	]
	{
		// we can't call map_trinary directly, since it uses Group and we
		// don't support mutually recursive top-level functions :-(
		// copy-paste it here, keep in sync with the version in _stdenv
		map_nary fn args
			= fn args, groups == []
			= Group (map process [0, 1 .. shortest - 1])
		{
			groups = filter is_Group args;

			shortest = foldr1 min_pair (map (len @ get_value) groups);

			process n
				= [], lor (map (is_noval n) args)
				= map_nary fn (map (extract n) args)
			{
				extract n arg
					= arg.value?n, is_Group arg
					= arg;

				is_noval n arg = is_Group arg && arg.value?n == [];
			}
		}

		map_unary fn a = map_nary (list_1ary fn) [a];
	}
}

/* Single real number ... eg slider.
 */
Real value = class 
	_Object {
	_check_args = [
		[value, "value", check_real]
	];

	// methods
	oo_binary_table op x = [
		[this.Real (op.fn this.value x.value), 
			is_Real x &&
			op.type == Operator_type.ARITHMETIC],
		[this.Real (op.fn this.value x), 
			is_real x && 
			op.type == Operator_type.ARITHMETIC],
		[op.fn this.value x.value, 
			is_Real x && 
			op.type == Operator_type.RELATIONAL],
		[op.fn this.value x, 
			!is_class x]
	] ++ super.oo_binary_table op x;

	oo_unary_table op = [
		[this.Real (op.fn this.value),
			op.type == Operator_type.ARITHMETIC],
		[op.fn this.value,
			true]
	];
}

/* Single bool ... eg Toggle.
 */
Bool value = class 
	_Object {
	_check_args = [
		[value, "value", check_bool]
	];

	// methods
	oo_binary_table op x = [
		[op.fn this.value x, 
			op.op_name == "if_then_else"],
		[this.Bool (op.fn this.value x.value), 
			is_Bool x],
		[this.Bool (op.fn this.value x), 
			is_bool x]
	] ++ super.oo_binary_table op x;

	oo_unary_table op = [
		[this.Bool (op.fn this.value),
			op.type == Operator_type.ARITHMETIC ||
			op.type == Operator_type.RELATIONAL],
		[op.fn this.value,
			true]
	];
}

/* An editable string.
 */
String caption value = class 
	_Object {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_string]
	];
}

/* An editable real number.
 */
Number caption value = class 
	scope.Real value {
	_check_args = [
		[caption, "caption", check_string]
	];

	Real x = this.Number caption x;
}

/* An editable expression.
 */
Expression caption expr = class 
	(if is_class expr then expr else _Object) {
	_check_args = [
		[caption, "caption", check_string],
		[expr, "expr", check_any]
	];
}

/* A ticking clock.
 */
Clock interval value = class 
	scope.Real value {
	_check_args = [
		[interval, "interval", check_real]
	];

	Real x = this.Clock interval x;
}

/* An editable filename.
 */
Pathname caption value = class 
	_Object {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_string]
	];
}

/* An editable fontname.
 */
Fontname caption value = class 
	_Object {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_string]
	];
}

/* Vector type ... just a finite list of real. Handy for wrapping an
 * argument to eg. im_lintra_vec. Make it behave like a single pixel image.
 */
Vector value = class
	_Object {
	_check_args = [
		[value, "value", check_real_list]
	];

	bands = len value;

	// methods
	oo_binary_table op x = [
		// Vector ++ Vector means bandwise join
		[this.Vector (op.fn this.value x.value), 
			is_Vector x &&
			(op.op_name == "join" || op.op_name == "join'")],
		[this.Vector (op.fn this.value [get_number x]), 
			has_number x &&
			(op.op_name == "join" || op.op_name == "join'")],
		// Vector ? number means extract element
		[op.fn this.value (get_real x), 
			has_real x &&
			(op.op_name == "subscript" || 
				op.op_name == "subscript'")],
		// extra check for lengths equal
		[this.Vector (map_binaryl op.fn this.value x.value), 
			is_Vector x &&
			len value == len x.value &&
			op.type == Operator_type.ARITHMETIC],
		[this.Vector (map_binaryl op.fn this.value (get_real x)), 
			has_real x &&
			op.type == Operator_type.ARITHMETIC],

		// need extra length check
		[this.Vector (map bool_to_real 
			(map_binaryl op.fn this.value x.value)), 
			is_Vector x &&
			len value == len x.value &&
			op.type == Operator_type.RELATIONAL],
		[this.Vector (map bool_to_real 
			(map_binaryl op.fn this.value (get_real x))), 
			has_real x &&
			op.type == Operator_type.RELATIONAL],
		[this.Vector (op.fn this.value x.value),
			is_Vector x &&
			len value == len x.value &&
			op.type == Operator_type.COMPOUND_REWRAP],
		[x.Image (vec op'.op_name x.value value),
			is_Image x],
		[vec op'.op_name x value,
			is_image x],
		[op.fn this.value x, 
			is_real x]
	] ++ super.oo_binary_table op x
	{
		op' = oo_converse op;
	};

	oo_unary_table op = [
		[this.Vector (map_unaryl op.fn this.value),
			op.type == Operator_type.ARITHMETIC],
		[this.Vector (map bool_to_real
			(map_unaryl op.fn this.value)),
			op.type == Operator_type.RELATIONAL],
		[this.Vector (op.fn this.value),
			op.type == Operator_type.COMPOUND_REWRAP],
		[op.fn this.value,
			true]
	];

	// turn an ip bool (or a number, for Vector) into VIPSs 255/0
	bool_to_real x
		= 255, is_bool x && x
		= 255, is_number x && x != 0
		= 0;
}

/* A rectangular array of real.
 */
Matrix_base value = class 
	_Object {
	_check_args = [
		[value, "value", check_matrix]
	];

	// calculate these from value
	width = len value?0;
	height = len value;

	// extract a rectanguar area
	extract left top width height
		= this.Matrix_base 
				((map (take width) @ map (drop left) @
				take height @ drop top) value);

	// methods
	oo_binary_table op x = [
		// mat multiply is special
		[this.Matrix_base mul.value,
			is_Matrix x &&
			op.op_name == "multiply"],
		[this.Matrix_base mul'.value,
			is_Matrix x &&
			op.op_name == "multiply'"],

		// mat divide is also special
		[this.Matrix_base div.value,
			is_Matrix x &&
			op.op_name == "divide"],
		[this.Matrix_base div'.value,
			is_Matrix x &&
			op.op_name == "divide'"],

		// power -1 means invert
		[this.Matrix_base inv.value,
			is_real x && x == -1 &&
			op.op_name == "power"],
		[this.Matrix_base sq.value,
			is_real x && x == 2 &&
			op.op_name == "power"],
		[error "matrix **-1 and **2 only",
			op.op_name == "power" ||
			op.op_name == "power'"],

		// matrix op vector ... treat a vector as a 1 row matrix
		[this.Matrix_base (map (map_binaryl op'.fn x.value) this.value),
			is_Vector x &&
			op.type == Operator_type.ARITHMETIC],
		[this.Matrix_base (map_binaryl op.fn this.value x.value),
			(is_Matrix x || is_Real x) && 
			op.type == Operator_type.ARITHMETIC],

		[this.Matrix_base (map_binaryl op.fn this.value x),
			is_real x && 
			op.type == Operator_type.ARITHMETIC],

		// compound ... don't do iteration
		[this.Matrix_base (op.fn this.value x.value),
			(is_Matrix x || is_Real x || is_Vector x) &&
			op.type == Operator_type.COMPOUND_REWRAP]
	] ++ super.oo_binary_table op x
	{
		mul = im_matmul this x;
		mul' = im_matmul x this;
		div = im_matmul this (im_matinv x);
		div' = im_matmul x (im_matinv this);
		inv = im_matinv this;
		sq = im_matmul this this;
		op' = oo_converse op;
	}

	oo_unary_table op = [
		[this.Matrix_base (map_unaryl op.fn this.value),
			op.type == Operator_type.ARITHMETIC],
		[this.Matrix_base (op.fn this.value),
			op.type == Operator_type.COMPOUND_REWRAP],
		[op.fn this.value,
			true]
	];
}

/* How to display a matrix: text, sliders, toggles, or text plus scale/offset.
 */
Matrix_display = class {
        text = 0;
        slider = 1;
        toggle = 2;
        text_scale_offset = 3;

        is_display = member [text, slider, toggle, text_scale_offset];
}

/* A matrix as VIPS sees them ... add scale, offset and filename. For nip, add
 * a display type as well to control how the widget renders.
 */
Matrix_vips value scale offset filename display = class 
	scope.Matrix_base value {
	_check_args = [
		[scale, "scale", check_real],
		[offset, "offset", check_real],
		[filename, "filename", check_string],
		[display, "display", check_matrix_display]
	];

	Matrix_base x = this.Matrix_vips x scale offset filename display;
}

/* A plain 'ol matrix which can be passed to VIPS.
 */
Matrix value = class 
	Matrix_vips value 1 0 "" Matrix_display.text {}

/* Specialised constructors ... for convolutions, recombinations and
 * morphologies.
 */
Matrix_con scale offset value = class
	Matrix_vips value scale offset "" Matrix_display.text_scale_offset {};

Matrix_rec value = class
	Matrix_vips value 1 0 "" Matrix_display.slider {};

Matrix_mor value = class
	Matrix_vips value 1 0 "" Matrix_display.toggle {};

Matrix_file filename = (im_read_dmask @ expand @ search) filename;

/* A CIE colour ... a triple, plus a format (eg XYZ, Lab etc)
 */
Colour colour_space value = class
	scope.Vector value {
	_check_args = [
		[colour_space, "colour_space", check_colour_space]
	];
	_check_all = [
		[len value == 3, "len value == 3"]
	];

	Vector x = this.Colour colour_space x;

	// make a colour-ish thing from an image
	// back to Colour if we have another 3 band image
	// to a vector if bands > 1
	// to a number otherwise
	itoc im
		= this.Colour nip_type (to_matrix im).value?0, 
			bands == 3
		= scope.Vector (map mean (bandsplit im)),
			bands > 1
		= mean im
	{
		type = get_header "Type" im;
		bands = get_header "Bands" im;
		nip_type = Image_type.colour_spaces.lookup 1 0 type;
	}

	// methods
	oo_binary_table op x = [
		[itoc (op.fn 
			((float) (to_image this).value) 
			((float) (to_image x).value)),
			// here REWRAP means go via image
			op.type == Operator_type.COMPOUND_REWRAP]
	] ++ super.oo_binary_table op x;

	oo_unary_table op = [
		[itoc (op.fn ((float) (to_image this).value)),
			op.type == Operator_type.COMPOUND_REWRAP]
	] ++ super.oo_unary_table op;
}

// a subclass with widgets for picking a space and value
Colour_picker default_colour default_value = class 
	Colour space.value_name colour.expr {
	_vislevel = 3;

	space = Option_enum Image_type.colour_spaces "Colour space" default_colour;
	colour = Expression "Colour value" default_value;

	Colour_edit colour_space value = 
		Colour_picker colour_space value;
}

/* Base scale type.
 */
Scale caption from to value = class 
	scope.Real value {
	_check_args = [
		[caption, "caption", check_string],
		[from, "from", check_real],
		[to, "to", check_real]
	];
	_check_all = [
		[from < to, "from < to"]
	];

	Real x = this.Scale caption from to x;

	// methods
	oo_binary_table op x = [
		[this.Scale caption (op.fn this.from x.from) (op.fn this.to x.to)
			(op.fn this.value x.value), 
			is_Scale x &&
			op.type == Operator_type.ARITHMETIC],
		[this.Scale caption (op.fn this.from x) (op.fn this.to x)
			(op.fn this.value x), 
			is_real x && 
			op.type == Operator_type.ARITHMETIC]
	] ++ super.oo_binary_table op x;
}

/* Base toggle type.
 */
Toggle caption value = class 
	scope.Bool value {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_bool]
	];

	Bool x = this.Toggle caption x;
}

/* Base option type.
 */
Option caption labels value = class 
	scope.Real value {
	_check_args = [
		[caption, "caption", check_string],
		[labels, "labels", check_string_list],
		[value, "value", check_uint]
	];
}

Option_enum enum caption value_name = class
	Option caption enum.names (index (equal value_name) enum.names) {
	// corresponding thing
	value_thing = enum.get_thing value_name;

	Option_edit caption labels value 
		= this.Option_enum enum caption (enum.names ? value);
}

/* A rectangle. width and height can be -ve.
 */
Rect left top width height = class 
	_Object {
	_check_args = [
		[left, "left", check_real],
		[top, "top", check_real],
		[width, "width", check_real],
		[height, "height", check_real]
	];

	// derived
	right = left + width;
	bottom = top + height;

	oo_binary_table op x = [
		[equal x, 
			is_Rect x &&
			(op.op_name == "equal" || op.op_name == "equal'")],
		[!equal x, 
			is_Rect x &&
			(op.op_name == "not_equal" || 
				op.op_name == "not_equal'")],

		// binops with a complex are the same as (comp op comp)
		[oo_binary_function op this (Rect (re x) (im x) 0 0),
			is_complex x],

		// all others are just pairwise
		[this.Rect left' top' width' height',
			is_Rect x && 
			op.type == Operator_type.ARITHMETIC],
		[this.Rect left'' top'' width'' height'',
			has_number x && 
			op.type == Operator_type.ARITHMETIC]
	] ++ super.oo_binary_table op x
	{
		left' = op.fn left x.left;
		top' = op.fn top x.top;
		width' = op.fn width x.width;
		height' = op.fn height x.height;

		left'' = op.fn left x';
		top'' = op.fn top x';
		width'' = op.fn width x';
		height'' = op.fn height x';
		x' = get_number x;
	}

	oo_unary_table op = [
		// arithmetic uops just map
		[this.Rect left' top' width' height',
			op.type == Operator_type.ARITHMETIC],

		// compound uops are just like ops on complex
		// do (width, height) so thing like abs(Arrow) work as you'd expect
		[op.fn (width, height),
			op.type == Operator_type.COMPOUND]
	] ++ super.oo_unary_table op
	{
		left' = op.fn left;
		top' = op.fn top;
		width' = op.fn width;
		height' = op.fn height;
	}

	// empty? ie. contains no pixels
	is_empty = width == 0 || height == 0;

	// normalised version, ie. make width/height +ve and flip the origin
	nleft
		= left + width, width < 0
		= left;
	ntop
		= top + height, height < 0
		= top;
	nwidth = abs width;
	nheight = abs height;
	nright = nleft + nwidth;
	nbottom = ntop + nheight;

	equal x = left == x.left && top == x.top &&
		  width == x.width && height == x.height;

	// contains a point?
	includes_point x y
		= nleft <= x && x <= nright && ntop <= y && y <= nbottom;

	// contains a rect? just test top left and bottom right points
	includes_rect r
		= includes_point r.nleft r.ntop &&
			includes_point r.nright r.nbottom;

	// bounding box of two rects
	// if either is empty, can just return the other
	union r
		= r, is_empty
		= this, r.is_empty
		= Rect left' top' width' height'
	{
		left' = min_pair nleft r.nleft;
		top' = min_pair ntop r.ntop;
		width' = max_pair nright r.nright - left';
		height' = max_pair nbottom r.nbottom - top';
	}

	// intersection of two rects ... empty rect if no intersection
	intersect r
		= Rect left' top' width'' height''
	{
		left' = max_pair nleft r.nleft;
		top' = max_pair ntop r.ntop;
		width' = min_pair nright r.nright - left';
		height' = min_pair nbottom r.nbottom - top';
		width''
			= width', width > 0
			= 0;
		height''
			= height', height > 0
			= 0;
	}

	// expand/collapse by n pixels
	margin_adjust n
		= Rect (left - n) (top - n) (width + 2 * n) (height + 2 * n);
}

/* Values for Compression field in image.
 */
Image_compression = class {
	NO_COMPRESSION = 0;
	TCSF_COMPRESSION = 1;
	JPEG_COMPRESSION = 2;
	LABPACK_COMPRESSED = 3;
	RGB_COMPRESSED = 4;
	LUM_COMPRESSED = 5;
}

/* Values for Coding field in image.
 */
Image_coding = class {
	NOCODING = 0;
	COLQUANT = 1;
	LABPACK = 2;
}

/* Values for BandFmt field in image.
 */
Image_format = class {
	DPCOMPLEX = 9;
	DOUBLE = 8;
	COMPLEX = 7;
	FLOAT = 6;
	INT = 5;
	UINT = 4;
	SHORT = 3;
	USHORT = 2;
	CHAR = 1;
	UCHAR = 0;
	NOTSET = -1;

	maxval fmt 
		= [
			255,		// UCHAR
			127,		// CHAR
			65535,		// USHORT
			32767,		// SHORT
			4294967295,	// UINT
			2147483647,	// INT
			255,		// FLOAT
			255,		// COMPLEX
			255,		// DOUBLE
			255		// DPCOMPLEX
		] ? fmt, fmt >= 0 && fmt <= DPCOMPLEX
		= error (_ "bad value for BandFmt");
}

/* A lookup table.
 */
Table value = class 
	_Object {
	_check_args = [
		[value, "value", check_rectangular]
	];

	/* present col x: is there an x in column col
	 */
	present col x = member (map (extract col) value) x;

	/* Look on column from, return matching item in column to.
	 */
	lookup from to x
		= value?n?to, n >= 0
		= error (_ "item" ++ " " ++ print x ++ " " ++ _ "not in table")
	{
		n = index (equal x) (map (extract from) value);
	}
}

/* A two column lookup table with the first column a string and the second a
 * thing. Used for representing various enums. Option_enum makes a selector
 * from one of these.
 */
Enum value = class 
	Table value {
	_check_args = [
		[value, "value", check_enum]
	]
	{
		check_enum = [is_enum, _ "is [[char, *]]"];
		is_enum x = 
			is_rectangular x && 
			is_listof is_string (map (extract 0) x);
	}

	// handy ... all the names and things as lists
	names = map (extract 0) value;
	things = map (extract 1) value;

	// is a legal name or thing
	has_name x = this.present 1 x;
	has_thing x = this.present 0 x;

	// map things to strings and back
	get_name x = this.lookup 1 0 x;
	get_thing x = this.lookup 0 1 x;
}

/* Type field.
 */
Image_type = class {
	MULTIBAND = 0;
	B_W = 1;
	LUMINANCE = 2;
	XRAY = 3;
	IR = 4;
	YUV = 5;
	RED_ONLY = 6;
	GREEN_ONLY = 7;
	BLUE_ONLY = 8;
	POWER_SPECTRUM = 9;
	HISTOGRAM = 10;
	LUT = 11;
	XYZ = 12;
	LAB = 13;
	CMC = 14;
	CMYK = 15;
	LABQ = 16;
	RGB = 17;
	UCS = 18;
	LCH = 19;
	LABS = 21;
	sRGB = 22;
	YXY = 23;
	FOURIER = 24;
	RGB16 = 25;
	GREY16 = 26;

	/* Table to get names <-> numbers.
	 */
	type_names = Enum [
		["MULTIBAND", MULTIBAND],
		["B_W", B_W],
		["LUMINANCE", LUMINANCE],
		["XRAY", XRAY],
		["IR", IR],
		["YUV", YUV],
		["RED_ONLY", RED_ONLY],
		["GREEN_ONLY", GREEN_ONLY],
		["BLUE_ONLY", BLUE_ONLY],
		["POWER_SPECTRUM", POWER_SPECTRUM],
		["HISTOGRAM", HISTOGRAM],
		["LUT", LUT],
		["XYZ", XYZ],
		["LAB", LAB],
		["CMC", CMC],
		["CMYK", CMYK],
		["LABQ", LABQ],
		["RGB", RGB],
		["UCS", UCS],
		["LCH", LCH],
		["LABS", LABS],
		["sRGB", sRGB],
		["YXY", YXY],
		["FOURIER", FOURIER],
		["RGB16", RGB16],
		["GREY16", GREY16]
	];

	/* Table relating nip's colour space names and VIPS's Type numbers.
	 * Options generated from this, so match the order to the order in the
	 * Colour menu.
	 */
	colour_spaces = Enum [
		["sRGB", sRGB],
		["Lab", LAB],
		["LCh", LCH],
		["XYZ", XYZ],
		["Yxy", YXY],
		["UCS", UCS]
	];

	/* A slightly larger table ... the types of colorimetric image we can
	 * have. Add mono, and the S and Q forms of LAB.
	 */
	image_colour_spaces = Enum [
		["Mono", B_W],
		["sRGB", sRGB],
		["RGB16", RGB16],
		["GREY16", GREY16],
		["Lab", LAB],
		["LabQ", LABQ],
		["LabS", LABS],
		["LCh", LCH],
		["XYZ", XYZ],
		["Yxy", YXY],
		["UCS", UCS]
	];
}

/* Base image type. Simple layer over vips_image.
 */
Image value = class 
	_Object {
	_check_args = [
		[value, "value", check_image]
	];

	// fields from VIPS header
	width = get_width value;
	height = get_height value;
	bands = get_bands value;
	format = get_format value;
	bits = get_bits value;
	coding = get_coding value;
	type = get_type value;
	xres = get_header "Xres" value;
	yres = get_header "Yres" value;
	xoffset = get_header "Xoffset" value;
	yoffset = get_header "Yoffset" value;
	filename = get_header "filename" value;
	
	// convenience ... the area our pixels occupy, as a rect
	rect = Rect 0 0 width height;

	// operator overloading
	// (op Image Vector) done in Vector class
	oo_binary_table op x = [
		// handle image ++ constant here
		[wrap join_result_image,
			(has_real x || is_Vector x) &&
			(op.op_name == "join" || op.op_name == "join'")],
		// image ++ image is slightly different ... we want to
		// sizealike, but we must not bandalike
		[wrap 
			(op.fn (get_image resized?0) (get_image resized?1)),
			has_image x &&
			(op.op_name == "join" || op.op_name == "join'")],
		[wrap ite_result_image,
			op.op_name == "if_then_else"],
		// arithmetic and reational binops between image resize
		// and band_alike images to match
		[wrap 
			(op.fn (get_image rebanded?0) (get_image rebanded?1)),
			has_image x &&
			(op.type == Operator_type.ARITHMETIC ||
				op.type == Operator_type.RELATIONAL)],
		// other op types don't resize
		[wrap (op.fn this.value (get_image x)),
			has_image x],
		[wrap (op.fn this.value (get_number x)),
			has_number x],
		// if it's not a class on the RHS, handle here ... just apply and
		// rewrap
		[wrap (op.fn this.value x),
			!is_class x]
		// all other cases handled by other classes
	] ++ super.oo_binary_table op x
	{
		// wrap the result with this 
		// x can be a non-image, eg. compare "Image v == []" vs. "Image v ==
		// 12"
		wrap x
			= x, op.type == Operator_type.COMPOUND ||
				!is_image x
			= this.Image x;

		join_result_image 
			= value ++ new_stuff, op.op_name == "join"
			= new_stuff ++ value
		{
			new_stuff = image_new width height new_bands
				format
				coding
				Image_type.B_W x xoffset yoffset;
			new_bands
				= get_bands x, has_bands x
				= 1;
		}

		then_part = x?0;
		else_part = x?1;

		// get things about our output from inputs in this order
		objects = [then_part, else_part, this];

		// properties of our output image
		target_bands = get_member_list has_bands get_bands objects;
		target_format = get_member_list has_format get_format objects;
		target_type = get_member_list has_type get_type objects;

		to_image x
			= x, is_image x
			= x.value, is_Image x
			= black + x
		{
			black = im_black width height target_bands;
		}

		then_image = to_image then_part;
		else_image = to_image else_part;

		then_image' = clip2fmt target_format then_image;
		else_image' = clip2fmt target_format else_image;

		ite_resized = size_alike [value, then_image', else_image'];

		ite_result_image = image_set_type target_type
			(if ite_resized?0 then ite_resized?1 else ite_resized?2);

		resized = size_alike [this, x];
		rebanded = bands_alike resized;
	}

	// FIXME ... yuk ... don't use operator hints, just always rewrap if
	// we have an image result
	// forced on us by things like abs:
	// 	abs Vector -> real
	//	abs Image -> Image
	// does not fit well with COMPOUND/whatever scheme
	oo_unary_table op = [
		[this.Image result,
			is_image result],
		[result,
			true]
	]
	{
		result = op.fn this.value;
	}
}

/* Construct an image from a file.
 */
Image_file filename = class 
	Image value {
	_check_args = [
		[filename, "filename", check_string]
	];

	value = vips_image filename;
}

Region image left top width height = class 
	Image value {
	_check_args = [
		[image, "Image", check_Image],
		[left, "left", check_real],
		[top, "top", check_real],
		[width, "width", check_preal],
		[height, "height", check_preal]
	];

	// a rect for our coordinates
	// region.rect gets the rect for the extracted image
	region_rect = Rect left top width height;

	// we need to always succeed ... value is our enclosing image if we're
	// out of bounds
	value 
		= extract_area left top width height image.value,
			image.rect.includes_rect region_rect
		= image.value;
}

Area image left top width height = class
	scope.Region image left top width height {
	Region image left top width height 
		= this.Area image left top width height;
}

Arrow image left top width height = class 
	scope.Rect left top width height {
	_check_args = [
		[image, "Image", check_Image],
		[left, "left", check_real],
		[top, "top", check_real],
		[width, "width", check_real],
		[height, "height", check_real]
	];

	Rect l t w h = this.Arrow image l t w h;
}

HGuide image top = class 
	scope.Arrow image image.rect.left top image.width 0 {
	Arrow image left top width height = this.HGuide image top;
}

VGuide image left = class 
	scope.Arrow image left image.rect.top 0 image.height {
	Arrow image left top width height = this.VGuide image left;
}

Mark image left top = class 
	scope.Arrow image left top 0 0 {
	Arrow image left top width height = this.Mark image left top;
}

// convenience functions: ... specify position as [0 .. 1)

Region_relative image u v w h
	= Region image 
		(image.width * u)
		(image.height * v)
		(image.width * w)
		(image.height * h);

Area_relative image u v w h
	= Area image 
		(image.width * u)
		(image.height * v)
		(image.width * w)
		(image.height * h);

Arrow_relative image u v w h
	= Arrow image 
		(image.width * u)
		(image.height * v)
		(image.width * w)
		(image.height * h);

VGuide_relative image v 
	= VGuide image (image.height * v);

HGuide_relative image u 
	= HGuide image (image.width * u);

Mark_relative image u v
	= Mark image 
		(image.width * u)
		(image.height * v);

Interpolate = class {
	NEAREST_NEIGHBOUR = 0;
	BILINEAR = 1;
	BICUBIC = 2;

	/* Table to map interpol numbers to descriptive strings
	 */
	names = Enum [
		[_ "Nearest neighbour", NEAREST_NEIGHBOUR],
		[_ "Bilinear", BILINEAR],
		[_ "Bicubic", BICUBIC]
	];
}

Render_intent = class {
	PERCEPTUAL = 0;
	RELATIVE = 1;
	SATURATION = 2;
	ABSOLUTE = 3;

	/* Table to get names <-> numbers.
	 */
	names = Enum [
		[_ "Perceptual", PERCEPTUAL],
		[_ "Relative", RELATIVE],
		[_ "Saturation", SATURATION],
		[_ "Absolute", ABSOLUTE]
	];
}

// abstract base class for toolkit menus
Menu = class {}

// a "----" line in a menu
Menuseparator = class Menu {}

// abstract base class for items in menus
Menuitem label tooltip = class Menu {}

Menupullright label tooltip = class Menuitem label tooltip {}

Menuaction label tooltip = class Menuitem label tooltip {}

/* Plots.
 */

Plot_style = class {
	POINT = 0;
	LINE = 1;
	SPLINE = 2;
	BAR = 3;

	names = Enum [
		[_ "Point", POINT],
		[_ "Line", LINE],
		[_ "Spline", SPLINE],
		[_ "Bar", BAR]
	];
}

Plot_format = class {
	YYYY = 0;
	XYYY = 1;
	XYXY = 2;

	names = Enum [
		[_ "YYYY", YYYY],
		[_ "XYYY", XYXY],
		[_ "XYXY", XYXY]
	];
}

Plot_type = class {
	/* Lots of Ys (ie. multiple line plots).
	 */
	YYYY = 0;

	/* First column of matrix is X position, others are Ys (ie. multiple XY 
	 * line plots, all with the same Xes).
	 */
	XYYY = 1;

	/* Many independent XY plots.
	 */
	XYXY = 2;
}

/* "options" is a list of ["key", value] pairs.
 */
Plot options value = class 
	scope.Image value {
	Image value = this.Plot options value;
}

Plot_matrix options value = class 
	Plot options (to_image value).value {
}

Plot_histogram value = class
	scope.Plot [] value {
}

Plot_xy value = class
	scope.Plot [["format", Plot_format.XYYY]] value {
}
