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

#include "sceneGraph/lightManager.h"
#include "dgl/dgl.h"
#include "console/console.h"
#include "console/simBase.h"
#include "sceneGraph/sceneGraph.h"
#include "console/consoleTypes.h"

namespace {
   static GLfloat zeroColor[]    = {0.f, 0.f, 0.f, 0.f};
   static GLfloat whiteColor[]   = {1.f, 1.f, 1.f, 1.f};

   // global settings for the lightmanager
   static ColorF gOpenGLLightingAmbientColor(0.f, 0.f, 0.f, 0.f);
   static ColorF gOpenGLMaterialAmbientColor(0.f, 0.f, 0.f, 0.f);
   static ColorF gOpenGLMaterialDiffuseColor(1.f, 1.f, 1.f, 1.f);
   static S32 gOpenGLMaxHardwareLights = LightManager::DefaultMaxLights;

   bool colorIsZero(const ColorF & col)
   {
      return((col.red == 0.f) && (col.green == 0.f) && (col.blue == 0.f));
   }
}

//------------------------------------------------------------------------------
// Class LightManager
//------------------------------------------------------------------------------
ConsoleFunction( resetLighting, void, 1, 1, "Resets GL lighting.")
{
   if(!gClientSceneGraph)
   {
      Con::errorf(ConsoleLogEntry::General, "LightManager::cResetLighting: no client scenegraph!");
      return;
   }
   LightManager * lighting = gClientSceneGraph->getLightManager();
   if(!lighting)
   {
      Con::errorf(ConsoleLogEntry::General, "LightManager::cResetLighting: lightmanager not found!");
      return;
   }
   lighting->resetGL();
}

LightManager::LightManager()
{
   mGLInitialized = false;
   mGLLightsInstalled = false;
   mGLLightCount = 0;
   mAmbientLightColor.set(0.f,0.f,0.f);
   mVectorLightsAttenuation = 1.f;
   mVectorLightsEnabled = true;

   mSunLight = NULL;

#ifdef DEBUG
   Con::addVariable("$pref::OpenGL::lightingAmbientColor", TypeColorF, &gOpenGLLightingAmbientColor);
   Con::addVariable("$pref::OpenGL::materialAmbientColor", TypeColorF, &gOpenGLMaterialAmbientColor);
   Con::addVariable("$pref::OpenGL::materialDiffuseColor", TypeColorF, &gOpenGLMaterialDiffuseColor);
#endif
   Con::addVariable("$pref::OpenGL::maxHardwareLights", TypeS32, &gOpenGLMaxHardwareLights);
}

void LightManager::setMaxGLLights(U32 max)
{
   // ask gl for max light value, and check if this is greater..
   GLint glMaxLights = 0;
   glGetIntegerv(GL_MAX_LIGHTS, &glMaxLights);

   // clamp to the number gl returned
   if(max > glMaxLights)
   {
      Con::errorf(ConsoleLogEntry::General, "LightManager::setMaxGLLights: too many lights.");
      max = glMaxLights;
   }

   mMaxGLLights = max;

   // cleanup any installed lights
   if(mGLLightsInstalled)
      uninstallGLLights();
}

// will crash if called prior to PlatformVideo:: initialization!
void LightManager::resetGL()
{
   if(!mGLInitialized)
      return;

   if(mGLLightsInstalled)
      uninstallGLLights();

   // set ambient color
   glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, (const F32 *)gOpenGLMaterialAmbientColor);
   glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, (const F32 *)gOpenGLMaterialDiffuseColor);

   setMaxGLLights(gOpenGLMaxHardwareLights);
}

void LightManager::registerLights(bool lightingScene)
{
   if(!mGLInitialized)
   {
      mGLInitialized = true;
      resetGL();
   }

   if(mGLLightsInstalled)
   {
      Con::warnf(ConsoleLogEntry::General, "LightManager::registerLights: lights are already installed.");
      uninstallGLLights();
   }

   // clear out the light list
   mLights.clear();

   // walk the lightlist and query objects for their lights
   SimSet * lightSet = Sim::getLightSet();
   for(SimObject ** itr = lightSet->begin(); itr != lightSet->end(); itr++)
      (*itr)->registerLights(this, lightingScene);
}

void LightManager::addLight(LightInfo * light)
{
   mLights.push_back(light);

   if(light->mType == LightInfo::Vector)
      mSunLight = light;
}

void LightManager::removeLight(LightInfo * light)
{
   for(U32 i = 0; i < mLights.size(); i++)
   {
      if(mLights[i] == light)
      {
         mLights.erase_fast(i);
         break;
      }
   }

   if(mSunLight == light)
      mSunLight = NULL;
}

