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

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

#include "osspriteop.h"
#include "colourtrans.h"

#include "ztypes.h"
#include "osdepend.h"
#include "fileio.h"
#include "v6.h"
#include "v6ro.h"
#include "pngro.h"
#include "graphics.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
};

typedef struct cache_entry cache_entry;

struct cache_entry
{
    cache_entry *next;
    gfx_dir *directory;
    osspriteop_area *sprite;
};

static cache_entry *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, osspriteop_header *sprite/*, osspriteop_trans_tab *tab*/);

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

#ifdef POSTPONE
    if (directory->cacheable)
        v6ro_cache_picture(directory);
#endif

    for (e=Cache; e; e=e->next)
    	if (e->directory==directory)
    	{
    	    if (cached)
    	    	*cached=TRUE;
    	    if (directory->image_cm_addr)
    	        last_cm = directory->image_cm_addr;
    	    return e->sprite;
    	}

    if (cached)
    	*cached=FALSE;

    if (blorb_map)
        return build_sprite_from_png(fp, directory);

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

    area_size=((directory->phys_width+3)&~3) * directory->phys_height
                 + sizeof(osspriteop_area) + sizeof(osspriteop_header) +
                 + 2048 + 256;

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

    area=malloc(area_size);
    if (!area)
    	return area;

    area->size=area_size;
    area->first=16;

    osspriteop_clear_sprites(osspriteop_USER_AREA, area);
    osspriteop_create_sprite(osspriteop_USER_AREA, area, "pic", FALSE,
                             directory->phys_width, directory->phys_height,
                             os_MODE8BPP45X45);
    sprite=osspriteop_select_sprite(osspriteop_USER_AREA, area, (osspriteop_id) "pic");

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

    osspriteop_create_true_palette(osspriteop_PTR, area, (osspriteop_id) sprite);

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

    colours = getc(fp);
    fread(&colourmap[2][RED], 1, colours * 3, fp);
    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, area, (osspriteop_id) sprite);

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

    decompress_image(fp, directory, sprite/*, (osspriteop_trans_tab *) &tab*/);

    return area;
}

static void decompress_image(FILE *fp, gfx_dir *g, osspriteop_header *sprite/*, osspriteop_trans_tab *tab*/)
{
    int i;
    int code, old = 0, first;
    compress_t comp;
    int swidth=(sprite->width+1)*4;
    int x=0;
    char *buf=(char *)sprite+sprite->image;
    char *mbuf=(char *)sprite+sprite->mask;
    int code_table[CODE_TABLE_SIZE][2];
    unsigned char buffer[CODE_TABLE_SIZE];

    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);

        do
        {
            if ((g->image_flags & 1) && buffer[i-1]==0)
            	mbuf[x]=0;
            //buf[x++] = tab->c[buffer[--i]];
            buf[x++] = buffer[--i];
            if (x==g->phys_width)
            {
                x=0;
                buf+=swidth;
                mbuf+=swidth;
            }
        }
        while (i > 0);
    }

}/* decompress_image */

static int read_code(FILE *fp, compress_t *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)
{
    cache_entry *e, *next;
    int i;

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

void v6ro_cache_picture(gfx_dir *p)
{
    cache_entry *e;
    int already_cached;

    if (p==0)
    	return;

#ifdef POSTPONE
    if (!p->image_cm_addr && !p->cacheable)
    {
        /* Postpone caching in a hope of getting the right palette */
        p->cacheable = 1;
        return;
    }

    p->cacheable = 0;
#endif

    e=malloc(sizeof(cache_entry));
    if (e==0)
    	return;

    e->next=Cache;
    e->sprite=build_sprite(bfp ? bfp : GFile, p, &already_cached);
    if (e->sprite==0 || already_cached)
    {
        free(e);
        return;
    }
    e->directory=p;

    Cache=e;
}
