/******************************************************************************
vim:sts=4:ts=4:sw=4

	exif.c - ruby interface to libexif library

	Copyright (C) 2002 Ryuichi Tamura (r-tam@fsinet.or.jp)


*******************************************************************************/
#include "ruby.h"
#include <libexif/exif-ifd.h>
#include <libexif/exif-data.h>
#include <libexif/exif-note.h>
#include <libexif/exif-utils.h>
#include <libexif/exif-tag.h>

#define Get_Exif(obj, exif) { \
	Data_Get_Struct((obj), Exif, (exif)); \
	if (!(exif)->ed) \
		rb_raise(eExifError, "should set data first"); \
}


typedef struct _Exif Exif;

struct _Exif {
	ExifData *ed;
	int ifd;
};

#define IFD_RB_DEFAULT (EXIF_IFD_0 - 1)

static VALUE cExif, mExifByteOrder, mExifIfd;
static VALUE eExifError, eExifInvalidFormat;
static VALUE eExifInvalidIFD, eExifTagNotFound, eExifThumbnailNotFound;

static void
rb_exif_free(Exif *exif)
{
	if (exif){
		exif_data_free(exif->ed);
		exif->ed = NULL;
		free(exif);
	}
}

static VALUE
rb_exif_s_new(int argc, VALUE *argv, VALUE klass)
{
	VALUE obj;
	Exif *exif;
	exif = ALLOC(Exif);
	exif->ed = NULL;
	exif->ifd = IFD_RB_DEFAULT;
	obj =  Data_Wrap_Struct(klass, 0, rb_exif_free, exif);
	rb_obj_call_init(obj, argc, argv);
	return obj;
}

static ExifData *
rb_exif_data_new_from_file(VALUE fpath)
{
	ExifData *data;
	Check_Type(fpath, T_STRING);
	data = exif_data_new_from_file(RSTRING(fpath)->ptr);
	if (!data){
		FILE *f;
		f = fopen(RSTRING(fpath)->ptr,"rb");
		if (!f)
			rb_raise(rb_eArgError, "unable to open file - '%s'",
			         RSTRING(fpath)->ptr);
		fclose(f);
		rb_raise(eExifInvalidFormat, 
		         "'%s' does not contain EXIF data", RSTRING(fpath)->ptr);
	}
	return data;
}


static VALUE
rb_exif_initialize(int argc, VALUE *argv, VALUE obj)
{
	VALUE fpath;
	Exif *exif;
	Data_Get_Struct(obj, Exif, exif);
	if (rb_scan_args(argc, argv, "01", &fpath) == 1) {
		exif->ed = rb_exif_data_new_from_file(fpath);
	}
	else {
		exif->ed = exif_data_new();
	}
	return Qnil;
}

static void 
rb_exif_data_new_from_data(ExifData *ed, VALUE str)
{
	Check_Type(str, T_STRING);
	if (ed){
		exif_data_free(ed);
		ed = NULL;
	}
	ed = exif_data_new_from_data((unsigned char *)RSTRING(str)->ptr, RSTRING(str)->len);
}

static VALUE
rb_exif_set_data(VALUE obj, VALUE str)
{
	Exif *exif;
	Data_Get_Struct(obj, Exif, exif);
	rb_exif_data_new_from_data(exif->ed, str);
	return obj;
}

static VALUE
rb_exif_get_byte_order(VALUE obj)
{
	Exif *exif;
	Get_Exif(obj, exif);
	return FIX2INT(exif_data_get_byte_order(exif->ed));
}

static VALUE
rb_exif_set_byte_order(VALUE obj, VALUE byteorder)
{
	Exif *exif;
	Get_Exif(obj, exif);
	exif_data_set_byte_order(exif->ed, FIX2INT(byteorder));
	return obj;
}

static void
rb_exif_yield_tag_value(ExifEntry *entry, void *data)
{
	VALUE k, v;
	char buf[7];
	unsigned char *ids = data;
	memset(buf, 0, sizeof(buf));
	sprintf(buf, "0x%04x", entry->tag);
	k = *ids ? rb_str_new2(buf) : rb_str_new2(exif_tag_get_title(entry->tag));
	v = rb_str_new2(exif_entry_get_value(entry));
	rb_yield(rb_assoc_new(k, v));
}


