/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0 
 * (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License. 
 */

#include "avro/allocation.h"
#include "avro/basics.h"
#include "avro/errors.h"
#include "avro/legacy.h"
#include "avro/refcount.h"
#include "avro/schema.h"
#include "avro_private.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "datum.h"
#include "schema.h"
#include "encoding.h"

#define DEFAULT_TABLE_SIZE 32

static void avro_datum_init(avro_datum_t datum, avro_type_t type)
{
	datum->type = type;
	datum->class_type = AVRO_DATUM;
	avro_refcount_set(&datum->refcount, 1);
}

static void
avro_str_free_wrapper(void *ptr, size_t sz)
{
	// don't need sz, since the size is stored in the string buffer
	AVRO_UNUSED(sz);
	avro_str_free((char *)ptr);
}

static avro_datum_t avro_string_private(char *str, int64_t size,
					avro_free_func_t string_free)
{
	struct avro_string_datum_t *datum =
	    (struct avro_string_datum_t *) avro_new(struct avro_string_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new string datum");
		return NULL;
	}
	datum->s = str;
	datum->size = size;
	datum->free = string_free;

	avro_datum_init(&datum->obj, AVRO_STRING);
	return &datum->obj;
}

avro_datum_t avro_string(const char *str)
{
	char *p = avro_strdup(str);
	if (!p) {
		avro_set_error("Cannot copy string content");
		return NULL;
	}
	avro_datum_t s_datum = avro_string_private(p, 0, avro_str_free_wrapper);
	if (!s_datum) {
		avro_str_free(p);
	}

	return s_datum;
}

avro_datum_t avro_givestring(const char *str,
			     avro_free_func_t free)
{
	int64_t  sz = strlen(str)+1;
	return avro_string_private((char *)str, sz, free);
}

int avro_string_get(avro_datum_t datum, char **p)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_string(datum), "string datum");
	check_param(EINVAL, p, "string buffer");

	*p = avro_datum_to_string(datum)->s;
	return 0;
}

static int avro_string_set_private(avro_datum_t datum,
	       			   const char *p, int64_t size,
				   avro_free_func_t string_free)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_string(datum), "string datum");
	check_param(EINVAL, p, "string content");

	struct avro_string_datum_t *string = avro_datum_to_string(datum);
	if (string->free) {
		string->free(string->s, string->size);
	}
	string->free = string_free;
	string->s = (char *)p;
	string->size = size;
	return 0;
}

int avro_string_set(avro_datum_t datum, const char *p)
{
	char *string_copy = avro_strdup(p);
	int rval;
	if (!string_copy) {
		avro_set_error("Cannot copy string content");
		return ENOMEM;
	}
	rval = avro_string_set_private(datum, string_copy, 0,
				       avro_str_free_wrapper);
	if (rval) {
		avro_str_free(string_copy);
	}
	return rval;
}

int avro_givestring_set(avro_datum_t datum, const char *p,
			avro_free_func_t free)
{
	int64_t  size = strlen(p)+1;
	return avro_string_set_private(datum, p, size, free);
}

static avro_datum_t avro_bytes_private(char *bytes, int64_t size,
				       avro_free_func_t bytes_free)
{
	struct avro_bytes_datum_t *datum;
	datum = (struct avro_bytes_datum_t *) avro_new(struct avro_bytes_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new bytes datum");
		return NULL;
	}
	datum->bytes = bytes;
	datum->size = size;
	datum->free = bytes_free;

	avro_datum_init(&datum->obj, AVRO_BYTES);
	return &datum->obj;
}

avro_datum_t avro_bytes(const char *bytes, int64_t size)
{
	char *bytes_copy = (char *) avro_malloc(size);
	if (!bytes_copy) {
		avro_set_error("Cannot copy bytes content");
		return NULL;
	}
	memcpy(bytes_copy, bytes, size);
	avro_datum_t  result =
		avro_bytes_private(bytes_copy, size, avro_alloc_free_func);
	if (result == NULL) {
		avro_free(bytes_copy, size);
	}
	return result;
}

avro_datum_t avro_givebytes(const char *bytes, int64_t size,
			    avro_free_func_t free)
{
	return avro_bytes_private((char *)bytes, size, free);
}

