//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------


#include "interior/interiorInstance.h"
#include "interior/lightUpdateGrouper.h"
#include "interior/interior.h"
#include "math/mRandom.h"

void InteriorInstance::echoTriggerableLights()
{
   // DMMFIX: Only the first detail for now...
   Interior* pInterior = mInteriorRes->getDetailLevel(0);

   Con::printf("Interior: %s", mInteriorFileName);
   Con::printf("  %d Triggerable lights:", pInterior->mNumTriggerableLights);

   // Triggerable lights are always the first in the array...
   for (U32 i = 0; i < pInterior->mNumTriggerableLights; i++) {
      const char* pName = pInterior->getName(pInterior->mAnimatedLights[i].nameIndex);
      U32 type          = pInterior->mAnimatedLights[i].flags & Interior::AnimationTypeMask;
      float duration    = pInterior->mAnimatedLights[i].duration;
      U32 numStates     = pInterior->mAnimatedLights[i].stateCount;

      Con::printf("  - %s [%s, Duration: %g, NumStates: %d]",
                      pName, Interior::getLightTypeString(Interior::LightType(type)),
                      duration, numStates);
   }
}

void InteriorInstance::activateLight(const char* pLightName)
{
   if (bool(mInteriorRes) == false) {
      AssertWarn(false, "Activating a light on an unloaded interior!");
      return;
   }

   // Now, it's a real pain in the ass to try to keep track of light states on detail
   //  changes as we did in tribes 1.  There, we analyzed the state on a detail change
   //  and tried to duplicate that state on the detail level we were switching to.
   //  Inspiration: forget that, and just animate the lights on all the details all
   //  the time.  Unless the detail is rendering, the lightmap data will never be
   //  downloaded, and the amount of time necessary to keep the lights updated on
   //  a detail level is absolutely miniscule.  Much easier.
   //
   for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) {
      Interior* pInterior = mInteriorRes->getDetailLevel(i);

      for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) {
         const char* pILightName = pInterior->getName(pInterior->mAnimatedLights[j].nameIndex);
         if (dStricmp(pLightName, pILightName) == 0) {
            activateLight(i, j);
            break;
         }
      }
   }
}

void InteriorInstance::deactivateLight(const char* pLightName)
{
   if (bool(mInteriorRes) == false) {
      AssertWarn(false, "Deactivating a light on an unloaded interior!");
      return;
   }

   for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) {
      Interior* pInterior = mInteriorRes->getDetailLevel(i);

      for (U32 j = 0; j < pInterior->mNumTriggerableLights; j++) {
         const char* pILightName = pInterior->getName(pInterior->mAnimatedLights[j].nameIndex);
         if (dStricmp(pLightName, pILightName) == 0) {
            deactivateLight(i, j);
            break;
         }
      }
   }
}

void InteriorInstance::updateAllLights(const U32 ms)
{
   if (bool(mInteriorRes) == false)
      return;

   for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) {
      LightInfo& rLightInfo = mLightInfo[i];

      for (U32 j = 0; j < rLightInfo.mLights.size(); j++) {
         if (mAlarmState == Normal) {
            if (rLightInfo.mLights[j].active == true && rLightInfo.mLights[j].alarm == false)
               updateLightTime(i, j, ms);
         } else {
            if (rLightInfo.mLights[j].alarm == true)
               updateLightTime(i, j, ms);
         }
      }
   }
}


//--------------------------------------------------------------------------
void InteriorInstance::activateLight(const U32 detail, const U32 lightIndex)
{
   AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
   AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");

   LightInfo& rLightInfo    = mLightInfo[detail];
   LightInfo::Light& rLight = rLightInfo.mLights[lightIndex];

   if (rLight.active == false) {
      rLight.active   = true;
      rLight.curState = 0;
      rLight.curTime  = 0;

      Interior* pInterior = mInteriorRes->getDetailLevel(detail);
      Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[lightIndex].stateIndex];
      rLight.curColor.set(rState.red, rState.green, rState.blue);

      installLight(detail, lightIndex);

      if (isServerObject() && lightIndex < pInterior->mNumTriggerableLights) {
         U32 key = makeUpdateKey(detail, lightIndex);
         U32 mask = mUpdateGrouper->getKeyMask(key);
         setMaskBits(mask);
      }
   } else {
      // Light is already active, no need to play around further...
      //
   }
}


//--------------------------------------------------------------------------
void InteriorInstance::deactivateLight(const U32 detail, const U32 lightIndex)
{
   AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
   AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");

   LightInfo& rLightInfo    = mLightInfo[detail];
   LightInfo::Light& rLight = rLightInfo.mLights[lightIndex];

   if (rLight.active == true) {
      // DMMFIX

      rLight.active   = false;
      rLight.curState = 0;
      rLight.curTime  = 0;

      Interior* pInterior = mInteriorRes->getDetailLevel(detail);
      Interior::LightState& rState = pInterior->mLightStates[pInterior->mAnimatedLights[lightIndex].stateIndex];
      rLight.curColor.set(rState.red, rState.green, rState.blue);

      installLight(detail, lightIndex);

      if (isServerObject() && lightIndex < pInterior->mNumTriggerableLights) {
         U32 key = makeUpdateKey(detail, lightIndex);
         U32 mask = mUpdateGrouper->getKeyMask(key);
         setMaskBits(mask);
      }
   } else {
      // Light is already inactive, no need to play around further...
      //
   }
}


