/*
Copyright (C) 1994-1995  Apogee Software, Ltd.
Copyright (C) 2002-2015  icculus.org, GNU/Linux port
Copyright (C) 2017-2018  Steven LeVesque

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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
#include "rt_def.h"
#include "rt_view.h"
#include "z_zone.h"
#include "w_wad.h"
#include "lumpy.h"
#include "rt_util.h"
#include "rt_vid.h"
#include "rt_game.h"
#include "rt_draw.h"
#include "rt_ted.h"
#include "isr.h"
#include "rt_rand.h"
#include "rt_sound.h"
#include "modexlib.h"
#include "rt_menu.h"

#include <stdlib.h>

#include "rt_main.h"
#include "rt_battl.h"
#include "rt_floor.h"
#include "rt_str.h"
#include "watcom.h"
#include "develop.h"

#define LIGHTNINGLEVEL 4
#define MINLIGHTNINGLEVEL   2
#define MAXLIGHTNINGLEVEL   10

/*
=============================================================================

                               GLOBALS

=============================================================================
*/
extern int G_weaponscale;


int    StatusBar = 0;
int    lightninglevel=0;
boolean  lightning=false;
int    normalshade;
int    darknesslevel;
int    maxshade;
int    minshade;
int    baseminshade;
int    basemaxshade;
int    viewheight;
int    viewwidth;
longword   heightnumerator;
fixed  scale;
int    screenofs;
int    centerx;
int    centery;
int    centeryfrac;
int    fulllight;
int    weaponscale;
int    viewsize;
byte * colormap;
byte * redmap;
byte * greenmap;
byte * playermaps[MAXPLAYERCOLORS];
//short  pixelangle[MAXVIEWWIDTH];
short  pixelangle[MAXSCREENWIDTH];
byte   gammatable[GAMMAENTRIES];
int    gammaindex;
int    focalwidth=160;
int    yzangleconverter;
byte   uniformcolors[MAXPLAYERCOLORS]= {
    25,
    222,
    29,
    206,
    52,
    6,
    155,
    16,
    90,
    129,
    109
};


/*
=============================================================================

                               LOCAL

=============================================================================
*/

static char *YourComputerSucksString = "Buy a 486! :)";

static int viewsizes[MAXVIEWSIZES*2]= { 80,48,
                                        128,72,
                                        160,88,
                                        192,104,
                                        224,120,
                                        256,136,
                                        288,152,
                                        320,168,
                                        320,184,
                                        320,200,
                                        320,200
                                      };

static int ColorMapLoaded=0;

static int      lightningtime=0;
static int      lightningdelta=0;
static int      lightningdistance=0;
static int      lightningsoundtime=0;
static boolean  periodic=false;
static int      periodictime=0;

void SetViewDelta ( void );
void UpdatePeriodicLighting (void);

/*
====================
=
= ResetFocalWidth
=
====================
*/
void ResetFocalWidth ( void )
{
    focalwidth=iGLOBAL_FOCALWIDTH;//FOCALWIDTH;
    SetViewDelta();
}

/*
====================
=
= ChangeFocalWidth
=
====================
*/
void ChangeFocalWidth ( int amount )
{
    focalwidth=iGLOBAL_FOCALWIDTH+amount;//FOCALWIDTH+amount;
    SetViewDelta();
}


/*
====================
=
= SetViewDelta
=
====================
*/

void SetViewDelta ( void )
{
//iGLOBAL_SCREENHEIGHT
//iGLOBAL_SCREENWIDTH
//
// calculate scale value for vertical height calculations
// and sprite x calculations
//

    scale = (centerx*focalwidth)/(160);
//
// divide heightnumerator by a posts distance to get the posts height for
// the heightbuffer.  The pixel height is height>>HEIGHTFRACTION
//
    heightnumerator = (((focalwidth/10)*centerx*4096)<<HEIGHTFRACTION);
}

/*
====================
=
= CalcProjection
=
====================
*/

