/* -*- mode: C; c-file-style: "linux" -*- */
/* GdkPixbuf library - QTIF image loader
 *
 * This module extracts image data from QTIF format and uses
 * other GDK pixbuf modules to decode the image data.
 *
 * Copyright (C) 2008 Kevin Peng
 * 
 * Authors: Kevin Peng <kevin@zycomtech.com>
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */


#include "config.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include "gdk-pixbuf.h"
#include "gdk-pixbuf-private.h"
#include "gdk-pixbuf-io.h"

/***
 * Definitions
 */
/* GdkPixbufLoader seems better at guessing image type out of byte data. */
#define USE_GDK_PIXBUF_LOADER TRUE

/* Read buffer size */
#define READ_BUFFER_SIZE    8192

/* Only allow atom of size up to 10MB. */
#define ATOM_SIZE_MAX       100000000

/* Aborts after going to through this many atoms. */
#define QTIF_ATOM_COUNT_MAX 10u

/* QTIF static image data tag "idat". */
#define QTIF_TAG_IDATA      0x69646174u

/* QTIF static image description tag "idsc". */
#define QTIF_TAG_IDESC      0x69647363u

/* Borrowed from gdk-pixbuf-io.c. */
#define SNIFF_BUFFER_SIZE 4096


/***
 * Types
 */
/* QTIF Atom Header */
/* QTIF State */
typedef enum {
    STATE_READY,
    STATE_DATA,
    STATE_META
} QTIFState;


typedef struct {
    guint32 length;
    guint32 tag;
} QtHeader;

/* QTIF loader context */
typedef struct {
    GdkPixbufLoader *loader;
    gpointer        user_data;
    QTIFState       state;
    guint32         run_length;

    guchar          header_buffer[sizeof(QtHeader)];

    GdkPixbufModuleSizeFunc size_func;
    GdkPixbufModulePreparedFunc prepare_func;
    GdkPixbufModuleUpdatedFunc update_func;
} QTIFContext;

/***
 * Local function prototypes
 */
static GdkPixbuf *gdk_pixbuf__qtif_image_load (FILE *f, GError **error);
static gpointer gdk_pixbuf__qtif_image_begin_load (GdkPixbufModuleSizeFunc size_func,
                                                   GdkPixbufModulePreparedFunc prepare_func,
                                                   GdkPixbufModuleUpdatedFunc update_func,
                                                   gpointer user_data,
                                                   GError **error);
static gboolean gdk_pixbuf__qtif_image_stop_load (gpointer context, GError **error);
static gboolean gdk_pixbuf__qtif_image_load_increment(gpointer context,
                                                      const guchar *buf, guint size,
                                                      GError **error);
static void gdk_pixbuf__qtif_cb_size_prepared(GdkPixbufLoader *loader,
                                              gint width,
                                              gint height,
                                              gpointer user_data);
static void gdk_pixbuf__qtif_cb_area_prepared(GdkPixbufLoader *loader, gpointer user_data);
static void gdk_pixbuf__qtif_cb_area_updated(GdkPixbufLoader *loader,
                                             gint x,
                                             gint y,
                                             gint width,
                                             gint height,
                                             gpointer user_data);

/***
 * Function definitions.
 */
 /* Loading QTIF from a file descriptor. */
