/* NetHack 5.0    giftiles.c    $NHDT-Date: 1596498335 2020/08/03 23:45:35 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1 $ */
/* Copyright (c) Ray Chason, 2016. */
/* NetHack may be freely redistributed.  See license for details. */

/* giftiles.c -- read a tile map in GIF format */
/* Reference: GIF specification,, retrieved from
 * http://www.w3.org/Graphics/GIF/spec-gif89a.txt */

#include "config.h"
#include "tileset.h"

#define MIN_LZW_BITS 3
#define MAX_LZW_BITS 12
#define END_OF_DATA 0x7FFF

/* For use when reading the GIF as a bitstream */
struct Bitstream {
    /* The file */
    FILE *fp;

    /* For unpacking LZW codes */
    unsigned long bits;
    unsigned char num_bits;
    unsigned char bit_width;
    unsigned char initial_bit_width;
    unsigned char block_size;

    /* The dictionary */
    struct {
        unsigned char byte;
        unsigned short next;
    } dictionary[1 << MAX_LZW_BITS];
    unsigned short dict_size;

    /* The string currently being decoded */
    unsigned char string[1 << MAX_LZW_BITS];
    unsigned short str_size;

    unsigned short last_code;
};

struct DataBlock {
    size_t size;
    size_t index;
    unsigned char *data;
};

static boolean read_data_block(struct Bitstream *gif,
                               struct DataBlock *block);
static void free_data_block(struct DataBlock *block);
static unsigned short read_u16(const unsigned char buf[2]);
static void init_decoder(struct Bitstream *gif, unsigned bit_width);
static void reset_decoder(struct Bitstream *gif);
static int decode(struct Bitstream *gif, struct DataBlock *block);
static int get_code(struct Bitstream *gif, struct DataBlock *block);
static unsigned interlace_incr(unsigned y, unsigned height);

/*
 * GIF specifies a canvas, which may have a palette (the "global color table")
 * of up to 256 colors, of which one is a background color, and zero or more
 * images, each of which may have its own palette (a "local color table")
 * independently of the canvas. We will join all palettes found into a single
 * palette, and indicate that the image uses a palette if, and only if, the
 * various palettes do not total more than 256 colors.
 */