void CalcProjection ( void )
{
    int   i;
    int   frac;
    int   intang;
    byte * table;
    byte * ptr;
    int   length;
    int  * pangle;



// Already being called in ResetFocalWidth
//    SetViewDelta();

//
// load in tables file
//


//Hey, isn't this stuff already loaded in?
//Why don't we make this a lump?
    table=W_CacheLumpName("tables",PU_STATIC, CvtNull, 1);
    ptr=table;

//
// get size of table
//

    memcpy(&length,ptr,sizeof(int));
    SwapIntelLong(&length);
    ptr+=sizeof(int);
    pangle=SafeMalloc(length*sizeof(int));
    memcpy(pangle,ptr,length*sizeof(int));

    frac=((length*65536/centerx))>>1;
    for (i=0; i<centerx; i++)
    {
        // start 1/2 pixel over, so viewangle bisects two middle pixels
        intang=pangle[frac>>16];
        SwapIntelLong(&intang);
        pixelangle[centerx-1-i] =(short) intang;
        pixelangle[centerx+i] =(short) -intang;
        frac+=(length*65536/centerx);
    }
    table=W_CacheLumpName("tables",PU_CACHE, CvtNull, 1);
    SafeFree(pangle);
}


/*
==========================
=
= SetViewSize
=
==========================
*/

extern int hudRescaleFactor;

void SetViewSize
(
    int size
)

{
    int height;
    int maxheight;
    int screenx;
    int screeny;
    int topy;
    /*
    if (size>=10){

       SetTextMode (  );
       screenx=screenx;
       return;
    }
    */

    height = 0;
    for (height = 0; height < 21;)
    {
        viewsizes[height++] = iGLOBAL_SCREENWIDTH;
        viewsizes[height++] = iGLOBAL_SCREENHEIGHT;
    }


    if ((size<0) || (size>=MAXVIEWSIZES)) { //bna added
        printf("Illegal screen size = %d\n",size);
        size = 8;//set default value
        viewsize = 8;
    }

    //if ((size<0) || (size>=MAXVIEWSIZES))
    //   Error("Illegal screen size = %ld\n",size);

    viewwidth  = viewsizes[ size << 1 ];         // must be divisable by 16
    viewheight = viewsizes[ ( size << 1 ) + 1 ]; // must be even

    maxheight = iGLOBAL_SCREENHEIGHT;
    topy      = 0;

    // Only keep the kills flag
    StatusBar &= ~( BOTTOM_STATUS_BAR | TOP_STATUS_BAR |
                    STATUS_PLAYER_STATS );

    if ( SHOW_KILLS() )
    {
        // Account for height of kill boxes
        maxheight -= 24;
    }

    if ( size < 9 )
    {
        StatusBar |= TOP_STATUS_BAR;

        // Account for height of top status bar
        maxheight -= 16 * hudRescaleFactor;
        topy      += 16 * hudRescaleFactor;
    }

//   if ( size == 7 ){maxheight -= 16;}//bna++
//   if ( size <= 6 ){topy -= 8;}//bna++

    if ( size < 8 )
    {
        // Turn on health and ammo bar
        StatusBar |= BOTTOM_STATUS_BAR;

        maxheight -= 16 * hudRescaleFactor;

    }
    else if ( size < 10 )
    {
        // Turn on transparent health and ammo bar
        StatusBar |= STATUS_PLAYER_STATS;
    }
    //   SetTextMode (  );
    //   viewheight=viewheight;
    height = viewheight;
    if ( height > 168*iGLOBAL_SCREENHEIGHT/200 )
    {
        // Prevent weapon from being scaled too big
        height = 168*iGLOBAL_SCREENHEIGHT/200;
    }

    weaponscale = ( height << 16 ) / 168;//( height << 16 ) = 170 * 65536


    centerx     = viewwidth >> 1;
    centery     = viewheight >> 1;
    centeryfrac = (centery << 16);
    yzangleconverter = ( 0xaf85 * viewheight ) / 300;

    // Center the view horizontally
    screenx = ( iGLOBAL_SCREENWIDTH - viewwidth ) >> 1;

    if ( viewheight >= maxheight )
    {
        screeny = topy;
        viewheight = maxheight;
    }
    else
    {
        // Center the view vertically
        screeny = ( ( maxheight - viewheight ) >> 1 ) + topy;
    }

    // Calculate offset of view window
    screenofs = screenx + ylookup[ screeny ];

//
// calculate trace angles and projection constants
//

    ResetFocalWidth();


// Already being called in ResetFocalWidth
//   SetViewDelta();


    CalcProjection();

}


