//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "platform/platform.h"
#include "forest/forestWindMgr.h"

#include "platform/profiler.h"
#include "core/tAlgorithm.h"
#include "core/module.h"
#include "console/consoleTypes.h"
#include "math/mathUtils.h"
#include "T3D/gameBase/gameConnection.h"
#include "forest/forest.h"
#include "forest/forestWindAccumulator.h"
#include "T3D/fx/particleEmitter.h"


MODULE_BEGIN( ForestWindMgr )

   MODULE_INIT
   {
      ManagedSingleton< ForestWindMgr >::createSingleton();
      ForestWindMgr::initConsole();
   }
   
   MODULE_SHUTDOWN
   {
      ManagedSingleton< ForestWindMgr >::deleteSingleton();
   }

MODULE_END;


ForestWindMgr::WindAdvanceSignal ForestWindMgr::smAdvanceSignal;  

F32 ForestWindMgr::smWindEffectRadius = 25.0f;


ForestWindMgr::ForestWindMgr()
{
   setProcessTicks( true );
   mSources = new IdToWindMap();
   mPrevSources = new IdToWindMap();
}

ForestWindMgr::~ForestWindMgr()
{
   IdToWindMap::Iterator sourceIter = mSources->begin();
   for (; sourceIter != mSources->end(); ++sourceIter)
      delete (*sourceIter).value;

   delete mSources;
   delete mPrevSources;
}

void ForestWindMgr::initConsole()
{
   Con::addVariable( "$pref::windEffectRadius", TypeF32, &smWindEffectRadius, "Radius to affect the wind.\n"
	   "@ingroup Rendering\n");
}

void ForestWindMgr::addEmitter( ForestWindEmitter *emitter )
{
   mEmitters.push_back( emitter );
}

void ForestWindMgr::removeEmitter( ForestWindEmitter *emitter )
{
   ForestWindEmitterList::iterator iter = find( mEmitters.begin(), 
                                                mEmitters.end(), 
                                                emitter );

   AssertFatal( iter != mEmitters.end(), 
      "SpeedTreeWindMgr::removeEmitter() - Bad emitter!" );

   mEmitters.erase( iter );
}

void ForestWindMgr::processTick()
{
   const F32 timeDelta = 0.032f;

   if ( mEmitters.empty() )
      return;

   PROFILE_SCOPE(ForestWindMgr_AdvanceTime);

   // Advance all ForestWinds.
   {
      PROFILE_SCOPE(ForestWindMgr_AdvanceTime_ForestWind_ProcessTick);

      ForestWindEmitterList::iterator iter = mEmitters.begin();
      for ( ; iter != mEmitters.end(); iter++ )
      {
         if (  (*iter)->getWind() &&
               (*iter)->isEnabled() )
         {
            (*iter)->updateMountPosition();

            ForestWind *wind = (*iter)->getWind();
            if ( wind )
               wind->processTick();
         }
      }
   }   

   // Assign the new global wind value used by the particle system.
   {
      ForestWindEmitter *pWindEmitter = getGlobalWind();
      if ( pWindEmitter == NULL )      
         ParticleEmitter::setWindVelocity( Point3F::Zero );
      else
      {
         ForestWind *pWind = pWindEmitter->getWind();
         ParticleEmitter::setWindVelocity( pWind->getDirection() * pWind->getStrength() );
      }
   }

   // Get the game connection and camera object
   // in order to retrieve the camera position.
   GameConnection *conn = GameConnection::getConnectionToServer();
   if ( !conn )
      return;

   GameBase *cam = conn->getCameraObject();
   if ( !cam )
      return;   

   const Point3F &camPos = cam->getPosition();

      

   // Gather TreePlacementInfo for trees near the camera.
   {
      PROFILE_SCOPE( ForestWindMgr_AdvanceTime_GatherTreePlacementInfo );
      
      smAdvanceSignal.trigger( camPos, smWindEffectRadius, &mPlacementInfo );
   }

   // Prepare to build a new local source map.
   {
      PROFILE_SCOPE( ForestWindMgr_AdvanceTime_SwapSources );
      AssertFatal( mPrevSources->isEmpty(), "prev sources not empty!" );
      
      swap( mSources, mPrevSources );      

      AssertFatal( mSources->isEmpty(), "swap failed!" );
   }

   // Update wind for each TreePlacementInfo
   {
      PROFILE_SCOPE( ForestWindMgr_AdvanceTime_UpdateWind );

      for( S32 i = 0; i < mPlacementInfo.size(); i++ )
      {
         const TreePlacementInfo &info = mPlacementInfo[i];      
         updateWind( camPos, info, timeDelta );    
      }

      mPlacementInfo.clear();
   }

   // Clean up any accumulators in the
   // previous local source map.
   {
      PROFILE_SCOPE( ForestWindMgr_AdvanceTime_Cleanup );

      IdToWindMap::Iterator sourceIter = mPrevSources->begin();
      for (; sourceIter != mPrevSources->end(); ++sourceIter)
      {
         ForestWindAccumulator *accum = (*sourceIter).value;

         AssertFatal( accum, "Got null accumulator!" );
         delete accum;
      }

      mPrevSources->clear();
   }
}

