#include "evas_common_private.h"
#include "evas_private.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <gif_lib.h>

typedef struct _Frame_Info Frame_Info;
typedef struct _Loader_Info Loader_Info;
typedef struct _File_Info File_Info;

struct _File_Info
{
   unsigned char *map;
   int pos, len; // yes - gif uses ints for file sizes.
};

struct _Loader_Info
{
   Eina_File *f;
   Evas_Image_Load_Opts *opts;
   Evas_Image_Animated *animated;
   GifFileType *gif;
   int imgnum;
   File_Info fi;
};

struct _Frame_Info
{
   int x, y, w, h;
   unsigned short delay; // delay time in 1/100ths of a sec
   short transparent : 10; // -1 == not, anything else == index
   short dispose : 6; // 0, 1, 2, 3 (others invalid)
   Eina_Bool interlace : 1; // interlaced or not
};

#define LOADERR(x) \
do { \
   *error = (x); \
   goto on_error; \
} while (0)
#define PIX(_x, _y) rows[yin + _y][xin + _x]
#define CMAP(_v) colors[_v]
#define PIXLK(_p) ARGB_JOIN(0xff, CMAP(_p).Red, CMAP(_p).Green, CMAP(_p).Blue)

// utility funcs...

// brute force find frame index - gifs are normally saml so ok for now
static Image_Entry_Frame *
_find_frame(Evas_Image_Animated *animated, int index)
{
   Eina_List *l;
   Image_Entry_Frame *frame;

   if (!animated->frames) return NULL;
   EINA_LIST_FOREACH(animated->frames, l, frame)
     {
        if (frame->index == index) return frame;
     }
   return NULL;
}

// fill in am image with a specific rgba color value
static void
_fill_image(DATA32 *data, int rowpix, DATA32 val, int x, int y, int w, int h)
{
   int xx, yy;
   DATA32 *p;

   for (yy = 0; yy < h; yy++)
     {
        p = data + ((y + yy) * rowpix) + x;
        for (xx = 0; xx < w; xx++)
          {
             *p = val;
             p++;
          }
     }
}

// fix coords and work out an x and y inset in orig data if out of image bounds
static void
_clip_coords(int imw, int imh, int *xin, int *yin,
             int x0, int y0, int w0, int h0,
             int *x, int *y, int *w, int *h)
{
   if (x0 < 0)
     {
        w0 += x0;
        *xin = -x0;
        x0 = 0;
     }
   if ((x0 + w0) > imw) w0 = imw - x0;
   if (y0 < 0)
     {
        h0 += y0;
        *yin = -y0;
        y0 = 0;
     }
   if ((y0 + h0) > imh) h0 = imh - y0;
   *x = x0;
   *y = y0;
   *w = w0;
   *h = h0;
}

// file a rgba data pixle blob with a frame color (bg or trans) depending...
static void
_fill_frame(DATA32 *data, int rowpix, GifFileType *gif, Frame_Info *finfo,
            int x, int y, int w, int h)
{
   // solid color fill for pre frame region
   if (finfo->transparent < 0)
     {
        ColorMapObject *cmap;
        int bg;
        GifColorType colors[256];
        int cnum;

        // work out color to use from cmap
        if (gif->Image.ColorMap) cmap = gif->Image.ColorMap;
        else cmap = gif->SColorMap;
        bg = gif->SBackGroundColor;

        if (cmap)
          {
             // fill in local color table of guaranteed 256 with cmap & pad
             for (cnum = 0; cnum < cmap->ColorCount; cnum++)
               colors[cnum] = cmap->Colors[cnum];
             for (cnum = cmap->ColorCount; cnum < 256; cnum++)
               colors[cnum] = cmap->Colors[0];
          }
        else
          memset(colors, 0, sizeof(colors));
        // and do the fill
        _fill_image
          (data, rowpix,
           ARGB_JOIN(0xff, CMAP(bg).Red, CMAP(bg).Green, CMAP(bg).Blue),
           x, y, w, h);
     }
   // fill in region with 0 (transparent)
   else
     _fill_image(data, rowpix, 0, x, y, w, h);
}

// store common fields from gif file info into frame info
static void
_store_frame_info(GifFileType *gif, Frame_Info *finfo)
{
   finfo->x = gif->Image.Left;
   finfo->y = gif->Image.Top;
   finfo->w = gif->Image.Width;
   finfo->h = gif->Image.Height;
   finfo->interlace = gif->Image.Interlace;
}

