/*
 * Copyright © 2008 Chris Wilson <chris@chris-wilson.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * The Original Code is the cairo graphics library.
 *
 * The Initial Developer of the Original Code is Chris Wilson.
 *
 * Contributor(s):
 *	Chris Wilson <chris@chris-wilson.co.uk>
 */

#include "cairo-script-private.h"

#include <limits.h> /* INT_MAX */
#include <string.h>

csi_status_t
csi_array_new (csi_t *ctx,
	       csi_integer_t initial_size,
	       csi_object_t *obj)

{
    csi_array_t *array;

    if (ctx->free_array == NULL ||
	ctx->free_array->stack.size <= initial_size)
    {
	csi_status_t status;

	array = _csi_slab_alloc (ctx, sizeof (csi_array_t));
	if (_csi_unlikely (array == NULL))
	    return _csi_error (CSI_STATUS_NO_MEMORY);

	status = _csi_stack_init (ctx, &array->stack,
				  initial_size ? initial_size : 32);
	if (_csi_unlikely (status)) {
	    _csi_slab_free (ctx, array, sizeof (csi_array_t));
	    return status;
	}
    } else {
	array = ctx->free_array;
	ctx->free_array = NULL;
    }

    array->base.type = CSI_OBJECT_TYPE_ARRAY;
    array->base.ref = 1;

    obj->type = CSI_OBJECT_TYPE_ARRAY;
    obj->datum.array = array;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_array_put (csi_t *ctx,
	       csi_array_t *array,
	       csi_integer_t elem,
	       csi_object_t *value)
{
    if (_csi_unlikely (elem < 0))
	return _csi_error (CSI_STATUS_INVALID_SCRIPT);

    if (_csi_unlikely (elem >= array->stack.len)) {
	csi_status_t status;

	status = _csi_stack_grow (ctx, &array->stack, elem + 1);
	if (_csi_unlikely (status))
	    return status;

	memset (array->stack.objects + array->stack.len,
		0, (elem - array->stack.len + 1) * sizeof (csi_object_t));
	array->stack.len = elem + 1;
    }

    csi_object_free (ctx, &array->stack.objects[elem]);
    array->stack.objects[elem] = *csi_object_reference (value);

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_array_get (csi_t *ctx,
	       csi_array_t *array,
	       csi_integer_t elem,
	       csi_object_t *value)
{
    if (_csi_unlikely (elem < 0))
	return _csi_error (CSI_STATUS_INVALID_SCRIPT);

    if (_csi_unlikely (elem > array->stack.len))
	return _csi_error (CSI_STATUS_INVALID_SCRIPT);

    *value = array->stack.objects[elem];
    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_array_append (csi_t *ctx,
		  csi_array_t *array,
		  csi_object_t *obj)
{
    return _csi_stack_push (ctx, &array->stack, csi_object_reference (obj));
}

inline csi_status_t
_csi_array_execute (csi_t *ctx, csi_array_t *array)
{
    csi_integer_t i;
    csi_status_t status;

    if (_csi_unlikely (array->stack.len == 0))
	return CSI_STATUS_SUCCESS;

    for (i = 0; i < array->stack.len; i++) {
	csi_object_t *obj = &array->stack.objects[i];

	if (obj->type & CSI_OBJECT_ATTR_EXECUTABLE) {
	    if (obj->type == (CSI_OBJECT_TYPE_ARRAY |
			      CSI_OBJECT_ATTR_EXECUTABLE))
	    {
		status = _csi_push_ostack_copy (ctx, &array->stack.objects[i]);
	    }
	    else
		status = csi_object_execute (ctx, &array->stack.objects[i]);
	} else
	    status = _csi_push_ostack_copy (ctx, &array->stack.objects[i]);
	if (_csi_unlikely (status))
	    return status;
    }

    return CSI_STATUS_SUCCESS;
}

void
csi_array_free (csi_t *ctx, csi_array_t *array)
{
#if CSI_DEBUG_MALLOC
    _csi_stack_fini (ctx, &array->stack);
    _csi_slab_free (ctx, array, sizeof (csi_array_t));
#else
    csi_integer_t n;

    for (n = 0; n < array->stack.len; n++)
	csi_object_free (ctx, &array->stack.objects[n]);
    array->stack.len = 0;

    if (ctx->free_array != NULL) {
	if (array->stack.size > ctx->free_array->stack.size) {
	    csi_array_t *tmp = ctx->free_array;
	    ctx->free_array = array;
	    array = tmp;
	}

	_csi_stack_fini (ctx, &array->stack);
	_csi_slab_free (ctx, array, sizeof (csi_array_t));
    } else
	ctx->free_array = array;
#endif
}

static cairo_bool_t
_dictionary_name_equal (const void *_a, const void *_b)
{
    return TRUE;
}

csi_status_t
csi_dictionary_new (csi_t *ctx,
		    csi_object_t *obj)

{
    csi_dictionary_t *dict;

    if (ctx->free_dictionary != NULL) {
	dict = ctx->free_dictionary;
	ctx->free_dictionary = NULL;
    } else {
	csi_status_t status;

	dict = _csi_slab_alloc (ctx, sizeof (csi_dictionary_t));
	if (_csi_unlikely (dict == NULL))
	    return _csi_error (CSI_STATUS_NO_MEMORY);

	status = _csi_hash_table_init (&dict->hash_table,
				       _dictionary_name_equal);
	if (_csi_unlikely (status)) {
	    _csi_slab_free (ctx, dict, sizeof (csi_dictionary_t));
	    return status;
	}
    }

    dict->base.type = CSI_OBJECT_TYPE_DICTIONARY;
    dict->base.ref = 1;

    obj->type = CSI_OBJECT_TYPE_DICTIONARY;
    obj->datum.dictionary = dict;

    return CSI_STATUS_SUCCESS;
}

struct _dictionary_entry_pluck {
    csi_t *ctx;
    csi_hash_table_t *hash_table;
};

static void
_dictionary_entry_pluck (void *entry, void *data)
{
    csi_dictionary_entry_t *dict_entry;
    struct _dictionary_entry_pluck *pluck_data;

    dict_entry = entry;
    pluck_data = data;

    _csi_hash_table_remove (pluck_data->hash_table, entry);
    csi_object_free (pluck_data->ctx, &dict_entry->value);
    _csi_slab_free (pluck_data->ctx, entry, sizeof (csi_dictionary_entry_t));
}

void
csi_dictionary_free (csi_t *ctx,
		     csi_dictionary_t *dict)
{
    struct _dictionary_entry_pluck data;

    data.ctx = ctx;
    data.hash_table = &dict->hash_table;
    _csi_hash_table_foreach (&dict->hash_table,
			     _dictionary_entry_pluck,
			     &data);

#if CSI_DEBUG_MALLOC
    _csi_hash_table_fini (&dict->hash_table);
    _csi_slab_free (ctx, dict, sizeof (csi_dictionary_t));
#else
    if (ctx->free_dictionary != NULL) {
	_csi_hash_table_fini (&dict->hash_table);
	_csi_slab_free (ctx, dict, sizeof (csi_dictionary_t));
    } else
	ctx->free_dictionary = dict;
#endif
}

csi_status_t
csi_dictionary_put (csi_t *ctx,
		    csi_dictionary_t *dict,
		    csi_name_t name,
		    csi_object_t *value)
{
    csi_dictionary_entry_t *entry;
    csi_status_t status;

    entry = _csi_hash_table_lookup (&dict->hash_table,
				    (csi_hash_entry_t *) &name);
    if (entry != NULL) {
	/* replace the existing entry */
	csi_object_free (ctx, &entry->value);
	entry->value = *csi_object_reference (value);
	return CSI_STATUS_SUCCESS;
    }

    entry = _csi_slab_alloc (ctx, sizeof (*entry));
    if (_csi_unlikely (entry == NULL))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    entry->hash_entry.hash = name;
    status = _csi_hash_table_insert (&dict->hash_table, &entry->hash_entry);
    if (_csi_unlikely (status)) {
	_csi_slab_free (ctx, entry, sizeof (*entry));
	return status;
    }

    entry->value = *csi_object_reference (value);

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_dictionary_get (csi_t *ctx,
		    csi_dictionary_t *dict,
		    csi_name_t name,
		    csi_object_t *value)
{
    csi_dictionary_entry_t *entry;

    entry = _csi_hash_table_lookup (&dict->hash_table,
				    (csi_hash_entry_t *) &name);
    if (_csi_unlikely (entry == NULL))
	return _csi_error (CSI_STATUS_INVALID_SCRIPT);

    *value = entry->value;
    return CSI_STATUS_SUCCESS;
}

csi_boolean_t
csi_dictionary_has (csi_dictionary_t *dict,
		    csi_name_t name)
{
    return _csi_hash_table_lookup (&dict->hash_table,
				   (csi_hash_entry_t *) &name) != NULL;
}

void
csi_dictionary_remove (csi_t *ctx,
		       csi_dictionary_t *dict,
		       csi_name_t name)
{
    csi_dictionary_entry_t *entry;

    entry = _csi_hash_table_lookup (&dict->hash_table,
				    (csi_hash_entry_t *) &name);
    if (entry != NULL) {
	_csi_hash_table_remove (&dict->hash_table, &entry->hash_entry);
	csi_object_free (ctx, &entry->value);
	_csi_slab_free (ctx, entry, sizeof (csi_dictionary_entry_t));
    }
}

csi_status_t
csi_matrix_new (csi_t *ctx,
		csi_object_t *obj)
{
    csi_matrix_t *matrix;

    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
    if (_csi_unlikely (matrix == NULL))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
    matrix->base.ref = 1;
    cairo_matrix_init_identity (&matrix->matrix);

    obj->type = CSI_OBJECT_TYPE_MATRIX;
    obj->datum.matrix = matrix;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_matrix_new_from_array (csi_t *ctx,
			   csi_object_t *obj,
			   csi_array_t *array)
{
    csi_matrix_t *matrix;

    if (_csi_unlikely (array->stack.len != 6))
	return _csi_error (CSI_STATUS_INVALID_SCRIPT);

    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
    if (_csi_unlikely (matrix == NULL))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
    matrix->base.ref = 1;
    cairo_matrix_init (&matrix->matrix,
		       csi_number_get_value (&array->stack.objects[0]),
		       csi_number_get_value (&array->stack.objects[1]),
		       csi_number_get_value (&array->stack.objects[2]),
		       csi_number_get_value (&array->stack.objects[3]),
		       csi_number_get_value (&array->stack.objects[4]),
		       csi_number_get_value (&array->stack.objects[5]));

    obj->type = CSI_OBJECT_TYPE_MATRIX;
    obj->datum.matrix = matrix;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_matrix_new_from_matrix (csi_t *ctx,
			    csi_object_t *obj,
			    const cairo_matrix_t *m)
{
    csi_matrix_t *matrix;

    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
    if (_csi_unlikely (matrix == NULL))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
    matrix->base.ref = 1;
    matrix->matrix = *m;

    obj->type = CSI_OBJECT_TYPE_MATRIX;
    obj->datum.matrix = matrix;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_matrix_new_from_values (csi_t *ctx,
			    csi_object_t *obj,
			    double v[6])
{
    csi_matrix_t *matrix;

    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
    if (_csi_unlikely (matrix == NULL))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
    matrix->base.ref = 1;
    cairo_matrix_init (&matrix->matrix, v[0], v[1], v[2], v[3], v[4], v[5]);

    obj->type = CSI_OBJECT_TYPE_MATRIX;
    obj->datum.matrix = matrix;

    return CSI_STATUS_SUCCESS;
}

void
csi_matrix_free (csi_t *ctx,
		 csi_matrix_t *obj)
{
    _csi_slab_free (ctx, obj, sizeof (csi_matrix_t));
}


csi_status_t
csi_name_new (csi_t *ctx,
	      csi_object_t *obj,
	      const char *str,
	      int len)
{
    csi_status_t status;

    status = _csi_intern_string (ctx, &str, len);
    if (_csi_unlikely (status))
	return status;

    obj->type = CSI_OBJECT_TYPE_NAME;
    obj->datum.name = (csi_name_t) str;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_name_new_static (csi_t *ctx,
		     csi_object_t *obj,
		     const char *str)
{
    csi_status_t status;

    status = _csi_intern_string (ctx, &str, strlen (str));
    if (_csi_unlikely (status))
	return status;

    obj->type = CSI_OBJECT_TYPE_NAME;
    obj->datum.name = (csi_name_t) str;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_string_new (csi_t *ctx,
		csi_object_t *obj,
		const char *str,
		int len)
{
    csi_string_t *string;

    if (len < 0)
	len = strlen (str);
    if (_csi_unlikely (len >= INT_MAX))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    if (ctx->free_string == NULL || ctx->free_string->len <= len) {
	string = _csi_slab_alloc (ctx, sizeof (csi_string_t));
	if (_csi_unlikely (string == NULL))
	    return _csi_error (CSI_STATUS_NO_MEMORY);

	string->string = _csi_alloc (ctx, len + 1);
	if (_csi_unlikely (string->string == NULL)) {
	    _csi_slab_free (ctx, string, sizeof (csi_string_t));
	    return _csi_error (CSI_STATUS_NO_MEMORY);
	}
    } else {
	string = ctx->free_string;
	ctx->free_string = NULL;
    }

    if (str != NULL) {
	memcpy (string->string, str, len);
	string->string[len] = '\0';
    }
    string->len = len;
    string->deflate = 0;

    string->base.type = CSI_OBJECT_TYPE_STRING;
    string->base.ref = 1;

    obj->type = CSI_OBJECT_TYPE_STRING;
    obj->datum.string = string;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_string_deflate_new (csi_t *ctx,
			csi_object_t *obj,
			void *bytes,
			int in_len,
			int out_len)
{
    csi_status_t status;
    csi_string_t *string;

    status = csi_string_new (ctx, obj, bytes, in_len);
    if (_csi_unlikely (status))
	return status;

    string = obj->datum.string;
    string->deflate = out_len;

    return CSI_STATUS_SUCCESS;
}

csi_status_t
csi_string_new_from_bytes (csi_t *ctx,
	                   csi_object_t *obj,
			   char *bytes,
			   unsigned int len)
{
    csi_string_t *string;

    if (_csi_unlikely (len >= INT_MAX))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    string = _csi_slab_alloc (ctx, sizeof (csi_string_t));
    if (_csi_unlikely (string == NULL))
	return _csi_error (CSI_STATUS_NO_MEMORY);

    string->string = bytes;
    string->len = len;
    string->deflate = 0;

    string->base.type = CSI_OBJECT_TYPE_STRING;
    string->base.ref = 1;

    obj->type = CSI_OBJECT_TYPE_STRING;
    obj->datum.string = string;

    return CSI_STATUS_SUCCESS;
}

static inline csi_status_t
_csi_string_execute (csi_t *ctx, csi_string_t *string)
{
    csi_status_t status;
    csi_object_t obj;

    if (_csi_unlikely (string->len == 0))
	return CSI_STATUS_SUCCESS;

    status = csi_file_new_for_bytes (ctx, &obj, string->string, string->len);
    if (_csi_unlikely (status))
	return status;

    status = _csi_scan_file (ctx, obj.datum.file);
    csi_object_free (ctx, &obj);

    return status;
}

void
csi_string_free (csi_t *ctx, csi_string_t *string)
{
#if CSI_DEBUG_MALLOC
    _csi_free (ctx, string->string);
    _csi_slab_free (ctx, string, sizeof (csi_string_t));
#else
    if (ctx->free_string != NULL) {
	if (string->len > ctx->free_string->len) {
	    csi_string_t *tmp = ctx->free_string;
	    ctx->free_string = string;
	    string = tmp;
	}

	_csi_free (ctx, string->string);
	_csi_slab_free (ctx, string, sizeof (csi_string_t));
    } else
	ctx->free_string = string;
#endif
}

csi_status_t
csi_object_execute (csi_t *ctx, csi_object_t *obj)
{
    csi_status_t status;
    csi_object_t indirect;

 INDIRECT:
    switch (obj->type & CSI_OBJECT_TYPE_MASK) {
    case CSI_OBJECT_TYPE_NAME:
	status = _csi_name_lookup (ctx, obj->datum.name, &indirect);
	if (_csi_unlikely (status))
	    return status;
	if (indirect.type & CSI_OBJECT_ATTR_EXECUTABLE) {
	    obj = &indirect;
	    goto INDIRECT;
	} else
	    return _csi_push_ostack_copy (ctx, &indirect);

    case CSI_OBJECT_TYPE_OPERATOR:
	return obj->datum.op (ctx);

    case CSI_OBJECT_TYPE_ARRAY:
	return _csi_array_execute (ctx, obj->datum.array);
    case CSI_OBJECT_TYPE_FILE:
	return _csi_file_execute (ctx, obj->datum.file);
    case CSI_OBJECT_TYPE_STRING:
	return _csi_string_execute (ctx, obj->datum.string);

    default:
	return _csi_push_ostack_copy (ctx, obj);
    }
}

csi_object_t *
csi_object_reference (csi_object_t *obj)
{
    if (CSI_OBJECT_IS_CAIRO (obj)) {
	switch (obj->type & CSI_OBJECT_TYPE_MASK) {
	case CSI_OBJECT_TYPE_CONTEXT:
	    cairo_reference (obj->datum.cr);
	    break;
	case CSI_OBJECT_TYPE_FONT:
	    cairo_font_face_reference (obj->datum.font_face);
	    break;
	case CSI_OBJECT_TYPE_PATTERN:
	    cairo_pattern_reference (obj->datum.pattern);
	    break;
	case CSI_OBJECT_TYPE_SCALED_FONT:
	    cairo_scaled_font_reference (obj->datum.scaled_font);
	    break;
	case CSI_OBJECT_TYPE_SURFACE:
	    cairo_surface_reference (obj->datum.surface);
	    break;
	}
    } else if (CSI_OBJECT_IS_COMPOUND (obj)) {
	obj->datum.object->ref++;
    }

    return obj;
}

void
csi_object_free (csi_t *ctx,
		 csi_object_t *obj)
{
    if (CSI_OBJECT_IS_CAIRO (obj)) {
	switch (obj->type & CSI_OBJECT_TYPE_MASK) {
	case CSI_OBJECT_TYPE_CONTEXT:
	    cairo_destroy (obj->datum.cr);
	    break;
	case CSI_OBJECT_TYPE_FONT:
	    cairo_font_face_destroy (obj->datum.font_face);
	    break;
	case CSI_OBJECT_TYPE_PATTERN:
	    cairo_pattern_destroy (obj->datum.pattern);
	    break;
	case CSI_OBJECT_TYPE_SCALED_FONT:
	    cairo_scaled_font_destroy (obj->datum.scaled_font);
	    break;
	case CSI_OBJECT_TYPE_SURFACE:
	    cairo_surface_destroy (obj->datum.surface);
	    break;
	}
    } else if (CSI_OBJECT_IS_COMPOUND (obj)) {
	if (--obj->datum.object->ref)
	    return;

	switch (obj->type & CSI_OBJECT_TYPE_MASK) {
	case CSI_OBJECT_TYPE_ARRAY:
	    csi_array_free (ctx, obj->datum.array);
	    break;
	case CSI_OBJECT_TYPE_DICTIONARY:
	    csi_dictionary_free (ctx, obj->datum.dictionary);
	    break;
	case CSI_OBJECT_TYPE_FILE:
	    _csi_file_free (ctx, obj->datum.file);
	    break;
	case CSI_OBJECT_TYPE_MATRIX:
	    csi_matrix_free (ctx, obj->datum.matrix);
	    break;
	case CSI_OBJECT_TYPE_STRING:
	    csi_string_free (ctx, obj->datum.string);
	    break;
	default:
	    break;
	}
    }
}

csi_status_t
csi_object_as_file (csi_t *ctx,
		    csi_object_t *src,
		    csi_object_t *file)
{
    int type = csi_object_get_type (src);
    switch (type) {
    case CSI_OBJECT_TYPE_FILE:
	*file = *csi_object_reference (src);
	return CSI_STATUS_SUCCESS;
    case CSI_OBJECT_TYPE_STRING:
	 return csi_file_new_from_string (ctx, file, src->datum.string);
    case CSI_OBJECT_TYPE_ARRAY:
#if 0
	if (src->type & CSI_OBJECT_ATTR_EXECUTABLE)
	    return _csi_file_new_from_procedure (cs, src);
#endif
    default:
	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
    }
}

static int
lexcmp (void const *a, size_t alen,
	void const *b, size_t blen)
{
    size_t len = alen < blen ? alen : blen;
    int cmp = memcmp (a, b, len);
    if (cmp)
	return cmp;
    if (alen == blen)
	return 0;
    return alen < blen ? -1 : +1;
}

csi_boolean_t
csi_object_eq (csi_object_t *a,
	       csi_object_t *b)
{
    csi_object_type_t atype = csi_object_get_type (a);
    csi_object_type_t btype = csi_object_get_type (b);

    if (atype == btype) {
	switch (atype) {
	case CSI_OBJECT_TYPE_BOOLEAN:
	    return a->datum.boolean == b->datum.boolean;
	case CSI_OBJECT_TYPE_INTEGER:
	    return a->datum.integer == b->datum.integer;
	case CSI_OBJECT_TYPE_REAL:
	    return a->datum.real == b->datum.real;
	case CSI_OBJECT_TYPE_NAME:
	    return a->datum.name == b->datum.name;
	case CSI_OBJECT_TYPE_STRING:
	    return 0 == lexcmp (a->datum.string->string,
				a->datum.string->len,
				b->datum.string->string,
				b->datum.string->len);
	case CSI_OBJECT_TYPE_NULL:
	case CSI_OBJECT_TYPE_MARK:
	    return TRUE;
	case CSI_OBJECT_TYPE_OPERATOR:
	    return a->datum.op == b->datum.op;
	case CSI_OBJECT_TYPE_ARRAY:
	case CSI_OBJECT_TYPE_DICTIONARY:
	case CSI_OBJECT_TYPE_FILE:
	case CSI_OBJECT_TYPE_MATRIX:
	case CSI_OBJECT_TYPE_CONTEXT:
	case CSI_OBJECT_TYPE_FONT:
	case CSI_OBJECT_TYPE_PATTERN:
	case CSI_OBJECT_TYPE_SCALED_FONT:
	case CSI_OBJECT_TYPE_SURFACE:
	    return a->datum.ptr == b->datum.ptr;
	}
    }

    if (atype < btype) {
	csi_object_t *c;
	csi_object_type_t ctype;
	c = a; a = b; b = c;
	ctype = atype; atype = btype; btype = ctype;
    }

    switch ((int) atype) {
    case CSI_OBJECT_TYPE_INTEGER:
	if (btype == CSI_OBJECT_TYPE_BOOLEAN) {
	    return a->datum.integer == b->datum.boolean;
	}
	break;
    case CSI_OBJECT_TYPE_REAL:
	if (btype == CSI_OBJECT_TYPE_INTEGER) {
	    return a->datum.real == b->datum.integer;
	}
	else if (btype == CSI_OBJECT_TYPE_BOOLEAN) {
	    return a->datum.real == b->datum.boolean;
	}
	break;

    case CSI_OBJECT_TYPE_STRING:
	if (btype == CSI_OBJECT_TYPE_NAME) {
	    const char *bstr = (const char *) b->datum.name;
	    return 0 == lexcmp (a->datum.string->string,
				a->datum.string->len,
				bstr,
				strlen (bstr));
	}
	break;

    default:
	break;
    }

    return FALSE;
}

csi_status_t
csi_object_compare (csi_object_t *a,
		    csi_object_t *b,
		    int *out)
{
    csi_object_type_t atype = csi_object_get_type (a);
    csi_object_type_t btype = csi_object_get_type (b);
    int sign;

    if (csi_object_eq (a, b)){
	*out = 0;
	return CSI_STATUS_SUCCESS;
    }

#define CMP(x,y) ((x) < (y) ? -1 : +1)

    if (atype == btype) {
	switch (atype) {
	case CSI_OBJECT_TYPE_BOOLEAN:
	    *out = CMP (a->datum.boolean, b->datum.boolean);
	    return CSI_STATUS_SUCCESS;
	case CSI_OBJECT_TYPE_INTEGER:
	    *out = CMP (a->datum.integer, b->datum.integer);
	    return CSI_STATUS_SUCCESS;
	case CSI_OBJECT_TYPE_REAL:
	    *out = CMP (a->datum.real, b->datum.real);
	    return CSI_STATUS_SUCCESS;
	case CSI_OBJECT_TYPE_NAME: {
	    const char *x = (char const *) a->datum.name;
	    const char *y = (char const *) b->datum.name;
	    *out = lexcmp (x, strlen(x), y, strlen (y));
	    return CSI_STATUS_SUCCESS;
	}
	case CSI_OBJECT_TYPE_STRING:
	    *out = lexcmp (a->datum.string->string,
			   a->datum.string->len,
			   b->datum.string->string,
			   b->datum.string->len);
	    return CSI_STATUS_SUCCESS;
	case CSI_OBJECT_TYPE_NULL:
	case CSI_OBJECT_TYPE_MARK:
	case CSI_OBJECT_TYPE_OPERATOR:
	case CSI_OBJECT_TYPE_ARRAY:
	case CSI_OBJECT_TYPE_DICTIONARY:
	case CSI_OBJECT_TYPE_FILE:
	case CSI_OBJECT_TYPE_MATRIX:
	case CSI_OBJECT_TYPE_CONTEXT:
	case CSI_OBJECT_TYPE_FONT:
	case CSI_OBJECT_TYPE_PATTERN:
	case CSI_OBJECT_TYPE_SCALED_FONT:
	case CSI_OBJECT_TYPE_SURFACE:
	    goto TYPE_CHECK_ERROR;
	}
    }

    sign = +1;
    if (atype < btype) {
	csi_object_t *c;
	csi_object_type_t ctype;
	c = a; a = b; b = c;
	ctype = atype; atype = btype; btype = ctype;
	sign = -1;
    }

    switch ((int) atype) {
    case CSI_OBJECT_TYPE_INTEGER:
	if (btype == CSI_OBJECT_TYPE_BOOLEAN) {
	    *out = sign * CMP (a->datum.integer, !!b->datum.boolean);
	    return CSI_STATUS_SUCCESS;
	}
	break;
    case CSI_OBJECT_TYPE_REAL:
	if (btype == CSI_OBJECT_TYPE_INTEGER) {
	    *out = sign * CMP (a->datum.real, b->datum.integer);
	    return CSI_STATUS_SUCCESS;
	}
	else if (btype == CSI_OBJECT_TYPE_BOOLEAN) {
	    *out = sign * CMP (a->datum.real, !!b->datum.boolean);
	    return CSI_STATUS_SUCCESS;
	}
	break;

    case CSI_OBJECT_TYPE_STRING:
	if (btype == CSI_OBJECT_TYPE_NAME) {
	    const char *bstr = (const char *) b->datum.name;
	    *out = sign * lexcmp (a->datum.string->string,
				  a->datum.string->len,
				  bstr,
				  strlen (bstr));
	    return CSI_STATUS_SUCCESS;
	}
	break;

    default:
	break;
    }

#undef CMP

 TYPE_CHECK_ERROR:
    return _csi_error (CSI_STATUS_SCRIPT_INVALID_TYPE);
}
