/*  Sarien - A Sierra AGI resource interpreter engine
 *  Copyright (C) 1999-2001 Stuart George and Claudio Matsuoka
 *
 *  $Id: view.c,v 1.40 2001/09/08 00:21:24 cmatsuoka Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; see docs/COPYING for further details.
 */

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "sarien.h"
#include "agi.h"
#include "sprite.h"


static void mirror_cel (struct view_cel *vc)
{
        UINT8 *p;
        int x, y;

        p = vc->data;

        for (y = 0; y < vc->height; y++) {
                int z = vc->width - 1;
                int w = y * vc->width;

                x = 0;
                do {
                        int temp = p[w + x];
                        p[w + x] = p[w + z];
                        p[w + z] = temp;
                } while (++x <= --z);
        }
}

static void decode_cel (struct view_cel *vc, UINT8 *data)
{
        int h, c, l, d;
        UINT8 *p;

        p = vc->data;
        memset (p, vc->transparency, vc->height * vc->width);

        if (vc->width == 0 || vc->height == 0)
                return;

        for (d = h = 0; h < vc->height; h++) {
                p = vc->data + (h * vc->width);

                while (42) {
                        if ((l = data[d++]) == 0)
                                break;
                        c = (l >> 4) & 0xf;
                        l = l & 0xf;
                        memset (p, c, l);
                        p += l;
                }
        }
}

static void _set_cel (struct vt_entry *v, int n)
{
        v->current_cel = n;

        /* Safety check for NULL loop_data */
        if (v->loop_data == NULL) {
                _D ("WARNING: loop_data is NULL");
                v->cel_data = NULL;
                v->x_size = 0;
                v->y_size = 0;
                return;
        }

        /* Added by Amit Vainsencher <amitv@subdimension.com> to prevent
         * crash in KQ1 -- not in the Sierra interpreter
         */
        if(v->loop_data->num_cels == 0)
                return;

        v->cel_data = &v->loop_data->cel[n];
        v->x_size = v->cel_data->width;
        v->y_size = v->cel_data->height;
}

static void _set_loop (struct vt_entry *v, int n)
{
        _D ("vt entry #%d, loop = %d", v->entry, n);
        v->current_loop = n;

        /* Safety check for unloaded views */
        if (v->current_view >= MAX_DIRS || game.views[v->current_view].loop == NULL) {
                _D ("WARNING: view %d not loaded or invalid", v->current_view);
                v->loop_data = NULL;
                v->num_cels = 0;
                v->current_cel = 0;
                return;
        }

        v->loop_data = &game.views[v->current_view].loop[n];
        v->num_cels = v->loop_data->num_cels;
        if (v->current_cel >= v->num_cels)
                v->current_cel = 0;
}

static void update_view (struct vt_entry *v)
{
        int cel, last_cel;

        if (v->flags & DONTUPDATE) {
                v->flags &= ~DONTUPDATE;
                return;
        }

        cel = v->current_cel;
        last_cel = v->num_cels - 1;

        switch (v->cycle) {
        case CYCLE_NORMAL:
                if (++cel > last_cel)
                        cel = 0;
                break;
        case CYCLE_END_OF_LOOP:
                if (cel < last_cel) {
                        if (++cel != last_cel)
                                break;
                        setflag (v->parm1, TRUE);
                        v->flags &= ~CYCLING;
                        v->direction = 0;
                        v->cycle = CYCLE_NORMAL;
                }
                break;
        case CYCLE_REV_LOOP:
                if (cel == 0) {
                        setflag (v->parm1, TRUE);
                        v->flags &= ~CYCLING;
                        v->direction = 0;
                        v->cycle = CYCLE_NORMAL;
                } else {
                        cel--;
                }
                break;
        case CYCLE_REVERSE:
                if (cel == 0) {
                        cel = last_cel;
                } else {
                        cel--;
                }
                break;
        }

        set_cel (v, cel);
}

/*
 * Public functions
 */

/**
 * Decode an AGI view resource.
 * This function decodes the raw data of the specified AGI view resource
 * and fills the corresponding views array element.
 * @param n number of view resource to decode
 */
