/*
 * graphics.c
 *
 * Code for dealing with the Infocom .MG1 graphics
 */

#include <stdio.h>
#include <stdlib.h>

#include <flex.h>

#include <oslib/osspriteop.h>
#include <oslib/colourtrans.h>

#include "ztypes.h"
#include "osdepend.h"
#include "fileio.h"
#include "v6.h"
#include "v6ro.h"
#include "graphics.h"
#include "pngro.h"
#include "jpegro.h"

#define MAX_BIT 512
#define CODE_SIZE 8
#define CLEAR_CODE (1 << CODE_SIZE)
#define CODE_TABLE_SIZE 4096

#define PREFIX 0
#define PIXEL 1
#define RED 0
#define GREEN 1
#define BLUE 2

static const int mask[16] = {
    0x0000, 0x0001, 0x0003, 0x0007,
    0x000f, 0x001f, 0x003f, 0x007f,
    0x00ff, 0x01ff, 0x03ff, 0x07ff,
    0x0fff, 0x1fff, 0x3fff, 0x7fff
};

static Bitmap *Cache;

typedef struct
{
    int next_code;
    int slen;
    int sptr;
    int tlen;
    int tptr;
} compress_t;

static unsigned char colourmap[32][3];
static unsigned char code_buffer[CODE_TABLE_SIZE];

static int read_code(FILE *fp, compress_t *comp);
static void decompress_image(FILE *fp, gfx_dir *g, Bitmap *bmp);
static void uncache_bitmap(Bitmap *bmp);
static void cache_bitmap(Bitmap *bmp, gfx_dir *directory);

Bitmap *build_sprite(FILE * restrict fp, gfx_dir * restrict directory, int urgent)
{
    osspriteop_header *sprite;
    int area_size, i, colours;
    os_colour * restrict pcol, palword;
    Bitmap *e, * restrict bmp;
    //static osspriteop_TRANS_TAB(256) tab;     /* It's for 256 col -> 256 col, ATM */
    static long last_cm;
    long cm_addr;

    for (e=Cache; e; e=e->next)
    	if (e->directory==directory)
    	{
    	    if (directory->adaptive)
    	    {
    	        /* Need to check palette */
    	        if (!pngro_match_palette(&e->sprite))
    	            break;
    	    }
    	    if (directory->image_cm_addr)
    	        last_cm = directory->image_cm_addr;
    	    return e;
    	}

    if (directory->image_data_addr == 0)
    {
        char buffer[20];
        sprintf(buffer, "%d", directory->image_number);
        warning_lookup_1("NoGfxN", buffer);
        return NULL;
    }

    if (directory->jpeg)
        return load_jpeg(fp, directory, urgent);

    if (blorb_map)
    {
        if (e) uncache_bitmap(e);
        bmp = build_sprite_from_png(fp, directory, urgent);
        if (e && bmp) cache_bitmap(bmp, directory);
        return bmp;
    }

    area_size=((directory->phys_width+3)&~3) * directory->phys_height;

    if (directory->image_flags & 1)
    	area_size*=2;

    area_size += sizeof(osspriteop_area) + sizeof(osspriteop_header) +
                 + 2048 + 256;

    do
        bmp=malloc(sizeof(Bitmap));
    while (!bmp && urgent && release_memory());
    if (!bmp)
    {
        if (urgent) warning_lookup("NoMemG");
    	return bmp;
    }

    bmp->next=NULL;
    bmp->directory=NULL;
    do
        i=flex_alloc((flex_ptr) &bmp->sprite, area_size);
    while (!i && urgent && release_memory());
    if (!i)
    {
        free(bmp);
        if (urgent) warning_lookup("NoMemG");
        return NULL;
    }

    bmp->sprite->size=area_size;
    bmp->sprite->first=16;

    osspriteop_clear_sprites(osspriteop_USER_AREA, bmp->sprite);
    osspriteop_create_sprite(osspriteop_USER_AREA, bmp->sprite, "pic", FALSE,
                             directory->phys_width, directory->phys_height,
                             os_MODE8BPP45X45);

    cm_addr = directory->image_cm_addr;
    if (cm_addr)
        last_cm = cm_addr;
    else
        cm_addr = last_cm;

    osspriteop_create_true_palette(osspriteop_USER_AREA, bmp->sprite, (osspriteop_id) "pic");

    if (fseek (fp, cm_addr, SEEK_SET) != 0)
        fatal_lookup("GfxErr");

    colours = getc(fp);
    fread(&colourmap[2][RED], 1, colours * 3, fp);
    /* Anchor dereferenced */
    sprite=osspriteop_select_sprite(osspriteop_USER_AREA, bmp->sprite, (osspriteop_id) "pic");
    colours += 2;
    pcol=(os_colour *)(sprite+1);
    for (i=0; i<18; i++)
    {
        palword=(colourmap[i][BLUE] << 24) |
                (colourmap[i][GREEN] << 16) |
                (colourmap[i][RED] << 8);
	*pcol++=palword;
	*pcol++=palword;
    }

    if (directory->image_flags & 1)
        osspriteop_create_mask(osspriteop_PTR, bmp->sprite, (osspriteop_id) sprite);
    /* Anchor dereference invalidated */

    if (fseek(fp, directory->image_data_addr, SEEK_SET) != 0)
        fatal_lookup("GfxErr");

    decompress_image(fp, directory, bmp);

    return bmp;
}

