/*=============================================================================
    Name    : autolod.c
    Purpose : routines for maintaining a more even framerate

    Created 9/8/1998 by khent
    Copyright Relic Entertainment, Inc.  All rights reserved.
=============================================================================*/

#include "glcaps.h"
#include "autolod.h"
#include "lod.h"
#include "statscript.h"
#include "options.h"


/*=============================================================================
    Data
=============================================================================*/

static udword alodNumPolys;
static udword alodTargetPolys = 2000;
static udword alodPolyDelta;
static real32 alodIdealScaleFactor;
static real32 alodMaxScaleFactor;
static real32 alodMinScaleFactor;
static real32 alodScaleFactorDelta = 0.008f;
static bool   alodAmPanicking;

static real32 alodFastMinScale = 0.2f;
static real32 alodFastMaxScale = 1.2f;
static udword alodFastTargetPolys = 2000;
static udword alodFastTargetDelta = 350;
static real32 alodSlowMinScale = 0.1f;
static real32 alodSlowMaxScale = 0.7f;
static udword alodSlowTargetPolys = 1400;
static udword alodSlowTargetDelta = 300;

static udword alodActivePolyDelta;

typedef enum
{
    alodOK,
    alodGoingDown,
    alodGoingUp,
    alodGotDown
} alodState_t;

static alodState_t alodState;
static bool alodEnabled;

sdword alodDownDelta = 0;

scriptEntry AutoLODTweaks[] =
{
    makeEntry(alodScaleFactorDelta, scriptSetReal32CB),
    makeEntry(alodFastMinScale, scriptSetReal32CB),
    makeEntry(alodFastMaxScale, scriptSetReal32CB),
    makeEntry(alodFastTargetPolys, scriptSetUdwordCB),
    makeEntry(alodFastTargetDelta, scriptSetUdwordCB),
    makeEntry(alodSlowMinScale, scriptSetReal32CB),
    makeEntry(alodSlowMaxScale, scriptSetReal32CB),
    makeEntry(alodSlowTargetPolys, scriptSetUdwordCB),
    makeEntry(alodSlowTargetDelta, scriptSetUdwordCB),
    endEntry
};


/*=============================================================================
    Code
=============================================================================*/

/*-----------------------------------------------------------------------------
    Name        : alodStartup
    Description : initialize the autolod module
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodStartup(void)
{
    alodIdealScaleFactor = lodScaleFactor;

    alodReset();
}

/*-----------------------------------------------------------------------------
    Name        : alodReset
    Description : reset the autolod module
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodReset(void)
{
    lodScaleFactor = alodIdealScaleFactor;

    alodState = alodOK;
    alodAmPanicking = FALSE;
    alodEnabled = TRUE;

    if (RGL && (RGLtype == SWtype))
    {
        alodSetMinMax(alodSlowMinScale, alodSlowMaxScale);
        alodSetTargetPolys(0/*alodSlowTargetPolys*/, alodSlowTargetDelta);
    }
    else
    {
        alodSetMinMax(alodFastMinScale, alodFastMaxScale);
        alodSetTargetPolys(0/*alodFastTargetPolys*/, alodFastTargetDelta);
    }
}

/*-----------------------------------------------------------------------------
    Name        : alodShutdown
    Description : shutdown the autolod module
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodShutdown(void)
{
    lodScaleFactor = alodIdealScaleFactor;
    alodState = alodOK;
    alodAmPanicking = FALSE;
}

/*-----------------------------------------------------------------------------
    Name        : alodSetMinMax
    Description : set the lodScaleFactor limits
    Inputs      : minScale - minimum scale factor
                  maxScale - maximum scale factor
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodSetMinMax(real32 minScale, real32 maxScale)
{
    alodMinScaleFactor = minScale;
    alodMaxScaleFactor = maxScale;
}

/*-----------------------------------------------------------------------------
    Name        : alodGetMin
    Description : return minScale
    Inputs      :
    Outputs     :
    Return      : minScale
----------------------------------------------------------------------------*/
real32 alodGetMin(void)
{
    return alodMinScaleFactor;
}

/*-----------------------------------------------------------------------------
    Name        : alodGetMax
    Description : return maxScale
    Inputs      :
    Outputs     :
    Return      : maxScale
----------------------------------------------------------------------------*/
real32 alodGetMax(void)
{
    return alodMaxScaleFactor;
}

/*-----------------------------------------------------------------------------
    Name        : alodSetTargetPolys
    Description : set the acceptable onscreen polygon limits
    Inputs      : targetPolys - ideal number of polys onscreen
                  polyDelta - acceptable delta, used to prevent hysteresis
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodSetTargetPolys(udword targetPolys, udword polyDelta)
{
    if (targetPolys != 0)
    {
        alodTargetPolys = targetPolys;
    }
    if (polyDelta != 0)
    {
        alodActivePolyDelta = alodPolyDelta = polyDelta;
    }
}

/*-----------------------------------------------------------------------------
    Name        : alodGetTargetPolys
    Description : get the acceptable onscreen polygon limits
    Inputs      : targetPolys, polyDelta - where poly limits go
    Outputs     : targetPolys, polyDelta are set
    Return      :
----------------------------------------------------------------------------*/
void alodGetTargetPolys(udword* targetPolys, udword* polyDelta)
{
    *targetPolys = alodTargetPolys;
    *polyDelta = alodPolyDelta;
}

