/*
--------------------------------------------------------------------------------
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.
--------------------------------------------------------------------------------
*/

#pragma warning(disable:4355)

#include "SkyX.h"
#include "SkyXAstronomicalModelBasic.h"

namespace SkyX
{
  SkyX::SkyX(Ogre::SceneManager* sm, Ogre::String resourceGroup, AstronomicalModel* overrideAstronomicalModel)
	{
		mSceneManager = sm;
		mLightingMode = LM_LDR;
		mStarfield = true;
		mTimeMultiplier = 0.1f;
		mTimeOffset = 0.0f;
		mResourceGroup = resourceGroup;
    mTimeOffset = 0.0f;

    if(overrideAstronomicalModel!=0)
    {
      astronomicalModel = overrideAstronomicalModel;
      internalAstronomicalModel = false;
    }
    else
    {
      astronomicalModel = new AstronomicalModelBasic();
      internalAstronomicalModel = true;
    }
    mMeshManager = new MeshManager(this);
		mAtmosphereManager = new AtmosphereManager(this);
		mGPUManager = new GPUManager(this);
		mMoonManager = new MoonManager(this);
		mCloudsManager = new CloudsManager(this);
		mVCloudsManager = new VCloudsManager(this);

    // Init
		mMeshManager->setMaterialName(mGPUManager->getSkydomeMaterialName());
		mAtmosphereManager->_update(0.0f, mAtmosphereManager->getOptions(), true);
	}

	SkyX::~SkyX()
	{
    // Unregister all viewports.
    unregisterAllViewports();

    // If an external astronomical model was provided, let the user delete it.
    if(internalAstronomicalModel)
      delete astronomicalModel;

		mCloudsManager->removeAll();
		delete mMeshManager;
		delete mAtmosphereManager;
		delete mGPUManager;
		delete mMoonManager;
		delete mCloudsManager;
		delete mVCloudsManager;
	}

  void SkyX::registerViewport(Ogre::Viewport* newViewport)
  {
    ViewportList::iterator iSearchedViewport = mViewportList.find(newViewport);
    if(iSearchedViewport != mViewportList.end())
      OGRE_EXCEPT(Ogre::Exception::ERR_DUPLICATE_ITEM, "Viewport already registered!", "SkyX::registerViewport");

    // Add to the viewports list
    mViewportList.insert(newViewport);
    newViewport->addListener(this);

    // Check if the viewport's parent render target is allready registered.
    Ogre::RenderTarget* parentRenderTarger = newViewport->getTarget();
    RenderTargetList::iterator searchedRenderTarget = mRenderTargetList.find(parentRenderTarger);
    if(searchedRenderTarget == mRenderTargetList.end())
    {
      // Not found, register SkyX as a listener on it, and add it to the list.
      parentRenderTarger->addListener(this);
      mRenderTargetList.insert(RenderTargetList::value_type(parentRenderTarger, 1));
    }
    else
    {
      // Found, increase the reference count.
      mRenderTargetList[parentRenderTarger] += 1;
    }
  }

  void SkyX::unregisterViewport(Ogre::Viewport* existingViewport)
  {
    ViewportList::iterator iSearchedViewport = mViewportList.find(existingViewport);
    if(iSearchedViewport == mViewportList.end())
      OGRE_EXCEPT(Ogre::Exception::ERR_DUPLICATE_ITEM, "Viewport not registered!", "SkyX::unregisterViewport");

    // Check if we need to unregister SkyX from the viewport's parent rendertarget.
    Ogre::RenderTarget* parentRenderTarger = existingViewport->getTarget();
    mRenderTargetList[parentRenderTarger] -= 1;
    if(mRenderTargetList[parentRenderTarger] == 0)
      parentRenderTarger->removeListener(this);

    // Remove from the viewport list
    existingViewport->removeListener(this);
    mViewportList.erase(iSearchedViewport);
  }

  void SkyX::unregisterAllViewports()
  {
    ViewportList::iterator iViewport = mViewportList.begin();
    while(iViewport != mViewportList.end())
    {
      (*iViewport)->removeListener(this);
      iViewport++;
    }
    mViewportList.clear();

    RenderTargetList::iterator iRenderTarget = mRenderTargetList.begin();
    while(iRenderTarget != mRenderTargetList.end())
    {
      iRenderTarget->first->removeListener(this);
      iRenderTarget++;
    }
    mRenderTargetList.clear();
  }

	void SkyX::update(const Ogre::Real& timeSinceLastFrame)
	{
    // Update moon & sun light directions
	  if (mTimeMultiplier != 0)
    {
      float timemultiplied = timeSinceLastFrame * mTimeMultiplier;
      astronomicalModel->update(timemultiplied);
      mTimeOffset += timemultiplied;
    }

    // TODO JEFF
	  mVCloudsManager->update(timeSinceLastFrame);
	}

	void SkyX::setLightingMode(const LightingMode& lm)
	{
		mLightingMode = lm;
		mMeshManager->setMaterialName(mGPUManager->getSkydomeMaterialName());       // Update skydome material
		mCloudsManager->registerAll();                                              // Update layered clouds material
		mGPUManager->_updateFP();                                                   // Update ground passes materials
		mAtmosphereManager->_update(0.0f, mAtmosphereManager->getOptions(), true);  // Update parameters
	}

	void SkyX::setStarfieldEnabled(const bool& Enabled)
	{
		mStarfield = Enabled;

		// Update skydome material
		mMeshManager->setMaterialName(mGPUManager->getSkydomeMaterialName());

		// Update parameters
		mAtmosphereManager->_update(0.0f, mAtmosphereManager->getOptions(), true);
	}

  void SkyX::viewportDestroyed(Ogre::Viewport* viewport)
  {
    unregisterViewport(viewport);
  }

  void SkyX::preViewportUpdate(const Ogre::RenderTargetViewportEvent& evt)
  {
    Ogre::Camera* viewportCamera = evt.source->getCamera();
    if(viewportCamera != 0)
      if(viewportCamera->getSceneManager() == mSceneManager)
        _notifyCameraChanged(viewportCamera);
  }

  void SkyX::_notifyCameraChanged(Ogre::Camera* newCamera)
  {
    mMeshManager->_notifyCameraChanged(newCamera);
    mMoonManager->_notifyCameraChanged(newCamera);
  }

}
