/* exif-loader.c
 *
 * Copyright (c) 2002 Lutz Mueller <lutz@users.sourceforge.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA.
 */

#include <config.h>

#include <libexif/exif-loader.h>
#include <libexif/exif-utils.h>
#include <libexif/i18n.h>

#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#undef JPEG_MARKER_DCT
#define JPEG_MARKER_DCT  0xc0
#undef JPEG_MARKER_DHT
#define JPEG_MARKER_DHT  0xc4
#undef JPEG_MARKER_SOI
#define JPEG_MARKER_SOI  0xd8
#undef JPEG_MARKER_DQT
#define JPEG_MARKER_DQT  0xdb
#undef JPEG_MARKER_APP0
#define JPEG_MARKER_APP0 0xe0
#undef JPEG_MARKER_APP1
#define JPEG_MARKER_APP1 0xe1
#undef JPEG_MARKER_APP2
#define JPEG_MARKER_APP2 0xe2
#undef JPEG_MARKER_APP4
#define JPEG_MARKER_APP4 0xe4
#undef JPEG_MARKER_APP5
#define JPEG_MARKER_APP5 0xe5
#undef JPEG_MARKER_APP11
#define JPEG_MARKER_APP11 0xeb
#undef JPEG_MARKER_APP13
#define JPEG_MARKER_APP13 0xed
#undef JPEG_MARKER_APP14
#define JPEG_MARKER_APP14 0xee
#undef JPEG_MARKER_COM
#define JPEG_MARKER_COM 0xfe

typedef enum {
	EL_READ = 0,
	EL_READ_SIZE_BYTE_24,
	EL_READ_SIZE_BYTE_16,
	EL_READ_SIZE_BYTE_08,
	EL_READ_SIZE_BYTE_00,
	EL_SKIP_BYTES,
	EL_EXIF_FOUND
} ExifLoaderState;

typedef enum {
	EL_DATA_FORMAT_UNKNOWN,
	EL_DATA_FORMAT_EXIF,
	EL_DATA_FORMAT_JPEG,
	EL_DATA_FORMAT_FUJI_RAW
} ExifLoaderDataFormat;

/*! \internal */
struct _ExifLoader {
	ExifLoaderState state;
	ExifLoaderDataFormat data_format;

	/*! Small buffer used for detection of format */
	unsigned char b[12];

	/*! Number of bytes in the small buffer \c b */
	unsigned char b_len;

	unsigned int size;
	unsigned char *buf;
	unsigned int bytes_read;

	unsigned int ref_count;

	ExifLog *log;
	ExifMem *mem;
};

/*! Magic number for EXIF header */
static const unsigned char ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};

static void *
exif_loader_alloc (ExifLoader *l, unsigned int i)
{
	void *d;

	if (!l || !i) 
		return NULL;

	d = exif_mem_alloc (l->mem, i);
	if (d) 
		return d;

	EXIF_LOG_NO_MEMORY (l->log, "ExifLog", i);
	return NULL;
}

void
exif_loader_write_file (ExifLoader *l, const char *path)
{
	FILE *f;
	int size;
	unsigned char data[1024];

	if (!l || !path)
		return;

	f = fopen (path, "rb");
	if (!f) {
		exif_log (l->log, EXIF_LOG_CODE_NONE, "ExifLoader",
			  _("The file '%s' could not be opened."), path);
		return;
	}
	while (1) {
		size = fread (data, 1, sizeof (data), f);
		if (size <= 0) 
			break;
		if (!exif_loader_write (l, data, size)) 
			break;
	}
	fclose (f);
}

static unsigned int
exif_loader_copy (ExifLoader *eld, unsigned char *buf, unsigned int len)
{
	if (!eld || (len && !buf) || (eld->bytes_read >= eld->size)) 
		return 0;

	/* If needed, allocate the buffer. */
	if (!eld->buf) 
		eld->buf = exif_loader_alloc (eld, eld->size);
	if (!eld->buf) 
		return 0;

	/* Copy memory */
	len = MIN (len, eld->size - eld->bytes_read);
	memcpy (eld->buf + eld->bytes_read, buf, len);
	eld->bytes_read += len;

	return (eld->bytes_read >= eld->size) ? 0 : 1;
}

