/*
This file is part of Caelum.
See http://www.ogre3d.org/wiki/index.php/Caelum 

Copyright (c) 2006-2007 Caelum team. See Contributors.txt for details.

Caelum 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 3 of the License, or
(at your option) any later version.

Caelum 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 Caelum. If not, see <http://www.gnu.org/licenses/>.
*/

#include "CaelumPrecompiled.h"
#include "GroundFog.h"
#include "CaelumExceptions.h"
#include "InternalUtilities.h"

namespace Caelum
{
	const Ogre::String GroundFog::DEFAULT_PASS_NAME = "CaelumGroundFog";

	GroundFog::GroundFog(
			Ogre::SceneManager *sceneMgr,
			Ogre::SceneNode *caelumRootNode,
			const Ogre::String &domeMaterialName,
			const Ogre::String &domeEntityName):
			mScene(sceneMgr)
	{
        Ogre::String uniqueSuffix = InternalUtilities::pointerToString (this);

		mDomeMaterial.reset(InternalUtilities::checkLoadMaterialClone (domeMaterialName, domeMaterialName + uniqueSuffix));
        mDomeParams.setup(mDomeMaterial->getTechnique(0)->getPass(0)->getFragmentProgramParameters());

		// Create dome entity, using a prefab sphere.
        // The prefab sphere has a radius of 50 units.
        // If this changes in future version of ogre this might break.
        mDomeEntity.reset (mScene->createEntity (domeEntityName, Ogre::SceneManager::PT_SPHERE));
        mDomeEntity->setMaterialName (mDomeMaterial->getName ());
        mDomeEntity->setCastShadows (false);
        mDomeEntity->setRenderQueueGroup (CAELUM_RENDER_QUEUE_GROUND_FOG);
		sceneMgr->getRenderQueue()->getQueueGroup(CAELUM_RENDER_QUEUE_GROUND_FOG)->setShadowsEnabled(false);
		
        // Create dome node
		mDomeNode.reset (caelumRootNode->createChildSceneNode ());
		mDomeNode->attachObject (mDomeEntity.get());
		
		// Initialize default fog parameters
		mDensity = 0.1;
		mVerticalDecay = 0.2;
		mGroundLevel = 5;
		mFogColour = Ogre::ColourValue::Black;

        forceUpdate();
	}

	GroundFog::~GroundFog() {
        // Disable passes.
        setDensity(0);
	}

	GroundFog::PassSet& GroundFog::getPasses() {
		return mPasses;
	}

	const GroundFog::PassSet& GroundFog::getPasses() const {
		return mPasses;
	}

	void GroundFog::findFogPassesByName (const Ogre::String& passName) {
		Ogre::MaterialManager *matManager = Ogre::MaterialManager::getSingletonPtr();
		Ogre::MaterialManager::ResourceMapIterator matIt = matManager->getResourceIterator();
		while (matIt.hasMoreElements()) {
#if (OGRE_VERSION < ((1 << 16) | (9 << 8) | 0))
            Ogre::MaterialPtr mat = matIt.getNext();
#else
            Ogre::MaterialPtr mat = matIt.getNext().staticCast<Ogre::Material>();
#endif
			Ogre::Material::TechniqueIterator techIt = mat->getTechniqueIterator();
			while (techIt.hasMoreElements()) {
				Ogre::Technique *tech = techIt.getNext();
				Ogre::Technique::PassIterator passIt = tech->getPassIterator();
				while (passIt.hasMoreElements()) {
					Ogre::Pass *pass = passIt.getNext();
					if (pass->getName() == passName) {
						mPasses.insert(pass);
					}
				}
			}
		}
		forceUpdate();
	}

	void GroundFog::setDensity (Ogre::Real density) {
		if (Ogre::Math::Abs(mDensity - density) > 0.000001) {
            for (PassFogParamsVector::const_iterator it = mPassFogParams.begin(), end = mPassFogParams.end(); it != end; ++it) {
			    it->fogDensity.set(it->fpParams, density);
			}
			mDensity = density;
		}
		updateSkyFogging();
	}

	Ogre::Real GroundFog::getDensity () const {
		return mDensity;
	}

	void GroundFog::setColour (const Ogre::ColourValue &colour) {
        bool different = 
                Ogre::Math::Abs(mFogColour.r - colour.r) > 0.001 ||
                Ogre::Math::Abs(mFogColour.g - colour.g) > 0.001 ||
                Ogre::Math::Abs(mFogColour.b - colour.b) > 0.001 ||
                Ogre::Math::Abs(mFogColour.a - colour.a) > 0.001;
        if (different) {
            for (PassFogParamsVector::const_iterator it = mPassFogParams.begin(), end = mPassFogParams.end(); it != end; ++it) {
			    it->fogColour.set(it->fpParams, colour);
		    }
		    mFogColour = colour;
        }
		updateSkyFogging();
	}