static int avro_bytes_set_private(avro_datum_t datum, const char *bytes,
				  const int64_t size,
				  avro_free_func_t bytes_free)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_bytes(datum), "bytes datum");

	struct avro_bytes_datum_t *b = avro_datum_to_bytes(datum);
	if (b->free) {
		b->free(b->bytes, b->size);
	}

	b->free = bytes_free;
	b->bytes = (char *)bytes;
	b->size = size;
	return 0;
}

int avro_bytes_set(avro_datum_t datum, const char *bytes, const int64_t size)
{
	int rval;
	char *bytes_copy = (char *) avro_malloc(size);
	if (!bytes_copy) {
		avro_set_error("Cannot copy bytes content");
		return ENOMEM;
	}
	memcpy(bytes_copy, bytes, size);
	rval = avro_bytes_set_private(datum, bytes_copy, size, avro_alloc_free_func);
	if (rval) {
		avro_free(bytes_copy, size);
	}
	return rval;
}

int avro_givebytes_set(avro_datum_t datum, const char *bytes,
		       const int64_t size, avro_free_func_t free)
{
	return avro_bytes_set_private(datum, bytes, size, free);
}

int avro_bytes_get(avro_datum_t datum, char **bytes, int64_t * size)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_bytes(datum), "bytes datum");
	check_param(EINVAL, bytes, "bytes");
	check_param(EINVAL, size, "size");

	*bytes = avro_datum_to_bytes(datum)->bytes;
	*size = avro_datum_to_bytes(datum)->size;
	return 0;
}

avro_datum_t avro_int32(int32_t i)
{
	struct avro_int32_datum_t *datum =
	    (struct avro_int32_datum_t *) avro_new(struct avro_int32_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new int datum");
		return NULL;
	}
	datum->i32 = i;

	avro_datum_init(&datum->obj, AVRO_INT32);
	return &datum->obj;
}

int avro_int32_get(avro_datum_t datum, int32_t * i)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_int32(datum), "int datum");
	check_param(EINVAL, i, "value pointer");

	*i = avro_datum_to_int32(datum)->i32;
	return 0;
}

int avro_int32_set(avro_datum_t datum, const int32_t i)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_int32(datum), "int datum");

	avro_datum_to_int32(datum)->i32 = i;
	return 0;
}

avro_datum_t avro_int64(int64_t l)
{
	struct avro_int64_datum_t *datum =
	    (struct avro_int64_datum_t *) avro_new(struct avro_int64_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new long datum");
		return NULL;
	}
	datum->i64 = l;

	avro_datum_init(&datum->obj, AVRO_INT64);
	return &datum->obj;
}

int avro_int64_get(avro_datum_t datum, int64_t * l)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_int64(datum), "long datum");
	check_param(EINVAL, l, "value pointer");

	*l = avro_datum_to_int64(datum)->i64;
	return 0;
}

int avro_int64_set(avro_datum_t datum, const int64_t l)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_int64(datum), "long datum");

	avro_datum_to_int64(datum)->i64 = l;
	return 0;
}

avro_datum_t avro_float(float f)
{
	struct avro_float_datum_t *datum =
	    (struct avro_float_datum_t *) avro_new(struct avro_float_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new float datum");
		return NULL;
	}
	datum->f = f;

	avro_datum_init(&datum->obj, AVRO_FLOAT);
	return &datum->obj;
}

int avro_float_set(avro_datum_t datum, const float f)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_float(datum), "float datum");

	avro_datum_to_float(datum)->f = f;
	return 0;
}

int avro_float_get(avro_datum_t datum, float *f)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_float(datum), "float datum");
	check_param(EINVAL, f, "value pointer");

	*f = avro_datum_to_float(datum)->f;
	return 0;
}

avro_datum_t avro_double(double d)
{
	struct avro_double_datum_t *datum =
	    (struct avro_double_datum_t *) avro_new(struct avro_double_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new double atom");
		return NULL;
	}
	datum->d = d;

	avro_datum_init(&datum->obj, AVRO_DOUBLE);
	return &datum->obj;
}

int avro_double_set(avro_datum_t datum, const double d)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_double(datum), "double datum");

	avro_datum_to_double(datum)->d = d;
	return 0;
}

int avro_double_get(avro_datum_t datum, double *d)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_double(datum), "double datum");
	check_param(EINVAL, d, "value pointer");

	*d = avro_datum_to_double(datum)->d;
	return 0;
}

