/***************************************************************************/
/*                                                                         */
/*                                                                         */
/* Raven 3D Engine                                                         */
/* Copyright (C) 1996 by Softdisk Publishing                               */
/*                                                                         */
/* Original Design:                                                        */
/*  John Carmack of id Software                                            */
/*                                                                         */
/* Enhancements by:                                                        */
/*  Robert Morgan of Channel 7............................Main Engine Code */
/*  Todd Lewis of Softdisk Publishing......Tools,Utilities,Special Effects */
/*  John Bianca of Softdisk Publishing..............Low-level Optimization */
/*  Carlos Hasan..........................................Music/Sound Code */
/*                                                                         */
/*                                                                         */
/***************************************************************************/

#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "d_global.h"
#include "d_disk.h"
#include "r_refdef.h"
#include "protos.h"
#include "d_ints.h"
#include "audio.h"


/**** VARIABLES ****/

int     windowHeight = INIT_VIEW_HEIGHT;
int     windowWidth  = INIT_VIEW_WIDTH;
int     windowLeft   = 0;
int     windowTop    = 0;
int     windowSize   = INIT_VIEW_HEIGHT * INIT_VIEW_WIDTH;
intptr_t     viewLocation = 0; //0xA0000; //pvmk - switch to intptr_t
fixed_t CENTERX      = INIT_VIEW_WIDTH / 2;
fixed_t CENTERY      = INIT_VIEW_HEIGHT / 2;
fixed_t SCALE;
fixed_t ISCALE;
int     backtangents[TANANGLES * 2];
int     autoangle2[MAXAUTO][MAXAUTO];
int     scrollmin, scrollmax, bloodcount, metalcount;

extern SoundCard SC;

/**** FUNCTIONS ****/

void r_publicstub2(void);

void RF_PreloadGraphics(void)
{
    int i;
    int doorlump;

    // find the number of lumps of each type
    spritelump = CA_GetNamedNum("startsprites");
    numsprites = CA_GetNamedNum("endsprites") - spritelump;
    walllump   = CA_GetNamedNum("startwalls");
    numwalls   = CA_GetNamedNum("endwalls") - walllump;
    flatlump   = CA_GetNamedNum("startflats");
    numflats   = CA_GetNamedNum("endflats") - flatlump;
    doorlump   = CA_GetNamedNum("door_1");
    printf(".");
    // load the lumps
    for (i = 1; i < numsprites; i++)
    {
        DemandLoadMonster(spritelump + i, 1);
        //   CA_CacheLump(spritelump+i);
        if (i % 50 == 0)
        {
            printf(".");
            if (newascii && lastascii == 27)
                return;
        }
    }
    printf(".");
    if (!debugmode)
        for (i = doorlump; i < numwalls + walllump; i++)
            CA_CacheLump(i);
    else
    {
        CA_CacheLump(walllump + 1);
        CA_CacheLump(flatlump + 1);
    }
    printf(".");
}


void RF_InitTargets(void)
{
    double  at, atf;
    int     j, angle, x1, y1, i;
    fixed_t x, y;

    memset(autoangle2, -1, sizeof(autoangle2));
    i = 0;
    do
    {
        at    = atan((double) i / (double) MAXAUTO);
        atf   = at * (double) ANGLES / (2 * PI);
        angle = rint(atf);
        for (j = 0; j < MAXAUTO * 2; j++)
        {
            y  = FIXEDMUL(sintable[angle], j << FRACBITS);
            x  = FIXEDMUL(costable[angle], j << FRACBITS);
            x1 = x >> FRACBITS;
            y1 = y >> FRACBITS;
            if (x1 >= MAXAUTO || y1 >= MAXAUTO || autoangle2[x1][y1] != -1)
                continue;
            autoangle2[x1][y1] = angle;
        }
        i++;
    } while (angle < DEGREE45 + DEGREE45_2);

    for (i = MAXAUTO - 1; i > 0; i--)
        for (j = 0; j < MAXAUTO; j++)
            if (autoangle2[j][i] == -1)
                autoangle2[j][i] = autoangle2[j][i - 1];
    for (i = MAXAUTO - 1; i > 0; i--)
        for (j = 0; j < MAXAUTO; j++)
            if (autoangle2[j][i] == -1)
                autoangle2[j][i] = autoangle2[j][i - 1];
}