// check if image fills "screen space" and if so, if it is transparent
// at all then the image could be transparent - OR if image doesnt fill,
// then it could be trasnparent (full coverage of screen). some gifs will
// be recognized as solid here for faster rendering, but not all.
static void
_check_transparency(Eina_Bool *full, Frame_Info *finfo, int w, int h)
{
   if ((finfo->x == 0) && (finfo->y == 0) &&
       (finfo->w == w) && (finfo->h == h))
     {
        if (finfo->transparent >= 0) *full = EINA_FALSE;
     }
   else *full = EINA_FALSE;
}

// allocate frame and frame info and append to list and store fields
static Frame_Info *
_new_frame(Evas_Image_Animated *animated,
           int transparent, int dispose, int delay,
           int index)
{
   Image_Entry_Frame *frame;
   Frame_Info *finfo;

   // allocate frame and frame info data (MUSt be separate)
   frame = calloc(1, sizeof(Image_Entry_Frame));
   if (!frame) return NULL;
   finfo = calloc(1, sizeof(Frame_Info));
   if (!finfo)
     {
        free(frame);
        return NULL;
     }
   // record transparent index to be used or -1 if none
   // for this SPECIFIC frame
   finfo->transparent = transparent;
   // record dispose mode (3 bits)
   finfo->dispose = dispose;
   // record delay (2 bytes so max 65546 /100 sec)
   finfo->delay = delay;
   // record the index number we are at
   frame->index = index;
   // that frame is stored AT image/screen size
   frame->info = finfo;
   animated->frames = eina_list_append(animated->frames, frame);
   return finfo;
}

// decode a gif image into rows then expand to 32bit into the destination
// data pointer
static Eina_Bool
_decode_image(GifFileType *gif, DATA32 *data, int rowpix, int xin, int yin,
              int transparent, int fw, int fh,
              int x, int y, int w, int h, Eina_Bool fill)
{
   int intoffset[] = { 0, 4, 2, 1 };
   int intjump[] = { 8, 8, 4, 2 };
   int i, xx, yy, pix;
   GifRowType *rows;
   GifPixelType *pixels;
   Eina_Bool ret = EINA_FALSE;
   ColorMapObject *cmap;
   DATA32 *p;
   GifColorType colors[256];
   int cnum;

   // build a blob of memory to have pointers to rows of pixels
   // AND store the decoded gif pixels (1 byte per pixel) as welll
   rows = malloc((fh * sizeof(GifRowType)) + (fw * fh * sizeof(GifPixelType)));
   if (!rows) goto on_error;

   // fill in the pointers at the start
   for (yy = 0; yy < fh; yy++)
     {
        rows[yy] = ((unsigned char *)rows) + (fh * sizeof(GifRowType)) +
          (yy * fw * sizeof(GifPixelType));
     }

   // if give is interlaced, walk interlace pattern and decode into rows
   if (gif->Image.Interlace)
     {
        for (i = 0; i < 4; i++)
          {
             for (yy = intoffset[i]; yy < fh; yy += intjump[i])
               {
                  if (DGifGetLine(gif, rows[yy], fw) != GIF_OK)
                    goto on_error;
               }
          }
     }
   // normal top to bottom - decode into rows
   else
     {
        for (yy = 0; yy < fh; yy++)
          {
             if (DGifGetLine(gif, rows[yy], fw) != GIF_OK)
               goto on_error;
          }
     }

   // work out what colormap to use
   if (gif->Image.ColorMap) cmap = gif->Image.ColorMap;
   else cmap = gif->SColorMap;

   if (cmap)
     {
        // fill in local color table of guaranteed 256 entries with cmap & pad
        for (cnum = 0; cnum < cmap->ColorCount; cnum++)
          colors[cnum] = cmap->Colors[cnum];
        for (cnum = cmap->ColorCount; cnum < 256; cnum++)
          colors[cnum] = cmap->Colors[0];
     }
   else
     memset(colors, 0, sizeof(colors));
   // if we need to deal with transparent pixels at all...
   if (transparent >= 0)
     {
        // if we are told to FILL (overwrite with transparency kept)
        if (fill)
          {
             for (yy = 0; yy < h; yy++)
               {
                  pixels = &(PIX(0, yy));
                  p = data + ((y + yy) * rowpix) + x;
                  for (xx = 0; xx < w; xx++)
                    {
                       pix = *pixels;
                       pixels++;
                       if (pix != transparent) *p = PIXLK(pix);
                       else *p = 0;
                       p++;
                    }
               }
          }
        // paste on top with transparent pixels untouched
        else
          {
             for (yy = 0; yy < h; yy++)
               {
                  pixels = &(PIX(0, yy));
                  p = data + ((y + yy) * rowpix) + x;
                  for (xx = 0; xx < w; xx++)
                    {
                       pix = *pixels;
                       pixels++;
                       if (pix != transparent) *p = PIXLK(pix);
                       p++;
                    }
               }
          }
     }
   else
     {
        // walk pixels without worring about transparency at all
        for (yy = 0; yy < h; yy++)
          {
             pixels = &(PIX(0, yy));
             p = data + ((y + yy) * rowpix) + x;
             for (xx = 0; xx < w; xx++)
               {
                  pix = *pixels;
                  pixels++;
                  *p = PIXLK(pix);
                  p++;
               }
          }
     }
   ret = EINA_TRUE;

on_error:
   free(rows);
   return ret;
}

