/*
--------------------------------------------------------------------------------
This source file is part of SkyX.
Visit ---

Copyright (C) 2009 Xavier Verguín González <xavierverguin@hotmail.com>
                                           <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.
--------------------------------------------------------------------------------
*/

#include "SkyxAtmosphereManager.h"
#include "Skyx.h"
#include "SkyxGPUManager.h"

namespace SkyX
{

	AtmosphereManager::AtmosphereManager(SkyX *s)
	{
    mSkyX = s;
	}

	AtmosphereManager::~AtmosphereManager()
	{
	}

	void AtmosphereManager::_update(const Ogre::Real& advanceTime, const Options& NewOptions, const bool& ForceToUpdateAll)
	{
		GPUManager* mGPUManager = mSkyX->getGPUManager();

		if (advanceTime
    || ForceToUpdateAll)
		{
			if (mSkyX->isStarfieldEnabled())
        mGPUManager->setGpuProgramParameter(GPUManager::GPUP_FRAGMENT, "uTime", mSkyX->_getTimeOffset()*0.5f, false);

      Ogre::Vector3 sunDirection = mSkyX->getAstronomicalModel()->getSunDirection();
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uLightDir", -sunDirection);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_FRAGMENT, "uLightDir", -sunDirection, false);
			mSkyX->getMoonManager()->update();
		}

		if (NewOptions.InnerRadius != mOptions.InnerRadius
    || NewOptions.OuterRadius != mOptions.OuterRadius
    || ForceToUpdateAll)
		{
			mOptions.InnerRadius = NewOptions.InnerRadius;
			mOptions.OuterRadius = NewOptions.OuterRadius;

      float Scale = 1.0f / (mOptions.OuterRadius - mOptions.InnerRadius);
      float ScaleDepth = (mOptions.OuterRadius - mOptions.InnerRadius) / 2.0f;
      float ScaleOverScaleDepth = Scale / ScaleDepth;

			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uInnerRadius", mOptions.InnerRadius);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uCameraPos", Ogre::Vector3(0, mOptions.InnerRadius + (mOptions.OuterRadius-mOptions.InnerRadius)*mOptions.HeightPosition, 0));

			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uScale", Scale);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uScaleDepth", ScaleDepth);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uScaleOverScaleDepth", ScaleOverScaleDepth);
		}

		if (NewOptions.HeightPosition != mOptions.HeightPosition
    || ForceToUpdateAll)
		{
			mOptions.HeightPosition = NewOptions.HeightPosition;

			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uCameraPos", Ogre::Vector3(0, mOptions.InnerRadius + (mOptions.OuterRadius-mOptions.InnerRadius)*mOptions.HeightPosition, 0));
		}

		if (NewOptions.RayleighMultiplier != mOptions.RayleighMultiplier
    || NewOptions.SunIntensity != mOptions.SunIntensity
    || ForceToUpdateAll)
		{
			mOptions.RayleighMultiplier = NewOptions.RayleighMultiplier;

			float Kr4PI  = mOptions.RayleighMultiplier * 4.0f * Ogre::Math::PI;
      float KrESun = mOptions.RayleighMultiplier * mOptions.SunIntensity;

      mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uKr4PI", Kr4PI);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uKrESun", KrESun);
		}

		if (NewOptions.MieMultiplier != mOptions.MieMultiplier
    || NewOptions.SunIntensity  != mOptions.SunIntensity
    || ForceToUpdateAll)
		{
			mOptions.MieMultiplier = NewOptions.MieMultiplier;

      float Km4PI  = mOptions.MieMultiplier * 4.0f * Ogre::Math::PI;
      float KmESun = mOptions.MieMultiplier * mOptions.SunIntensity;

      mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uKm4PI", Km4PI);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uKmESun", KmESun, false);
		}

		if (NewOptions.NumberOfSamples != mOptions.NumberOfSamples
    || ForceToUpdateAll)
		{
			mOptions.NumberOfSamples = NewOptions.NumberOfSamples;

			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uNumberOfSamples", mOptions.NumberOfSamples);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uSamples", static_cast<Ogre::Real>(mOptions.NumberOfSamples));
		}

		if (NewOptions.WaveLength != mOptions.WaveLength
    || ForceToUpdateAll)
		{
			mOptions.WaveLength = NewOptions.WaveLength;

			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_VERTEX, "uInvWaveLength", 
				Ogre::Vector3(1.0f / Ogre::Math::Pow(mOptions.WaveLength.x, 4.0f),
				              1.0f / Ogre::Math::Pow(mOptions.WaveLength.y, 4.0f),
				              1.0f / Ogre::Math::Pow(mOptions.WaveLength.z, 4.0f)));
		}

		if (NewOptions.G != mOptions.G
    || ForceToUpdateAll)
		{
			mOptions.G = NewOptions.G;

			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_FRAGMENT, "uG", mOptions.G, false);
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_FRAGMENT, "uG2", mOptions.G*mOptions.G, false);
		}

		if ((NewOptions.Exposure != mOptions.Exposure || ForceToUpdateAll)
    && (mSkyX->getLightingMode() == SkyX::LM_LDR))
		{
			mOptions.Exposure = NewOptions.Exposure;
			mGPUManager->setGpuProgramParameter(GPUManager::GPUP_FRAGMENT, "uExposure", mOptions.Exposure);
		}

		mSkyX->getCloudsManager()->_updateInternal();
	}

	const float AtmosphereManager::_scale(const float& cos, const float& uScaleDepth) const
	{
		float x = 1.0f - cos;
		return uScaleDepth * Ogre::Math::Exp(-0.00287f + x*(0.459f + x*(3.83f + x*(-6.80f + x*5.25f))));
	}

}
