/*
--------------------------------------------------------------------------------
This source file is part of SkyX.
Visit http://www.paradise-studios.net/products/skyx/

Copyright (C) 2009-2011 Xavier Verguín González <xavyiy@gmail.com>

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
--------------------------------------------------------------------------------
*/

#ifndef _SkyX_VClouds_VClouds_H_
#define _SkyX_VClouds_VClouds_H_

#include "Prerequisites.h"

#include "VClouds/DataManager.h"
#include "VClouds/GeometryManager.h"

namespace SkyX { namespace VClouds{

	class DllExport VClouds
	{
	public:
		/** Geometry settings
		 */
		struct GeometrySettings 
		{
			/// Height: x = Altitude over the camera, y: Field height (both in world coordinates)
			Ogre::Vector2 Height;
			/// Angles
			Ogre::Radian Alpha, Beta;
			/// Radius
			float Radius;
			/// Number of blocks
			int NumberOfBlocks;
			/// Number of slices per geometry zone
			int Na, Nb, Nc;

			/** Default constructor
			 */
			GeometrySettings()
				: Height(Ogre::Vector2(10,50))
				, Alpha(Ogre::Degree(12)), Beta(Ogre::Degree(40))
				, Radius(100)
				, NumberOfBlocks(12)
				, Na(10), Nb(8), Nc(6)
			{
			}

			/** Constructor
			    @param _Height x = Cloud field y-coord start, y: Field height (both in world coordinates)
				@param _Radius Radius
				@param _Alpha Alpha angle
				@param _Beta Beta angle
				@param _NumberOfBlocks Number of geometry blocks
				@param _Na Number of slices in A zone
				@param _Nb Number of slices in B zone
				@param _Nc Number of slices in C zone
			 */
			GeometrySettings(const Ogre::Vector2& _Height, const float& _Radius,
					const Ogre::Radian& _Alpha = Ogre::Degree(12), const Ogre::Radian& _Beta = Ogre::Degree(40), 
					const int& _NumberOfBlocks = 12, const int& _Na = 10, const int& _Nb = 8, const int& _Nc = 6)
				: Height(_Height)
				, Alpha(_Radius), Beta(_Beta)
				, Radius(_Radius)
				, NumberOfBlocks(_NumberOfBlocks)
				, Na(_Na), Nb(_Nb), Nc(_Nc)
			{
			}
		};

		/** Camera data struct
		 */
		struct CameraData
		{
		public:
			/** Default constructor
			 */
			inline CameraData()
				: camera(0)
				, lastPosition(Ogre::Vector3(0,0,0))
				, cameraOffset(Ogre::Vector2(0,0))
				, geometryDisplacement(Ogre::Vector3(0,0,0))
			{
			}

			/** Constructor
			    @param c Camera
			 */
			inline CameraData(Ogre::Camera* c)
				: camera(c)
				, lastPosition(c->getDerivedPosition())
				, cameraOffset(Ogre::Vector2(0,0))
				, geometryDisplacement(Ogre::Vector3(0,0,0))
			{
			}

			/// Camera
			Ogre::Camera* camera;
			/// Last camera position
			Ogre::Vector3 lastPosition;
			/// Camera offset
			Ogre::Vector2 cameraOffset;
			/// Geometry displacement
			Ogre::Vector3 geometryDisplacement;
		};

		/** Simple constructor
			@param sm Scene manager 
		 */
		VClouds(Ogre::SceneManager *sm);

		/** Destructor
		 */
		~VClouds();

		/** Create
		 */
		void create();

		/** Create
			@param gs Geometry settings
		 */
		void create(const GeometrySettings& gs);

		/** Create
			@param Height x = Cloud field y-coord start, y: Field height (both in world coordinates)
			@param Radius Radius
		 */
		void create(const Ogre::Vector2& Height, const float& Radius);

		/** Remove
		 */
		void remove();

		/** Update, to be invoked per frame
		    @param timeSinceLastFrame Time since last frame
         */
        void update(const Ogre::Real& timeSinceLastFrame);

		/** Notify camera render, to be invoked per-camera and per-frame
			@param c Rendering camera
		    @param timeSinceLastCameraFrame Time since last CAMERA frame
         */
        void notifyCameraRender(Ogre::Camera* c, const Ogre::Real& timeSinceLastCameraFrame);

		/** Register camera
		    @param c Camera
			@remarks If a rendering camera is used(in notifyCameraRender(...)) without having registered it before,
			         all will work as expected but a warning will be logged since the user should manually unregister 
					 the camera one time it'll be remove
		 */
		void registerCamera(Ogre::Camera* c);

		/** Unregister camera
		    @param c Camera
			@remarks After having used a camera (i.e. before removing the camera), the user should manually unregister it
		 */
		void unregisterCamera(Ogre::Camera* c);