//--------------------------------------------------------------------------
U32 LightManager::installGLLights(const SphereF & sphere)
{
   // score the lights:
   for(U32 i = 0; i < mLights.size(); i++)
   {
      if(!mVectorLightsEnabled && (mLights[i]->mType == LightInfo::Vector))
         mLights[i]->mScore = 0;
      else
         scoreLight(mLights[i], sphere);
   }

   return(installGLLights());
}

U32 LightManager::installGLLights(const Box3F & box)
{
   SphereF sphere;
   box.getCenter(&sphere.center);
   sphere.radius = Point3F(box.max - sphere.center).len();

   return(installGLLights(sphere));
}

//--------------------------------------------------------------------------
U32 LightManager::installGLLights()
{
   AssertFatal(!mGLLightsInstalled, "LightManager::installGLLights: lights are already installed!");
   mGLLightsInstalled = true;

   // sort them
   dQsort(mLights.address(), mLights.size(), sizeof(LightInfo*), compareLights);

   // grab the best ones...
   U32 count = 0;
   while(count < mLights.size())
   {
      if(count >= mMaxGLLights)
         break;

      if(!mLights[count]->mScore)
         break;

      installGLLight(mLights[count++]);
   }

   // enable ambient and lighting always:
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (const F32 *)ColorF(gOpenGLLightingAmbientColor + mAmbientLightColor));
   glEnable(GL_LIGHTING);

   return(count);
}

//--------------------------------------------------------------------------
// - restores ambient light changes
// - restores vector light enabled flag
void LightManager::uninstallGLLights()
{
   if(!mGLLightsInstalled)
   {
      Con::errorf(ConsoleLogEntry::General, "LightManager::uninstallGLLights: no lights installed!");
      return;
   }
   mGLLightsInstalled = false;

   // restore values:
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (const F32*)gOpenGLLightingAmbientColor);

   mAmbientLightColor.set(0.f, 0.f, 0.f);
   mVectorLightsAttenuation = 1.f;
   mVectorLightsEnabled = true;

   glDisable(GL_LIGHTING);
   for(U32 i = 0; i < mGLLightCount; i++)
      glDisable(GL_LIGHT0 + i);
   mGLLightCount = 0;
}

void LightManager::installGLLight(LightInfo * lightInfo)
{
   AssertFatal(mGLLightCount < mMaxGLLights, "LightManager::installGLLight: too many lights installed");

   GLfloat lightColor[]   = {lightInfo->mColor.red, lightInfo->mColor.green, lightInfo->mColor.blue, 1.f};
   GLfloat ambientColor[] = {lightInfo->mAmbient.red, lightInfo->mAmbient.green, lightInfo->mAmbient.blue, 1.f};
   GLfloat lightPos[]     = {lightInfo->mPos.x, lightInfo->mPos.y, lightInfo->mPos.z, 1.f};
   GLfloat lightDir[]     = {-lightInfo->mDirection.x, -lightInfo->mDirection.y, -lightInfo->mDirection.z, 0.f};

   U32 light = GL_LIGHT0 + mGLLightCount++;

   glEnable(light);

   switch(lightInfo->mType)
   {
      case LightInfo::Spot:

         // set the light params
         glLightfv(light, GL_POSITION,       (const GLfloat*)lightPos);
         glLightfv(light, GL_SPOT_DIRECTION, (const GLfloat*)lightDir);
         glLightfv(light, GL_DIFFUSE,        (const GLfloat*)lightColor);
         glLightfv(light, GL_AMBIENT,        (const GLfloat*)zeroColor);
         glLightfv(light, GL_SPECULAR,       (const GLfloat*)lightColor);

         break;

      // falls through to LightInfo::Ambient
      case LightInfo::Vector:
      {
         F32 ambientFactor = (ambientColor[0] + ambientColor[1] + ambientColor[2]) / 3.f;
         F32 factor = mClampF(mVectorLightsAttenuation - ambientFactor, 0.f, 1.f);

         // attenuate (for in shadow...)
         for(U32 i = 0; i < 3; i++)
            lightColor[i] *= factor;
      }

      case LightInfo::Ambient:

         // set the light params
         glLightfv(light, GL_POSITION, (const GLfloat*)lightDir);
         glLightfv(light, GL_DIFFUSE,  (const GLfloat*)lightColor);
         glLightfv(light, GL_AMBIENT,  (const GLfloat*)ambientColor);
         glLightfv(light, GL_SPECULAR, (const GLfloat*)lightColor);

         break;

      case LightInfo::Point:

         // set the light params
         glLightfv(light, GL_POSITION, (const GLfloat*)lightPos);
         glLightfv(light, GL_DIFFUSE,  (const GLfloat*)lightColor);
         glLightfv(light, GL_AMBIENT,  (const GLfloat*)zeroColor);
         glLightfv(light, GL_SPECULAR, (const GLfloat*)lightColor);

         glLightf(light, GL_SPOT_CUTOFF, 180.f);

         glLightf(light, GL_CONSTANT_ATTENUATION, 0.f);
         glLightf(light, GL_LINEAR_ATTENUATION, 1.0f);
         glLightf(light, GL_QUADRATIC_ATTENUATION, 0.f);

         break;
   }
}