void InitTables(void)
/* Builds tangent tables for -90 degrees to +90 degrees
   and pixel angle table */
{
    double tang, value, ivalue;
    int    intval, i;

    // tangent values for wall tracing
    for (i = 0; i < TANANGLES / 2; i++)
    {
        tang = (i + 0.5) * PI / (TANANGLES * 2);
        //   tang=i*PI/(TANANGLES*2);
        value                                   = tan(tang);
        ivalue                                  = 1 / value;
        value                                   = rint(value * FRACUNIT);
        ivalue                                  = rint(ivalue * FRACUNIT);
        tangents[TANANGLES + i]                 = -value;
        tangents[TANANGLES + TANANGLES - 1 - i] = -ivalue;
        tangents[i]                             = ivalue;
        tangents[TANANGLES - 1 - i]             = value;
    }
    // high precision sin / cos for distance calculations
    for (i = 0; i < TANANGLES; i++)
    {
        tang = (i + 0.5) * PI / (TANANGLES * 2);
        //   tang=i*PI/(TANANGLES*2);
        value                        = sin(tang);
        intval                       = rint(value * FRACUNIT);
        sines[i]                     = intval;
        sines[TANANGLES * 4 + i]     = intval;
        sines[TANANGLES * 2 - 1 - i] = intval;
        sines[TANANGLES * 2 + i]     = -intval;
        sines[TANANGLES * 4 - 1 - i] = -intval;
    }
    cosines = &sines[TANANGLES];
    for (i = 0; i < TANANGLES * 2; i++)
        backtangents[i] = ((windowWidth / 2) * tangents[i]) >> FRACBITS;
}


void InitReverseCam(void)
{
    int i, intval;

    for (i = 0; i < 65; i++)
    {
        intval         = rint(atan(((double) 32 - ((double) i + 1.0)) / (double) 32) / (double) PI
                      * (double) TANANGLES * (double) 2);
        pixelangle[i]  = intval;
        pixelcosine[i] = cosines[intval & (TANANGLES * 4 - 1)];
    }
    memcpy(campixelangle, pixelangle, sizeof(pixelangle));
    memcpy(campixelcosine, pixelcosine, sizeof(pixelcosine));
}


void RF_Startup(void)
{
    int    i;
    double angle;
    int    lightlump;

    //lock_region((void near*) RF_GetFloorZ, (char*) r_publicstub2 - (char near*) RF_GetFloorZ);
    //lock_region(&actionhook, sizeof(actionhook));
    //lock_region(&actionflag, sizeof(actionflag));
    memset(framevalid, 0, sizeof(framevalid));
    printf(".");
    frameon = 0;
    // trig tables
    for (i = 0; i <= ANGLES; i++)
    {
        angle       = (double) (i * PI * 2) / (double) (ANGLES + 1);
        sintable[i] = rint(sin(angle) * FRACUNIT);
        costable[i] = rint(cos(angle) * FRACUNIT);
    }
    printf(".");
    SetViewSize(windowWidth, windowHeight);
    // set up lights
    // Allocates a page aligned buffer and load in the light tables
    lightlump    = CA_GetNamedNum("lights");
    numcolormaps = infotable[lightlump].size / 256;
    colormaps    = malloc((size_t) 256 * (numcolormaps + 1));
    colormaps    = (byte*) (((intptr_t) colormaps + 255) & ~0xff); //was casting pointer to int, brilliant! -betopp
    CA_ReadLump(lightlump, colormaps);
    RF_SetLights((fixed_t) MAXZ);
    RF_ClearWorld();
    printf(".");
    // initialize the translation to no animation
    flattranslation = malloc((size_t) (numflats + 1) * 4);
    walltranslation = malloc((size_t) (numwalls + 1) * 4);
    if (!debugmode)
    {
        for (i = 0; i <= numflats; i++)
            flattranslation[i] = i;
        for (i = 0; i <= numwalls; i++)
            walltranslation[i] = i;
    }
    else
    {
        for (i = 1; i <= numflats; i++)
            flattranslation[i] = 1;
        for (i = 1; i <= numwalls; i++)
            walltranslation[i] = 1;
        flattranslation[0] = 0;
        walltranslation[0] = 0;
    }
    actionhook = NULL;
    actionflag = 0;
    RF_InitTargets();
    InitTables();
    printf(".");
    InitReverseCam();
    InitWalls();
    printf(".");
}