// flush out older rgba frame images to save memory but skip current frame
// and previous frame (prev needed for dispose mode 3)
static void
_flush_older_frames(Evas_Image_Animated *animated,
                    int w, int h,
                    Image_Entry_Frame *thisframe,
                    Image_Entry_Frame *prevframe)
{
   Eina_List *l;
   Image_Entry_Frame *frame;
   // target is the amount of memory we want to be under for stored frames
   int total = 0, target = 512 * 1024;

   // total up the amount of memory used by stored frames for this image
   EINA_LIST_FOREACH(animated->frames, l, frame)
     {
        if (frame->data) total++;
     }
   total *= (w * h * sizeof(DATA32));
   // if we use less than target (512k) for frames - dont flush
   if (total < target) return;
   // clean oldest frames first and go until below target or until we loop
   // around back to this frame (curent)
   EINA_LIST_FOREACH(animated->frames, l, frame)
     {
        if (frame == thisframe) break;
     }
   if (!l) return;
   // start on next frame after thisframe
   l = l->next;
   // handle wrap to start
   if (!l) l = animated->frames;
   // now walk until we hit thisframe again... then stop walk.
   while (l)
     {
        frame = l->data;
        if (frame == thisframe) break;
        if (frame->data)
          {
             if ((frame != thisframe) && (frame != prevframe))
               {
                  free(frame->data);
                  frame->data = NULL;
                  // subtract memory used and if below target - stop flush
                  total -= (w * h * sizeof(DATA32));
                  if (total < target) break;
               }
          }
        // go to next - handle wrap to start
        l = l->next;
        if (!l) l = animated->frames;
     }
}

static int
_file_read(GifFileType *gft, GifByteType *buf, int len)
{
   File_Info *fi = gft->UserData;

   if (fi->pos >= fi->len) return 0; // if at or past end - no
   if ((fi->pos + len) >= fi->len) len = fi->len - fi->pos;
   memcpy(buf, fi->map + fi->pos, len);
   fi->pos += len;
   return len;
}

static Eina_Bool
evas_image_load_file_head_gif2(void *loader_data,
                               Emile_Image_Property *prop,
                               int *error)
{
   Loader_Info *loader = loader_data;
   Evas_Image_Animated *animated = loader->animated;
   Eina_File *f = loader->f;
   Eina_Bool ret = EINA_FALSE;
   File_Info fi;
   GifRecordType rec;
   GifFileType *gif = NULL;
   // it is possible which gif file have error midle of frames,
   // in that case we should play gif file until meet error frame.
   int imgnum = 0;
   int loop_count = -1;
   Frame_Info *finfo = NULL;
   Eina_Bool full = EINA_TRUE;

   // init prop struct with some default null values
   prop->w = 0;
   prop->h = 0;

   // map the file and store/track info
   fi.map = eina_file_map_all(f, EINA_FILE_RANDOM);
   if (!fi.map) LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE);
   fi.len = eina_file_size_get(f);
   fi.pos = 0;

   // actually ask libgif to open the file