boolean
read_gif_tiles(const char *filename, struct TileSetImage *image)
{
    struct Bitstream gif;
    struct DataBlock block;

    unsigned char buf[1024];
    size_t size, num_pixels, i;

    /* Image data not returned to the caller */
    boolean have_gct;   /* global color table is present */
    unsigned gct_size;  /* global color table size */
    unsigned back_color; /* index for background color */
    unsigned trans_color = 0xFFFF; /* index for transparent palette entry */

    block.data = NULL;      /* custodial */
    gif.fp = NULL;          /* custodial */

    image->width = 0;
    image->height = 0;
    image->pixels = NULL;       /* custodial, returned */
    image->indexes = NULL;      /* custodial, returned */
    image->image_desc = NULL;   /* custodial, returned */
    image->tile_width = 0;
    image->tile_height = 0;

    gif.fp = fopen(filename, "rb");
    if (gif.fp == NULL) goto error;

    /* 17. Header */
    size = fread(buf, 1, 6, gif.fp);
    if (size < 6) goto error;
    if (memcmp(buf, "GIF87a", 6) != 0 && memcmp(buf, "GIF89a", 6) != 0)
        goto error;

    /* 18. Logical screen descriptor */
    size = fread(buf, 1, 7, gif.fp);
    if (size < 7) goto error;
    image->width = read_u16(buf + 0);
    image->height = read_u16(buf + 2);
    have_gct = (buf[4] & 0x80) != 0;
    gct_size = 1 << ((buf[4] & 0x07) + 1);
    back_color = buf[5];
    if (image->width == 0 || image->height == 0) goto error;

    /* 19. Global Color Table */
    for (i = 0; i < SIZE(image->palette); ++i) {
        image->palette[i].r = 0;
        image->palette[i].g = 0;
        image->palette[i].b = 0;
        image->palette[i].a = 255;
    }
    if (have_gct) {
        size = fread(buf, 3, gct_size, gif.fp);
        if (size < gct_size) goto error;
        for (i = 0; i < gct_size; ++i) {
            image->palette[i].r = buf[i * 3 + 0];
            image->palette[i].g = buf[i * 3 + 1];
            image->palette[i].b = buf[i * 3 + 2];
            image->palette[i].a = 255;
        }
    }

    /* Allocate pixel area; watch out for overflow */
    num_pixels = (size_t) image->width * (size_t) image->height;
    if (num_pixels / image->width != image->height) goto error; /* overflow */
    size = num_pixels * sizeof(image->pixels[0]);
    if (size / sizeof(image->pixels[0]) != num_pixels) goto error; /* overflow */
    image->pixels = (struct Pixel *) alloc(size);
    image->indexes = (unsigned char *) alloc(num_pixels);

    /* Fill with the background color */
    for (i = 0; i < num_pixels; ++i) {
        image->pixels[i] = image->palette[back_color];
        image->indexes[i] = back_color;
    }

    /* Read the image data */
    while (TRUE) {
        int b = fgetc(gif.fp);
        if (b == EOF) goto error;

        /* 27. Trailer (0x3B) */
        if (b == 0x3B) break;

        switch (b) {
        case 0x2C:
            /* 20. Image descriptor (0x2C) */
            {
                unsigned img_left, img_top, img_width, img_height;
                boolean have_lct, interlace;
                unsigned lct_start, lct_size;
                struct Pixel lct[256];
                int new_b;
                unsigned x, y, x2, y2;

                size = fread(buf, 1, 9, gif.fp);
                if (size < 9) goto error;
                img_left = read_u16(buf + 0);
                img_top = read_u16(buf + 2);
                img_width = read_u16(buf + 4);
                img_height = read_u16(buf + 6);
                have_lct = (buf[8] & 0x80) != 0;
                interlace = (buf[8] & 0x40) != 0;
                lct_size = 1 << ((buf[8] & 0x07) + 1);

                /* 21. Local color table */
                lct_start = 0;
                memcpy(lct, image->palette, sizeof(lct));
                if (have_lct) {
                    size = fread(buf, 3, lct_size, gif.fp);
                    if (size < lct_size) goto error;
                    for (i = 0; i < lct_size; ++i) {
                        lct[i].r = buf[i * 3 + 0];
                        lct[i].g = buf[i * 3 + 1];
                        lct[i].b = buf[i * 3 + 2];
                        lct[i].a = 255;
                    }
                    /*
                     * The combined palette may exceed 256 colors, in which
                     * case the indexes array will be discarded, indicating a
                     * full-color image.
                     */
                    if (lct_size + gct_size <= 256) {
                        memcpy(image->palette, lct, sizeof(lct[0]) * lct_size);
                    }
                    lct_start = gct_size;
                    gct_size += lct_start;
                }
                if (trans_color != 0xFFFF) {
                    lct[trans_color].a = 0;
                    if (!have_lct) {
                        /* FIXME: this will affect all images using the global
                         * color table, not just the current one */
                        image->palette[trans_color].a = 0;
                    }
                }
                /* 22. Table based image data */
                new_b = fgetc(gif.fp);
                if (new_b == EOF) goto error;
                if (new_b < MIN_LZW_BITS - 1 || MAX_LZW_BITS - 1 < new_b) goto error;
                init_decoder(&gif, new_b);
                x = 0;
                y = 0;
                if (!read_data_block(&gif, &block)) goto error;
                while (TRUE) {
                    new_b = decode(&gif, &block);
                    if (new_b == EOF) goto error;
                    if (new_b == END_OF_DATA) break;
                    if (y >= img_height) goto error;
                    x2 = img_left + x;
                    y2 = img_top + y;
                    if (x2 < image->width && y2 < image->height) {
                        image->pixels[y2 * image->width + x2] = lct[new_b];
                        image->indexes[y2 * image->width + x2] = new_b + lct_start;
                    }
                    ++x;
                    if (x >= img_width) {
                        x = 0;
                        if (interlace) {
                            y = interlace_incr(y, img_height);
                        } else {
                            ++y;
                        }
                    }
                }
                free_data_block(&block);
                trans_color = 0xFFFF;
            }
            break;

        case 0x21:
            /* Extension blocks */
            {
                int label;

                label = fgetc(gif.fp);
                if (label == EOF) goto error;
                if (!read_data_block(&gif, &block)) goto error;
                switch (label) {
                case 0xF9:
                    /* 23. Graphic control extension (0xF9) */
                    if (block.size >= 4 && (block.data[0] & 0x01) != 0) {
                        /* image has a transparent index */
                        trans_color = block.data[3];
                    }
                    break;

#if 0
                case 0xFE:
                    /* 24. Comment extension (0xFE) */
                    break;

                case 0x01:
                    /* 25. Plain text extension (0x01) */
                    break;
#endif

                case 0xFF:
                    /* 26. Application extension (0xFF) */
                    if (block.size > 11
                    &&  memcmp(block.data, "NETHACK3GIF", 11) == 0
                    &&  image->image_desc == NULL) {
                        memmove(block.data, block.data + 11, block.size - 11);
                        block.data[block.size - 11] = '\0';
                        image->image_desc = (char *) block.data;
                        block.data = NULL;
                    }
                    break;

                default:
                    /* Unknown extension type */
                    break;
                }
                free_data_block(&block);
            }
            break;

        default:
            goto error;
        }
    }

    fclose(gif.fp);
    free_data_block(&block);
    if (gct_size > 256) {
        /* Max palette size exceeded; indexes array is not meaningful */
        free(image->indexes);
        image->indexes = NULL;
    }
    return TRUE;

error:
    if (gif.fp) fclose(gif.fp);
    free_data_block(&block);
    free(image->pixels);
    image->pixels = NULL;
    free(image->indexes);
    image->indexes = NULL;
    free(image->image_desc);
    image->image_desc = NULL;
    return FALSE;
}