static GdkPixbuf *gdk_pixbuf__qtif_image_load (FILE *f, GError **error)
{
    guint count;

    if(f == NULL)
    {
        g_set_error_literal (error, GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_BAD_OPTION,
                             _("Input file descriptor is NULL."));
        return NULL;
    }

    for(count = QTIF_ATOM_COUNT_MAX; count != 0u; count--)
    {
        QtHeader hdr;
        size_t rd;
        
        /* Read QtHeader. */
        rd = fread(&hdr, 1, sizeof(QtHeader), f);
        if(rd != sizeof(QtHeader))
        {
            g_set_error_literal(error, GDK_PIXBUF_ERROR,
                                GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                _("Failed to read QTIF header"));
            return NULL;
        }

        hdr.length = GUINT32_FROM_BE(hdr.length) - sizeof(QtHeader);
        if(hdr.length > ATOM_SIZE_MAX)
        {
            g_set_error(error, GDK_PIXBUF_ERROR,
                        GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                        _("QTIF atom size too large (%d bytes)"), hdr.length);
            return NULL;
        }

        switch(GUINT32_FROM_BE(hdr.tag))
        {
        case QTIF_TAG_IDATA: /* "idat" data atom. */
            {
#if USE_GDK_PIXBUF_LOADER
                /* Load image using GdkPixbufLoader. */
                guchar *buf;
                GdkPixbufLoader *loader;
                GdkPixbuf *pixbuf = NULL;

                /* Allocate read buffer. */
                buf = g_try_malloc(READ_BUFFER_SIZE);
                if(buf == NULL)
                {
                    g_set_error(error, GDK_PIXBUF_ERROR,
                                GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                _("Failed to allocate %d bytes for file read buffer"), READ_BUFFER_SIZE);
                    return NULL;
                }

                /* Create GdkPixbufLoader. */
                loader = gdk_pixbuf_loader_new();
                if(loader == NULL)
                {
                    g_set_error(error, GDK_PIXBUF_ERROR,
                                GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                _("QTIF atom size too large (%d bytes)"), hdr.length);
                    goto clean_up;
                }

                /* Read atom data. */
                while(hdr.length != 0u)
                {
                    GError *tmp;
                    rd = (hdr.length > READ_BUFFER_SIZE) ? READ_BUFFER_SIZE : hdr.length;
                    
                    rd = fread(buf, 1, rd, f);
                    if(rd < 0)
                    {
                        g_set_error(error, GDK_PIXBUF_ERROR,
                                    GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                    _("File error when reading QTIF atom: %s"), g_strerror(errno));
                        break;
                    }

                    if(!gdk_pixbuf_loader_write(loader, buf, rd, &tmp))
                    {
                        g_propagate_error (error, tmp);
                        break;
                    }
                    hdr.length -= rd;
                }
                
clean_up:
                /* Release loader */
                if(loader != NULL)
                {
                    gdk_pixbuf_loader_close(loader, error);
                    pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
                    if(pixbuf != NULL)
                    {
                        g_object_ref(pixbuf);
                    }
                    g_object_unref(loader);
                }
                if(buf != NULL)
                {
                    g_free(buf);
                }
                return pixbuf;
#else
                /* Load image using _gdk_pixbuf_generic_image_load().\
                 * This gets difficult to determine the correct image decoder module
                 * without filename and has not worked for me yet. */
                size_t size;
                guchar buffer[SNIFF_BUFFER_SIZE];
                GdkPixbufModule *image_module;

                size = fread (&buffer, 1, SNIFF_BUFFER_SIZE, f);
                if (size <= 0) {
                    g_set_error_literal(error,
                                        GDK_PIXBUF_ERROR,
                                        GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                        _("Error reading image file."));
                    return NULL;
                }

                image_module = _gdk_pixbuf_get_module (buffer, size, NULL, error);
                if ((image_module == NULL) || (image_module->module == NULL))
                {
                    return NULL;
                }

            	fseek (f, -size, SEEK_CUR);

            	return _gdk_pixbuf_generic_image_load (image_module, f, error);
#endif                
            }
        
        default:
            /* Skip any other types of atom. */
            if(!fseek(f, hdr.length, SEEK_CUR))
            {
                g_set_error(error, GDK_PIXBUF_ERROR,
                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                            _("Failed to skip the next %d bytes with seek()."), hdr.length);
                return NULL;
            }
            break;
        }
    }
    return NULL;
}

static gpointer gdk_pixbuf__qtif_image_begin_load (GdkPixbufModuleSizeFunc size_func,
                                                   GdkPixbufModulePreparedFunc prepare_func,
                                                   GdkPixbufModuleUpdatedFunc update_func,
                                                   gpointer user_data,
                                                   GError **error)
{
    QTIFContext *context;

    /* Create context struct. */
    context = g_new0(QTIFContext, 1);
    if(context == NULL)
    {
        g_set_error_literal (error, GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                             _("Failed to QTIF context structure."));
        return NULL;
    }

    /* Create GdkPixbufLoader object. */
    context->loader = gdk_pixbuf_loader_new();
    if(context->loader == NULL)
    {
        g_set_error_literal (error, GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_FAILED,
                             _("Failed to create GdkPixbufLoader object."));
        return NULL;
    }

    /* Fill context parameters. */
    context->user_data = user_data;
    context->state = STATE_READY;
    context->run_length = 0u;

    /* Connect signals. */
    if(size_func != NULL)
    {
        context->size_func = size_func;
        g_signal_connect(context->loader, "size-prepared",
                         G_CALLBACK(gdk_pixbuf__qtif_cb_size_prepared),
                         context);
    }
    if(prepare_func != NULL)
    {
        context->prepare_func = prepare_func;
        g_signal_connect(context->loader, "area-prepared",
                         G_CALLBACK(gdk_pixbuf__qtif_cb_area_prepared),
                         context);
    }
    if(update_func != NULL)
    {
        context->update_func = update_func;
        g_signal_connect(context->loader, "area-updated",
                         G_CALLBACK(gdk_pixbuf__qtif_cb_area_updated),
                         context);
    }

	return context;
}

/*
 * context - returned from image_begin_load
 *
 * free context, unref gdk_pixbuf
 */