avro_datum_t avro_boolean(int8_t i)
{
	struct avro_boolean_datum_t *datum =
	    (struct avro_boolean_datum_t *) avro_new(struct avro_boolean_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new boolean datum");
		return NULL;
	}
	datum->i = i;
	avro_datum_init(&datum->obj, AVRO_BOOLEAN);
	return &datum->obj;
}

int avro_boolean_set(avro_datum_t datum, const int8_t i)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_boolean(datum), "boolean datum");

	avro_datum_to_boolean(datum)->i = i;
	return 0;
}

int avro_boolean_get(avro_datum_t datum, int8_t * i)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_boolean(datum), "boolean datum");
	check_param(EINVAL, i, "value pointer");

	*i = avro_datum_to_boolean(datum)->i;
	return 0;
}

avro_datum_t avro_null(void)
{
	static struct avro_obj_t obj = {
		AVRO_NULL,
		AVRO_DATUM,
		1
	};
	return avro_datum_incref(&obj);
}

avro_datum_t avro_union(avro_schema_t schema,
			int64_t discriminant, avro_datum_t value)
{
	check_param(NULL, is_avro_schema(schema), "schema");

	struct avro_union_datum_t *datum =
	    (struct avro_union_datum_t *) avro_new(struct avro_union_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new union datum");
		return NULL;
	}
	datum->schema = avro_schema_incref(schema);
	datum->discriminant = discriminant;
	datum->value = avro_datum_incref(value);

	avro_datum_init(&datum->obj, AVRO_UNION);
	return &datum->obj;
}

int64_t avro_union_discriminant(const avro_datum_t datum)
{
	return avro_datum_to_union(datum)->discriminant;
}

avro_datum_t avro_union_current_branch(avro_datum_t datum)
{
	return avro_datum_to_union(datum)->value;
}

int avro_union_set_discriminant(avro_datum_t datum,
				int discriminant,
				avro_datum_t *branch)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_union(datum), "union datum");

	struct avro_union_datum_t  *unionp = avro_datum_to_union(datum);

	avro_schema_t  schema = unionp->schema;
	avro_schema_t  branch_schema =
	    avro_schema_union_branch(schema, discriminant);

	if (branch_schema == NULL) {
		// That branch doesn't exist!
		avro_set_error("Branch %d doesn't exist", discriminant);
		return EINVAL;
	}

	if (unionp->discriminant != discriminant) {
		// If we're changing the branch, throw away any old
		// branch value.
		if (unionp->value != NULL) {
			avro_datum_decref(unionp->value);
			unionp->value = NULL;
		}

		unionp->discriminant = discriminant;
	}

	// Create a new branch value, if there isn't one already.
	if (unionp->value == NULL) {
		unionp->value = avro_datum_from_schema(branch_schema);
	}

	if (branch != NULL) {
		*branch = unionp->value;
	}

	return 0;
}

avro_datum_t avro_record(avro_schema_t schema)
{
	check_param(NULL, is_avro_schema(schema), "schema");

	struct avro_record_datum_t *datum =
	    (struct avro_record_datum_t *) avro_new(struct avro_record_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new record datum");
		return NULL;
	}
	datum->field_order = st_init_numtable_with_size(DEFAULT_TABLE_SIZE);
	if (!datum->field_order) {
		avro_set_error("Cannot create new record datum");
		avro_freet(struct avro_record_datum_t, datum);
		return NULL;
	}
	datum->fields_byname = st_init_strtable_with_size(DEFAULT_TABLE_SIZE);
	if (!datum->fields_byname) {
		avro_set_error("Cannot create new record datum");
		st_free_table(datum->field_order);
		avro_freet(struct avro_record_datum_t, datum);
		return NULL;
	}

	datum->schema = avro_schema_incref(schema);
	avro_datum_init(&datum->obj, AVRO_RECORD);
	return &datum->obj;
}

int
avro_record_get(const avro_datum_t datum, const char *field_name,
		avro_datum_t * field)
{
	union {
		avro_datum_t field;
		st_data_t data;
	} val;
	if (is_avro_datum(datum) && is_avro_record(datum) && field_name) {
		if (st_lookup
		    (avro_datum_to_record(datum)->fields_byname,
		     (st_data_t) field_name, &(val.data))) {
			*field = val.field;
			return 0;
		}
	}
	avro_set_error("No field named %s", field_name);
	return EINVAL;
}