#if GIFLIB_MAJOR >= 5
   gif = DGifOpen(&fi, _file_read, NULL);
#else
   gif = DGifOpen(&fi, _file_read);
#endif
   if (!gif) LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
   // get the gif "screen size" (the actual image size)
   prop->w = gif->SWidth;
   prop->h = gif->SHeight;
   // if size is invalid - abort here
   if ((prop->w < 1) || (prop->h < 1) ||
       (prop->w > IMG_MAX_SIZE) || (prop->h > IMG_MAX_SIZE) ||
       IMG_TOO_BIG(prop->w, prop->h))
     {
        if (IMG_TOO_BIG(prop->w, prop->h))
          LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED);
        LOADERR(EVAS_LOAD_ERROR_GENERIC);
     }
   // walk through gif records in file to figure out info
   do
     {
        if (DGifGetRecordType(gif, &rec) == GIF_ERROR)
          {
             // if we have a gif that ends part way through a sequence
             // (or animation) consider it valid and just break - no error
             if (imgnum > 1) break;
             LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
          }
        // get image description section
        if (rec == IMAGE_DESC_RECORD_TYPE)
          {
             int img_code;
             GifByteType *img;

             // get image desc
             if (DGifGetImageDesc(gif) == GIF_ERROR)
               LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
             // skip decoding and just walk image to next
             if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR)
               LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
             // skip till next...
             while (img)
               {
                  img = NULL;
                  DGifGetCodeNext(gif, &img);
               }
             // store geometry in the last frame info data
             if (finfo)
               {
                  _store_frame_info(gif, finfo);
                  _check_transparency(&full, finfo, prop->w, prop->h);
               }
             // or if we dont have a finfo entry - create one even for stills
             else
               {
                  // allocate and save frame with field data
                  finfo = _new_frame(animated, -1, 0, 0, imgnum + 1);
                  if (!finfo)
                    LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED);
                  // store geometry info from gif image
                  _store_frame_info(gif, finfo);
                  // check for transparency/alpha
                  _check_transparency(&full, finfo, prop->w, prop->h);
               }
             imgnum++;
          }
        // we have an extension code block - for animated gifs for sure
        else if (rec == EXTENSION_RECORD_TYPE)
          {
             int ext_code;
             GifByteType *ext;

             ext = NULL;
             // get the first extension entry
             DGifGetExtension(gif, &ext_code, &ext);
             while (ext)
               {
                  // graphic control extension - for animated gif data
                  // and transparent index + flag
                  if (ext_code == 0xf9)
                    {
                       // create frame and store it in image
                       finfo = _new_frame
                         (animated,
                          (ext[1] & 1) ? ext[4] : -1, // transparency index
                          (ext[1] >> 2) & 0x7, // dispose mode
                          ((int)ext[3] << 8) | (int)ext[2], // delay
                          imgnum + 1);
                       if (!finfo)
                         LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED);
                    }
                  // netscape extension indicating loop count...
                  else if (ext_code == 0xff) /* application extension */
                    {
                       if (!strncmp((char *)(&ext[1]), "NETSCAPE2.0", 11) ||
                           !strncmp((char *)(&ext[1]), "ANIMEXTS1.0", 11))
                         {
                            ext = NULL;
                            DGifGetExtensionNext(gif, &ext);
                            if (ext[1] == 0x01)
                              {
                                 loop_count = ((int)ext[3] << 8) | (int)ext[2];
                                 if (loop_count > 0) loop_count++;
                              }
                         }
                    }
                  // and continue onto the next extension entry
                  ext = NULL;
                  DGifGetExtensionNext(gif, &ext);
               }
          }
     }
   while (rec != TERMINATE_RECORD_TYPE);

   // if the gif main says we have more than one image or our image counting
   // says so, then this image is animated - indicate this
   if ((gif->ImageCount > 1) || (imgnum > 1))
     {
        animated->animated = 1;
        animated->loop_count = loop_count;
        animated->loop_hint = EVAS_IMAGE_ANIMATED_HINT_LOOP;
        animated->frame_count = MIN(gif->ImageCount, imgnum);
     }
   if (!full) prop->alpha = 1;
   animated->cur_frame = 1;

   // no errors in header scan etc. so set err and return value
   *error = EVAS_LOAD_ERROR_NONE;
   ret = EINA_TRUE;