static void
rb_exif_data_foreach_content_func(ExifContent *content, void *data)
{
	exif_content_foreach_entry(content, rb_exif_yield_tag_value, data);
}

static VALUE
rb_exif_each(int argc, VALUE *argv, VALUE obj)
{
	Exif *exif;
	VALUE use_tag_id;
	unsigned char ids = 0;
	if (rb_scan_args(argc, argv, "01", &use_tag_id) == 1){
		ids = FIX2UINT(use_tag_id);	
	}
	Get_Exif(obj, exif);
	exif_data_foreach_content(exif->ed, rb_exif_data_foreach_content_func, &ids);
	return obj;
}

static ExifIfd exif_ifd_from_string (const char *);

static VALUE
rb_exif_set_ifd(VALUE obj, VALUE ifd)
{
	Exif *exif;
	int i;
	Get_Exif(obj, exif);
	switch(TYPE(ifd)){
	  case T_FIXNUM:
		i = FIX2INT(ifd);
		if (i < IFD_RB_DEFAULT || i > EXIF_IFD_INTEROPERABILITY){
			rb_raise(rb_eRuntimeError, "wrong constant");
		}
		break;
	  case T_STRING:
		i = exif_ifd_from_string(RSTRING(ifd)->ptr);
		if (i == -1){
			rb_raise(rb_eRuntimeError, "unknown IFD: '%s'", RSTRING(ifd)->ptr);
		}
		break;
	  default:
		rb_raise(rb_eTypeError, "wrong type of an argument");
		break;
	}
	exif->ifd = i;
	return obj;
}

static VALUE
rb_exif_get_ifd(VALUE obj)
{
	Exif *exif;
	const char *name;
	Get_Exif(obj, exif);
	name = exif_ifd_get_name(exif->ifd);
	if (!name)
		return Qnil;
	return rb_str_new2(name);
}

static ExifTag exif_tag_from_string (const char *);
static ExifTag exif_tag_from_tagid (ExifTag);

static VALUE
rb_exif_get_tag(VALUE obj, VALUE tagid)
{
	Exif *exif;
	ExifTag tag;
	ExifEntry *e;
	const char *found;
	int i;

	Get_Exif(obj, exif);
	switch (TYPE(tagid)) {
	  case T_STRING:
		tag = exif_tag_from_string(RSTRING(tagid)->ptr);
		if (!tag){
			rb_raise(eExifError, "invalid tag: '%s'", RSTRING(tagid)->ptr);
		}
		break;
	  case T_FIXNUM:
		tag = exif_tag_from_tagid(FIX2INT(tagid));
		if (!tag){
			rb_raise(eExifError, "invalid tag: 0x%04x", tagid);
		}
		break;
	  default:
		rb_raise(rb_eTypeError, "wrong type of arguments");
		break;
	}
	if (exif->ifd >= 0){
		e = exif_content_get_entry(exif->ed->ifd[exif->ifd], tag);
		if (!e){
			rb_raise(eExifTagNotFound, 
			"IFD '%s' does not contain tag '%s'(0x%04x)",
			exif_ifd_get_name(exif->ifd), exif_tag_get_title(tag), tag);
		}
		return rb_str_new2(exif_entry_get_value(e));
	}
	for (i = 0; i < EXIF_IFD_COUNT; i++){
		e = exif_content_get_entry(exif->ed->ifd[i], tag);
		if (e)
			break;
	}
	found = exif_entry_get_value(e);
	return found ? rb_str_new2(found) : Qnil;
}

static void
rb_exif_set_tag_0(ExifData *ed, ExifEntry *e, VALUE val)
{
	ExifByteOrder o;
	o = exif_data_get_byte_order(ed);
	/* exif_entry_dump(e, 1); */

}