int
avro_record_set(avro_datum_t datum, const char *field_name,
		const avro_datum_t field_value)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_record(datum), "record datum");
	check_param(EINVAL, field_name, "field_name");

	char *key = (char *)field_name;
	avro_datum_t old_field;

	if (avro_record_get(datum, field_name, &old_field) == 0) {
		/* Overriding old value */
		avro_datum_decref(old_field);
	} else {
		/* Inserting new value */
		struct avro_record_datum_t *record =
		    avro_datum_to_record(datum);
		key = avro_strdup(field_name);
		if (!key) {
			avro_set_error("Cannot copy field name");
			return ENOMEM;
		}
		st_insert(record->field_order,
			  record->field_order->num_entries,
			  (st_data_t) key);
	}
	avro_datum_incref(field_value);
	st_insert(avro_datum_to_record(datum)->fields_byname,
		  (st_data_t) key, (st_data_t) field_value);
	return 0;
}

avro_datum_t avro_enum(avro_schema_t schema, int i)
{
	check_param(NULL, is_avro_schema(schema), "schema");

	struct avro_enum_datum_t *datum =
	    (struct avro_enum_datum_t *) avro_new(struct avro_enum_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new enum datum");
		return NULL;
	}
	datum->schema = avro_schema_incref(schema);
	datum->value = i;

	avro_datum_init(&datum->obj, AVRO_ENUM);
	return &datum->obj;
}

int avro_enum_get(const avro_datum_t datum)
{
	return avro_datum_to_enum(datum)->value;
}

const char *avro_enum_get_name(const avro_datum_t datum)
{
	int  value = avro_enum_get(datum);
	avro_schema_t  schema = avro_datum_to_enum(datum)->schema;
	return avro_schema_enum_get(schema, value);
}

int avro_enum_set(avro_datum_t datum, const int symbol_value)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_enum(datum), "enum datum");

	avro_datum_to_enum(datum)->value = symbol_value;
	return 0;
}

int avro_enum_set_name(avro_datum_t datum, const char *symbol_name)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_enum(datum), "enum datum");
	check_param(EINVAL, symbol_name, "symbol name");

	avro_schema_t  schema = avro_datum_to_enum(datum)->schema;
	int  symbol_value = avro_schema_enum_get_by_name(schema, symbol_name);
	if (symbol_value == -1) {
		avro_set_error("No symbol named %s", symbol_name);
		return EINVAL;
	}
	avro_datum_to_enum(datum)->value = symbol_value;
	return 0;
}

static avro_datum_t avro_fixed_private(avro_schema_t schema,
				       const char *bytes, const int64_t size,
				       avro_free_func_t fixed_free)
{
	check_param(NULL, is_avro_schema(schema), "schema");
	struct avro_fixed_schema_t *fschema = avro_schema_to_fixed(schema);
	if (size != fschema->size) {
		avro_free((char *) bytes, size);
		avro_set_error("Fixed size (%zu) doesn't match schema (%zu)",
			       (size_t) size, (size_t) fschema->size);
		return NULL;
	}

	struct avro_fixed_datum_t *datum =
	    (struct avro_fixed_datum_t *) avro_new(struct avro_fixed_datum_t);
	if (!datum) {
		avro_free((char *) bytes, size);
		avro_set_error("Cannot create new fixed datum");
		return NULL;
	}
	datum->schema = avro_schema_incref(schema);
	datum->size = size;
	datum->bytes = (char *)bytes;
	datum->free = fixed_free;

	avro_datum_init(&datum->obj, AVRO_FIXED);
	return &datum->obj;
}

avro_datum_t avro_fixed(avro_schema_t schema,
			const char *bytes, const int64_t size)
{
	char *bytes_copy = (char *) avro_malloc(size);
	if (!bytes_copy) {
		avro_set_error("Cannot copy fixed content");
		return NULL;
	}
	memcpy(bytes_copy, bytes, size);
	return avro_fixed_private(schema, bytes_copy, size, avro_alloc_free_func);
}

avro_datum_t avro_givefixed(avro_schema_t schema,
			    const char *bytes, const int64_t size,
			    avro_free_func_t free)
{
	return avro_fixed_private(schema, bytes, size, free);
}