void RF_ClearWorld(void)
{
    int i;

    firstscaleobj.prev = NULL;
    firstscaleobj.next = &lastscaleobj;
    lastscaleobj.prev  = &firstscaleobj;
    lastscaleobj.next  = NULL;
    freescaleobj_p     = scaleobjlist;
    memset(scaleobjlist, 0, sizeof(scaleobjlist));
    for (i = 0; i < MAXSPRITES - 1; i++)
        scaleobjlist[i].next = &scaleobjlist[i + 1];
    firstelevobj.prev = NULL;
    firstelevobj.next = &lastelevobj;
    lastelevobj.prev  = &firstelevobj;
    lastelevobj.next  = NULL;
    freeelevobj_p     = elevlist;
    memset(elevlist, 0, sizeof(elevlist));
    for (i = 0; i < MAXELEVATORS - 1; i++)
        elevlist[i].next = &elevlist[i + 1];
    numdoors      = 0;
    numspawnareas = 0;
    bloodcount    = 0;
    metalcount    = 0;
}


doorobj_t* RF_GetDoor(int tilex, int tiley)
{
    doorobj_t* door;

    if (numdoors == MAXDOORS)
        MS_Error("RF_GetDoor: Too many doors placed! (%i,%i)", numdoors, MAXDOORS);
    door = &doorlist[numdoors];
    numdoors++;
    door->tilex = tilex;
    door->tiley = tiley;
    mapflags[tiley * MAPROWS + tilex] |= FL_DOOR;
    return door;
}


scaleobj_t* RF_GetSprite(void)
/* returns a new sprite */
{
    scaleobj_t* new;

    if (!freescaleobj_p)
        MS_Error("RF_GetSprite: Out of spots in scaleobjlist!");
    new            = freescaleobj_p;
    freescaleobj_p = freescaleobj_p->next;
    memset(new, 0, sizeof(scaleobj_t));
    new->next         = (scaleobj_t*) &lastscaleobj;
    new->prev         = lastscaleobj.prev;
    lastscaleobj.prev = new;
    new->prev->next   = new;
    return new;
}


elevobj_t* RF_GetElevator(void)
/* returns a elevator structure */
{
    elevobj_t* new;

    if (!freeelevobj_p)
        MS_Error("RF_GetElevator: Too many elevators placed!");
    new           = freeelevobj_p;
    freeelevobj_p = freeelevobj_p->next;
    memset(new, 0, sizeof(elevobj_t));
    new->next        = (elevobj_t*) &lastelevobj;
    new->prev        = lastelevobj.prev;
    lastelevobj.prev = new;
    new->prev->next  = new;
    return new;
}


spawnarea_t* RF_GetSpawnArea(void)
{
    if (numspawnareas == MAXSPAWNAREAS)
        MS_Error(
            "RF_GetSpawnArea: Too many Spawn Areas placed! (%i,%i)", numspawnareas, MAXSPAWNAREAS);
    ++numspawnareas;
    return &spawnareas[numspawnareas - 1];
}


void Event(int e, boolean send);


void RF_RemoveSprite(scaleobj_t* spr)
/* removes sprite from doublely linked list of sprites */
{
    spr->next->prev = spr->prev;
    spr->prev->next = spr->next;
    spr->next       = freescaleobj_p;
    freescaleobj_p  = spr;
}


void RF_RemoveElevator(elevobj_t* e)
{
    e->next->prev = e->prev;
    e->prev->next = e->next;
    e->next       = freeelevobj_p;
    freeelevobj_p = e;
}