ForestWindAccumulator* ForestWindMgr::getLocalWind( ForestItemKey key )
{
   PROFILE_SCOPE( ForestWindMgr_getLocalWind );

   ForestWindAccumulator *accumulator = NULL;
   IdToWindMap::Iterator iter = mSources->find( key );
   if ( iter != mSources->end() )
      accumulator = iter->value;

   if ( accumulator )
      return accumulator;
   else
   {
      /*
      ForestWindEmitterList::iterator iter = mEmitters.begin();
      for ( ; iter != mEmitters.end(); iter++ )
      {
         if (  (*iter)->getWind() &&
               (*iter)->isEnabled() &&
               !(*iter)->isLocalWind() )
         {
            return (*iter)->getWind();
         }
      }
      */
      return NULL;
   }
}

ForestWindEmitter* ForestWindMgr::getGlobalWind()
{
   ForestWindEmitterList::iterator itr = mEmitters.begin();
   for ( ; itr != mEmitters.end(); itr++ )
   {
      if ( !(*itr)->isRadialEmitter() )
         return *itr;
   }

   return NULL;
}

void ForestWindMgr::updateWind(  const Point3F &camPos, 
                                 const TreePlacementInfo &info, 
                                 F32 timeDelta )
{
   PROFILE_SCOPE(ForestWindMgr_updateWind);

   // See if we have the blended source available.
   ForestWindAccumulator *blendDest = NULL;
   {
      IdToWindMap::Iterator iter = mPrevSources->find( info.itemKey );
      if ( iter != mPrevSources->end() )
      {
         blendDest = iter->value;
         mPrevSources->erase( iter );
      }
   }

   // Get some stuff we'll need for finding the emitters.
   F32 treeHeight = info.scale * info.dataBlock->getObjBox().len_z();
   Point3F top = info.pos;
   top.z += treeHeight;
   if ( blendDest )
      top += ( 1.0f / info.scale ) * blendDest->getDirection();

   // Go thru the emitters to accumulate the total wind force.
   VectorF windForce( 0, 0, 0 );

   F32 time = Sim::getCurrentTime() / 1000.0f;

   ForestWindEmitterList::iterator iter = mEmitters.begin();
   for ( ; iter != mEmitters.end(); iter++ )
   {
      ForestWindEmitter *emitter = (*iter);

      // If disabled or no wind object... skip it.
      if ( !emitter->isEnabled() || !emitter->getWind() )
         continue;

      ForestWind *wind = emitter->getWind();

      F32 strength = wind->getStrength();
      
      if ( emitter->isRadialEmitter() )
      {
         Point3F closest = MathUtils::mClosestPointOnSegment( info.pos, top, emitter->getPosition() );
         Point3F dir = closest - emitter->getPosition();
         F32 lenSquared = dir.lenSquared();
         if ( lenSquared > emitter->getWindRadiusSquared() )
            continue;

         dir *= 1.0f / mSqrt( lenSquared );

         F32 att = lenSquared / emitter->getWindRadiusSquared();
         strength *= 1.0f - att;
         windForce += dir * strength;
      }
      else
      {
         F32 d = mDot( info.pos, Point3F::One ); //PlaneF( Point3F::Zero, wind->getDirection() ).distToPlane( Point3F( info.pos.x, info.pos.y, 0 ) );
         //F32 d = PlaneF( Point3F::Zero, wind->getDirection() ).distToPlane( Point3F( info.pos.x, info.pos.y, 0 ) );
         F32 scale = 1.0f + ( mSin( d + ( time / 10.0 ) ) * 0.5f );
         windForce += wind->getDirection() * strength * scale;
      }
   }

   // If we need a accumulator then we also need to presimulate.
   if ( !blendDest )
   {
      blendDest = new ForestWindAccumulator( info );
      blendDest->presimulate( windForce, 4.0f / TickSec );
   }
   else
      blendDest->updateWind( windForce, timeDelta );

   mSources->insertUnique( info.itemKey, blendDest );
}