#include "algif.h"
#include "lzw.h"

#include <stdlib.h>
#include <string.h>

GIF_ANIMATION *
algif_create_raw_animation (int frames_count)
{
    GIF_ANIMATION *gif = calloc (1, sizeof *gif);
    /* Create frames. */
    gif->frames_count = frames_count;
    gif->frames = calloc (gif->frames_count, sizeof *gif->frames);
    return gif;
}

/* Destroy a complete gif, including all frames. */
void
algif_destroy_raw_animation (GIF_ANIMATION *gif)
{
    int i;

    for (i = 0; i < gif->frames_count; i++)
    {
        GIF_FRAME *frame = gif->frames + i;

        if (frame->bitmap_8_bit)
            destroy_bitmap (frame->bitmap_8_bit);
    }
    if (gif->store)
        destroy_bitmap (gif->store);
    free (gif->frames);
    free (gif);
}

static void
write_palette (PACKFILE *file, GIF_PALETTE *palette, int bits)
{
    int i;
    for (i = 0; i < (1 << bits); i++)
    {
        pack_putc (palette->colors[i].r, file);
        pack_putc (palette->colors[i].g, file);
        pack_putc (palette->colors[i].b, file);
    }
}

static void
read_palette (PACKFILE * file, GIF_PALETTE *palette)
{
    int i;

    for (i = 0; i < palette->colors_count; i++)
    {
        palette->colors[i].r = pack_getc (file);
        palette->colors[i].g = pack_getc (file);
        palette->colors[i].b = pack_getc (file);
    }
}

/*
 * Compresses all the frames in the animation and writes to a gif file.
 * Nothing else like extensions or comments is written.
 *
 * Returns 0 on success.
 *
 * Note: All bitmaps must have a color depth of 8.
 */
int
algif_save_raw_animation (const char *filename, GIF_ANIMATION *gif)
{
    int frame;
    int i, j;
    PACKFILE *file;

    file = pack_fopen (filename, "w");
    if (!file)
        return -1;

    pack_fwrite ("GIF89a", 6, file);
    pack_iputw (gif->width, file);
    pack_iputw (gif->height, file);
    /* 7 global palette
     * 456 color richness
     * 3 sorted
     * 012 palette bits
     */
    for (i = 1, j = 0; i < gif->palette.colors_count; i *= 2, j++);
    pack_putc ((j ? 128 : 0) + 64 + 32 + 16 + (j ? j - 1 : 0), file);
    pack_putc (gif->background_index, file);
    pack_putc (0, file);        /* No aspect ratio. */

    if (j)
        write_palette (file, &gif->palette, j);

    if (gif->loop != -1)
    /* Loop count extension. */
    {
        pack_putc (0x21, file); /* Extension Introducer. */
        pack_putc (0xff, file); /* Application Extension. */
        pack_putc (11, file);    /* Size. */
        pack_fwrite ("NETSCAPE2.0", 11, file);
        pack_putc (3, file); /* Size. */
        pack_putc (1, file);
        pack_iputw (gif->loop, file);
        pack_putc (0, file);
    }

    for (frame = 0; frame < gif->frames_count; frame++)
    {
        int w = gif->frames[frame].bitmap_8_bit->w;
        int h = gif->frames[frame].bitmap_8_bit->h;

        pack_putc (0x21, file); /* Extension Introducer. */
        pack_putc (0xf9, file); /* Graphic Control Extension. */
        pack_putc (4, file);    /* Size. */
        /* Disposal method, and enable transparency. */
        i = gif->frames[frame].disposal_method << 2;
        if (gif->frames[frame].transparent_index != -1)
            i |= 1;
        pack_putc (i, file);
        pack_iputw (gif->frames[frame].duration, file); /* In 1/100th seconds. */
        if (gif->frames[frame].transparent_index != -1)
            pack_putc (gif->frames[frame].transparent_index, file);       /* Transparent color index. */
        else
             pack_putc (0, file);
        pack_putc (0x00, file); /* Terminator. */

        pack_putc (0x2c, file); /* Image Descriptor. */
        pack_iputw (gif->frames[frame].xoff, file);
        pack_iputw (gif->frames[frame].yoff, file);
        pack_iputw (w, file);
        pack_iputw (h, file);

         /* 7: local palette
         * 6: interlaced
         * 5: sorted
         * 012: palette bits
         */

        for (i = 1, j = 0; i < gif->frames[frame].palette.colors_count; i *= 2, j++);
        pack_putc ((j ? 128 : 0) + (j ? j - 1 : 0), file);

        if (j)
            write_palette (file, &gif->frames[frame].palette, j);

        LZW_encode (file, gif->frames[frame].bitmap_8_bit);

        pack_putc (0x00, file); /* Terminator. */
    }

    pack_putc (0x3b, file);     /* Trailer. */

    pack_fclose (file);
    return 0;
}