int decode_view (int n)
{
        int loop, cel;
        UINT8 *v, *lptr;
        UINT16 lofs, cofs;
        struct view_loop *vl;
        struct view_cel *vc;

        _D ("(%d)", n);
        v = game.views[n].rdata;

        assert (v != NULL);

        game.views[n].descr = lohi_getword (v + 3) ?
                strdup ((char*)v + lohi_getword (v + 3)) : strdup ("");

        /* if no loops exist, return! */
        if ((game.views[n].num_loops = lohi_getbyte (v + 2)) == 0)
                return err_NoLoopsInView;

        /* allocate memory for all views */
        game.views[n].loop =
                calloc (game.views[n].num_loops, sizeof(struct view_loop));

        if (game.views[n].loop == NULL)
                return err_NotEnoughMemory;

        /* decode all of the loops in this view */
        lptr = v + 5;           /* first loop address */

        for (loop = 0; loop < game.views[n].num_loops; loop++, lptr += 2) {
                lofs = lohi_getword (lptr);     /* loop header offset */
                vl = &game.views[n].loop[loop]; /* the loop struct */

                vl->num_cels = lohi_getbyte (v + lofs);
                _D(_D_WARN "view %d, num_cels = %d", n, vl->num_cels);
                vl->cel = calloc (vl->num_cels, sizeof (struct view_cel));
                if (vl->cel == NULL) {
                        free (game.views[n].loop);
                        game.views[n].num_loops = 0;
                        return err_NotEnoughMemory;
                }

                /* decode the cells */
                for (cel = 0; cel < vl->num_cels; cel++) {
                        cofs = lofs + lohi_getword (v + lofs + 1 + (cel * 2));
                        vc = &vl->cel[cel];

                        vc->width = lohi_getbyte (v + cofs);
                        vc->height = lohi_getbyte (v + cofs + 1);
                        vc->transparency = lohi_getbyte (v + cofs + 2) & 0xf;
                        vc->mirror_loop = (lohi_getbyte (v + cofs + 2) >>4) & 0x7;
                        vc->mirror = (lohi_getbyte (v + cofs + 2) >> 7) & 0x1;

                        /* skip over width/height/trans|mirror data */
                        cofs += 3;

                        vc->data = malloc ((vc->height + 1) * (vc->width + 1));

                        if (vc->data == NULL) {
                                for (cel--; cel >= 0; cel--)
                                        free (game.views[n].loop[loop].cel[cel].data);
                                for (; loop >= 0; loop--)
                                        free(game.views[n].loop[loop].cel);

                                free (game.views[n].loop);
                                game.views[n].num_loops = 0;
                                return err_NotEnoughMemory;
                        }

                        decode_cel (vc, v + cofs);

                        if (vc->mirror == 1 && vc->mirror_loop != loop)
                                mirror_cel (vc);
                } /* cel */
        } /* loop */

        return err_OK;
}

/**
 * Unloads all data in a view resource
 * @param n number of view resource
 */
void unload_view (int n)
{
        int x, y;

        if (~game.dir_view[n].flags & RES_LOADED)
                return;

        /* free all the loops */
        for (x = 0; x < game.views[n].num_loops; x++) {
                for (y = 0; y < game.views[n].loop[x].num_cels; y++)
                        if (game.views[n].loop[x].cel[y].data != NULL)
                                free (game.views[n].loop[x].cel[y].data);

                if (game.views[n].loop[x].cel != NULL)
                        free (game.views[n].loop[x].cel);
        }

        free (game.views[n].loop);
        free (game.views[n].descr);
        free (game.views[n].rdata);

        game.dir_view[n].flags &= ~RES_LOADED;
}

/**
 * Set a view table entry to use the specified cel of the current loop.
 * @param v pointer to view table entry
 * @param n number of cel
 */