static int avro_fixed_set_private(avro_datum_t datum,
				  const char *bytes, const int64_t size,
				  avro_free_func_t fixed_free)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_fixed(datum), "fixed datum");

	struct avro_fixed_datum_t *fixed = avro_datum_to_fixed(datum);
	struct avro_fixed_schema_t *schema = avro_schema_to_fixed(fixed->schema);
	if (size != schema->size) {
		avro_set_error("Fixed size doesn't match schema");
		return EINVAL;
	}

	if (fixed->free) {
		fixed->free(fixed->bytes, fixed->size);
	}

	fixed->free = fixed_free;
	fixed->bytes = (char *)bytes;
	fixed->size = size;
	return 0;
}

int avro_fixed_set(avro_datum_t datum, const char *bytes, const int64_t size)
{
	int rval;
	char *bytes_copy = (char *) avro_malloc(size);
	if (!bytes_copy) {
		avro_set_error("Cannot copy fixed content");
		return ENOMEM;
	}
	memcpy(bytes_copy, bytes, size);
	rval = avro_fixed_set_private(datum, bytes_copy, size, avro_alloc_free_func);
	if (rval) {
		avro_free(bytes_copy, size);
	}
	return rval;
}

int avro_givefixed_set(avro_datum_t datum, const char *bytes,
		       const int64_t size, avro_free_func_t free)
{
	return avro_fixed_set_private(datum, bytes, size, free);
}

int avro_fixed_get(avro_datum_t datum, char **bytes, int64_t * size)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_fixed(datum), "fixed datum");
	check_param(EINVAL, bytes, "bytes");
	check_param(EINVAL, size, "size");

	*bytes = avro_datum_to_fixed(datum)->bytes;
	*size = avro_datum_to_fixed(datum)->size;
	return 0;
}

static int
avro_init_map(struct avro_map_datum_t *datum)
{
	datum->map = st_init_strtable_with_size(DEFAULT_TABLE_SIZE);
	if (!datum->map) {
		avro_set_error("Cannot create new map datum");
		return ENOMEM;
	}
	datum->indices_by_key = st_init_strtable_with_size(DEFAULT_TABLE_SIZE);
	if (!datum->indices_by_key) {
		avro_set_error("Cannot create new map datum");
		st_free_table(datum->map);
		return ENOMEM;
	}
	datum->keys_by_index = st_init_numtable_with_size(DEFAULT_TABLE_SIZE);
	if (!datum->keys_by_index) {
		avro_set_error("Cannot create new map datum");
		st_free_table(datum->indices_by_key);
		st_free_table(datum->map);
		return ENOMEM;
	}
	return 0;
}

avro_datum_t avro_map(avro_schema_t schema)
{
	check_param(NULL, is_avro_schema(schema), "schema");

	struct avro_map_datum_t *datum =
	    (struct avro_map_datum_t *) avro_new(struct avro_map_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new map datum");
		return NULL;
	}

	if (avro_init_map(datum) != 0) {
		avro_freet(struct avro_map_datum_t, datum);
		return NULL;
	}

	datum->schema = avro_schema_incref(schema);
	avro_datum_init(&datum->obj, AVRO_MAP);
	return &datum->obj;
}

size_t
avro_map_size(const avro_datum_t datum)
{
	const struct avro_map_datum_t  *map = avro_datum_to_map(datum);
	return map->map->num_entries;
}

int
avro_map_get(const avro_datum_t datum, const char *key, avro_datum_t * value)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_map(datum), "map datum");
	check_param(EINVAL, key, "key");
	check_param(EINVAL, value, "value");

	union {
		avro_datum_t datum;
		st_data_t data;
	} val;

	struct avro_map_datum_t *map = avro_datum_to_map(datum);
	if (st_lookup(map->map, (st_data_t) key, &(val.data))) {
		*value = val.datum;
		return 0;
	}

	avro_set_error("No map element named %s", key);
	return EINVAL;
}

int avro_map_get_key(const avro_datum_t datum, int index,
		     const char **key)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_map(datum), "map datum");
	check_param(EINVAL, index >= 0, "index");
	check_param(EINVAL, key, "key");

	union {
		st_data_t data;
		char *key;
	} val;

	struct avro_map_datum_t *map = avro_datum_to_map(datum);
	if (st_lookup(map->keys_by_index, (st_data_t) index, &val.data)) {
		*key = val.key;
		return 0;
	}

	avro_set_error("No map element with index %d", index);
	return EINVAL;
}