//--------------------------------------------------------------------------
void InteriorInstance::updateLightTime(const U32 detail, const U32 lightIndex, const U32 ms)
{
   AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
   AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");

   LightInfo& rLightInfo = mLightInfo[detail];
   Interior* pInterior   = mInteriorRes->getDetailLevel(detail);

   LightInfo::Light& rLight         = rLightInfo.mLights[lightIndex];
   Interior::AnimatedLight& rILight = pInterior->mAnimatedLights[lightIndex];

   U32    oldState = rLight.curState;
   ColorI oldColor = rLight.curColor;

   // Ok, now we need to break this down a bit.  We pass the update along to
   //  the specialized updating functions based on lightType.
   switch (rILight.flags & Interior::AnimationTypeMask) {
     case Interior::AmbientLooping:
     case Interior::TriggerableLoop:
      updateLoopingLight(pInterior, rLight, lightIndex, ms);
      break;

     case Interior::AmbientFlicker:
     case Interior::TriggerableFlicker:
      updateFlickerLight(pInterior, rLight, lightIndex, ms);
      break;

     case Interior::TriggerableRamp:
      updateRampLight(pInterior, rLight, lightIndex, ms);
      break;

     default:
      AssertFatal(false, "Bad light type in updateLightTime");
   }

   if (rLight.curState != oldState ||
       rLight.curColor != oldColor) {
      // Need to reinstall the light
      installLight(detail, lightIndex);
   }
}


//--------------------------------------------------------------------------
void InteriorInstance::updateLoopingLight(Interior* interior, LightInfo::Light& light,
                                          const U32 lightIndex, const U32 ms)
{
   AssertISV( lightIndex < interior->mAnimatedLights.size( ), "out of bounds array access in InteriorInstance::updateLoopingLight" );
   Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex];

   light.curTime += ms;
   light.curTime %= rILight.duration;

   // Find the last state that has a active time below this new time...
   light.curState = 0;
   for (U32 i = 1; i < rILight.stateCount; i++) {
      Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i];
      if (rState.activeTime <= light.curTime)
         light.curState = i;
      else
         break;
   }

   // interpolate the color
   Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState];
   Interior::LightState* pNextState;

   U32 msIntoState = light.curTime - rState.activeTime;
   U32 msTotal;
   if (light.curState != (rILight.stateCount - 1)) {
      // Have one more good state
      pNextState  = &interior->mLightStates[rILight.stateIndex + light.curState + 1];
      msTotal = pNextState->activeTime - rState.activeTime;
   } else {
      // Have to interpolate against the first state...
      pNextState  = &interior->mLightStates[rILight.stateIndex];
      msTotal = rILight.duration - rState.activeTime;
   }

   F32 interp = F32(msIntoState) / F32(msTotal);
   F32 red    = F32(rState.red)   * (1.0f - interp) + F32(pNextState->red)   * interp;
   F32 green  = F32(rState.green) * (1.0f - interp) + F32(pNextState->green) * interp;
   F32 blue   = F32(rState.blue)  * (1.0f - interp) + F32(pNextState->blue)  * interp;

   light.curColor.set(U8(red + 0.5f), U8(green + 0.5f), U8(blue + 0.5f));
}


//--------------------------------------------------------------------------
void InteriorInstance::updateFlickerLight(Interior* interior, LightInfo::Light& light,
                                          const U32 lightIndex, const U32 ms)
{
   Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex];

   U32 switchPeriod = interior->mLightStates[interior->mAnimatedLights[lightIndex].stateIndex + 1].activeTime;
   U32 oldTime    = light.curTime;
   light.curTime += ms;
   if (light.curTime < switchPeriod)
      return;

   light.curTime = 0;

   // Ok, pick a random number from 0 to the light duration, and find the state that
   //  it falls in.

   static MRandomLCG randomGen;
   U32 pickedTime = randomGen.randI(0, rILight.duration);

   light.curState = 0;
   for (U32 i = 1; i < rILight.stateCount; i++) {
      Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i];
      if (rState.activeTime <= pickedTime)
         light.curState = i;
      else
         break;
   }

   Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState];
   light.curColor.set(rState.red, rState.green, rState.blue);
}