on_error: // jump here on any errors to clean up
#if (GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
   if (gif) DGifCloseFile(gif, NULL);
#else
   if (gif) DGifCloseFile(gif);
#endif
   if (fi.map) eina_file_map_free(f, fi.map);
   return ret;
}

static Eina_Bool
evas_image_load_file_data_gif2(void *loader_data,
                               Emile_Image_Property *prop,
                               void *pixels,
                               int *error)
{
   Loader_Info *loader = loader_data;
   Evas_Image_Animated *animated = loader->animated;
   Eina_File *f = loader->f;
   Eina_Bool ret = EINA_FALSE;
   GifRecordType rec;
   GifFileType *gif = NULL;
   Image_Entry_Frame *frame;
   int index = 0, imgnum = 0;
   Frame_Info *finfo;

   // XXX: this is so wrong - storing current frame IN the image
   // so we have to load multiple times to animate. what if the
   // same image is shared/loaded in 2 ore more places AND animated
   // there?

   // use index stored in image (XXX: yuk!)
   index = animated->cur_frame;
   // if index is invalid for animated image - error out
   if ((animated->animated) &&
       ((index <= 0) || (index > animated->frame_count)))
     LOADERR(EVAS_LOAD_ERROR_GENERIC);
   // find the given frame index
   frame = _find_frame(animated, index);
   if (frame)
     {
        if ((frame->loaded) && (frame->data))
          {
             // frame is already there and decoded - jump to end
             goto on_ok;
          }
     }
   else
     LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE);

open_file:
   // actually ask libgif to open the file
   gif = loader->gif;
   if (!gif)
     {
        // there was no file previously opened
        // map the file and store/track info
        loader->fi.map = eina_file_map_all(f, EINA_FILE_RANDOM);
        if (!loader->fi.map) LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE);
        loader->fi.len = eina_file_size_get(f);
        loader->fi.pos = 0;

#if GIFLIB_MAJOR >= 5
        gif = DGifOpen(&(loader->fi), _file_read, NULL);
#else
        gif = DGifOpen(&(loader->fi), _file_read);
