/*
 * Copyright (C) 1996-1997 Id Software, Inc.
 *
 * 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; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */
// d_surf.c: rasterization driver surface heap manager

#include "quakedef.h"
#include "d_local.h"
#include "r_local.h"

float         surfscale;
qboolean      r_cache_thrash;          // set if surface cache is thrashing

int           sc_size;
surfcache_t  *sc_rover, *sc_base;

#define	GUARDSIZE	4

int D_SurfaceCacheForRes (int width, int height) {
    int           size, pix;

    if (COM_CheckParm ("-surfcachesize")) {
        size = Q_atoi (com_argv[COM_CheckParm ("-surfcachesize") + 1]) * 1024;
        return size;
    }

    size = SURFCACHE_SIZE_AT_320X200;

    pix = width * height;
    if (pix > 64000)
        size += (pix - 64000) * 3;

    return size;
}

void D_CheckCacheGuard (void) {
    int           i;
    byte         *s;

    s = (byte *) sc_base + sc_size;
    for (i = 0; i < GUARDSIZE; i++)
        if (s[i] != (byte) i)
            Sys_Error ("D_CheckCacheGuard: failed");
}

void D_ClearCacheGuard (void) {
    int           i;
    byte         *s;

    s = (byte *) sc_base + sc_size;
    for (i = 0; i < GUARDSIZE; i++)
        s[i] = (byte) i;
}

/*
 * D_InitCaches
 */
void D_InitCaches (void *buffer, int size) {
    // if (!msg_suppress_1)
    // Con_Printf ("%ik surface cache\n", size/1024);

    sc_size = size - GUARDSIZE;
    sc_base = (surfcache_t *) buffer;
    sc_rover = sc_base;

    sc_base->next = NULL;
    sc_base->owner = NULL;
    sc_base->size = sc_size;

    D_ClearCacheGuard ();
}

/*
 * D_FlushCaches
 */
void D_FlushCaches (void) {
    surfcache_t  *c;

    if (!sc_base)
        return;

    for (c = sc_base; c; c = c->next) {
        if (c->owner)
            *c->owner = NULL;
    }

    sc_rover = sc_base;
    sc_base->next = NULL;
    sc_base->owner = NULL;
    sc_base->size = sc_size;
}

/*
 * D_SCAlloc
 */
surfcache_t  *D_SCAlloc (int width, int size) {
    surfcache_t  *new;
    qboolean      wrapped_this_time;

    if (width < 0 || width > 256)
        Sys_Error ("D_SCAlloc: bad cache width %d", width);

    if (size <= 0 || size > 0x10000)
        Sys_Error ("D_SCAlloc: bad cache size %d", size);

    size = (int) &((surfcache_t *) 0)->data[size];
    size = (size + 3) & ~3;
    if (size > sc_size)
        Sys_Error ("D_SCAlloc: %i > cache size", size);

    // if there is not size bytes after the rover, reset to the start
    wrapped_this_time = false;

    if (!sc_rover || (byte *) sc_rover - (byte *) sc_base > sc_size - size) {
        if (sc_rover)
            wrapped_this_time = true;
        sc_rover = sc_base;
    }
    // colect and free surfcache_t blocks until the rover block is large enough
    new = sc_rover;
    if (sc_rover->owner)
        *sc_rover->owner = NULL;

    while (new->size < size) {
        // free another
        sc_rover = sc_rover->next;
        if (!sc_rover)
            Sys_Error ("D_SCAlloc: hit the end of memory");
        if (sc_rover->owner)
            *sc_rover->owner = NULL;

        new->size += sc_rover->size;
        new->next = sc_rover->next;
    }

    // create a fragment out of any leftovers
    if (new->size - size > 256) {
        sc_rover = (surfcache_t *) ((byte *) new + size);
        sc_rover->size = new->size - size;
        sc_rover->next = new->next;
        sc_rover->width = 0;
        sc_rover->owner = NULL;
        new->next = sc_rover;
        new->size = size;
    } else {
        sc_rover = new->next;
    }

    new->width = width;
    // DEBUG
    if (width > 0)
        new->height = (size - sizeof (*new) + sizeof (new->data)) / width;

    new->owner = NULL;                 // should be set properly after return

    if (d_roverwrapped) {
        if (wrapped_this_time || (sc_rover >= d_initial_rover))
            r_cache_thrash = true;
    } else if (wrapped_this_time) {
        d_roverwrapped = true;
    }

    D_CheckCacheGuard ();              // DEBUG
    return new;
}