//******************************************************************************
//
// DrawCPUJape ()
//
//******************************************************************************

void DrawCPUJape
(
    void
)

{
    int width;
    int height;

    CurrentFont = tinyfont;
    VW_MeasurePropString( YourComputerSucksString, &width, &height );

    DrawGameString( 160 - width / 2, 100 + 48 / 2 + 2,
                    YourComputerSucksString, true );
}


/*
==========================
=
= SetupScreen
=
==========================
*/

void SetupScreen ( boolean flip )
{
    pic_t *shape;

    SetViewSize(viewsize);

    if ( viewsize < 7 )
    {
        shape =  ( pic_t * )W_CacheLumpName( "backtile", PU_CACHE, Cvt_pic_t, 1 );
        //DrawTiledRegion( 0, 16, 320, 200 - 32, 0, 16, shape );
        DrawTiledRegion( 0, 16*hudRescaleFactor, iGLOBAL_SCREENWIDTH, iGLOBAL_SCREENHEIGHT - 16*hudRescaleFactor, 0, 16, shape );//bna++
    }

    if ( viewsize == 0 )
    {
        DrawCPUJape();
    }
    if (flip == true){
        boolean wasStretched = false;
        
        if(StretchScreen)
        {
            wasStretched = true;
        }
        DisableScreenStretch();
        DrawPlayScreen (true);
        ThreeDRefresh();
        if (wasStretched == true)
        {
            EnableScreenStretch();
        }
    }
}



void LoadColorMap( void )
{
    int i,j;
    int lump, length;

    if (ColorMapLoaded==1)
        Error("Called LoadColorMap twice\n");
    ColorMapLoaded=1;
//
//   load in the light tables
//   256 byte align tables
//

    lump = W_GetNumForName("colormap");
    length = W_LumpLength (lump) + 255;
    colormap = SafeMalloc (length);
    colormap = (byte *)( ((long)colormap + 255)&~0xff);
    W_ReadLump (lump,colormap);

// Fix fire colors in colormap

    for (i=31; i>=16; i--)
        for (j=0xea; j<0xf9; j++)
            colormap[i*256+j]=colormap[(((i-16)/4+16))*256+j];

// Get special maps

    lump = W_GetNumForName("specmaps");
    length = W_LumpLength (lump+1) + 255;
    redmap = SafeMalloc (length);
    redmap = (byte *)( ((long)redmap + 255)&~0xff);
    W_ReadLump (lump+1,redmap);
    greenmap = redmap+(16*256);

// Get player colormaps

//   if (modemgame==true)
    {
        lump = W_GetNumForName("playmaps")+1;
        for (i=0; i<MAXPLAYERCOLORS; i++)
        {
            length = W_LumpLength (lump+i) + 255;
            playermaps[i] = SafeMalloc (length);
            playermaps[i] = (byte *)( ((long)playermaps[i] + 255)&~0xff);
            W_ReadLump (lump+i,playermaps[i]);
        }
    }

    if (!quiet)
        printf("RT_VIEW: Colormaps Initialized\n");

}