int avro_map_get_index(const avro_datum_t datum, const char *key,
		       int *index)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_map(datum), "map datum");
	check_param(EINVAL, key, "key");
	check_param(EINVAL, index, "index");

	st_data_t  data;

	struct avro_map_datum_t *map = avro_datum_to_map(datum);
	if (st_lookup(map->indices_by_key, (st_data_t) key, &data)) {
		*index = (int) data;
		return 0;
	}

	avro_set_error("No map element with key %s", key);
	return EINVAL;
}

int
avro_map_set(avro_datum_t datum, const char *key,
	     const avro_datum_t value)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	check_param(EINVAL, is_avro_map(datum), "map datum");
	check_param(EINVAL, key, "key");
	check_param(EINVAL, is_avro_datum(value), "value");

	char *save_key = (char *)key;
	avro_datum_t old_datum;

	struct avro_map_datum_t  *map = avro_datum_to_map(datum);

	if (avro_map_get(datum, key, &old_datum) == 0) {
		/* Overwriting an old value */
		avro_datum_decref(old_datum);
	} else {
		/* Inserting a new value */
		save_key = avro_strdup(key);
		if (!save_key) {
			avro_set_error("Cannot copy map key");
			return ENOMEM;
		}
		int  new_index = map->map->num_entries;
		st_insert(map->indices_by_key, (st_data_t) save_key,
			  (st_data_t) new_index);
		st_insert(map->keys_by_index, (st_data_t) new_index,
			  (st_data_t) save_key);
	}
	avro_datum_incref(value);
	st_insert(map->map, (st_data_t) save_key, (st_data_t) value);
	return 0;
}

static int
avro_init_array(struct avro_array_datum_t *datum)
{
	datum->els = st_init_numtable_with_size(DEFAULT_TABLE_SIZE);
	if (!datum->els) {
		avro_set_error("Cannot create new array datum");
		return ENOMEM;
	}
	return 0;
}

avro_datum_t avro_array(avro_schema_t schema)
{
	check_param(NULL, is_avro_schema(schema), "schema");

	struct avro_array_datum_t *datum =
	    (struct avro_array_datum_t *) avro_new(struct avro_array_datum_t);
	if (!datum) {
		avro_set_error("Cannot create new array datum");
		return NULL;
	}

	if (avro_init_array(datum) != 0) {
		avro_freet(struct avro_array_datum_t, datum);
		return NULL;
	}

	datum->schema = avro_schema_incref(schema);
	avro_datum_init(&datum->obj, AVRO_ARRAY);
	return &datum->obj;
}

int
avro_array_get(const avro_datum_t array_datum, int64_t index, avro_datum_t * value)
{
	check_param(EINVAL, is_avro_datum(array_datum), "datum");
	check_param(EINVAL, is_avro_array(array_datum), "array datum");
	check_param(EINVAL, value, "value pointer");

	union {
		st_data_t data;
		avro_datum_t datum;
	} val;

        const struct avro_array_datum_t * array = avro_datum_to_array(array_datum);
	if (st_lookup(array->els, index, &val.data)) {
		*value = val.datum;
		return 0;
	}

	avro_set_error("No array element with index %ld", (long) index);
	return EINVAL;
}

size_t
avro_array_size(const avro_datum_t datum)
{
	const struct avro_array_datum_t  *array = avro_datum_to_array(datum);
	return array->els->num_entries;
}

int
avro_array_append_datum(avro_datum_t array_datum,
			const avro_datum_t datum)
{
	check_param(EINVAL, is_avro_datum(array_datum), "datum");
	check_param(EINVAL, is_avro_array(array_datum), "array datum");
	check_param(EINVAL, is_avro_datum(datum), "element datum");

	struct avro_array_datum_t *array = avro_datum_to_array(array_datum);
	st_insert(array->els, array->els->num_entries,
		  (st_data_t) avro_datum_incref(datum));
	return 0;
}

static int char_datum_free_foreach(char *key, avro_datum_t datum, void *arg)
{
	AVRO_UNUSED(arg);

	avro_datum_decref(datum);
	avro_str_free(key);
	return ST_DELETE;
}