static gboolean
gdk_pixbuf__qtif_image_stop_load (gpointer data, GError **error)
{
    QTIFContext *context = (QTIFContext *)data;
    gdk_pixbuf_loader_close(context->loader, error);
    if(context->prepare_func != NULL)
    {
        GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(context->loader);
        if(pixbuf != NULL)
        {
            g_object_ref(pixbuf);
        }
        (context->prepare_func)(pixbuf, NULL, context->user_data);
    }
    g_object_unref(context->loader);
    g_free(context);
	return TRUE;
}

/*
 * context - from image_begin_load
 * buf - new image data
 * size - length of new image data
 *
 * append image data onto inrecrementally built output image
 */
static gboolean
gdk_pixbuf__qtif_image_load_increment (gpointer data,
                                       const guchar *buf, guint size,
                                       GError **error)
{
    gboolean ret = TRUE;
    QTIFContext *context = (QTIFContext *)data;
    

    while(size != 0u)
    {
        switch(context->state)
        {
        case STATE_READY:
            /* Copy to header buffer in context, in case supplied data is not enough. */
            while((context->run_length < sizeof(QtHeader)) && (size != 0u))
            {
                context->header_buffer[context->run_length] = *buf;
                context->run_length++;
                buf++;
                size--;
            }

            /* Parse buffer as QT header. */
            if(context->run_length == sizeof(QtHeader))
            {
                QtHeader *hdr = (QtHeader *)context->header_buffer;
                context->run_length = GUINT32_FROM_BE(hdr->length) - sizeof(QtHeader);
                switch(GUINT32_FROM_BE(hdr->tag))
                {
                default:
                    context->state = STATE_META;
                    break;
                
                case 0x69646174u: /* "idat" data atom. */
                    context->state = STATE_DATA;
                    break;
                }
            }
            break;

        case STATE_DATA:
            if(context->run_length > size)
            {
                ret = gdk_pixbuf_loader_write(context->loader, buf, size, error);
                context->run_length -= size;
                size = 0u;
            }
            else
            {
                ret = gdk_pixbuf_loader_write(context->loader, buf, context->run_length, error);
                buf = &buf[context->run_length];
                size -= context->run_length;
                context->run_length = 0u;
                context->state = STATE_READY;
                return TRUE;
            }
            break;

        case STATE_META:
            if(context->run_length > size)
            {
                context->run_length -= size;
                size = 0u;
            }
            else
            {
                buf = &buf[context->run_length];
                size -= context->run_length;
                context->run_length = 0u;
                context->state = STATE_READY;
            }
            break;

        default:
            break;
        }
    }

    return ret;
}

/* Event handlers */
static void gdk_pixbuf__qtif_cb_size_prepared(GdkPixbufLoader *loader,
                                              gint width,
                                              gint height,
                                              gpointer user_data)
{
    QTIFContext *context = (QTIFContext *)user_data;
    if((context != NULL) && (context->size_func != NULL))
    {
        (context->size_func)(&width, &height, context->user_data);
    }
}

static void gdk_pixbuf__qtif_cb_area_prepared(GdkPixbufLoader *loader, gpointer user_data)
{
    QTIFContext *context = (QTIFContext *)user_data;
    if((loader != NULL) && (context != NULL) && (context->prepare_func != NULL))
    {
        GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(context->loader);
        (context->prepare_func)(pixbuf, NULL, context->user_data);
    }
}

static void gdk_pixbuf__qtif_cb_area_updated(GdkPixbufLoader *loader,
                                             gint x,
                                             gint y,
                                             gint width,
                                             gint height,
                                             gpointer user_data)
{
    QTIFContext *context = (QTIFContext *)user_data;
    if((loader != NULL) && (context != NULL) && (context->update_func != NULL))
    {
        GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(context->loader);
        (context->update_func)(pixbuf, x, y, width, height, context->user_data);
    }
}

	
#ifndef INCLUDE_qtif
#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
#else
#define MODULE_ENTRY(function) void _gdk_pixbuf__qtif_ ## function
#endif

MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
{
    module->load = gdk_pixbuf__qtif_image_load;
    module->begin_load = gdk_pixbuf__qtif_image_begin_load;
    module->stop_load = gdk_pixbuf__qtif_image_stop_load;
    module->load_increment = gdk_pixbuf__qtif_image_load_increment;
}

MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
{
    static GdkPixbufModulePattern signature[] = {
        { "abcdidsc", "xxxx    ", 100 },
        { "abcdidat", "xxxx    ", 100 },
        { NULL, NULL, 0 }
    };
    static gchar * mime_types[] = {
        "image/x-quicktime",
        "image/qtif",
        NULL
    };
    static gchar * extensions[] = {
        "qtif",
        "qif",
        NULL
    };

    info->name = "qtif";
    info->signature = signature;
    info->description = N_("The QTIF image format");
    info->mime_types = mime_types;
    info->extensions = extensions;
    info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
    info->license = "LGPL";
}

