
/* 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_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_instanceof "Matrix_base", "Matrix"];
check_colour_space = [is_colour_space, "colour_space"];
check_rectangular = [is_rectangular, "rectangular [[*]]"];
check_Guide = [is_Guide, "HGuide or VGuide"];
check_Point = check_instance "Point";
check_Colour = check_instance "Colour";

/* Check a set of args. Grab _check_table. It's a list of two check
 * lists: the first checks each arg, and the second checks all args 
 * together. 
 *
 * - each line in argcheck 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 allcheck 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
	= x, badargs == [] && badalls == []
	= error message
{
	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 = errors.badargs ++ "\"" ++ x.name ++ "\"\n" ++
		argmsg ++ allmsg ++
		"\nusage\n" ++ indent ++ usage ++ "\nwhere\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 " ++ 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;

	/* Default: no checks ... override in subclasses.
	 */
	_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 = [];
}

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

	// methods
        oo_binary_table op x = [ 
		[ this.Real (op.fn this.value x.value), 
			is_instanceof "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_instanceof "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 ]
	] ++ super.oo_unary_table op;
}

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

	// methods
        oo_binary_table op x = [ 
		[ if value then x?0 else x?1,
			op.op_name == "if_then_else" ],
		[ this.Bool (op.fn this.value x.value), 
			is_instanceof "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 ]
	] ++ super.oo_unary_table op;
}

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

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

	Real value = Number caption value;
}

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

// the old name
Filename = Pathname "Pick a file";

/* 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]
	] ++ super._check_args;

	bands = len value;

	// methods
        oo_binary_table op x = [ 
		// Vector ++ Vector means bandwise join
		[ this.Vector (op.fn this.value x.value), 
			is_instanceof "Vector" x &&
			(op.op_name == "join" || op.op_name == "join'") ],
		// extra check for lengths equal
		[ this.Vector (map_binary op.fn this.value x.value), 
			is_instanceof "Vector" x &&
			len value == len x.value &&
			op.type == Operator_type.ARITHMETIC ],
		[ this.Vector (map_binary op.fn this.value x.value), 
			is_instanceof "Real" x &&
			op.type == Operator_type.ARITHMETIC ],
		[ this.Vector (map_binary op.fn this.value x), 
			is_real x && 
			op.type == Operator_type.ARITHMETIC ],

		// need extra length check
		[ this.Vector (map bool_to_real 
			(map_binary op.fn this.value x.value)), 
			is_instanceof "Vector" x &&
			len value == len x.value &&
			op.type == Operator_type.RELATIONAL ],
		[ this.Vector (map bool_to_real 
			(map_binary op.fn this.value x.value)), 
			is_instanceof "Real" x &&
			op.type == Operator_type.RELATIONAL ],
		[ this.Vector (map bool_to_real
			(map_binary op.fn this.value x)), 
			is_real x && 
			op.type == Operator_type.RELATIONAL ],
		[ this.Vector (op.fn this.value x.value),
			is_instanceof "Vector" x &&
			len value == len x.value &&
			op.type == Operator_type.COMPOUND_REWRAP ],
		[ x.Image (vec op'.op_name x.value value),
			is_instanceof "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_unary op.fn this.value),
			op.type == Operator_type.ARITHMETIC ],
		[ this.Vector (map bool_to_real
			(map_unary 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 ]
	] ++ super.oo_unary_table op;

	// 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]
	] ++ super._check_args;

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

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

		// mat divide is also special
                [ this.Matrix_base div.value,
			is_instanceof "Matrix_base" x &&
			op.op_name == "divide" ],
                [ this.Matrix_base div'.value,
			is_instanceof "Matrix_base" 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_binary op'.fn x.value) this.value),
			is_instanceof "Vector" x &&
			op.type == Operator_type.ARITHMETIC ],
                [ this.Matrix_base (map_binary op.fn this.value x.value),
			(is_instanceof "Matrix_base" x || 
			 is_instanceof "Real" x) && 
			op.type == Operator_type.ARITHMETIC ],

                [ this.Matrix_base (map_binary 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_instanceof "Matrix_base" x || 
			 is_instanceof "Real" x || 
			 is_instanceof "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_unary 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 ]
	] ++ super.oo_unary_table op;
}

/* 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]
	] ++ super._check_args;

	Matrix_base value = Matrix_vips value 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 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]
	] ++ super._check_args;
	_check_all = [
		[len value == 3, "len value == 3"]
	] ++ super._check_all;

	// 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 = im_header_int "Type" im;
		bands = im_header_int "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;

	Vector value = Colour colour_space value;
}

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

	// 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_instanceof "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;

	Real value = Scale caption from to value;
}

Slider = Scale "";

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

	Bool value = Toggle caption value;
}

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

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

	/* 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 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]
	] ++ super._check_args;

	// derived
	right = left + width;
	bottom = top + 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;

	// 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;
	}

	// equal to another rect
	equal r = left == r.left && top == r.top && 
		width == r.width && height == r.height;

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

	// operator overloading
	// just define equal and not equal
        oo_binary_table op x = [ 
		[ equal x, 
			is_instanceof "Rect" x &&
			(op.op_name == "equal" || op.op_name == "equal'") ],
		[ !equal x, 
			is_instanceof "Rect" x &&
			(op.op_name == "not_equal" || 
				op.op_name == "not_equal'") ]
	] ++ super.oo_binary_table op x;
}

/* 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
		  ] ? fmt, fmt >= 0 && fmt <= INT
		= error errors.bandFmt;
}

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

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

	/* Table relating nip's colour space names and VIPS's Type numbers.
	 */
	colour_spaces = Table [
		[ "XYZ", Image_type.XYZ ],
		[ "Yxy", Image_type.YXY ],
		[ "Lab", Image_type.LAB ],
		[ "LCh", Image_type.LCH ],
		[ "UCS", Image_type.UCS ],
		[ "RGB", Image_type.RGB ],
		[ "sRGB", Image_type.sRGB ]
	];
}

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

	// fields from VIPS header
	width = im_header_int "Xsize" value;
	height = im_header_int "Ysize" value;
	bands = im_header_int "Bands" value;
	format = im_header_int "BandFmt" value;
	coding = im_header_int "Coding" value;
	type = im_header_int "Type" value;
	xres = im_header_double "Xres" value;
	yres = im_header_double "Yres" value;
	xoffset = im_header_int "Xoffset" value;
	yoffset = im_header_int "Yoffset" value;
	filename = im_header_string "filename" value;
	
	// convenience ... the area our pixels occupy as a rect
	rect = Rect (-xoffset) (-yoffset) width height;

	// extract an area, addressed in nip cordinates
	extract_area left top width height = im_extract_area value 
		(left + xoffset) (top + yoffset) width height;

	// operator overloading
	// (op Image Vector) done in Vector class
        oo_binary_table op x = [
                [ this.Image (op.fn this.value x.value),
			is_instanceof "Image" x ||
			is_instanceof "Real" x ],
                [ this.Image result_image,
			op.op_name == "if_then_else" ],
		[ this.Image (vec op.op_name value x.value),
			is_instanceof "Vector" x ],
                [ this.Image (op.fn this.value x),
			is_number x || is_image x ]
	] ++ super.oo_binary_table op x
	{
		to_image x
			= x, is_image x
			= x.value, is_instanceof "Image" x
			= black + x
		{
			black = im_black width height target_bands;
		}

		// get a member from the first of a list of objects to have it
		get_property has get objects
			= hd members, members != []
			= error "unable to get property"
		{
			members = map get (filter has objects);
		}

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

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

		// properties of our output image
		target_bands = get_property 
			(has_member "bands") (get_member "bands") objects;
		target_format = get_property 
			(has_member "format") (get_member "format") objects;
		target_type = get_property has_type get_type objects;

		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;

		result_image = image_set_type target_type
			(if value then then_image' else else_image');
	}

	// 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 ]
	] ++ super.oo_unary_table op
	{
		result = op.fn this.value;
	}
}

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

	file = Filename str;

	value = vips_image file.value;
}

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]
	] ++ super._check_args;

	// 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 
		= image.extract_area left top width height,
			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 = Area image left top width height;
}