static void
deinterlace (BITMAP *bmp)
{
    BITMAP *n = create_bitmap_ex (bitmap_color_depth (bmp), bmp->w, bmp->h);
    int y, i = 0;
    for (y = 0; y < n->h; y += 8)
    {
        blit (bmp, n, 0, i++, 0, y, n->w, 1);
    }
    for (y = 4; y < n->h; y += 8)
    {
        blit (bmp, n, 0, i++, 0, y, n->w, 1);
    }
    for (y = 2; y < n->h; y += 4)
    {
        blit (bmp, n, 0, i++, 0, y, n->w, 1);
    }
    for (y = 1; y < n->h; y += 2)
    {
        blit (bmp, n, 0, i++, 0, y, n->w, 1);
    }
    blit (n, bmp, 0, 0, 0, 0, n->w, n->h);
    destroy_bitmap (n);
}

static GIF_ANIMATION *
load_object (PACKFILE * file, long size)
{
    int version;
    BITMAP *bmp = NULL;
    int i, j;
    GIF_ANIMATION *gif = calloc (1, sizeof *gif);
    GIF_FRAME frame;
    int have_global_palette = 0;

    (void) size;

    gif->frames_count = 0;

    /* is it really a GIF? */
    if (pack_getc (file) != 'G')
        goto error;
    if (pack_getc (file) != 'I')
        goto error;
    if (pack_getc (file) != 'F')
        goto error;
    if (pack_getc (file) != '8')
        goto error;
    /* '7' or '9', for 87a or 89a. */
    version = pack_getc (file);
    if (version != '7' && version != '9')
        goto error;
    if (pack_getc (file) != 'a')
        goto error;

    gif->width = pack_igetw (file);
    gif->height = pack_igetw (file);
    i = pack_getc (file);
    /* Global color table? */
    if (i & 128)
        gif->palette.colors_count = 1 << ((i & 7) + 1);
    else
        gif->palette.colors_count = 0;
    /* Background color is only valid with a global palette. */
    gif->background_index = pack_getc (file);

    /* Skip aspect ratio. */
    pack_fseek (file, 1);

    if (gif->palette.colors_count)
    {
        read_palette (file, &gif->palette);
        have_global_palette = 1;
    }

    memset(&frame, 0, sizeof frame); /* For first frame. */
    frame.transparent_index = -1;

    do
    {
        i = pack_getc (file);

        switch (i)
        {
            case 0x2c:         /* Image Descriptor */
            {
                int w, h;
                int interlaced = 0;

                frame.xoff = pack_igetw (file);
                frame.yoff = pack_igetw (file);
                w = pack_igetw (file);
                h = pack_igetw (file);
                bmp = create_bitmap_ex (8, w, h);
                if (!bmp)
                    goto error;
                i = pack_getc (file);

                /* Local palette. */
                if (i & 128)
                {
                    frame.palette.colors_count = 1 << ((i & 7) + 1);
                    read_palette (file, &frame.palette);
                }
                else
                {
                    frame.palette.colors_count = 0;
                }

                if (i & 64)
                    interlaced = 1;

                if (LZW_decode (file, bmp))
                    goto error;

                if (interlaced)
                    deinterlace (bmp);

                frame.bitmap_8_bit = bmp;
                bmp = NULL;

                gif->frames_count++;
                gif->frames =
                    realloc (gif->frames,
                             gif->frames_count * sizeof *gif->frames);
                gif->frames[gif->frames_count - 1] = frame;

                memset(&frame, 0, sizeof frame); /* For next frame. */
                frame.transparent_index = -1;

                break;
            }
            case 0x21: /* Extension Introducer. */
                j = pack_getc (file); /* Extension Type. */
                i = pack_getc (file); /* Size. */
                if (j == 0xf9) /* Graphic Control Extension. */
                {
                    /* size must be 4 */
                    if (i != 4)
                        goto error;
                    i = pack_getc (file);
                    frame.disposal_method = (i >> 2) & 7;
                    frame.duration = pack_igetw (file);
                    if (i & 1)  /* Transparency? */
                    {
                        frame.transparent_index = pack_getc (file);
                    }
                    else
                    {
                        pack_fseek (file, 1); 
                        frame.transparent_index = -1;
                    }
                    i = pack_getc (file); /* Size. */
                }
                /* Application Extension. */
                else if (j == 0xff)
                {
                    if (i == 11)
                    {
                        char name[12];
                        pack_fread (name, 11, file);
                        i = pack_getc (file); /* Size. */
                        name[11] = '\0';
                        if (!strcmp (name, "NETSCAPE2.0"))
                        {
                            if (i == 3)
                            {
                                j = pack_getc (file);
                                gif->loop = pack_igetw (file);
                                if (j != 1)
                                    gif->loop = 0;
                                i = pack_getc (file); /* Size. */
                            }
                        }
                    }
                }

                /* Possibly more blocks until terminator block (0). */
                while (i)
                {
                    pack_fseek (file, i);
                    i = pack_getc (file);
                }
                break;
            case 0x3b:
                /* GIF Trailer. */
                pack_fclose (file);
                return gif;
        }
    }
    while (TRUE);
  error:
    if (file)
        pack_fclose (file);
    if (gif)
        algif_destroy_raw_animation (gif);
    if (bmp)
        destroy_bitmap (bmp);
    return NULL;
}