/*
 * D_SCDump
 */
void D_SCDump (void) {
    surfcache_t  *test;

    for (test = sc_base; test; test = test->next) {
        if (test == sc_rover)
            Sys_Printf ("ROVER:\n");
        printf ("%p : %i bytes     %i width\n", test, test->size, test->width);
    }
}

// 

// if the num is not a power of 2, assume it will not repeat

int MaskForNum (int num) {
    if (num == 128)
        return 127;
    if (num == 64)
        return 63;
    if (num == 32)
        return 31;
    if (num == 16)
        return 15;
    return 255;
}

int D_log2 (int num) {
    int           c = 0;

    while (num >>= 1)
        c++;

    return c;
}

// 

/*
 * D_CacheSurface
 */
surfcache_t  *D_CacheSurface (msurface_t * surface, int miplevel) {
    surfcache_t  *cache;

    // if the surface is animating or flashing, flush the cache
    r_drawsurf.texture = R_TextureAnimation (surface->texinfo->texture);
    r_drawsurf.lightadj[0] = d_lightstylevalue[surface->styles[0]];
    r_drawsurf.lightadj[1] = d_lightstylevalue[surface->styles[1]];
    r_drawsurf.lightadj[2] = d_lightstylevalue[surface->styles[2]];
    r_drawsurf.lightadj[3] = d_lightstylevalue[surface->styles[3]];

    // see if the cache holds apropriate data
    cache = surface->cachespots[miplevel];

    if (cache && !cache->dlight        /* && surface->dlightframe !=
                                        * r_framecount */
        && cache->texture == r_drawsurf.texture && cache->lightadj[0] == r_drawsurf.lightadj[0]
        && cache->lightadj[1] == r_drawsurf.lightadj[1]
        && cache->lightadj[2] == r_drawsurf.lightadj[2]
        && cache->lightadj[3] == r_drawsurf.lightadj[3]) {
        if (surface->dlightframe == r_framecount)
            r_drawsurf.dlightonly = true;       // surface was not lit but is
        // (possibly) going to
        else
            return cache;
    } else {
        r_drawsurf.dlightonly = false;
    }

    // determine shape of surface
    surfscale = 1.0 / (1 << miplevel);
    r_drawsurf.surfmip = miplevel;
    r_drawsurf.surfwidth = surface->extents[0] >> miplevel;
    r_drawsurf.rowbytes = r_drawsurf.surfwidth;
    r_drawsurf.surfheight = surface->extents[1] >> miplevel;

    // allocate memory if needed
    if (!cache)                        // if a texture just animated, don't
        // reallocate it
    {
        cache = D_SCAlloc (r_drawsurf.surfwidth, r_drawsurf.surfwidth * r_drawsurf.surfheight);
        surface->cachespots[miplevel] = cache;
        cache->owner = &surface->cachespots[miplevel];
        cache->mipscale = surfscale;
    }

    cache->dlight = (surface->dlightframe == r_framecount) ? 1 : 0;

    r_drawsurf.surfdat = (pixel_t *) cache->data;

    cache->texture = r_drawsurf.texture;
    cache->lightadj[0] = r_drawsurf.lightadj[0];
    cache->lightadj[1] = r_drawsurf.lightadj[1];
    cache->lightadj[2] = r_drawsurf.lightadj[2];
    cache->lightadj[3] = r_drawsurf.lightadj[3];

    // draw and light the surface texture
    r_drawsurf.surf = surface;

    c_surf++;

    if (!R_DrawSurface ())
        cache->dlight = 0;

    return surface->cachespots[miplevel];
}