static VALUE
rb_exif_set_tag(VALUE obj, VALUE tagid, VALUE val)
{
	Exif *exif;
	ExifTag tag;
	ExifEntry *e;
	
	rb_raise(rb_eNotImpError, "soryy, not yet implemented");
	Check_Type(tagid, T_STRING);
	Check_Type(val, T_STRING);
	Get_Exif(obj, exif);
	tag = exif_tag_from_string(RSTRING(tagid)->ptr);
	if (!tag || !exif_tag_get_name(tag)){
		rb_raise(eExifError, "invalid tag: '%s'", RSTRING(tagid)->ptr);
	}
	e = exif_content_get_entry(exif->ed->ifd[exif->ifd], tag);
	if (!e){
		e = exif_entry_new();
		exif_content_add_entry(exif->ed->ifd[exif->ifd], e);
		exif_entry_initialize(e, tag);
	}
	rb_exif_set_tag_0(exif->ed, e, val);
	return obj;
}
static VALUE
rb_exif_list_tags_at_ifd(VALUE obj, VALUE ifd)
{
	Exif *exif;
	unsigned int tag;
	int i;
	const char *name;
	char buf[7];
	VALUE ret = rb_ary_new();
	i = FIX2INT(ifd);
	if (i < EXIF_IFD_0 || i > EXIF_IFD_INTEROPERABILITY){
			rb_raise(rb_eRuntimeError, "wrong constant");
	}
	Get_Exif(obj, exif);
	for (tag = 0; tag < 0xffff; tag++) {
		name = exif_tag_get_title(tag);
		if (!name)
			continue;
		if (exif_content_get_entry(exif->ed->ifd[i], tag)){
			memset(buf, 0, sizeof(buf));
			sprintf(buf, "0x%04x", tag);
			rb_ary_push(ret, rb_assoc_new(rb_str_new2(name), rb_str_new2(buf)));
		}
	}
	return ret;
}

static VALUE
rb_exif_s_get_tag_description(VALUE klass, VALUE tagid)
{
	ExifTag tag;
	VALUE cdr;
	char buf[7];
	switch (TYPE(tagid)){
	  case T_FIXNUM:
	    tag = FIX2INT(tagid);
		if (!exif_tag_get_name(tag))
			rb_raise(eExifError, "invalid tag id: 0x%04x(%d)", 
			         FIX2INT(tagid), FIX2INT(tagid));
		break;
	  case T_STRING:
	    tag = exif_tag_from_string(RSTRING(tagid)->ptr);
		if (!tag || !exif_tag_get_name(tag)) {
			rb_raise(eExifError, "invalid tag: '%s'", RSTRING(tagid)->ptr);
		}
		break;
	  default:
	    rb_raise(rb_eTypeError, "wrong type of an argument");
		break;
	}
	memset(buf, 0, sizeof(buf));
	sprintf(buf, "0x%04x", tag);
	cdr = rb_ary_new();
	rb_ary_push(cdr, rb_str_new2(exif_tag_get_name(tag)));
	rb_ary_push(cdr, rb_str_new2(exif_tag_get_description(tag)));
	rb_ary_push(cdr, rb_str_new2(buf));
	return rb_assoc_new(rb_str_new2(exif_tag_get_title(tag)), cdr);
}

static VALUE
rb_exif_extract_thumbnail(VALUE obj, VALUE dest)
{
	Exif *exif;
	Get_Exif(obj, exif);
	if (!exif->ed->data)
		rb_raise(eExifThumbnailNotFound, "thumbnail not found");
	rb_funcall(dest, rb_intern("<<"), 1, rb_str_new(exif->ed->data, exif->ed->size));
	return obj;
}

static VALUE
rb_exif_set_thumbnail(VALUE obj, VALUE src)
{
	Exif *exif;
	Get_Exif(obj, exif);
	if (exif->ed->data){
		free(exif->ed->data);
		exif->ed->data = NULL;
		exif->ed->size = 0;
	}
	Check_Type(src, T_STRING);
	exif->ed->size = RSTRING(src)->len;
	exif->ed->data = xmalloc(sizeof(char)*exif->ed->size);
	MEMMOVE(exif->ed->data, RSTRING(src)->ptr, char, exif->ed->size);
	return obj;
}