fixed_t RF_GetFloorZ(fixed_t x, fixed_t y)
{
    fixed_t h1, h2, h3, h4;
    int     tilex, tiley, mapspot;
    int     polytype;
    fixed_t fx, fy;
    fixed_t top, bottom, water;

    tilex    = x >> (FRACBITS + TILESHIFT);
    tiley    = y >> (FRACBITS + TILESHIFT);
    mapspot  = tiley * MAPSIZE + tilex;
    polytype = (mapflags[mapspot] & FL_FLOOR) >> FLS_FLOOR;
    if (floorpic[mapspot] >= 57 && floorpic[mapspot] <= 59)
        water = -(20 << FRACBITS);
    else
        water = 0;
    if (polytype == POLY_FLAT)
        return (floorheight[mapspot] << FRACBITS) + water;
    h1 = floorheight[mapspot] << FRACBITS;
    h2 = floorheight[mapspot + 1] << FRACBITS;
    h3 = floorheight[mapspot + MAPSIZE] << FRACBITS;
    h4 = floorheight[mapspot + MAPSIZE + 1] << FRACBITS;
    fx = (x & (TILEUNIT - 1)) >> 6;  // range from 0 to fracunit-1
    fy = (y & (TILEUNIT - 1)) >> 6;
    if (polytype == POLY_SLOPE)
    {
        if (h1 == h2)
            return h1 + FIXEDMUL(h3 - h1, fy) + water;
        else
            return h1 + FIXEDMUL(h2 - h1, fx) + water;
    }
    // triangulated slopes
    // set the outside corner of the triangle that the point is NOT in s
    // plane with the other three
    if (polytype == POLY_ULTOLR)
    {
        if (fx > fy)
            h3 = h1 - (h2 - h1);
        else
            h2 = h1 + (h1 - h3);
    }
    else
    {
        if (fx < FRACUNIT - fy)
            h4 = h2 + (h2 - h1);
        else
            h1 = h2 - (h4 - h2);
    }
    top    = h1 + FIXEDMUL(h2 - h1, fx);
    bottom = h3 + FIXEDMUL(h4 - h3, fx);
    return top + FIXEDMUL(bottom - top, fy) + water;
}


fixed_t RF_GetCeilingZ(fixed_t x, fixed_t y)
/* find how high the ceiling is at x,y */
{
    fixed_t h1, h2, h3, h4;
    int     tilex, tiley, mapspot;
    int     polytype;
    fixed_t fx, fy;
    fixed_t top, bottom;

    tilex    = x >> (FRACBITS + TILESHIFT);
    tiley    = y >> (FRACBITS + TILESHIFT);
    mapspot  = tiley * MAPSIZE + tilex;
    polytype = (mapflags[mapspot] & FL_CEILING) >> FLS_CEILING;
    // flat
    if (polytype == POLY_FLAT)
        return ceilingheight[mapspot] << FRACBITS;
    // constant slopes
    if (polytype == POLY_SLOPE)
    {
        h1 = ceilingheight[mapspot] << FRACBITS;
        h2 = ceilingheight[mapspot + 1] << FRACBITS;
        if (h1 == h2)
        {
            h3 = ceilingheight[mapspot + MAPSIZE] << FRACBITS;
            fy = (y & (TILEUNIT - 1)) >> 6;
            return h1 + FIXEDMUL(h3 - h1, fy);  // north/south slope
        }
        else
        {
            fx = (x & (TILEUNIT - 1)) >> 6;
            return h1 + FIXEDMUL(h2 - h1, fx);  // east/west slope
        }
    }
    // triangulated slopes
    // set the outside corner of the triangle that the point is NOT in s
    // plane with the other three
    h1 = ceilingheight[mapspot] << FRACBITS;
    h2 = ceilingheight[mapspot + 1] << FRACBITS;
    h3 = ceilingheight[mapspot + MAPSIZE] << FRACBITS;
    h4 = ceilingheight[mapspot + MAPSIZE + 1] << FRACBITS;
    fx = (x & (TILEUNIT - 1)) >> 6;  // range from 0 to fracunit-1
    fy = (y & (TILEUNIT - 1)) >> 6;
    if (polytype == POLY_ULTOLR)
    {
        if (fx > fy)
            h3 = h1 - (h2 - h1);
        else
            h2 = h1 + (h1 - h3);
    }
    else
    {
        if (fx < FRACUNIT - fy)
            h4 = h2 + (h2 - h1);
        else
            h1 = h2 - (h4 - h2);
    }
    top    = h1 + FIXEDMUL(h2 - h1, fx);
    bottom = h3 + FIXEDMUL(h4 - h3, fx);
    return top + FIXEDMUL(bottom - top, fy);
}