/*
 * Allocates and reads a GIF_ANIMATION structure, filling in all the
 * frames found in the file. On error, nothing is allocated, and NULL is
 * returned. No extensions or comments are read in. If the gif contains
 * a transparency index, and it is no 0, it is swapped with 0 - so index
 * 0 will be the transparent color. There is no way to know when a file
 * contained no transparency originally. Frame duration is specified in
 * 1/100th seconds.
 *
 * All bitmaps will have a color depth of 8.
 */
GIF_ANIMATION *
algif_load_raw_animation (const char *filename)
{
    PACKFILE *file;
    GIF_ANIMATION *gif = NULL;

    file = pack_fopen (filename, "r");
    if (file)
        gif = load_object (file, 0);
    return gif;
}

static int
get_rgbcolor (RGB *rgb)
{
    return makecol (rgb->r, rgb->g, rgb->b);
}

/* Renders the next frame in a GIF animation to the specified bitmap and
 * the given position. You need to call this in order on the same
 * destination for frames [0..gif->frames_count - 1] to properly render all
 * the frames in the GIF.
 */
void
algif_render_frame (GIF_ANIMATION *gif, BITMAP *bitmap, int frame, int xpos,
                    int ypos)
{
    int x, y, w, h;
    GIF_FRAME *f = &gif->frames[frame];
    GIF_PALETTE *pal;
    if (frame == 0)
        rectfill (bitmap, xpos, ypos, xpos + gif->width - 1,
              ypos + gif->height - 1, bitmap_mask_color (bitmap));
    else
    {
        GIF_FRAME *p = &gif->frames[frame - 1];
        if (p->disposal_method == 2)
            rectfill (bitmap, xpos + p->xoff, ypos + p->yoff,
                xpos + p->xoff + p->bitmap_8_bit->w - 1,
                ypos + p->yoff + p->bitmap_8_bit->h - 1,
                bitmap_mask_color (bitmap));
        else if (p->disposal_method == 3 && gif->store)
        {
            blit (gif->store, bitmap, 0, 0, xpos + p->xoff, ypos + p->yoff,
                gif->store->w, gif->store->h);
            destroy_bitmap (gif->store);
            gif->store = NULL;
        }
    }
    w = f->bitmap_8_bit->w;
    h = f->bitmap_8_bit->h;
    if (f->disposal_method == 3)
    {
        if (gif->store)
            destroy_bitmap (gif->store);
        gif->store = create_bitmap_ex (bitmap_color_depth (bitmap), w, h);
        blit (bitmap, gif->store, xpos + f->xoff, ypos + f->yoff, 0, 0, w, h);
    }
    pal = &gif->frames[frame].palette;
    if (pal->colors_count == 0)
        pal = &gif->palette;

    //int i;
    //for (i = 0; i < pal->colors_count; i++)
    //    printf("%d: %d %d %d\n", i, pal->colors[i].r, pal->colors[i].g, pal->colors[i].b);

    for (y = 0; y < h; y++)
    {
        for (x = 0; x < w; x++)
        {
            int c = getpixel (f->bitmap_8_bit, x, y);
            if (c != f->transparent_index)
            {
                putpixel (bitmap, xpos + f->xoff + x, ypos + f->yoff + y,
                          get_rgbcolor (&pal->colors[c]));
            }
        }
    }
}

/* Registers gif as bitmap and datafile type. */
void
algif_init (void)
{
    register_bitmap_file_type ("gif", load_gif, save_gif, NULL);
    register_datafile_object (DAT_GIF,
                              (void *(*)(PACKFILE *, long)) load_object,
                              (void (*)(void *))
                              algif_destroy_raw_animation);
}