	const Ogre::ColourValue GroundFog::getColour () const {
        return mFogColour;
	}

	void GroundFog::setVerticalDecay (Ogre::Real verticalDecay) {
		if (Ogre::Math::Abs(mVerticalDecay - verticalDecay) > 0.000001) {
            for (PassFogParamsVector::const_iterator it = mPassFogParams.begin(), end = mPassFogParams.end(); it != end; ++it) {
			    it->fogVerticalDecay.set(it->fpParams, verticalDecay);
			}
			mVerticalDecay = verticalDecay;
		}
		updateSkyFogging();
	}

	Ogre::Real GroundFog::getVerticalDecay () const {
		return mVerticalDecay;
	}

	void GroundFog::setGroundLevel (Ogre::Real groundLevel) {
		if (Ogre::Math::Abs(mGroundLevel - groundLevel) > 0.000001) {
            for (PassFogParamsVector::const_iterator it = mPassFogParams.begin(), end = mPassFogParams.end(); it != end; ++it) {
			    it->fogGroundLevel.set(it->fpParams, groundLevel);
			}
			mGroundLevel = groundLevel;
		}
		updateSkyFogging();
	}

	Ogre::Real GroundFog::getGroundLevel () const {
		return mGroundLevel;
	}

	void GroundFog::updateSkyFogging() {
        mDomeParams.fogDensity.set(mDomeParams.fpParams, mDensity);
		mDomeParams.fogVerticalDecay.set(mDomeParams.fpParams, mVerticalDecay);
		mDomeParams.fogGroundLevel.set(mDomeParams.fpParams, mGroundLevel);
		mDomeParams.fogColour.set(mDomeParams.fpParams, mFogColour);
	}

	void GroundFog::forceUpdate ()
    {
        updatePassFogParams();
        for (PassFogParamsVector::const_iterator it = mPassFogParams.begin(), end = mPassFogParams.end(); it != end; ++it) {
            const PassFogParams& params = *it;
            params.fogDensity.set(params.fpParams, mDensity);
			params.fogVerticalDecay.set(params.fpParams, mVerticalDecay);
			params.fogGroundLevel.set(params.fpParams, mGroundLevel);
			params.fogColour.set(params.fpParams, mFogColour);
		}
		updateSkyFogging();
	}

	void GroundFog::updatePassFogParams ()
    {
        mPassFogParams.clear();
        for (PassSet::const_iterator it = mPasses.begin(), end = mPasses.end(); it != end; ++it) {
            mPassFogParams.push_back(PassFogParams((*it)->getFragmentProgramParameters()));
        }
        std::sort(mPassFogParams.begin(), mPassFogParams.end(), PassFogParams::lessThanByParams);
        std::unique(mPassFogParams.begin(), mPassFogParams.end(), PassFogParams::equalByParams);
    }

    void GroundFog::FogParamsBase::setup(Ogre::GpuProgramParametersSharedPtr fpParams) {
        this->fpParams = fpParams;
        fogDensity.bind(fpParams, "fogDensity");
		fogVerticalDecay.bind(fpParams, "fogVerticalDecay");
		fogGroundLevel.bind(fpParams, "fogGroundLevel");
		fogColour.bind(fpParams, "fogColour");
    }

    void GroundFog::DomeFogParams::setup(Ogre::GpuProgramParametersSharedPtr fpParams) {
        FogParamsBase::setup(fpParams);
		cameraHeight.bind(fpParams, "cameraHeight");
    }

	void GroundFog::notifyCameraChanged (Ogre::Camera *cam)
	{
        CameraBoundElement::notifyCameraChanged (cam);

		// Send camera height to shader.
        float cameraHeight = cam->getDerivedPosition().dotProduct(mDomeNode->_getDerivedOrientation() * Ogre::Vector3::UNIT_Y);
        mDomeParams.cameraHeight.set(mDomeParams.fpParams, cameraHeight);
    }

    void GroundFog::setFarRadius (Ogre::Real radius)
    {
        CameraBoundElement::setFarRadius(radius);
        // Adjust for radius 50.
        mDomeNode->setScale(Ogre::Vector3::UNIT_SCALE * radius / 50.0);
	}
}