void RF_SetActionHook(void (*hook)(void))
{
    actionhook = hook;
    actionflag = 1;
}

void r_publicstub2(void) {}


void RF_SetLights(fixed_t blackz)
/* resets the color maps to new lighting values */
{
    // linear diminishing, table is actually logrithmic
    int i, table;

    blackz >>= FRACBITS;
    for (i = 0; i <= MAXZ >> FRACBITS; i++)
    {
        table = numcolormaps * i / blackz;
        if (table >= numcolormaps)
            table = numcolormaps - 1;
        zcolormap[i] = colormaps + table * 256;
    }
}


void RF_CheckActionFlag(void)
{
	#if 0
    if (SC.vrhelmet == 0)
        TimeUpdate();
	#endif 
	
    if (!actionflag)
        return;
    actionhook();
    actionflag = 0;
}


void RF_RenderView(fixed_t x, fixed_t y, fixed_t z, int angle)
{
    // #ifdef VALIDATE
    //  if (x<=0 || x>=((MAPSIZE-1)<<(FRACBITS+TILESHIFT)) || y<=0 ||
    //   y>=((MAPSIZE-1)<<(FRACBITS+TILESHIFT)))
    //   MS_Error("Invalid RF_RenderView (%p, %p, %p, %i)\n", x, y, z, angle);
    // #endif

    // viewx=(x&~0xfff) + 0x800;
    // viewy=(y&~0xfff) + 0x800;
    // viewz=(z&~0xfff) + 0x800;

    viewx     = x;
    viewy     = y;
    viewz     = z;
    viewangle = angle & ANGLES;
    RF_CheckActionFlag();
    SetupFrame();
    RF_CheckActionFlag();
    FlowView();
    RF_CheckActionFlag();
    RenderSprites();
    DrawSpans();
    RF_CheckActionFlag();
}


void SetViewSize(int width, int height)
{
    int i;

    if (width > MAX_VIEW_WIDTH)
        width = MAX_VIEW_WIDTH;
    if (height > MAX_VIEW_HEIGHT)
        height = MAX_VIEW_HEIGHT;
    windowHeight = height;
    windowWidth  = width;
    windowSize   = width * height;
    scrollmax    = windowHeight + scrollmin;
    CENTERX      = width / 2;
    CENTERY      = height / 2;
    SCALE        = (width / 2) << FRACBITS;
    ISCALE       = FRACUNIT / (width / 2);
    for (i = 0; i < height; i++)
        viewylookup[i] = viewbuffer + i * width;
    // slopes for rows and collumns of screen pixels
    // slightly biased to account for the truncation in coordinates
    for (i = 0; i <= width; i++)
        xslope[i] = rint((float) (i + 1 - CENTERX) / CENTERX * FRACUNIT);
    for (i = -MAXSCROLL; i < height + MAXSCROLL; i++)
        yslope[i + MAXSCROLL] = rint(-(float) (i - 0.5 - CENTERY) / CENTERX * FRACUNIT);
    for (i = 0; i < TANANGLES * 2; i++)
        backtangents[i] = ((width / 2) * tangents[i]) >> FRACBITS;
    hfrac = FIXEDDIV(BACKDROPHEIGHT << FRACBITS, (windowHeight / 2) << FRACBITS);
    afrac = FIXEDDIV(TANANGLES << FRACBITS, width << FRACBITS);
}

//pvmk - implement in plain C
fixed_t FIXEDMUL(fixed_t a, fixed_t b)
{
	return (((int64_t)a) * ((int64_t)b)) >> FRACBITS;
}

//pvmk - implement in plain C
fixed_t FIXEDDIV(fixed_t a, fixed_t b)
{
	return (((int64_t)a) << FRACBITS) / ((int64_t)b);
}