#define N_(val) val

static struct {
	ExifTag tag;
	const char *name;
	const char *title;
} ExifTagTable[] = {
	{EXIF_TAG_INTEROPERABILITY_INDEX, "InteroperabilityIndex",
	 "InteroperabilityIndex"},
	{EXIF_TAG_INTEROPERABILITY_VERSION, "InteroperabilityVersion",
	 "InteroperabilityVersion"},
	{EXIF_TAG_IMAGE_WIDTH, "ImageWidth", N_("Image Width")},
	{EXIF_TAG_IMAGE_LENGTH, "ImageLength", N_("Image Length")},
	{EXIF_TAG_BITS_PER_SAMPLE, "BitsPerSample", N_("Bits per Sample")},
	{EXIF_TAG_COMPRESSION, "Compression", N_("Compression")},
	{EXIF_TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation",
	 N_("Photometric Interpretation")},
	{EXIF_TAG_FILL_ORDER, "FillOrder", N_("Fill Order")},
	{EXIF_TAG_DOCUMENT_NAME, "DocumentName", N_("Document Name")},
	{EXIF_TAG_IMAGE_DESCRIPTION, "ImageDescription", N_("Image Description")},
	{EXIF_TAG_MAKE, "Make", N_("Manufacturer")},
	{EXIF_TAG_MODEL, "Model", N_("Model")},
	{EXIF_TAG_STRIP_OFFSETS, "StripOffsets", N_("Strip Offsets")},
	{EXIF_TAG_ORIENTATION, "Orientation", N_("Orientation")},
	{EXIF_TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel",
	 N_("Samples per Pixel")},
	{EXIF_TAG_ROWS_PER_STRIP, "RowsPerStrip", N_("Rows per Strip")},
	{EXIF_TAG_STRIP_BYTE_COUNTS, "StripByteCounts", N_("Strip Byte Count")},
	{EXIF_TAG_X_RESOLUTION, "XResolution", N_("x-Resolution")},
	{EXIF_TAG_Y_RESOLUTION, "YResolution", N_("y-Resolution")},
	{EXIF_TAG_PLANAR_CONFIGURATION, "PlanarConfiguration",
	 N_("Planar Configuration")},
	{EXIF_TAG_RESOLUTION_UNIT, "ResolutionUnit", N_("Resolution Unit")},
	{EXIF_TAG_TRANSFER_FUNCTION, "TransferFunction",
	 N_("Transfer Function")},
	{EXIF_TAG_SOFTWARE, "Software", N_("Software")},
	{EXIF_TAG_DATE_TIME, "DateTime", N_("Date and Time")},
	{EXIF_TAG_ARTIST, "Artist", N_("Artist")},
	{EXIF_TAG_WHITE_POINT, "WhitePoint", N_("White Point")},
	{EXIF_TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities",
	 N_("Primary Chromaticities")},
	{EXIF_TAG_TRANSFER_RANGE, "TransferRange", N_("Transfer Range")},
	{EXIF_TAG_JPEG_PROC, "JPEGProc", "JPEGProc"},
	{EXIF_TAG_JPEG_INTERCHANGE_FORMAT, "JPEGInterchangeFormat",
	 N_("JPEG Interchange Format")},
	{EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
	 "JPEGInterchangeFormatLength", N_("JPEG Interchange Format Length")},
	{EXIF_TAG_YCBCR_COEFFICIENTS, "YCbCrCoefficients",
	 N_("YCbCr Coefficients")},
	{EXIF_TAG_YCBCR_SUB_SAMPLING, "YCbCrSubSampling",
	 N_("YCbCr Sub-Sampling")},
	{EXIF_TAG_YCBCR_POSITIONING, "YCbCrPositioning",
	 N_("YCbCr Positioning")},
	{EXIF_TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite",
	 N_("Reference Black/White")},
	{EXIF_TAG_RELATED_IMAGE_FILE_FORMAT, "RelatedImageFileFormat",
	 "RelatedImageFileFormat"},
	{EXIF_TAG_RELATED_IMAGE_WIDTH, "RelatedImageWidth",
	 "RelatedImageWidth"},
	{EXIF_TAG_RELATED_IMAGE_LENGTH, "RelatedImageLength",
	 "RelatedImageLength"},
	{EXIF_TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim",
	 "CFARepeatPatternDim"},
	{EXIF_TAG_CFA_PATTERN, "CFAPattern",
	 N_("CFA Pattern")},
	{EXIF_TAG_BATTERY_LEVEL, "BatteryLevel", N_("Battery Level")},
	{EXIF_TAG_COPYRIGHT, "Copyright", N_("Copyright")},
	{EXIF_TAG_EXPOSURE_TIME, "ExposureTime", N_("Exposure Time")},
	{EXIF_TAG_FNUMBER, "FNumber", "FNumber"},
	{EXIF_TAG_IPTC_NAA, "IPTC/NAA", "IPTC/NAA"},
	{EXIF_TAG_EXIF_IFD_POINTER, "ExifIFDPointer", "ExifIFDPointer"},
	{EXIF_TAG_INTER_COLOR_PROFILE, "InterColorProfile",
	 "InterColorProfile"},
	{EXIF_TAG_EXPOSURE_PROGRAM, "ExposureProgram", "ExposureProgram"},
	{EXIF_TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity",
	 N_("Spectral Sensitivity")},
	{EXIF_TAG_GPS_INFO_IFD_POINTER, "GPSInfoIFDPointer", 
	 "GPSInfoIFDPointer"},
#if 0
	 /* GPS tags are not supported in this library */
#endif
	{EXIF_TAG_ISO_SPEED_RATINGS, "ISOSpeedRatings",
	 N_("ISO Speed Ratings")},
	{EXIF_TAG_OECF, "OECF", "OECF"},
	{EXIF_TAG_EXIF_VERSION, "ExifVersion", N_("Exif Version")},
	{EXIF_TAG_DATE_TIME_ORIGINAL, "DateTimeOriginal",
	 N_("Date and Time (original)")},
	{EXIF_TAG_DATE_TIME_DIGITIZED, "DateTimeDigitized",
	 N_("Date and Time (digitized)")},
	{EXIF_TAG_COMPONENTS_CONFIGURATION, "ComponentsConfiguration",
	 "ComponentsConfiguration"},
	{EXIF_TAG_COMPRESSED_BITS_PER_PIXEL, "CompressedBitsPerPixel",
	 N_("Compressed Bits per Pixel")},
	{EXIF_TAG_SHUTTER_SPEED_VALUE, "ShutterSpeedValue", 
	 N_("Shutter speed")},
	{EXIF_TAG_APERTURE_VALUE, "ApertureValue", N_("Aperture")},
	{EXIF_TAG_BRIGHTNESS_VALUE, "BrightnessValue", N_("Brightness")},
	{EXIF_TAG_EXPOSURE_BIAS_VALUE, "ExposureBiasValue",
	 N_("Exposure Bias")},
	{EXIF_TAG_MAX_APERTURE_VALUE, "MaxApertureValue", "MaxApertureValue"},
	{EXIF_TAG_SUBJECT_DISTANCE, "SubjectDistance",
	 N_("Subject Distance")},
	{EXIF_TAG_METERING_MODE, "MeteringMode", N_("Metering Mode")},
	{EXIF_TAG_LIGHT_SOURCE, "LightSource", N_("Light Source")},
	{EXIF_TAG_FLASH, "Flash", N_("Flash")},
	{EXIF_TAG_FOCAL_LENGTH, "FocalLength", N_("Focal Length")},
	{EXIF_TAG_MAKER_NOTE, "MakerNote", N_("Maker Note")},
	{EXIF_TAG_USER_COMMENT, "UserComment", N_("User Comment")},
	{EXIF_TAG_SUBSEC_TIME, "SubsecTime", "SubsecTime"},
	{EXIF_TAG_SUB_SEC_TIME_ORIGINAL, "SubSecTimeOriginal",
	 "SubSecTimeOriginal"},
	{EXIF_TAG_SUB_SEC_TIME_DIGITIZED, "SubSecTimeDigitized",
	 "SubSecTimeDigitized"},
	{EXIF_TAG_FLASH_PIX_VERSION, "FlashPixVersion", "FlashPixVersion"},
	{EXIF_TAG_COLOR_SPACE, "ColorSpace", N_("Color Space")},
	{EXIF_TAG_PIXEL_X_DIMENSION, "PixelXDimension", "PixelXDimension"},
	{EXIF_TAG_PIXEL_Y_DIMENSION, "PixelYDimension", "PixelYDimension"},
	{EXIF_TAG_RELATED_SOUND_FILE, "RelatedSoundFile",
	 "RelatedSoundFile"},
	{EXIF_TAG_INTEROPERABILITY_IFD_POINTER, "InteroperabilityIFDPointer",
	 "InteroperabilityIFDPointer"},
	{EXIF_TAG_FLASH_ENERGY, "FlashEnergy", N_("Flash Energy")},
	{EXIF_TAG_SPATIAL_FREQUENCY_RESPONSE, "SpatialFrequencyResponse",
	 N_("Spatial Frequency Response")},
	{EXIF_TAG_FOCAL_PLANE_X_RESOLUTION, "FocalPlaneXResolution",
	 N_("Focal Plane x-Resolution")},
	{EXIF_TAG_FOCAL_PLANE_Y_RESOLUTION, "FocalPlaneYResolution",
	 N_("Focal Plane y-Resolution")},
	{EXIF_TAG_FOCAL_PLANE_RESOLUTION_UNIT, "FocalPlaneResolutionUnit",
	 N_("Focal Plane Resolution Unit")},
	{EXIF_TAG_SUBJECT_LOCATION, "SubjectLocation",
	 N_("Subject Location")},
	{EXIF_TAG_EXPOSURE_INDEX, "ExposureIndex", N_("Exposure index")},
	{EXIF_TAG_SENSING_METHOD, "SensingMethod", N_("Sensing Method")},
	{EXIF_TAG_FILE_SOURCE, "FileSource", N_("File Source")},
	{EXIF_TAG_SCENE_TYPE, "SceneType", N_("Scene Type")},
	{EXIF_TAG_NEW_CFA_PATTERN, "CFAPattern",
	 N_("CFA Pattern")},
	{EXIF_TAG_SUBJECT_AREA, "SubjectArea", N_("Subject Area")},
	{EXIF_TAG_CUSTOM_RENDERED, "CustomRendered", N_("Custom Rendered")},
	{EXIF_TAG_EXPOSURE_MODE, "ExposureMode", N_("Exposure Mode")},
	{EXIF_TAG_WHITE_BALANCE, "WhiteBalance", N_("White Balance")},
	{EXIF_TAG_DIGITAL_ZOOM_RATIO, "DigitalZoomRatio",
	 N_("Digital Zoom Ratio")},
	{EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM, "FocalLengthIn35mmFilm",
	 N_("Focal Length In 35mm Film")},
	{EXIF_TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType",
	 N_("Scene Capture Type")},
	{EXIF_TAG_GAIN_CONTROL, "GainControl", N_("Gain Control")},
	{EXIF_TAG_CONTRAST, "Contrast", N_("Contrast")},
	{EXIF_TAG_SATURATION, "Saturation", N_("Saturation")},
	{EXIF_TAG_SHARPNESS, "Sharpness", N_("Sharpness")},
	{EXIF_TAG_DEVICE_SETTING_DESCRIPTION, "DeviceSettingDescription",
	 N_("Device Setting Description")},
	{EXIF_TAG_SUBJECT_DISTANCE_RANGE, "SubjectDistanceRange",
	 N_("Subject Distance Range")},
	{EXIF_TAG_IMAGE_UNIQUE_ID, "ImageUniqueID", N_("Image Unique ID")},
	{0, NULL, NULL}
};