#endif
        // if gif open failed... get out of here
        if (!gif)
          {
             if ((loader->fi.map) && (loader->f))
               eina_file_map_free(loader->f, loader->fi.map);
             loader->fi.map = NULL;
             LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
          }
        loader->gif = gif;
        loader->imgnum = 1;
     }

   // if we want to go backwards, we likely need/want to re-decode from the
   // start as we have nothnig to build on
   if ((index > 0) && (index < loader->imgnum) && (animated->animated))
     {
#if (GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
        if (loader->gif) DGifCloseFile(loader->gif, NULL);
#else
        if (loader->gif) DGifCloseFile(loader->gif);
#endif
        if ((loader->fi.map) && (loader->f))
          eina_file_map_free(loader->f, loader->fi.map);
        loader->gif = NULL;
        loader->fi.map = NULL;
        loader->imgnum = 0;
        goto open_file;
     }

   // our current position is the previous frame we decoded from the file
   imgnum = loader->imgnum;

   // walk through gif records in file to figure out info
   do
     {
        if (DGifGetRecordType(gif, &rec) == GIF_ERROR)
          LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
        if (rec == EXTENSION_RECORD_TYPE)
          {
             int                 ext_code;
             GifByteType        *ext;

             ext = NULL;
             DGifGetExtension(gif, &ext_code, &ext);
             while (ext)
               {
                  ext = NULL;
                  DGifGetExtensionNext(gif, &ext);
               }
          }
        // get image description section
        else if (rec == IMAGE_DESC_RECORD_TYPE)
          {
             int xin = 0, yin = 0, x = 0, y = 0, w = 0, h = 0;
             int img_code;
             GifByteType *img;
             Image_Entry_Frame *prevframe = NULL;
             Image_Entry_Frame *thisframe = NULL;

             // get image desc
             if (DGifGetImageDesc(gif) == GIF_ERROR)
               LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
             // get the previous frame entry AND the current one to fill in
             prevframe = _find_frame(animated, imgnum - 1);
             thisframe = _find_frame(animated, imgnum);
             // if we have a frame AND we're animated AND we have no data...
             if ((thisframe) && (!thisframe->data) && (animated->animated))
               {
                  Eina_Bool first = EINA_FALSE;

                  // allocate it
                  thisframe->data =
                    malloc(prop->w * prop->h * sizeof(DATA32));
                  if (!thisframe->data)
                    LOADERR(EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED);
                  // if we have no prior frame OR prior frame data... empty
                  if ((!prevframe) || (!prevframe->data))
                    {
                       first = EINA_TRUE;
                       finfo = thisframe->info;
                       memset(thisframe->data, 0,
                              prop->w * prop->h * sizeof(DATA32));
                    }
                  // we have a prior frame to copy data from...
                  else
                    {
                       finfo = prevframe->info;

                       // fix coords of sub image in case it goes out...
                       _clip_coords(prop->w, prop->h, &xin, &yin,
                                    finfo->x, finfo->y, finfo->w, finfo->h,
                                    &x, &y, &w, &h);
                       // if dispose mode is not restore - then copy pre frame
                       if (finfo->dispose != 3) // GIF_DISPOSE_RESTORE
                         memcpy(thisframe->data, prevframe->data,
                              prop->w * prop->h * sizeof(DATA32));
                       // if dispose mode is "background" then fill with bg
                       if (finfo->dispose == 2) // GIF_DISPOSE_BACKGND
                         _fill_frame(thisframe->data, prop->w, gif,
                                     finfo, x, y, w, h);
                       else if (finfo->dispose == 3) // GIF_DISPOSE_RESTORE
                         {
                            Image_Entry_Frame *prevframe2;

                            // we need to copy data from one frame back
                            // from the prev frame into the current frame
                            // (copy the whole image - at least the sample
                            // GifWin.cpp from libgif indicates this is what
                            // needs doing
                            prevframe2 = _find_frame(animated, imgnum - 2);
                            if (prevframe2)
                              memcpy(thisframe->data, prevframe2->data,
                                     prop->w * prop->h * sizeof(DATA32));
                         }
                       finfo = thisframe->info;
                    }
                  // now draw this frame on top
                  _clip_coords(prop->w, prop->h, &xin, &yin,
                               finfo->x, finfo->y, finfo->w, finfo->h,
                               &x, &y, &w, &h);
                  if (!_decode_image(gif, thisframe->data, prop->w,
                                     xin, yin, finfo->transparent,
                                     finfo->w, finfo->h,
                                     x, y, w, h, first))
                    LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE);
                  // mark as loaded and done
                  thisframe->loaded = EINA_TRUE;
                  // and flush old memory if needed (too much)
                  _flush_older_frames(animated, prop->w, prop->h,
                                      thisframe, prevframe);
               }
             // if we hve a frame BUT the image is not animated... different
             // path
             else if ((thisframe) && (!thisframe->data) &&
                      (!animated->animated))
               {
                  // if we don't have the data decoded yet - decode it
                  if ((!thisframe->loaded) || (!thisframe->data))
                    {
                       // use frame info but we WONT allocate frame pixels
                       finfo = thisframe->info;
                       _clip_coords(prop->w, prop->h, &xin, &yin,
                                    finfo->x, finfo->y, finfo->w, finfo->h,
                                    &x, &y, &w, &h);
                       // clear out all pixels
                       _fill_frame(pixels, prop->w, gif,
                                   finfo, 0, 0, prop->w, prop->h);
                       // and decode the gif with overwriting
                       if (!_decode_image(gif, pixels, prop->w,
                                          xin, yin, finfo->transparent,
                                          finfo->w, finfo->h,
                                          x, y, w, h, EINA_TRUE))
                         LOADERR(EVAS_LOAD_ERROR_CORRUPT_FILE);
                       // mark as loaded and done
                       thisframe->loaded = EINA_TRUE;
                    }
                  // flush mem we don't need (at expense of decode cpu)
               }
             else
               {
                  // skip decoding and just walk image to next
                  if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR)
                    LOADERR(EVAS_LOAD_ERROR_UNKNOWN_FORMAT);
                  while (img)
                    {
                       img = NULL;
                       DGifGetCodeNext(gif, &img);
                    }
               }
             imgnum++;
             // if we found the image we wanted - get out of here
             if (imgnum > index) break;
          }
     }
   while (rec != TERMINATE_RECORD_TYPE);

   // if we are at the end of the animation or not animated, close file
   loader->imgnum = imgnum;
   if ((animated->frame_count <= 1) || (rec == TERMINATE_RECORD_TYPE))
     {
#if (GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
        if (loader->gif) DGifCloseFile(loader->gif, NULL);
#else
        if (loader->gif) DGifCloseFile(loader->gif);
#endif
        if ((loader->fi.map) && (loader->f))
          eina_file_map_free(loader->f, loader->fi.map);
        loader->gif = NULL;
        loader->fi.map = NULL;
        loader->imgnum = 0;
     }