		/** Has been create() already called?
		    @return true if created() have been already called, false if not
		 */
		inline const bool& isCreated() const
		{
			return mCreated;
		}

		/** Set geometry settings
		    @param GeometrySettings Geometry settings
			@remarks Set geometry settings before call create(...)
		 */
		inline void setGeometrySettings(const GeometrySettings& gs)
		{
			mGeometrySettings = gs;
		}

		/** Get geometry settings
		    @return Geometry settings
		 */
		inline const GeometrySettings& getGeometrySettings() const
		{
			return mGeometrySettings;
		}

		/** Set distance falling params
		    @param DistanceFallingParams
				   DistanceFallingParams.x = Distance falling factor (How much the cloud field geometry falls with the distance)
									         Remember that the geometry falling is relative to the distance(height) between the camera
											 and the cloud field. Typical range is [0, ~2] 0 = no falling
				   DistanceFallingParams.y = Max falling (in world coords), useful when , i.e., you've water and you want to go in. 
									  	     That param will allow you to avoid the cloud field geometry falls into the ocean. 
											 -1 means not max falling. (default)
			@remarks See GoemetryBlock::_setVertexData(...) for more info
		*/
		inline void setDistanceFallingParams(const Ogre::Vector2& DistanceFallingParams)
		{
			mDistanceFallingParams = DistanceFallingParams;
		}

		/** Get distance falling params
		    @return DistanceFallingParams.x = Distance falling factor (How much the cloud field geometry falls with the distance)
									          Remember that the geometry falling is relative to the distance(height) between the camera
											  and the cloud field.
											  Typical range is [0, ~2] 0 = no falling
				    DistanceFallingParams.y = Max falling (in world coords), useful when , i.e., you've water and you want to go in. 
									  	      That param will allow you to avoid the cloud field geometry falls into the ocean. 
											  -1 means not max falling. (default)
			@remarks See GoemetryBlock::_setVertexData(...) for more info
		 */
		inline const Ogre::Vector2& getDistanceFallingParams() const
		{
			return mDistanceFallingParams;
		}

		/** Set wind direction
		    @param WindDirection Wind direction
		 */
		inline void setWindDirection(const Ogre::Radian& WindDirection)
		{
			mWindDirection = WindDirection;
		}

		/** Get wind direction
		    @return Wind direction
		 */
		inline const Ogre::Radian& getWindDirection() const
		{
			return mWindDirection;
		}

		/** Get wind direction as a Vector2
		    @return Wind direction
		 */
		inline const Ogre::Vector2 getWindDirectionV2() const
		{
			return Ogre::Vector2(Ogre::Math::Cos(mWindDirection), Ogre::Math::Sin(mWindDirection));
		}

		/** Set wind speed
		    @param WindSpeed Wind speed
		 */
		inline void setWindSpeed(const float& WindSpeed)
		{
			mWindSpeed = WindSpeed;
		}

		/** Get wind speed
		    @return Wind speed
		 */
		inline const float& getWindSpeed() const
		{
			return mWindSpeed;
		}

		/** Set sun direction
		    @param SunDirection Sun direction
		 */
		inline void setSunDirection(const Ogre::Vector3& SunDirection)
		{
			mSunDirection = SunDirection;
		}

		/** Get sun direction
		    @return Sun direction
		 */
		inline const Ogre::Vector3& getSunDirection() const
		{
			return mSunDirection;
		}

		/** Set sun color
		    @param SunColor Sun color
		 */
		void setSunColor(const Ogre::Vector3& SunColor);

		/** Get sun color
		    @return Sun color
		 */
		inline const Ogre::Vector3& getSunColor() const
		{
			return mSunColor;
		}

		/** Set ambient color
		    @param AmbientColor Ambient color
		 */
		void setAmbientColor(const Ogre::Vector3& AmbientColor);

		/** Get Ambient color
		    @return Ambient color
		 */
		inline const Ogre::Vector3& getAmbientColor() const
		{
			return mAmbientColor;
		}

		/** Set light response
		    @param LightResponse
				   x - Sun light power
				   y - Sun beta multiplier
				   z - Ambient color multiplier
				   w - Distance attenuation
	     */
		void setLightResponse(const Ogre::Vector4& LightResponse);

		/** Get light response
		    @return Light response
		 */
		inline const Ogre::Vector4& getLightResponse() const
		{
			return mLightResponse;
		}

		/** Set ambient factors
		    @param AmbientFactors x - constant, y - linear, z - cuadratic, w - cubic
	     */
		void setAmbientFactors(const Ogre::Vector4& AmbientFactors);

		/** Get ambient factors
		    @return Ambient factors
		 */
		inline const Ogre::Vector4& getAmbientFactors() const
		{
			return mAmbientFactors;
		}