void set_cel (struct vt_entry *v, int n)
{
        assert (v->view_data != NULL);
        assert (v->num_cels >= n);

        _set_cel (v, n);

        /* If position isn't appropriate, update it accordingly */
        if (v->x_pos + v->x_size > _WIDTH) {
                v->flags |= UPDATE_POS;
                v->x_pos = _WIDTH - v->x_size;
        }
        if (v->y_pos - v->y_size + 1 < 0) {
                v->flags |= UPDATE_POS;
                v->y_pos = v->y_size - 1;
        }
        if (v->y_pos <= game.horizon && (~v->flags & IGNORE_HORIZON)) {
                v->flags |= UPDATE_POS;
                v->y_pos = game.horizon + 1;
        }
}

/**
 * Set a view table entry to use the specified loop of the current view.
 * @param v pointer to view table entry
 * @param n number of loop
 */
void set_loop (struct vt_entry *v, int n)
{
        assert (v->view_data != NULL);

        /* Bounds check and clamp to valid range */
        if (n >= v->num_loops) {
                _D ("WARNING: loop %d out of bounds (max %d), clamping to 0", n, v->num_loops - 1);
                n = 0;
        }

        _set_loop (v, n);
        set_cel (v, v->current_cel);
}

/**
 * Set a view table entry to use the specified view resource.
 * @param v pointer to view table entry
 * @param n number of AGI view resource
 */
void set_view (struct vt_entry *v, int n)
{
        v->view_data = &game.views[n];
        v->current_view = n;
        v->num_loops = v->view_data->num_loops;
        set_loop (v, v->current_loop >= v->num_loops ? 0 : v->current_loop);
}

/**
 * Set the view table entry as updating.
 * @param v pointer to view table entry
 */
void start_update (struct vt_entry *v)
{
        if (~v->flags & UPDATE) {
                erase_both ();
                v->flags |= UPDATE;
                blit_both ();
        }
}

/**
 * Set the view table entry as non-updating.
 * @param v pointer to view table entry
 */
void stop_update (struct vt_entry *v)
{
        if (v->flags & UPDATE) {
                erase_both ();
                v->flags &= ~UPDATE;
                blit_both ();
        }
}


/* loops to use according to direction and number of loops in
 * the view resource
 */
static int loop_table_2[] = {
        0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01
};

static int loop_table_4[] = {
        0x04, 0x03, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x01
};

/**
 * Update view table entries.
 * This function is called at the end of each interpreter cycle
 * to update the view table entries and blit the sprites.
 */
void update_viewtable ()
{
        struct vt_entry *v;
        int i, loop;

        i = 0;
        for_each_vt_entry(v) {
                if ((v->flags & (ANIMATED|UPDATE|DRAWN)) !=
                        (ANIMATED|UPDATE|DRAWN))
                {
                        continue;
                }

                i++;

                loop = 4;
                if (~v->flags & FIX_LOOP) {
                        switch (v->num_loops) {
                        case 2:
                        case 3:
                                loop = loop_table_2[v->direction];
                                break;
                        case 4:
                                loop = loop_table_4[v->direction];
                                break;
                        default:
                                /* for KQ4 */
                                if (agi_get_release () == 0x3086)
                                        loop = loop_table_4[v->direction];
                                break;
                        }
                }

                if (v->step_time_count == 1 &&
                        loop != 4 &&
                        loop != v->current_loop)
                {
                        set_loop (v, loop);
                }

                if (~v->flags & CYCLING)
                        continue;

                if (v->cycle_time_count == 0)
                        continue;

                if (--v->cycle_time_count == 0) {
                        update_view (v);
                        v->cycle_time_count = v->cycle_time;
                }
        }

        if (i) {
#ifdef USE_CONSOLE
                /* To correctly update sprites when we use the console
                 * we must work with all sprites.
                 */
                if (console.y > 0) {
                        erase_both ();
                        update_position ();
                        blit_both ();
                        commit_both ();
                } else
#endif
                /* If we're not using the console, updating only
                 * the active sprites let us save some CPU cycles.
                 * This is how the original Sierra AGI works.
                 */
                {
                        erase_upd_sprites ();
                        update_position ();
                        blit_upd_sprites ();
                        commit_upd_sprites ();
                }

                game.view_table[0].flags &= ~(ON_WATER|ON_LAND);
        }
}

/* end: view.c */

