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

#ifndef _PRECIPITATION_H_
#define _PRECIPITATION_H_

#include "game/gameBase.h"
#include "audio/audioDataBlock.h"

/// How many drops are on a side of the material texture
#define DROPS_PER_SIDE 4
/// How many frames are on a side of a splash animation
#define FRAMES_PER_SIDE 2

//--------------------------------------------------------------------------
/// Precipitation datablock.
class PrecipitationData : public GameBaseData {
   typedef GameBaseData Parent;

  public:
   AudioProfile* soundProfile;
   S32           soundProfileId;    ///< Ambient sound

   StringTableEntry mDropName;      ///< Texture filename for raindrop
   StringTableEntry mSplashName;    ///< Texture filename for splash

   F32 mDropSize;                   ///< Droplet billboard size
   F32 mSplashSize;                 ///< Splash billboard size
   bool mUseTrueBillboards;         ///< True to use true billboards, false for axis-aligned billboards
   S32 mSplashMS;                   ///< How long in milliseconds a splash will last

   PrecipitationData();
   DECLARE_CONOBJECT(PrecipitationData);
   bool onAdd();
   static void  initPersistFields();
   virtual void packData(BitStream* stream);
   virtual void unpackData(BitStream* stream);
};
DECLARE_CONSOLETYPE(PrecipitationData)

struct Raindrop
{
   F32 velocity;           ///< How fast the drop is falling downwards
   Point3F position;       ///< Position of the drop
   Point3F renderPosition; ///< Interpolated render-position of the drop
   F32 time;               ///< Time into the turbulence function
   F32 mass;               ///< Mass of drop used for how much turbulence/wind effects the drop

   U32 texCoordIndex;      ///< Which piece of the material will be used

   bool toRender;          ///< Don't want to render all drops, just the ones that pass a few tests
   bool valid;             ///< Drop becomes invalid after hitting something.  Just keep updating
                           ///<   the position of it, but don't render until it hits the bottom
                           ///<   of the renderbox and respawns

   Point3F hitPos;         ///< Point at which the drop will collide with something

   Raindrop *nextSplashDrop; ///< Linked list cruft for easily adding/removing stuff from the splash list
   Raindrop *prevSplashDrop; ///< Same as next but previous!
   SimTime animStartTime;    ///< Animation time tracker
   U32 splashIndex;          ///< Texture index for which frame of the splash to render
   U32 hitType;              ///< What kind of object the drop will hit

   Raindrop* next;         ///< linked list cruft

   Raindrop()
   {
      velocity = 0;
      time = 0;
      mass = 1;
      texCoordIndex = 0;
      next = NULL;
      toRender = false;
      valid = true;
      nextSplashDrop = NULL;
      prevSplashDrop = NULL;
      animStartTime = 0;
      splashIndex = 0;
      hitType = 0;
      hitPos = Point3F(0,0,0);
   }
};

//--------------------------------------------------------------------------
class Precipitation : public GameBase
{
  private:

   typedef GameBase Parent;
   PrecipitationData*   mDataBlock;

   Raindrop *mDropHead;    ///< Drop linked list head
   Raindrop *mSplashHead;  ///< Splash linked list head
   Point2F texCoords[4 * DROPS_PER_SIDE*DROPS_PER_SIDE];  ///< texture coords for rain texture
   Point2F splashCoords[4 * FRAMES_PER_SIDE*FRAMES_PER_SIDE]; ///< texture coordinates for splash texture

   AUDIOHANDLE mAudioHandle;        ///< Ambient sound handle
   TextureHandle mDropHandle;    ///< Texture handle for raindrop
   TextureHandle mSplashHandle;     ///< Texture handle for splash

   //console exposed variables
   S32 mNumDrops;                ///< Number of drops in the scene
   F32 mPercentage;              ///< Server-side set var (NOT exposed to console)
                                 ///< which controls how many drops are present [0,1]

   F32 mMinSpeed;                ///< Minimum downward speed of drops
   F32 mMaxSpeed;                ///< Maximum downward speed of drops

   F32 mMinMass;                 ///< Minimum mass of drops
   F32 mMaxMass;                 ///< Maximum mass of drops