		/** Set global opacity
		    @param GlobalOpacity Global opacity: [0,1] range 0->Transparent cloud field
		 */
		inline void setGlobalOpacity(const Ogre::Real& GlobalOpacity)
		{
			mGlobalOpacity = GlobalOpacity;
		}

		/** Get global opacity
		    @return Global opacity
		 */
		inline const Ogre::Real& getGlobalOpacity() const
		{
			return mGlobalOpacity;
		}

		/** Set cloud field scale
		    @param CloudFieldScale Cloud field scale
		 */
		inline void setCloudFieldScale(const Ogre::Real& CloudFieldScale)
		{
			mCloudFieldScale = CloudFieldScale;
		}

		/** Get cloud field scale
		    @return Cloud field scale
		 */
		inline const Ogre::Real& getCloudFieldScale() const
		{
			return mCloudFieldScale;
		}

		/** Set noise scale
		    @param NoiseScale Noise scale
		 */
		inline void setNoiseScale(const Ogre::Real& NoiseScale)
		{
			mNoiseScale = NoiseScale;
		}

		/** Get noise scale
		    @return Noise scale
		 */
		inline const Ogre::Real& getNoiseScale() const
		{
			return mNoiseScale;
		}

		/** Set wheater parameters
		    Use this funtion to update the cloud field parameters, you'll get a smart and smooth transition from your old 
			setting to your new ones.
			@param Humidity Humidity, in other words: the percentage of clouds in [0,1] range.
			@param AverageCloudsSize Average clouds size, for example: if previous wheater clouds size parameter was very different from new one(i.e: more little)
			       only the old biggest clouds are going to be keept and the little ones are going to be replaced
		    @param DelayedResponse false to change wheather conditions over several updates, true to change it at the moment
		 */
		void setWheater(const float& Humidity, const float& AverageCloudsSize, const bool& DelayedResponse);

		/** Get wheater
		    @return Wheater parameters: x = Humidity, y = Average clouds size, both un [0,1] range
		 */
		inline const Ogre::Vector2& getWheater() const
		{
			return mWheater;
		}

		/** Set visible
		    @param visible true to set VClouds visible, false to hide it
		 */
		void setVisible(const bool& visible);

		/** Is VClouds visible?
		    @return true if VClouds is visible, false otherwise
		 */
		inline const bool& isVisible() const
		{
			return mVisible;
		}

		/** Get scene manager
		    @return Ogre::SceneManager pointer
		 */
		inline Ogre::SceneManager* getSceneManager()
		{
			return mSceneManager;
		}

		/** Get current rendering camera
		    @return Current rendering camera
		 */
		inline Ogre::Camera* getCamera()
		{
            return mCamera;
		}

		/** Get data manager
		    @return Data manager
		 */
		inline DataManager* getDataManager()
		{
			return mDataManager;
		}

		/** Get geometry manager
		    @return Geometry manager
		 */
		inline GeometryManager* getGeometryManager()
		{
			return mGeometryManager;
		}

		/** Get cameras data
		    @return Cameras data
			@remarks Only for internal use
		 */
		inline std::vector<CameraData>& _getCamerasData()
		{
			return mCamerasData;
		}

	private:
		/// Has been create(...) already called?
		bool mCreated;

		/// Geometry settings
		GeometrySettings mGeometrySettings;

		/// Geometry distance falling params
		Ogre::Vector2 mDistanceFallingParams;

		/// Wind direction
		Ogre::Radian mWindDirection;
		/// Wind speed
		float mWindSpeed;

		/// Wheater parameters: x = Humidity, y = Average clouds size, both un [0,1] range
		Ogre::Vector2 mWheater;
		/// Delayed response (This param is stored to allow the user call setWheater(...) before create() )
		bool mDelayedResponse;

		/// Sun direction
		Ogre::Vector3 mSunDirection;

		/// Sun color
		Ogre::Vector3 mSunColor;
		/// Ambient color
		Ogre::Vector3 mAmbientColor;

		/** Light response:
		    x - Sun light power
			y - Sun beta multiplier
		    z - Ambient color multiplier
		    w - Distance attenuation
	     */
		Ogre::Vector4 mLightResponse;
		/** Ambient factors
		    x - constant, y - linear, z - cuadratic, w - cubic
		 */
		Ogre::Vector4 mAmbientFactors;

		/// Global opacity
		float mGlobalOpacity;

		/// Cloud field scale
		float mCloudFieldScale;
		/// Noise scale
		float mNoiseScale;

		/// Is VClouds visible?
		bool mVisible;

		/// Data manager
		DataManager *mDataManager;
		/// Geometry manager
		GeometryManager *mGeometryManager;

		/// Ogre::SceneManager pointer
        Ogre::SceneManager *mSceneManager;
		/// Current rendering camera
		Ogre::Camera* mCamera;

		/// Cameras data
		std::vector<CameraData> mCamerasData;
	};

}}

#endif