on_ok:
   // no errors in header scan etc. so set err and return value
   *error = EVAS_LOAD_ERROR_NONE;
   ret = EINA_TRUE;

   // if it was an animated image we need to copy the data to the
   // pixels for the image from the frame holding the data
   if (animated->animated && frame->data)
     memcpy(pixels, frame->data, prop->w * prop->h * sizeof(DATA32));
   prop->premul = EINA_TRUE;

on_error: // jump here on any errors to clean up
   return ret;
}

// get the time between 2 frames in the timeline
static double
evas_image_load_frame_duration_gif2(void *loader_data,
                                    int start_frame,
                                    int frame_num)
{
   Loader_Info *loader = loader_data;
   Evas_Image_Animated *animated = loader->animated;
   Image_Entry_Frame *frame;
   int i, total = 0;

   // if its not animated or requested frame data is invalid
   if (!animated->animated) return -1.0;
   if ((start_frame + frame_num) > animated->frame_count) return -1.0;
   if (frame_num < 0) return -1.0;

   if (frame_num < 1) frame_num = 1;
   // walk frames from start frame though and total up delays
   for (i = start_frame; i < (start_frame + frame_num); i++)
     {
        Frame_Info *finfo;

        // find the frame
        frame = _find_frame(animated, i);
        // no frame? barf - bad file or i/o?
        if (!frame) return -1.0;
        // get delay and total it up
        finfo = frame->info;
        // if delay is sensible - use it else assume 10/100ths of a sec
        if (finfo->delay > 0) total += finfo->delay;
        else total += 10;
     }
   // return delay in seconds (since timing in gifs is in 1/100ths of a sec)
   return (double)total / 100.0;
}

// called on opening of a file load
static void *
evas_image_load_file_open_gif2(Eina_File *f,
                               Eina_Stringshare *key EINA_UNUSED, // XXX: we need to use key for frame #
                               Evas_Image_Load_Opts *opts,
                               Evas_Image_Animated *animated,
                               int *error)
{
   Loader_Info *loader = calloc(1, sizeof (Loader_Info));
   if (!loader)
     {
        *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
        return NULL;
     }
   loader->f = eina_file_dup(f);
   loader->opts = opts;
   loader->animated = animated;
   return loader;
}

// called on closing of an image file load (end of load)
static void
evas_image_load_file_close_gif2(void *loader_data)
{
   Loader_Info *loader = loader_data;
#if (GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
   if (loader->gif) DGifCloseFile(loader->gif, NULL);
#else
   if (loader->gif) DGifCloseFile(loader->gif);
#endif
   if ((loader->fi.map) && (loader->f))
     eina_file_map_free(loader->f, loader->fi.map);
   if (loader->f) eina_file_close(loader->f);
   free(loader);
}

// general module delcaration stuff
static Evas_Image_Load_Func evas_image_load_gif_func =
{
  EVAS_IMAGE_LOAD_VERSION,
  evas_image_load_file_open_gif2,
  evas_image_load_file_close_gif2,
  (void*) evas_image_load_file_head_gif2,
  NULL,
  (void*) evas_image_load_file_data_gif2,
  evas_image_load_frame_duration_gif2,
  EINA_TRUE,
  EINA_FALSE
};

// raw module api that the rest of the world sees
static int
module_open(Evas_Module *em)
{
   if (!em) return 0;
   em->functions = (void *)(&evas_image_load_gif_func);
   return 1;
}

static void
module_close(Evas_Module *em EINA_UNUSED)
{
}

static Evas_Module_Api evas_modapi =
{
   EVAS_MODULE_API_VERSION,
   "gif",
   "none",
   {
      module_open,
      module_close
   }
};

EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, gif);

#ifndef EVAS_STATIC_BUILD_GIF
EVAS_EINA_MODULE_DEFINE(image_loader, gif);
#endif