unsigned char
exif_loader_write (ExifLoader *eld, unsigned char *buf, unsigned int len)
{
	unsigned int i;

begin:
	if (!eld || (len && !buf)) 
		return 0;

	switch (eld->state) {
	case EL_EXIF_FOUND:
		return exif_loader_copy (eld, buf, len);
	case EL_SKIP_BYTES:
		if (eld->size > len) { 
			eld->size -= len; 
			return 1; 
		}
		len -= eld->size;
		buf += eld->size;
		eld->size = 0;
		eld->b_len = 0;
		switch (eld->data_format) {
		case EL_DATA_FORMAT_FUJI_RAW:
			eld->state = EL_READ_SIZE_BYTE_24;
			break;
		default:
			eld->state = EL_READ;
			break;
		}
		break;

	case EL_READ:
	default:
		break;
	}

	if (!len)
		return 1;
	exif_log (eld->log, EXIF_LOG_CODE_DEBUG, "ExifLoader",
		  "Scanning %i byte(s) of data...", len);

	/*
	 * First fill the small buffer. Only continue if the buffer
	 * is filled. Note that EXIF data contains at least 12 bytes.
	 */
	i = MIN (len, sizeof (eld->b) - eld->b_len);
	if (i) {
		memcpy (&eld->b[eld->b_len], buf, i);
		eld->b_len += i;
		if (eld->b_len < sizeof (eld->b)) 
			return 1;
		buf += i;
		len -= i;
	}

	switch (eld->data_format) {
	case EL_DATA_FORMAT_UNKNOWN:

		/* Check the small buffer against known formats. */
		if (!memcmp (eld->b, "FUJIFILM", 8)) {

			/* Skip to byte 84. There is another offset there. */
			eld->data_format = EL_DATA_FORMAT_FUJI_RAW;
			eld->size = 84;
			eld->state = EL_SKIP_BYTES;
			eld->size = 84;

		} else if (!memcmp (eld->b + 2, ExifHeader, sizeof (ExifHeader))) {

			/* Read the size (2 bytes). */
			eld->data_format = EL_DATA_FORMAT_EXIF;
			eld->state = EL_READ_SIZE_BYTE_08;
		}
	default:
		break;
	}

	for (i = 0; i < sizeof (eld->b); i++) {
		switch (eld->state) {
		case EL_EXIF_FOUND:
			if (!exif_loader_copy (eld, eld->b + i,
					sizeof (eld->b) - i)) 
				return 0;
			return exif_loader_copy (eld, buf, len);
		case EL_SKIP_BYTES:
			switch (eld->size) {
                            case 0:
			        eld->state = EL_READ;
				i--;   /* reprocess this byte */
				break;
                            case 1:
                                eld->size = 0;
			        eld->state = EL_READ;
				break;
                            default:
                                eld->size--;
				break;
			}
			break;

		case EL_READ_SIZE_BYTE_24:
			eld->size |= (unsigned int)eld->b[i] << 24;
			eld->state = EL_READ_SIZE_BYTE_16;
			break;
		case EL_READ_SIZE_BYTE_16:
			eld->size |= (unsigned int)eld->b[i] << 16;
			eld->state = EL_READ_SIZE_BYTE_08;
			break;
		case EL_READ_SIZE_BYTE_08:
			eld->size |= (unsigned int)eld->b[i] << 8;
			eld->state = EL_READ_SIZE_BYTE_00;
			break;
		case EL_READ_SIZE_BYTE_00:
			eld->size |= eld->b[i] << 0;
			switch (eld->data_format) {
			case EL_DATA_FORMAT_JPEG:
				eld->state = EL_SKIP_BYTES;
				if (eld->size < 2) {
				    /* Actually it's malformed... */
				    eld->size = 0;
				} else
				    eld->size -= 2;
				break;
			case EL_DATA_FORMAT_FUJI_RAW:
				eld->data_format = EL_DATA_FORMAT_EXIF;
				eld->state = EL_SKIP_BYTES;
				if (eld->size < 86) {
				    /* Actually it's malformed... */
				    eld->size = 0;
				} else
				    eld->size -= 86;	/* and put this in an else */
				break;
			case EL_DATA_FORMAT_EXIF:
				eld->state = EL_EXIF_FOUND;
				break;
			default:
				break;
			}
			break;

		default:
			switch (eld->b[i]) {
			case JPEG_MARKER_APP1:
			  if (!memcmp (eld->b + i + 3, ExifHeader, MIN((ssize_t)(sizeof(ExifHeader)), MAX(0, ((ssize_t)(sizeof(eld->b))) - ((ssize_t)i) - 3)))) {
					eld->data_format = EL_DATA_FORMAT_EXIF;
				} else {
					eld->data_format = EL_DATA_FORMAT_JPEG; /* Probably JFIF - keep searching for APP1 EXIF*/
				}
				eld->size = 0;
				eld->state = EL_READ_SIZE_BYTE_08;
				break;
			case JPEG_MARKER_DCT:
			case JPEG_MARKER_DHT:
			case JPEG_MARKER_DQT:
			case JPEG_MARKER_APP0:
			case JPEG_MARKER_APP2:
			case JPEG_MARKER_APP4:
			case JPEG_MARKER_APP5:
			case JPEG_MARKER_APP11:
			case JPEG_MARKER_APP13:
			case JPEG_MARKER_APP14:
			case JPEG_MARKER_COM:
				eld->data_format = EL_DATA_FORMAT_JPEG;
				eld->size = 0;
				eld->state = EL_READ_SIZE_BYTE_08;
				break;
			case 0xff:
			case JPEG_MARKER_SOI:
				break;
			default:
				exif_log (eld->log,
					EXIF_LOG_CODE_CORRUPT_DATA,
					"ExifLoader", _("The data supplied "
						"does not seem to contain "
						"EXIF data."));
				exif_loader_reset (eld);
				return 0;
			}
		}
	}

	/*
	 * If we reach this point, the buffer has not been big enough
	 * to read all data we need. Fill it with new data.
	 */
	eld->b_len = 0;
	goto begin;
}