static int array_free_foreach(int i, avro_datum_t datum, void *arg)
{
	AVRO_UNUSED(i);
	AVRO_UNUSED(arg);

	avro_datum_decref(datum);
	return ST_DELETE;
}

avro_schema_t avro_datum_get_schema(const avro_datum_t datum)
{
	check_param(NULL, is_avro_datum(datum), "datum");

	switch (avro_typeof(datum)) {
		/*
		 * For the primitive types, which don't store an
		 * explicit reference to their schema, we decref the
		 * schema before returning.  This maintains the
		 * invariant that this function doesn't add any
		 * additional references to the schema.  The primitive
		 * schemas won't be freed, because there's always at
		 * least 1 reference for their initial static
		 * initializers.
		 */

		case AVRO_STRING:
			{
				avro_schema_t  result = avro_schema_string();
				avro_schema_decref(result);
				return result;
			}
		case AVRO_BYTES:
			{
				avro_schema_t  result = avro_schema_bytes();
				avro_schema_decref(result);
				return result;
			}
		case AVRO_INT32:
			{
				avro_schema_t  result = avro_schema_int();
				avro_schema_decref(result);
				return result;
			}
		case AVRO_INT64:
			{
				avro_schema_t  result = avro_schema_long();
				avro_schema_decref(result);
				return result;
			}
		case AVRO_FLOAT:
			{
				avro_schema_t  result = avro_schema_float();
				avro_schema_decref(result);
				return result;
			}
		case AVRO_DOUBLE:
			{
				avro_schema_t  result = avro_schema_double();
				avro_schema_decref(result);
				return result;
			}
		case AVRO_BOOLEAN:
			{
				avro_schema_t  result = avro_schema_boolean();
				avro_schema_decref(result);
				return result;
			}
		case AVRO_NULL:
			{
				avro_schema_t  result = avro_schema_null();
				avro_schema_decref(result);
				return result;
			}

		case AVRO_RECORD:
			return avro_datum_to_record(datum)->schema;
		case AVRO_ENUM:
			return avro_datum_to_enum(datum)->schema;
		case AVRO_FIXED:
			return avro_datum_to_fixed(datum)->schema;
		case AVRO_MAP:
			return avro_datum_to_map(datum)->schema;
		case AVRO_ARRAY:
			return avro_datum_to_array(datum)->schema;
		case AVRO_UNION:
			return avro_datum_to_union(datum)->schema;

		default:
			return NULL;
	}
}

static void avro_datum_free(avro_datum_t datum)
{
	if (is_avro_datum(datum)) {
		switch (avro_typeof(datum)) {
		case AVRO_STRING:{
				struct avro_string_datum_t *string;
				string = avro_datum_to_string(datum);
				if (string->free) {
					string->free(string->s, string->size);
				}
				avro_freet(struct avro_string_datum_t, string);
			}
			break;
		case AVRO_BYTES:{
				struct avro_bytes_datum_t *bytes;
				bytes = avro_datum_to_bytes(datum);
				if (bytes->free) {
					bytes->free(bytes->bytes, bytes->size);
				}
				avro_freet(struct avro_bytes_datum_t, bytes);
			}
			break;
		case AVRO_INT32:{
				avro_freet(struct avro_int32_datum_t, datum);
			}
			break;
		case AVRO_INT64:{
				avro_freet(struct avro_int64_datum_t, datum);
			}
			break;
		case AVRO_FLOAT:{
				avro_freet(struct avro_float_datum_t, datum);
			}
			break;
		case AVRO_DOUBLE:{
				avro_freet(struct avro_double_datum_t, datum);
			}
			break;
		case AVRO_BOOLEAN:{
				avro_freet(struct avro_boolean_datum_t, datum);
			}
			break;
		case AVRO_NULL:
			/* Nothing allocated */
			break;

		case AVRO_RECORD:{
				struct avro_record_datum_t *record;
				record = avro_datum_to_record(datum);
				avro_schema_decref(record->schema);
				st_foreach(record->fields_byname,
					   HASH_FUNCTION_CAST char_datum_free_foreach, 0);
				st_free_table(record->field_order);
				st_free_table(record->fields_byname);
				avro_freet(struct avro_record_datum_t, record);
			}
			break;
		case AVRO_ENUM:{
				struct avro_enum_datum_t *enump;
				enump = avro_datum_to_enum(datum);
				avro_schema_decref(enump->schema);
				avro_freet(struct avro_enum_datum_t, enump);
			}
			break;
		case AVRO_FIXED:{
				struct avro_fixed_datum_t *fixed;
				fixed = avro_datum_to_fixed(datum);
				avro_schema_decref(fixed->schema);
				if (fixed->free) {
					fixed->free((void *)fixed->bytes,
						    fixed->size);
				}
				avro_freet(struct avro_fixed_datum_t, fixed);
			}
			break;
		case AVRO_MAP:{
				struct avro_map_datum_t *map;
				map = avro_datum_to_map(datum);
				avro_schema_decref(map->schema);
				st_foreach(map->map, HASH_FUNCTION_CAST char_datum_free_foreach,
					   0);
				st_free_table(map->map);
				st_free_table(map->indices_by_key);
				st_free_table(map->keys_by_index);
				avro_freet(struct avro_map_datum_t, map);
			}
			break;
		case AVRO_ARRAY:{
				struct avro_array_datum_t *array;
				array = avro_datum_to_array(datum);
				avro_schema_decref(array->schema);
				st_foreach(array->els, HASH_FUNCTION_CAST array_free_foreach, 0);
				st_free_table(array->els);
				avro_freet(struct avro_array_datum_t, array);
			}
			break;
		case AVRO_UNION:{
				struct avro_union_datum_t *unionp;
				unionp = avro_datum_to_union(datum);
				avro_schema_decref(unionp->schema);
				avro_datum_decref(unionp->value);
				avro_freet(struct avro_union_datum_t, unionp);
			}
			break;
		case AVRO_LINK:{
				/* TODO */
			}
			break;
		}
	}
}