//--------------------------------------------------------------------------
void InteriorInstance::updateRampLight(Interior* interior, LightInfo::Light& light,
                                       const U32 lightIndex, const U32 ms)
{
   Interior::AnimatedLight& rILight = interior->mAnimatedLights[lightIndex];

   light.curTime += ms;
   if (light.curTime > rILight.duration)
      light.curTime = rILight.duration;

   // Find the last state that has a active time below this new time...
   light.curState = 0;
   for (U32 i = 1; i < rILight.stateCount; i++) {
      Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + i];
      if (rState.activeTime <= light.curTime)
         light.curState = i;
      else
         break;
   }

   // interpolate the color
   Interior::LightState& rState = interior->mLightStates[rILight.stateIndex + light.curState];
   Interior::LightState* pNextState;

   U32 msIntoState = light.curTime - rState.activeTime;
   U32 msTotal;
   if (light.curState != (rILight.stateCount - 1)) {
      // Have one more good state
      pNextState  = &interior->mLightStates[rILight.stateIndex + light.curState + 1];
      msTotal = pNextState->activeTime - rState.activeTime;
   } else {
      // A ramp light does NOT NOT NOT interp against the first state
      pNextState = &rState;
      msTotal    = msIntoState;
   }

   F32 interp = F32(msIntoState) / F32(msTotal);
   F32 red    = F32(rState.red)   * (1.0f - interp) + F32(pNextState->red)   * interp;
   F32 green  = F32(rState.green) * (1.0f - interp) + F32(pNextState->green) * interp;
   F32 blue   = F32(rState.blue)  * (1.0f - interp) + F32(pNextState->blue)  * interp;

   light.curColor.set(U8(red + 0.5f), U8(green + 0.5f), U8(blue + 0.5f));
}


//--------------------------------------------------------------------------
void InteriorInstance::installLight(const U32 detail, const U32 lightIndex)
{
   AssertFatal(bool(mInteriorRes) && detail < mInteriorRes->getNumDetailLevels(), "Error, no interior resource, or out of range detail level");
   AssertFatal(lightIndex < mInteriorRes->getDetailLevel(detail)->mAnimatedLights.size(), "Error, out of bounds light index");

   LightInfo& rLightInfo    = mLightInfo[detail];
   LightInfo::Light& rLight = rLightInfo.mLights[lightIndex];

   // All we are allowed to assume is that the light time, color, and state are
   //  correct here. We must install all statedata, and invalidate all surfaces.
   //  First, let's retrieve the actual light from the Interior
   //
   Interior* pInterior = mInteriorRes->getDetailLevel(detail);
   Interior::AnimatedLight& rILight = pInterior->mAnimatedLights[lightIndex];
   Interior::LightState&    rIState = pInterior->mLightStates[rILight.stateIndex + rLight.curState];

   // Ok.  Now, cycle through the light's state data, and install it
   for (U32 i = rIState.dataIndex; i < (rIState.dataIndex + rIState.dataCount); i++) {
      Interior::LightStateData& rIData = pInterior->mStateData[i];
      LightInfo::StateDataInfo& rData  = rLightInfo.mStateDataInfo[rIData.lightStateIndex];

      if (rIData.mapIndex != 0xFFFFFFFF) {
         rData.curMap = &pInterior->mStateDataBuffer[rIData.mapIndex];
      } else {
         rData.curMap = NULL;
      }
      rData.curColor = rLight.curColor;
      rData.alarm    = (rILight.flags & Interior::AlarmLight) != 0;
      rLightInfo.mSurfaceInvalid.set(rIData.surfaceIndex);
   }
}

//--------------------------------------------------------------------------
void InteriorInstance::intensityMapMerge(U8*           lightMap,
                                         const U32     width,
                                         const U32     height,
                                         const U8*     intensityMap,
                                         const ColorI& color)
{
   // lightmap is a 24bit RGB texture, intensitymap is an 8 bit intensity
   //  map.  We want lightmap = [lightmap + (intensityMap * color)]

   // DMMFIX: SLOWSLOWSLOW!  Need MMX version of this at the very least,
   //  this version is only for clarity;
   for (U32 y = 0; y < height; y++) {
      for (U32 x = 0; x < width; x++) {
         U8* data      = &lightMap[(y * width + x) * 3];
         U32 intensity = intensityMap[(y * width + x)];

         U32 newRed   = data[0];
         U32 newGreen = data[1];
         U32 newBlue  = data[2];

         U32 addRed   = (U32(color.red)   * intensity + 0x80) >> 8;
         U32 addGreen = (U32(color.green) * intensity + 0x80) >> 8;
         U32 addBlue  = (U32(color.blue)  * intensity + 0x80) >> 8;

         newRed   += addRed;
         newGreen += addGreen;
         newBlue  += addBlue;

         data[0] = (newRed   <= 255) ? U8(newRed)   : 0xFF;
         data[1] = (newGreen <= 255) ? U8(newGreen) : 0xFF;
         data[2] = (newBlue  <= 255) ? U8(newBlue)  : 0xFF;
      }
   }
}