ExifLoader *
exif_loader_new (void)
{
	ExifMem *mem = exif_mem_new_default ();
	ExifLoader *l = exif_loader_new_mem (mem);

	exif_mem_unref (mem);

	return l;
}

ExifLoader *
exif_loader_new_mem (ExifMem *mem)
{
	ExifLoader *loader;

	if (!mem) 
		return NULL;
	
	loader = exif_mem_alloc (mem, sizeof (ExifLoader));
	if (!loader) 
		return NULL;
	loader->ref_count = 1;

	loader->mem = mem;
	exif_mem_ref (mem);

	return loader;
}

void
exif_loader_ref (ExifLoader *loader)
{
	if (loader) 
		loader->ref_count++;
}

static void
exif_loader_free (ExifLoader *loader)
{
	ExifMem *mem;

	if (!loader) 
		return;

	mem = loader->mem;
	exif_loader_reset (loader);
	exif_log_unref (loader->log);
	exif_mem_free (mem, loader);
	exif_mem_unref (mem);
}
	
void
exif_loader_unref (ExifLoader *loader)
{
	if (!loader) 
		return;
	if (!--loader->ref_count)
		exif_loader_free (loader);
}

void
exif_loader_reset (ExifLoader *loader)
{
	if (!loader) 
		return;
	exif_mem_free (loader->mem, loader->buf); loader->buf = NULL;
	loader->size = 0;
	loader->bytes_read = 0;
	loader->state = 0;
	loader->b_len = 0;
	loader->data_format = EL_DATA_FORMAT_UNKNOWN;
}

ExifData *
exif_loader_get_data (ExifLoader *loader)
{
	ExifData *ed;

	if (!loader || (loader->data_format == EL_DATA_FORMAT_UNKNOWN) ||
	    !loader->bytes_read)
		return NULL;

	ed = exif_data_new_mem (loader->mem);
	exif_data_log (ed, loader->log);
	exif_data_load_data (ed, loader->buf, loader->bytes_read);

	return ed;
}

void
exif_loader_get_buf (ExifLoader *loader, const unsigned char **buf,
						  unsigned int *buf_size)
{
	const unsigned char* b = NULL;
	unsigned int s = 0;

	if (loader) {
		if (loader->data_format == EL_DATA_FORMAT_UNKNOWN) {
			exif_log (loader->log, EXIF_LOG_CODE_DEBUG, "ExifLoader",
					  "Loader format unknown");
		} else {
			b = loader->buf;
			s = loader->bytes_read;
		}
	}

	if (buf)
		*buf = b;
	if (buf_size)
		*buf_size = s;
}

void
exif_loader_log (ExifLoader *loader, ExifLog *log)
{
	if (!loader) 
		return;
	exif_log_unref (loader->log);
	loader->log = log;
	exif_log_ref (log);
}
