///////////////////////////////////////////////////////////////////////////////
//  VARIABLE DECLARATIONS
///////////////////////////////////////////////////////////////////////////////

const string PRC_Rest_Generation = "PRC_Rest_Generation";
const string PRC_Rest_Generation_Check = "PRC_Rest_Generation_Check";
const string PRC_ForcedRestDetector_Generation = "PRC_ForcedRestDetector_Generation";

///////////////////////////////////////////////////////////////////////////////
//  INCLUDES
///////////////////////////////////////////////////////////////////////////////

#include "prc_alterations"

///////////////////////////////////////////////////////////////////////////////
//  FUNCTION DECLARATIONS
///////////////////////////////////////////////////////////////////////////////

// Returns the number of henchmen a player has.
int GetNumHenchmen(object oPC);

// returns the float time in seconds to close the given distance
float GetTimeToCloseDistance(float fMeters, object oPC, int bIsRunning = FALSE);

/* PRC ForceRest wrapper
 *
 * ForceRest does not trigger the module's OnRest event, nor will the targeted player show up
 * when GetLastPCRested is used. This wrapper can be used to ForceRest a target, while still
 * running the PRC's OnRest script.
 */
void PRCForceRest(object oPC);
void PRCForceRested(object oPC);

///////////////////////////////////////////////////////////////////////////////
//  FUNCTION DEFINITIONS
///////////////////////////////////////////////////////////////////////////////

int GetNumHenchmen(object oPC)
{
     if (!GetIsPC(oPC)) return -1;

     int nLoop, nCount;
     for (nLoop = 1; nLoop <= GetMaxHenchmen(); nLoop++)
     {
          if (GetIsObjectValid(GetHenchman(oPC, nLoop)))
          nCount++;
     }

     return nCount;
}

float GetTimeToCloseDistance(float fMeters, object oPC, int bIsRunning = FALSE)
{
     float fTime = 0.0;
     float fSpeed = 0.0;

     int iMoveRate = GetMovementRate(oPC);

     switch(iMoveRate)
     {
          case 0:
               fSpeed = 2.0;
               break;
          case 1:
               fSpeed = 0.0;
               break;
          case 2:
               fSpeed = 0.75;
               break;
          case 3:
               fSpeed = 1.25;
               break;
          case 4:
               fSpeed = 1.75;
               break;
          case 5:
               fSpeed = 2.25;
               break;
          case 6:
               fSpeed = 2.75;
               break;
          case 7:
               fSpeed = 2.0;  // could change to creature default in the appearance.2da.
               break;
          case 8:
               fSpeed = 5.50;
               break;
     }

     // movement speed doubled if running
     if(bIsRunning) fSpeed *= 2.0;

     // other effects that can change movement speed
     if( PRCGetHasEffect(EFFECT_TYPE_HASTE, oPC) ) fSpeed *= 2.0;
     if( PRCGetHasEffect(EFFECT_TYPE_MOVEMENT_SPEED_INCREASE, oPC) ) fSpeed *= 2.0;

     if( PRCGetHasEffect(EFFECT_TYPE_SLOW,  oPC) ) fSpeed /= 2.0;
     if( PRCGetHasEffect(EFFECT_TYPE_MOVEMENT_SPEED_DECREASE,  oPC) ) fSpeed /= 2.0;

     if( GetHasFeat(FEAT_BARBARIAN_ENDURANCE, oPC) ) fSpeed *= 1.1; // 10% gain
     if( GetHasFeat(FEAT_MONK_ENDURANCE, oPC) )
     {
          float fBonus = 0.1 * (GetLevelByClass(CLASS_TYPE_MONK, oPC) / 3 );
          if (fBonus > 0.90) fBonus = 0.9;

          fBonus += 1.0;
          fSpeed *= fBonus;
     }

     // final calculation
     fTime = fMeters / fSpeed;

     return fTime;
}

int PRC_NextGeneration(int nCurrentGeneration)
{
    nCurrentGeneration++;
    if (nCurrentGeneration > 30000) 
        nCurrentGeneration = 1;
    return nCurrentGeneration;
}