//------------------------------------------------------------------------------
void LightManager::getBestLights(const PlaneF * testPlanes, const U32 numPlanes,
   const Point3F & pos, LightInfoList & lightList, const U32 max)
{
   for(U32 i = 0; i < mLights.size(); i++)
      scoreLight(mLights[i], testPlanes, numPlanes, pos);

   fillLightList(lightList, max);
}

void LightManager::getBestLights(const SphereF & sphere, LightInfoList & lightList, const U32 max)
{
   // score the lights
   for(U32 i = 0; i < mLights.size(); i++)
      scoreLight(mLights[i], sphere);

   fillLightList(lightList, max);
}

void LightManager::getBestLights(const Box3F & box, LightInfoList & lightList, const U32 max)
{
   SphereF sphere;
   box.getCenter(&sphere.center);
   sphere.radius = Point3F(box.max - sphere.center).len();

   getBestLights(sphere, lightList, max);
}

const Point3F LightManager::getShadowLightDirection() const
{
   if(mSunLight)
      return mSunLight->mDirection;
   else
      // Do a lame default...
      return Point3F(0.573, 0.573, -0.573);
}

//------------------------------------------------------------------------------
void LightManager::fillLightList(LightInfoList & lightList, const U32 max)
{
   dQsort(mLights.address(), mLights.size(), sizeof(LightInfo*), compareLights);

   // grab the best ones...
   U32 count = 0;
   for(U32 i = 0; i < mLights.size(); i++)
   {
      if(!mLights[i]->mScore)
         continue;

      if(count++ >= max)
         break;

      lightList.push_back(mLights[i]);
   }
}

//------------------------------------------------------------------------------
// 0              not lit
// 1->100         point/spot lights
// 100->200       vector/ambient lights
void LightManager::scoreLight(LightInfo * lightInfo, const SphereF & sphere) const
{
   switch(lightInfo->mType)
   {
      case LightInfo::Ambient:
      case LightInfo::Vector:
      {
         // 100 + (color_intensity * 100)
         float intensity = (lightInfo->mColor.red   + lightInfo->mAmbient.red) * 0.346f +
                           (lightInfo->mColor.green + lightInfo->mAmbient.green) * 0.588f +
                           (lightInfo->mColor.blue  + lightInfo->mAmbient.blue) * 0.070f;

         lightInfo->mScore = 100 + S32(100.f * mClampF(intensity, 0.f, 1.f));
         break;
      }

      case LightInfo::Point:
      {
         Point3F diff = lightInfo->mPos - sphere.center;
         F32 lenSq = diff.lenSquared();
         F32 sumRadiusSq = (sphere.radius + lightInfo->mRadius) * (sphere.radius + lightInfo->mRadius);

         if(lenSq > sumRadiusSq)
         {
            lightInfo->mScore = 0;
            break;
         }

         F32 radSq = sphere.radius * sphere.radius;
         lightInfo->mScore = 1 + S32(99.f * (getMin((sumRadiusSq - lenSq), radSq) / radSq));
         break;
      }

      default:
         lightInfo->mScore = 1;
         break;
   }
}

void LightManager::scoreLight(LightInfo*     lightInfo,
                              const PlaneF*  /*testPlanes*/,
                              const U32      /*numPlanes*/,
                              const Point3F& /*pos*/) const
{
   switch(lightInfo->mType)
   {
      case LightInfo::Ambient:
      case LightInfo::Vector:
      {
         // 100 + (color_intensity * 100)
         float intensity = (lightInfo->mColor.red   + lightInfo->mAmbient.red) * 0.346f +
                           (lightInfo->mColor.green + lightInfo->mAmbient.green) * 0.588f +
                           (lightInfo->mColor.blue  + lightInfo->mAmbient.blue) * 0.070f;

         lightInfo->mScore = 100 + S32(100.f * mClampF(intensity, 0.f, 1.f));
         break;
      }

      case LightInfo::Point:
      {
         lightInfo->mScore = 1;
         break;
      }

      default:
         lightInfo->mScore = 1;
         break;
   }
}

//------------------------------------------------------------------------------
S32 QSORT_CALLBACK LightManager::compareLights(const void* a, const void* b)
{
   return((*(LightInfo**)b)->mScore - (*(LightInfo**)a)->mScore);
}