static ExifTag
exif_tag_from_string (const char *str)
{
	int i;

	if (!str)
		return 0;
	/* Is the string a tag's name or title? */
	for (i = 0; ExifTagTable[i].name != NULL; i++) {
		if (!strcmp (str, ExifTagTable[i].name))
			return (ExifTagTable[i].tag);
		if (!strcmp (str, ExifTagTable[i].title))
			return (ExifTagTable[i].tag);
	}
	return 0;
}

static ExifTag
exif_tag_from_tagid(ExifTag tag)
{
	int i;
	for (i = 0; ExifTagTable[i].tag != 0; i++) {
		if (ExifTagTable[i].tag == tag)
			return tag;
	}
	return 0;
}


static ExifIfd
exif_ifd_from_string (const char *string)
{
	unsigned int i;

	if (!string)
		return (-1);

	for (i = 0; i < EXIF_IFD_COUNT; i++) {
		if (!strcmp (string, exif_ifd_get_name (i)))
			return (i);
	}

	return (-1);
}

void
Init_exif(void)
{
	cExif = rb_define_class("Exif", rb_cObject);
	eExifError = rb_define_class_under(cExif, "Error", rb_eRuntimeError);
	eExifInvalidFormat = rb_define_class_under(cExif, "NotExifFormat", eExifError);
	eExifInvalidIFD = rb_define_class_under(eExifError, "InvalidIFD", eExifError);
	eExifTagNotFound = rb_define_class_under(eExifError, "TagNotFound", eExifError);
	eExifThumbnailNotFound = rb_define_class_under(eExifError, "ThumbnailNotFound", eExifError);

	rb_define_singleton_method(cExif, "new", rb_exif_s_new, -1);
	rb_define_private_method(cExif, "initialize", rb_exif_initialize, -1);
	rb_define_method(cExif, "data=", rb_exif_set_data, 1);
	rb_define_alias(cExif, "<<", "data=");
	rb_define_method(cExif, "each_entry", rb_exif_each, -1);
	rb_define_method(cExif, "byte_order", rb_exif_get_byte_order, 0);
	rb_define_method(cExif, "byte_order=", rb_exif_set_byte_order, 1);
	rb_define_method(cExif, "ifd=", rb_exif_set_ifd, 1);
	rb_define_method(cExif, "ifd", rb_exif_get_ifd, 0);
	rb_define_method(cExif, "[]", rb_exif_get_tag, 1);
	rb_define_method(cExif, "[]=", rb_exif_set_tag, 2);
	rb_define_singleton_method(cExif, "[]", rb_exif_s_get_tag_description, 1);
	rb_define_method(cExif, "list_tags", rb_exif_list_tags_at_ifd, 1);
	rb_define_method(cExif, "extract_thumbnail", rb_exif_extract_thumbnail, 1);
	rb_define_alias(cExif, "thumbnail", "extract_thumbnail");
	rb_define_method(cExif, "thumbnail=", rb_exif_set_thumbnail, 1);

	mExifByteOrder = rb_define_module_under(cExif, "ByteOrder");
	rb_define_const(mExifByteOrder, "MOTOROLA", INT2FIX(EXIF_BYTE_ORDER_MOTOROLA));
	rb_define_const(mExifByteOrder, "INTEL", INT2FIX(EXIF_BYTE_ORDER_INTEL));
	mExifIfd = rb_define_module_under(cExif, "IFD");
	rb_define_const(mExifIfd, "Zero", INT2FIX(EXIF_IFD_0));
	rb_define_const(mExifIfd, "One", INT2FIX(EXIF_IFD_1));
	rb_define_const(mExifIfd, "EXIF", INT2FIX(EXIF_IFD_EXIF));
	rb_define_const(mExifIfd, "GPS", INT2FIX(EXIF_IFD_GPS));
	rb_define_const(mExifIfd, "Interoperability", INT2FIX(EXIF_IFD_INTEROPERABILITY));
	rb_define_const(mExifIfd, "Any", INT2FIX(IFD_RB_DEFAULT));
}