static int
datum_reset_foreach(int i, avro_datum_t datum, void *arg)
{
	AVRO_UNUSED(i);
	int  rval;
	int  *result = (int *) arg;

	rval = avro_datum_reset(datum);
	if (rval == 0) {
		return ST_CONTINUE;
	} else {
		*result = rval;
		return ST_STOP;
	}
}

int
avro_datum_reset(avro_datum_t datum)
{
	check_param(EINVAL, is_avro_datum(datum), "datum");
	int  rval;

	switch (avro_typeof(datum)) {
		case AVRO_ARRAY:
		{
			struct avro_array_datum_t *array;
			array = avro_datum_to_array(datum);
			st_foreach(array->els, HASH_FUNCTION_CAST array_free_foreach, 0);
			st_free_table(array->els);

			rval = avro_init_array(array);
			if (rval != 0) {
				avro_freet(struct avro_array_datum_t, array);
				return rval;
			}
			return 0;
		}

		case AVRO_MAP:
		{
			struct avro_map_datum_t *map;
			map = avro_datum_to_map(datum);
			st_foreach(map->map, HASH_FUNCTION_CAST char_datum_free_foreach, 0);
			st_free_table(map->map);
			st_free_table(map->indices_by_key);
			st_free_table(map->keys_by_index);

			rval = avro_init_map(map);
			if (rval != 0) {
				avro_freet(struct avro_map_datum_t, map);
				return rval;
			}
			return 0;
		}

		case AVRO_RECORD:
		{
			struct avro_record_datum_t *record;
			record = avro_datum_to_record(datum);
			rval = 0;
			st_foreach(record->fields_byname,
				   HASH_FUNCTION_CAST datum_reset_foreach, (st_data_t) &rval);
			return rval;
		}

		case AVRO_UNION:
		{
			struct avro_union_datum_t *unionp;
			unionp = avro_datum_to_union(datum);
			return (unionp->value == NULL)? 0:
			    avro_datum_reset(unionp->value);
		}

		default:
			return 0;
	}
}

avro_datum_t avro_datum_incref(avro_datum_t datum)
{
	if (datum) {
		avro_refcount_inc(&datum->refcount);
	}
	return datum;
}

void avro_datum_decref(avro_datum_t datum)
{
	if (datum && avro_refcount_dec(&datum->refcount)) {
		avro_datum_free(datum);
	}
}

void avro_datum_print(avro_datum_t value, FILE * fp)
{
	AVRO_UNUSED(value);
	AVRO_UNUSED(fp);
}