/*
==========================
=
= SetupLightLevels
=
==========================
*/
#define LIGHTRATEBASE 252
#define LIGHTRATEEND  267
#define LIGHTLEVELBASE 216
#define LIGHTLEVELEND  223
void SetupLightLevels ( void )
{
    int glevel;

    periodic=false;
    fog=0;
    lightsource=0;

// Set up light level for level

    if (((word)MAPSPOT(2,0,1)>=104) && ((word)MAPSPOT(2,0,1)<=105))
        fog=(word)MAPSPOT(2,0,1)-104;
    else
        Error ("There is no Fog icon on map %d\n",gamestate.mapon);
    if ((word)MAPSPOT(3,0,1)==139)
    {
        if (fog==0)
        {
            lightsource=1;
            lights=Z_Malloc(MAPSIZE*MAPSIZE*(sizeof(unsigned long)),PU_LEVEL,NULL);
            memset (lights,0,MAPSIZE*MAPSIZE*(sizeof(unsigned long)));
        }
        else
            Error("You cannot use light sourcing on a level with fog on map %d\n",gamestate.mapon);
    }
    else if ((word)MAPSPOT(3,0,1))
        Error("You must use the lightsource icon or nothing at all at (3,0) in plane 1 on map %d\n",gamestate.mapon);
    if (((word)MAPSPOT(2,0,0)>=LIGHTLEVELBASE) && ((word)MAPSPOT(2,0,0)<=LIGHTLEVELEND))
        glevel=(MAPSPOT(2,0,0)-LIGHTLEVELBASE);
    else
        Error("You must specify a valid darkness level icon at (2,0) on map %d\n",gamestate.mapon);

    SetLightLevels ( glevel );

    if (((word)MAPSPOT(3,0,0)>=LIGHTRATEBASE) && ((word)MAPSPOT(3,0,0)<=LIGHTRATEEND))
        glevel=(MAPSPOT(3,0,0)-LIGHTRATEBASE);
    else
    {
//      Error("You must specify a valid darkness rate icon at (3,0) on map %ld\n",gamestate.mapon);
        glevel = 4;
    }

    SetLightRate ( glevel );
    lightningtime=0;
    lightningdistance=0;
    lightninglevel=0;
    lightningdelta=0;
    lightningsoundtime=0;
}

/*
==========================
=
= SetLightLevels
=
==========================
*/
void SetLightLevels ( int darkness )
{
    if (fog==0)
    {
        baseminshade=0x10+((7-darkness)>>1);
        basemaxshade=0x1f-(darkness>>1);
    }
    else
    {
        baseminshade=darkness;
        basemaxshade=0x10;
    }
    minshade=baseminshade;
    maxshade=basemaxshade;
    darknesslevel=darkness;
}

/*
==========================
=
= GetLightLevelTile
=
==========================
*/
int GetLightLevelTile ( void )
{
    if (fog==0)
    {
        return ((7-((baseminshade-0x10)<<1))+LIGHTLEVELBASE);
    }
    else
    {
        return (baseminshade+LIGHTLEVELBASE);
    }
}


/*
==========================
=
= SetLightRate
=
==========================
*/
void SetLightRate ( int rate )
{
    normalshade=(HEIGHTFRACTION+8)-rate;
    if (normalshade>14) normalshade=14;
    if (normalshade<3) normalshade=3;
}

/*
==========================
=
= GetLightRate
=
==========================
*/
int GetLightRate ( void )
{
    return ((HEIGHTFRACTION+8)-normalshade);
}

/*
==========================
=
= GetLightRateTile
=
==========================
*/
int GetLightRateTile ( void )
{
    return ((HEIGHTFRACTION+8)-normalshade+LIGHTRATEBASE);
}



/*
==========================
=
= UpdateLightLevel
=
==========================
*/
void UpdateLightLevel (int area)
{
    int numlights;
    int targetmin;
    int targetmax;
    int numtiles;

    if (fog==true)
        return;

    numtiles=(numareatiles[area]>>5)-2;
    numlights=(LightsInArea[area]-numtiles)>>1;

    if (numlights<0)
        numlights=0;
    if (numlights>GENERALNUMLIGHTS)
        numlights=GENERALNUMLIGHTS;
    targetmin=baseminshade+(GENERALNUMLIGHTS-numlights);
    targetmax=basemaxshade-numlights;
    if (targetmax<baseminshade)
        targetmax=baseminshade;
    if (targetmin>targetmax)
        targetmin=targetmax;

    if (minshade>targetmin)
        minshade-=1;
    else if (minshade<targetmin)
        minshade+=1;

    if (maxshade>targetmax)
        maxshade-=1;
    else if (maxshade<targetmax)
        maxshade+=1;
}

/*
==========================
=
= SetIllumination
=
= Postive value lightens
= Negative value darkens
=
==========================
*/
void SetIllumination (int level)
{
    if (fog)
        return;
    maxshade-=level;
    if (maxshade>31)
        maxshade=31;
    if (maxshade<0x10)
        maxshade=0x10;
    minshade-=level;
    if (minshade<0x10)
        minshade=0x10;
    if (minshade>31)
        minshade=31;
}

