/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/* GdkPixbuf library - MNG image loader
 *
 * Copyright (C) 2002 Matthias Clasen
 *
 * Authors: Matthias Clasen <maclas@gmx.de>
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <libmng.h>
#include "gdk-pixbuf-private.h"
#include "gdk-pixbuf-io.h"

#define BUFFER_SIZE 4096

typedef struct {
        mng_handle handle;

        guint priority;
        
        ModulePreparedNotifyFunc prepare_func;
        ModuleUpdatedNotifyFunc update_func;
        gpointer notify_user_data;

        guchar buffer[BUFFER_SIZE];
        guchar *bytes;
        guint nbytes;

        GdkPixbuf* pixbuf;

        gboolean has_frame;
        
        GError **error;
} LoadContext;


static mng_bool
readdata (mng_handle handle,
          mng_ptr buf,
          mng_uint32 buflen,
          mng_uint32p read)
{
        size_t n;
        LoadContext *lc = (LoadContext *)mng_get_userdata (handle);
        n = MIN(buflen, lc->nbytes);

        memcpy (buf, lc->bytes, n);
        lc->bytes += n;
        lc->nbytes -= n;
        *read = n;        

        return MNG_TRUE;
}

static mng_bool
processheader (mng_handle handle,
               mng_uint32 width,
               mng_uint32 height)
{
        LoadContext *lc = (LoadContext *)mng_get_userdata (handle);

        lc->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height);

        if (lc->prepare_func)
                (lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);

        return MNG_TRUE;
}

static mng_bool
openstream (mng_handle handle)
{
        return MNG_TRUE;
}

static mng_bool
closestream (mng_handle handle)
{
        return MNG_TRUE;
}

static mng_ptr
getcanvasline (mng_handle handle, mng_uint32 line)
{
        LoadContext *lc = (LoadContext *) mng_get_userdata (handle);
        return (mng_ptr)(lc->pixbuf->pixels + line * lc->pixbuf->rowstride);
}

static mng_ptr 
mng_alloc (mng_size_t len)
{
        mng_ptr p;

        p = g_try_malloc (len);
        if (!p)
                return NULL;
        memset (p, 0, len);
        return p;
}

static void 
mng_free (mng_ptr p, mng_size_t len)
{
        g_free (p);
}

static mng_bool
refresh (mng_handle handle,
         mng_uint32 x,
         mng_uint32 y,
         mng_uint32 width,
         mng_uint32 height)
{
        LoadContext *lc = (LoadContext *) mng_get_userdata (handle);

        /* libmng currently doesn't offer a good way to get at the current 
           priority, thus we'll always stop at the first frame currently. */
        if (lc->priority == 255) {
                lc->has_frame = TRUE;

                if (lc->update_func)
                        (lc->update_func) (lc->pixbuf, x, y, width, height, lc->notify_user_data);
        }

        return MNG_TRUE;
}

static mng_uint32
gettickcount (mng_handle handle)
{
        static int tick = 0;

        return tick++;
}

static mng_bool
settimer (mng_handle handle, mng_uint32 usec)
{
        return MNG_TRUE;
}

static gpointer
gdk_pixbuf__mng_image_begin_load (ModulePreparedNotifyFunc prepare_func,
				  ModuleUpdatedNotifyFunc update_func,
				  gpointer user_data,
                                  GError **error)
{
        int retcode;
        LoadContext* lc;

        lc = g_new0 (LoadContext, 1);

        lc->handle = mng_initialize ((mng_ptr)lc, mng_alloc, mng_free, NULL);
        if (!lc->handle) {
                g_free (lc);
                if (error && *error == NULL) {
                        g_set_error (error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                     _("error loading MNG image"));
                }
                return NULL;
        }
        
        lc->priority = 255;

        lc->bytes = lc->buffer;
        lc->nbytes = 0;

        lc->prepare_func = prepare_func;
        lc->update_func = update_func;
        lc->notify_user_data = user_data;

        lc->error = error;

        mng_setcb_openstream (lc->handle, openstream);
        mng_setcb_closestream (lc->handle, closestream);
        mng_setcb_readdata (lc->handle, readdata);
        mng_setcb_processheader (lc->handle, processheader);
        mng_setcb_getcanvasline (lc->handle, getcanvasline);
        mng_setcb_refresh (lc->handle, refresh);
        mng_setcb_gettickcount (lc->handle, gettickcount);
        mng_setcb_settimer (lc->handle, settimer);

        mng_set_suspensionmode (lc->handle, MNG_TRUE);

        retcode = mng_readdisplay (lc->handle);

        switch (retcode) {
            case MNG_NOERROR:
            case MNG_NEEDMOREDATA:
            case MNG_NEEDTIMERWAIT:
                    break;
            default:
                    mng_cleanup (&lc->handle);
                    if (lc->pixbuf)
                            g_object_unref (lc->pixbuf);
                    g_free (lc);
                    if (error && *error == NULL) {
                            g_set_error (error,
                                         GDK_PIXBUF_ERROR,
                                         GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                         g_strdup_printf(_("error extracting first frame: %d"), retcode));
                    }
                    return NULL;
        }

        return lc;
}

static gboolean
gdk_pixbuf__mng_image_stop_load (gpointer context, GError **error)
{
        LoadContext* lc = context;

        g_return_val_if_fail (lc != NULL, TRUE);

        mng_cleanup (&lc->handle);
        
        if (lc->pixbuf)
                g_object_unref (lc->pixbuf);
        
        g_free (lc);

        return TRUE;
}

static gboolean
gdk_pixbuf__mng_image_load_increment (gpointer context,
                                      const guchar *buf, guint size,
                                      GError **error)
{
        int retcode;
        LoadContext* lc = context;
        guint num_to_copy, num_left;
        const guchar *bytes;

        g_return_val_if_fail (lc != NULL, FALSE);

        if (lc->has_frame)
                return TRUE;

        bytes = buf;
        num_left = size;

        while (TRUE) {
                num_to_copy = MIN(BUFFER_SIZE - lc->nbytes, num_left);
   
                if (lc->nbytes > 0) 
                        memmove (lc->buffer, lc->bytes, lc->nbytes);

                memcpy (lc->buffer + lc->nbytes, bytes, num_to_copy); 
                lc->nbytes += num_to_copy;
                lc->bytes = lc->buffer;

                bytes += num_to_copy;
                num_left -= num_to_copy;

                if (lc->nbytes == 0) 
                        break;

                retcode = mng_display_resume (lc->handle);
                switch (retcode) {
                    case MNG_NOERROR:
                    case MNG_NEEDMOREDATA:
                    case MNG_NEEDTIMERWAIT:
                            break;
                    default:
                            if (lc->error && *lc->error == NULL) {
                                    g_set_error (lc->error,
                                                 GDK_PIXBUF_ERROR,
                                                 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                                 g_strdup_printf(_("error loading MNG image: %d"), retcode));
                            }
                            return FALSE;
                }
        } 

        return TRUE;
}



void
gdk_pixbuf__mng_fill_vtable (GdkPixbufModule *module)
{
  module->begin_load = gdk_pixbuf__mng_image_begin_load;
  module->stop_load = gdk_pixbuf__mng_image_stop_load;
  module->load_increment = gdk_pixbuf__mng_image_load_increment;
}