//TODO: TRIED THIS CLEANER VERSION AND IT DIDN'T WORK; FIX IT AND USE IT LATER
// effect _prc_inc_ForcedRestDetectorEffect()
// {
//     //Create an extraordinary effect, which is removed only by resting. 
//     //Therefore, if it disappears, we know that resting has happened.
//     return ExtraordinaryEffect(EffectVisualEffect(VFX_DUR_CESSATE_NEUTRAL));
//         //TODO: this could match effects that are not ours. Make it more unique somehow.
// }
// 
// void _prc_inc_ApplyForcedRestDetectorEffect(object oPC)
// {
//     ApplyEffectToObject(DURATION_TYPE_PERMANENT, _prc_inc_ForcedRestDetectorEffect(), oPC);
// }
// 
// int _prc_inc_TestForcedRestDetectorEffect(effect eEffect)
// {
//     effect eTestEffect = _prc_inc_ForcedRestDetectorEffect();
//     return GetEffectType(eEffect) == GetEffectType(eTestEffect) && 
//         GetEffectSubType(eEffect) == GetEffectSubType(eTestEffect) &&
//         GetEffectDurationType(eEffect) == GetEffectDurationType(eTestEffect) && 
//         GetEffectSpellId(eEffect) == -1;
// }

void _prc_inc_ApplyForcedRestDetectorEffect(object oPC)
{
    //Apply an extraordinary effect, which is removed only by resting
    ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(EffectVisualEffect(VFX_DUR_CESSATE_NEUTRAL)), oPC);
        //TODO: this could match effects that are not ours. Make it more unique somehow.
}

int _prc_inc_TestForcedRestDetectorEffect(effect eEffect)
{
    return GetEffectType(eEffect) == EFFECT_TYPE_VISUALEFFECT && GetEffectSubType(eEffect) == SUBTYPE_EXTRAORDINARY && GetEffectDurationType(eEffect) == DURATION_TYPE_PERMANENT && GetEffectSpellId(eEffect) == -1;
        //TODO: this could match effects that are not ours. Make it more unique somehow.
}

void _prc_inc_ForcedRestDetector(object oPC, int nExpectedGeneration)
{
    int nGeneration = GetLocalInt(oPC, PRC_ForcedRestDetector_Generation);
    if (nGeneration != nExpectedGeneration)
    {
        //There's another forced rest detector running, so stop ourselves and let that one continue
        DoDebug("STOPPING DUPLICATE FORCED REST DETECTOR");
        return;
    }
    nGeneration = PRC_NextGeneration(nGeneration);
    SetLocalInt(oPC, PRC_ForcedRestDetector_Generation, nGeneration);

    int nRestGeneration = GetLocalInt(oPC, PRC_Rest_Generation);
    int nRestGenerationCheck = GetLocalInt(oPC, PRC_Rest_Generation_Check);
    if (nRestGeneration != nRestGenerationCheck)
    {
        //A normal rest happened, and the delayed commands it schedules may still 
        //be executing or waiting to execute, so do nothing and check back later.
        SetLocalInt(oPC, PRC_Rest_Generation_Check, nRestGeneration);
        DelayCommand(10.0f, _prc_inc_ApplyForcedRestDetectorEffect(oPC));
        DelayCommand(10.1f, _prc_inc_ForcedRestDetector(oPC, nGeneration));
    }
    else
    {    
        int bFound = FALSE;
        effect eEffect = GetFirstEffect(oPC);
        while (GetIsEffectValid(eEffect))
        {
            if (_prc_inc_TestForcedRestDetectorEffect(eEffect))
            {
                bFound = TRUE;
                break;
            }
            eEffect = GetNextEffect(oPC);
        }
    
        if (!bFound)
        {
            DoDebug("FORCED REST DETECTED");
            PRCForceRested(oPC); //This executes the normal resting code, which will be detected above the next time this function executes
        }

        //Schedule next check
        DelayCommand(1.0f, _prc_inc_ForcedRestDetector(oPC, nGeneration));
    }
}

void StartForcedRestDetector(object oPC)
{
    //TODO: make a way for the detector to detect that forced rest has already happened, or else do it here now (but how?)
    DoDebug("STARTING FORCED REST DETECTOR");
    _prc_inc_ApplyForcedRestDetectorEffect(oPC);
    SetLocalInt(oPC, PRC_Rest_Generation_Check, GetLocalInt(oPC, PRC_Rest_Generation));
    int nGeneration = GetLocalInt(oPC, PRC_ForcedRestDetector_Generation);
    DelayCommand(1.0f, _prc_inc_ForcedRestDetector(oPC, nGeneration));
}

void PRCForceRest(object oPC)
{
    ForceRest(oPC);
    PRCForceRested(oPC);
}

void PRCForceRested(object oPC)
{
    //The PC has been forced rested--fix the problems this causes.
    SetLocalInt(oPC, "PRC_ForceRested", 1);
    ExecuteScript("prc_rest", oPC);
}