/*
==========================
=
= GetIlluminationDelta
=
==========================
*/
int GetIlluminationDelta (void)
{
    if (fog)
        return 0;
    else
        return maxshade-basemaxshade;
}

/*
==========================
=
= UpdateLightning
=
==========================
*/
void UpdateLightning (void)
{
    if (periodic==true)
    {
        UpdatePeriodicLighting();
        return;
    }
    if ((fog==1) || (lightning==false))
        return;

    if (lightningtime<=0)
    {
        if (lightningsoundtime>0)
            SD_Play3D (SD_LIGHTNINGSND, 0, lightningdistance);
        lightningtime=GameRandomNumber("UpdateLightning",0)<<1;
        lightningdistance=GameRandomNumber("UpdateLightning",0);
        lightninglevel=(255-lightningdistance)>>LIGHTNINGLEVEL;
        if (lightninglevel<MINLIGHTNINGLEVEL)
            lightninglevel=MINLIGHTNINGLEVEL;
        if (lightninglevel>MAXLIGHTNINGLEVEL)
            lightninglevel=MAXLIGHTNINGLEVEL;
        lightningdelta=lightninglevel>>1;
        lightningsoundtime=lightningdistance>>1;
        if (lightningdistance<100)
        {
            SetIllumination(lightninglevel);
        }
    }
    else if (lightninglevel>0)
    {
        lightninglevel-=lightningdelta;
        if (lightninglevel<=0)
        {
            lightninglevel=0;
        }
    }
    else
        lightningtime--;
    if (lightningsoundtime)
    {
        lightningsoundtime--;
        if (lightningsoundtime<=0)
        {
            int volume;

            volume=255-lightningdistance;
            if (volume>170) volume=170;
            SD_PlayPitchedSound ( SD_LIGHTNINGSND, volume,-(lightningdistance<<2));
            lightningsoundtime=0;
        }
    }
}

/*
==========================
=
= UpdatePeriodicLighting
=
==========================
*/
#define PERIODICMAG (6)
#define PERIODICSTEP (20)
#define PERIODICBASE (0x0f)
void UpdatePeriodicLighting (void)
{
    int val;

    val=FixedMul(PERIODICMAG,sintable[periodictime]);
    periodictime=(periodictime+PERIODICSTEP)&(FINEANGLES-1);
    basemaxshade=PERIODICBASE+(PERIODICMAG)+val;
    baseminshade=basemaxshade-GENERALNUMLIGHTS-1;
}

/*
==========================
=
= SetModemLightLevel
=
==========================
*/
void SetModemLightLevel ( int type )
{
    periodic=false;
    fulllight=false;
    switch (type)
    {
    case bo_light_dark:
        MAPSPOT(2,0,0)=(word)216;
        MAPSPOT(3,0,0)=(word)255;
        MAPSPOT(3,0,1)=(word)139;
        MAPSPOT(2,0,1)=(word)104;
        SetupLightLevels ();
        break;
    case bo_light_normal:
        break;
    case bo_light_bright:
        MAPSPOT(2,0,0)=(word)223;
        MAPSPOT(3,0,0)=(word)267;
        MAPSPOT(3,0,1)=(word)0;
        MAPSPOT(2,0,1)=(word)104;
        SetupLightLevels ();
        break;
    case bo_light_fog:
        MAPSPOT(2,0,0)=(word)219;
        MAPSPOT(3,0,0)=(word)259;
        MAPSPOT(2,0,1)=(word)105;
        MAPSPOT(3,0,1)=(word)0;
        SetupLightLevels ();
        break;
    case bo_light_periodic:
        fog=0;
        MAPSPOT(2,0,1)=(word)104;
        MAPSPOT(3,0,1)=(word)139;
        SetupLightLevels ();
        periodic=true;
        break;
    case bo_light_lightning:
        if (sky!=0)
        {
            MAPSPOT(2,0,0)=(word)222;
            MAPSPOT(3,0,0)=(word)255;
            MAPSPOT(3,0,1)=(word)139;
            MAPSPOT(2,0,1)=(word)104;
            SetupLightLevels ();
            lightning=true;
        }
        break;
    }
}