/*-----------------------------------------------------------------------------
    Name        : alodSetPolys
    Description : directly set the number of polys rendered
    Inputs      : polys - number of polys
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodSetPolys(udword polys)
{
    alodNumPolys = polys;
}

/*-----------------------------------------------------------------------------
    Name        : alodIncPolys
    Description : add to the number of polys rendered
    Inputs      : polys - number of polys to add to total
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodIncPolys(udword polys)
{
    if (alodEnabled)
    {
        alodNumPolys += polys;
    }
}

/*-----------------------------------------------------------------------------
    Name        : alodEnable
    Description : enable or disable the effect of calling alodIncPolys
    Inputs      : enable - TRUE or FALSE (enable or disable)
    Outputs     : alodEnabled is set to enable
    Return      :
----------------------------------------------------------------------------*/
void alodEnable(bool enable)
{
    alodEnabled = enable;
}

/*-----------------------------------------------------------------------------
    Name        : alodGetPolys
    Description : returns the number of polys rendered
    Inputs      :
    Outputs     : number of polys
    Return      :
----------------------------------------------------------------------------*/
udword alodGetPolys(void)
{
    return alodNumPolys;
}

/*-----------------------------------------------------------------------------
    Name        : alodGetPanic
    Description : returns a boolean indicating whether lodScaleFactor adjustment
                  didn't do the trick by itself
    Inputs      :
    Outputs     : TRUE if panicking, FALSE otherwise
    Return      :
----------------------------------------------------------------------------*/
bool alodGetPanic(void)
{
    return alodAmPanicking;
}

/*-----------------------------------------------------------------------------
    Name        : alodSetPanic
    Description : control whether framerate panic is ensuing
    Inputs      : panic - TRUE or FALSE
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodSetPanic(bool panic)
{
    alodAmPanicking = panic;
}

/*-----------------------------------------------------------------------------
    Name        : alodAdjustScaleFactor
    Description : applies auto lodScaleFactor tweaking
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void alodAdjustScaleFactor(void)
{
    real32 scaleDelta;
    udword delta;

    if (alodTargetPolys == 0 ||
        alodPolyDelta == 0 ||
        opNoLODVal)
    {
        //we're not active
        alodSetPanic(FALSE);
        lodScaleFactor = alodMaxScaleFactor;
        return;
    }

    //distance from ideal poly count
    delta = abs((sdword)(alodTargetPolys - alodNumPolys));

    alodSetPanic(FALSE);

    if (alodNumPolys > alodTargetPolys)
    {
        //may want to reduce detail

        if (alodState == alodGoingUp)
        {
            //avoid hysteresis
            if (alodNumPolys < (alodTargetPolys + alodPolyDelta))
            {
                return;
            }
        }

        alodState = alodGoingDown;
        alodActivePolyDelta = alodPolyDelta;//delta;

        //adjust scalefactor decrement wrt distance
        scaleDelta = alodScaleFactorDelta;
        if (delta > alodPolyDelta)
        {
            scaleDelta *= 2.0f;
            if (delta > 2*alodPolyDelta)
            {
                scaleDelta *= 2.0f;
            }
        }
        lodScaleFactor -= scaleDelta;   //reduce scalefactor
        if (lodScaleFactor < alodMinScaleFactor)
        {
            //cap scalefactor
            lodScaleFactor = alodMinScaleFactor;
            alodState = alodGotDown;

            if (alodNumPolys > (alodTargetPolys + alodPolyDelta))
            {
                //still too many polys, set PANIC mode
                alodSetPanic(TRUE);
            }
        }
    }
    else
    {
        //FIXME: this ALWAYS overshoots
        //may want to increase detail

        if (delta > alodPolyDelta)
        {
            if ((alodNumPolys < (alodTargetPolys - alodPolyDelta)) ||
                (alodState != alodGotDown))
            {
                alodState = alodGoingUp;

                //adjust scalefactor increment wrt distance
                scaleDelta = alodScaleFactorDelta;
                if (delta > alodPolyDelta)
                {
                    if (delta > 2*alodPolyDelta)
                    {
                        scaleDelta *= 4.0f;
                    }
                    else
                    {
                        scaleDelta *= 2.0f;
                    }
                }
                lodScaleFactor += scaleDelta;   //increase scalefactor
                if (lodScaleFactor > alodMaxScaleFactor)
                {
                    //cap scalefactor
                    lodScaleFactor = alodMaxScaleFactor;
                }
            }
        }
    }
#if LOD_SCALE_DEBUG
    if (lodDebugScaleFactor != 0.0f)
    {
        lodScaleFactor = lodDebugScaleFactor;
    }
#endif
}