   F32 mBoxWidth;                ///< How far away in the x and y directions drops will render
   F32 mBoxHeight;               ///< How high drops will render

   F32 mMaxTurbulence;           ///< Coefficient to sin/cos for adding turbulence
   F32 mTurbulenceSpeed;         ///< How fast the turbulence wraps in a circle
   bool mUseTurbulence;          ///< Whether to use turbulence or not (MAY EFFECT PERFORMANCE)

   bool mRotateWithCamVel;       ///< Rotate the drops relative to the camera velocity
                                 ///< This is useful for "streak" type drops

   bool mDoCollision;            ///< Whether or not to do collision

   struct
   {
      bool valid;
      U32 startTime;
      U32 totalTime;
      F32 startPct;
      F32 endPct;
   } mStormData;

   //other functions...
   void processTick(const Move*);
   void interpolateTick(F32 delta);

   VectorF getWindVelocity();
   void fillDropList();                      ///< Adds/removes drops from the list to have the right # of drops
   void killDropList();                      ///< Deletes the entire drop list
   void spawnDrop(Raindrop *drop);           ///< Fills drop info with random velocity, x/y positions, and mass
   void spawnNewDrop(Raindrop *drop);        ///< Same as spawnDrop except also does z position
   void findDropCutoff(Raindrop *drop);      ///< Casts a ray to see if/when a drop will collide
   inline void wrapDrop(Raindrop *drop, Box3F &box); ///< Wraps a drop within the specified box

   void createSplash(Raindrop *drop);        ///< Adds a drop to the splash list
   void destroySplash(Raindrop *drop);       ///< Removes a drop from the splash list


  protected:
   bool onAdd();
   void onRemove();

   // Rendering
   bool prepRenderImage(SceneState*, const U32, const U32, const bool);
   void renderObject(SceneState*, SceneRenderImage*);
   void renderPrecip(SceneState *state);
   void renderSplashes(SceneState *state);

  public:

   Precipitation();
   ~Precipitation();
   void inspectPostApply();

   enum
   {
      DataMask       = Parent::NextFreeMask << 0,
      PercentageMask = Parent::NextFreeMask << 1,
      StormMask      = Parent::NextFreeMask << 2,
      NextFreeMask   = Parent::NextFreeMask << 3
   };

   bool onNewDataBlock(GameBaseData* dptr);
   DECLARE_CONOBJECT(Precipitation);
   static void initPersistFields();

   U32  packUpdate(NetConnection*, U32 mask, BitStream* stream);
   void unpackUpdate(NetConnection*, BitStream* stream);

   void setPercentage(F32 pct);
   void modifyStorm(F32 pct, U32 ms);
};

inline void Precipitation::wrapDrop(Raindrop *drop, Box3F &box)
{
   bool recalcCutoff = false;
   //could probably be slightly optimized to get rid of the while loops
   if (drop->position.x < box.min.x)
   {
      while (drop->position.x < box.min.x)
         drop->position.x += mBoxWidth;
      recalcCutoff = true;
   }
   else if (drop->position.x > box.max.x)
   {
      while (drop->position.x > box.max.x)
         drop->position.x -= mBoxWidth;
      recalcCutoff = true;
   }

   if (drop->position.y < box.min.y)
   {
      while (drop->position.y < box.min.y)
         drop->position.y += mBoxWidth;
      recalcCutoff = true;
   }
   else if (drop->position.y > box.max.y)
   {
      while (drop->position.y > box.max.y)
         drop->position.y -= mBoxWidth;
      recalcCutoff = true;
   }

   if (drop->position.z < box.min.z)
   {
      spawnDrop(drop);
      drop->position.x += box.min.x;
      drop->position.y += box.min.y;
      while (drop->position.z < box.min.z)
         drop->position.z += mBoxHeight;
      recalcCutoff = true;
   }
   else if (drop->position.z > box.max.z)
   {
      while (drop->position.z > box.max.z)
         drop->position.z -= mBoxHeight;
      recalcCutoff = true;
   }
   
   if(recalcCutoff)
      findDropCutoff(drop);
}

#endif // PRECIPITATION_H_