static void decompress_image(FILE * restrict fp, gfx_dir * restrict g, Bitmap * restrict bmp)
{
    int code_table[CODE_TABLE_SIZE][2];
    unsigned char buffer[CODE_TABLE_SIZE];
    int i;
    int code, old = 0, first;
    compress_t comp;
    int swidth;
    int x=0;
    int spr_off=bmp->sprite->first;
    int img_off, msk_off;
    osspriteop_header *sprite;
    char * restrict buf;
    char * restrict mbuf;

    sprite = (osspriteop_header *) ((byte *) bmp->sprite + spr_off);
    swidth = (sprite->width+1)*4;
    img_off = spr_off + sprite->image;
    msk_off = spr_off + sprite->mask;

    comp.next_code = CLEAR_CODE + 2;
    comp.slen = 0;
    comp.sptr = 0;
    comp.tlen = CODE_SIZE + 1;
    comp.tptr = 0;

    for (i = 0; i < CODE_TABLE_SIZE; i++)
    {
        code_table[i][PREFIX] = CODE_TABLE_SIZE;
        code_table[i][PIXEL] = i;
    }

    for (;;)
    {
        if ((code = read_code(fp, &comp)) == (CLEAR_CODE + 1))
            return;
        if (code == CLEAR_CODE)
        {
            comp.tlen = CODE_SIZE + 1;
            comp.next_code = CLEAR_CODE + 2;
            code = read_code(fp, &comp);
        }
        else
        {
            first = (code == comp.next_code) ? old : code;
            while (code_table[first][PREFIX] != CODE_TABLE_SIZE)
                first = code_table[first][PREFIX];
            code_table[comp.next_code][PREFIX] = old;
            code_table[comp.next_code++][PIXEL] = code_table[first][PIXEL];
        }
        old = code;
        i = 0;
        do
            buffer[i++] = (unsigned char) code_table[code][PIXEL];
        while ((code = code_table[code][PREFIX]) != CODE_TABLE_SIZE);

        buf = (char *)bmp->sprite + img_off;
        mbuf = (char *)bmp->sprite + msk_off;
        do
        {
            if ((g->image_flags & 1) && buffer[i-1]==0)
            	mbuf[x]=0;
            buf[x++] = buffer[--i];
            if (x==g->phys_width)
            {
                x=0;
                buf+=swidth;
                mbuf+=swidth;
                img_off+=swidth;
                msk_off+=swidth;
            }
        }
        while (i > 0);
    }

}/* decompress_image */

static int read_code(FILE * restrict fp, compress_t * restrict comp)
{
    int code, bsize, tlen, tptr;

    code = 0;
    tlen = comp->tlen;
    tptr = 0;

    while (tlen)
    {
        if (comp->slen == 0)
        {
            if ((comp->slen = fread(code_buffer, 1, MAX_BIT, fp)) == 0)
                fatal_lookup("GfxErr");

            comp->slen *= 8;
            comp->sptr = 0;
        }
        bsize = ((comp->sptr + 8) & ~7) - comp->sptr;
        bsize = (tlen > bsize) ? bsize : tlen;
        code |= (((unsigned int) code_buffer[comp->sptr >> 3] >> (comp->sptr & 7)) & mask[bsize]) << tptr;

        tlen -= bsize;
        tptr += bsize;
        comp->slen -= bsize;
        comp->sptr += bsize;
    }
    if ((comp->next_code == mask[comp->tlen]) && (comp->tlen < 12))
        comp->tlen++;

    return code;

}/* read_code */

void v6ro_clear_cache(void)
{
    Bitmap *e, *next;

    for (e=Cache; e; e=next)
    {
        next=e->next;
        free_bitmap(e);
    }
    Cache=NULL;
}

int graphics_release_memory(void)
{
    if (Cache)
    {
         v6ro_clear_cache();
         flex_compact();
         return 1;
    }
    else
         return 0;
}

void free_bitmap(Bitmap *bmp)
{
    flex_free((flex_ptr) &bmp->sprite);
    free(bmp);
}

static void uncache_bitmap(Bitmap * restrict bmp)
{
    Bitmap **prev;

    for (prev = &Cache; *prev; prev = &(*prev)->next)
        if (*prev == bmp)
        {
            Bitmap *next = bmp->next;
            free_bitmap(bmp);
            *prev = next;
            return;
        }
}

static void cache_bitmap(Bitmap * restrict bmp, gfx_dir * restrict dir)
{
    bmp->next=Cache;
    bmp->directory=dir;
    Cache=bmp;
}

void v6ro_cache_picture(gfx_dir *p)
{
    Bitmap *e;

    if (p==0)
    	return;

    e=build_sprite(bfp ? bfp : GFile, p, 0);
    if (e==0 || e->directory)
        return;

    e->next=Cache;
    e->directory=p;
    Cache=e;
}