Arrow image left top width height = class 
	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]
	] ++ super._check_args;
	_check_all = [
		[image.rect.includes_rect this,
			"image.rect.includes_rect this"]
	] ++ super._check_all;

	// our rect, translated to image cods (ie. offset by origin)
	image_rect = Rect 
		(left + image.xoffset) (top + image.yoffset) width height;

	// methods
        oo_binary_table op x = [
                [ this.Arrow this.image left' top' width' height',
			is_instanceof "Arrow" x && 
			op.type == Operator_type.ARITHMETIC ]
	] ++ super.oo_binary_table op x
	{
		left' = op.fn this.left x.left;
		top' = op.fn this.top x.top;
		width' = op.fn this.width x.width;
		height' = op.fn this.height x.height;
	}

        oo_unary_search op = [
                [ this.Arrow this.image left' top' width' height',
			op.type == Operator_type.ARITHMETIC ]
	] ++ super.oo_unary_table op 
	{
		left' = op.fn this.left;
		top' = op.fn this.top;
		width' = op.fn this.width;
		height' = op.fn this.height;
	}
}

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

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

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

// Mark is the name nip2 expects for Point
Mark = Point;

// convenience functions: make relative on an image ... subtract origin 
// offset, ie. make in pixel coordinates

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

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

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

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

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

Point_relative image u v
	= Point image 
		(image.width * u - image.xoffset)
		(image.height * v - image.yoffset);

Interpolate = class {
	nearest_neighbour = 0;
	bilinear = 1;
	bicubic = 2;

	/* Table to map interpol numbers to descriptive strings
	 */
	names = Table [
		[ "Nearest neighbour", nearest_neighbour ],
		[ "Bilinear", bilinear ],
		[ "Bicubic", bicubic ]
	];
}

Separator = class {}

// renamed in 7.9
estpar = im_estpar;
resample = im_transform_search;