static void
init_decoder(struct Bitstream *gif, unsigned bit_width)
{
    unsigned i;
    unsigned clear;

    gif->bits = 0;
    gif->num_bits = 0;
    gif->initial_bit_width = bit_width;
    gif->block_size = 0;

    clear = 1 << bit_width;
    gif->dict_size = clear + 2;
    for (i = 0; i < clear; ++i) {
        gif->dictionary[i].byte = i;
        gif->dictionary[i].next = 0xFFFF;
    }

    gif->str_size = 0;

    reset_decoder(gif);
}

static void
reset_decoder(struct Bitstream *gif)
{
    /* Set the bit width */
    gif->bit_width = gif->initial_bit_width + 1;

    /* Reset the dictionary */
    gif->dict_size = (1 << gif->initial_bit_width) + 2;

    /* No last code */
    gif->last_code = 0xFFFF;
}

static int
decode(struct Bitstream *gif, struct DataBlock *block)
{
    int code;
    unsigned clear = 1 << gif->initial_bit_width;

    /* If a string is being decoded, return the next byte */
    if (gif->str_size != 0) {
        return gif->string[--gif->str_size];
    }

    /* Get the next code, until code other than clear */
    while (TRUE) {
        code = get_code(gif, block);
        if ((unsigned) code != clear) break;
        reset_decoder(gif);
    }

    if (code == EOF) return EOF;
    if ((unsigned) code == clear + 1) return END_OF_DATA;
    if (code > gif->dict_size) return EOF;

    /* Add a new string to the dictionary */
    if (gif->last_code != 0xFFFF && gif->dict_size < SIZE(gif->dictionary)) {
        unsigned next_code;
        if (code < gif->dict_size) {
            next_code = code;
        } else {
            next_code = gif->last_code;
        }
        while (next_code >= clear) {
            next_code = gif->dictionary[next_code].next;
        }
        gif->dictionary[gif->dict_size].next = gif->last_code;
        gif->dictionary[gif->dict_size].byte = next_code;
        ++gif->dict_size;
        if (gif->dict_size >= 1 << gif->bit_width
        &&  gif->bit_width < MAX_LZW_BITS) {
            ++gif->bit_width;
        }
    }
    gif->last_code = code;

    /* code is less than gif->dict_size and not equal to clear or clear + 1 */
    /* Prepare the decoded string for return; note that it is stored in
     * reverse order */
    while ((unsigned) code >= clear) {
        gif->string[gif->str_size++] = gif->dictionary[code].byte;
        code = gif->dictionary[code].next;
    }

    return code;
}

static int
get_code(struct Bitstream *gif, struct DataBlock *block)
{
    int code;

    while (gif->num_bits < gif->bit_width) {
        unsigned char b;
        if (block->index >= block->size) return EOF;
        b = block->data[block->index++];
        gif->bits |= (unsigned long)b << gif->num_bits;
        gif->num_bits += 8;
    }

    code = (int) (gif->bits & ((1UL << gif->bit_width) - 1));
    gif->bits >>= gif->bit_width;
    gif->num_bits -= gif->bit_width;
    return code;
}

static unsigned
interlace_incr(unsigned y, unsigned height)
{
    static const unsigned char incr[] = { 8, 2, 4, 2 };

    /* The lower three bits indicate the current pass */

    /* Advance to the next row of the current pass */
    y += incr[y & 0x3];

    /* Go to the next pass if y exceeds height */
    /* Might not be the immediately following pass if height is small */
    if (y >= height && (y & 0x7) == 0) {
        /* Pass 1 -> Pass 2 */
        y = 4;
    }
    if (y >= height && (y & 0x7) == 4) {
        /* Pass 2 -> Pass 3 */
        y = 2;
    }
    if (y >= height && (y & 0x3) == 2) {
        /* Pass 3 -> Pass 4 */
        y = 1;
    }

    return y;
}

/* Decode an unsigned 16 bit quantity */
static unsigned short
read_u16(const unsigned char buf[2])
{
    return ((unsigned short)buf[0] << 0)
         | ((unsigned short)buf[1] << 8);
}

static boolean
read_data_block(struct Bitstream *gif, struct DataBlock *block)
{
    long pos = ftell(gif->fp);
    int b;
    size_t i;

    free_data_block(block);

    /* Get the length of the data block */
    while (TRUE) {
        b = fgetc(gif->fp);
        if (b == EOF) return FALSE;
        if (b == 0) break;
        block->size += b;
        fseek(gif->fp, b, SEEK_CUR);
    }
    fseek(gif->fp, pos, SEEK_SET);

    /* Allocate memory */
    block->data = (unsigned char *) alloc(block->size);

    /* Read the data from the file */
    i = 0;
    while (TRUE) {
        b = fgetc(gif->fp);
        if (b == EOF) return FALSE;
        if (b == 0) break;
        if (fread(block->data + i, 1, b, gif->fp) != (unsigned) b)
            return FALSE;
        i += b;
    }

    block->index = 0;
    return TRUE;
}

static void
free_data_block(struct DataBlock *block)
{
    free(block->data);
    block->size = 0;
    block->data = NULL;
}
