// Hydrax Demo

// ---------------------------------------------------------------------------
// Include Hydrax header files
// ---------------------------------------------------------------------------
#include <Hydrax.h>
#include <Modules/ProjectedGrid/HydraxProjectedGrid.h>
#include <Noise/Perlin/HydraxPerlin.h>

// ----------------------------------------------------------------------------
// Include the main OGRE header files
// Ogre.h just expands to including lots of individual OGRE header files
// ----------------------------------------------------------------------------
#include <Ogre.h>

// ----------------------------------------------------------------------------
// Main function, minimalist ogre startup code
// ----------------------------------------------------------------------------
int main(int argc, char** argv[])
{
  bool success = true;
  Ogre::Root* ogreRoot = 0;
  Ogre::RenderWindow* ogreRenderWindow = 0;
  Ogre::SceneManager* ogreSceneManager = 0;
  Ogre::Camera* ogreCamera = 0;
  Hydrax::Hydrax* hydrax = 0;
  Hydrax::Module::Module* hydraxModule = 0;
  Hydrax::Noise::Noise* hydraxNoise = 0;

	try
	{
	    ogreRoot = new Ogre::Root("", "Hydrax.cfg", "Hydrax.log");

      // More code needed for run this sample with static linking, but that's not the purpose of it...
      #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
          Ogre::String prepend("/usr/local/lib/OGRE/")
      #elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32
          Ogre::String prepend("./");
      #else
          #error "Platform not tested!"
      #endif

      // Debug version plugins?
      #if OGRE_DEBUG_MODE == 1
        Ogre::String append("_d");
      #else
        Ogre::String append("");
      #endif
  
      // Load plugins needed for the demo
      #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
          append += ".dll";
          ogreRoot->loadPlugin(prepend +"RenderSystem_Direct3D9"+ append);
      #endif
      ogreRoot->loadPlugin(prepend +"RenderSystem_GL"+ append);
      ogreRoot->loadPlugin(prepend +"Plugin_OctreeSceneManager"+ append);
      ogreRoot->loadPlugin(prepend +"Plugin_CgProgramManager"+ append);

	    // Show config window
      if(!ogreRoot->restoreConfig())
          if(!ogreRoot->showConfigDialog())
            success = false;

      // Launch demo
      if(success)
      {
          // Initialise root, auto create render window
          ogreRenderWindow = ogreRoot->initialise(true);

          // Add resources
          ogreRoot->addResourceLocation("demos", "FileSystem");
          ogreRoot->addResourceLocation("medias", "FileSystem", HYDRAX_RESOURCE_GROUP);
          Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

          // Create scene.
          ogreSceneManager = ogreRoot->createSceneManager(Ogre::ST_GENERIC, "HydraxScene");
          ogreSceneManager->setAmbientLight(Ogre::ColourValue(1, 1, 1));

          // Create camera
          ogreCamera = ogreSceneManager->createCamera("HydraxCamera");
          ogreCamera->setNearClipDistance(0.1);
          ogreCamera->setFarClipDistance(10000); // TODO: delete this! 

          // Create viewport
          Ogre::Viewport* ogreViewport = ogreRenderWindow->addViewport(ogreCamera);

          // Create Hydrax system
          hydrax = new Hydrax::Hydrax(ogreSceneManager, ogreCamera, ogreViewport);

          // Use CG shaders for Hydrax
          hydrax->setShaderMode(Hydrax::MaterialManager::SM_CG);

          // Configure main options
          hydrax->setPosition(Ogre::Vector3::ZERO);
	        hydrax->setPlanesError(0.5);
	        hydrax->setFullReflectionDistance(1e+011);
	        hydrax->setGlobalTransparency(0.0f);
	        hydrax->setNormalDistortion(0.075f);
	        hydrax->setWaterColor(Ogre::Vector3(0.14, 0.36, 0.42));

          // Select a noise generator
          hydraxNoise = new Hydrax::Noise::Perlin();

          // Create hydrax geometry, projection grid
          Hydrax::Module::ProjectedGrid::Options hydraxModuleOptions;
          hydraxModule = new Hydrax::Module::ProjectedGrid(hydrax, 
                                                           hydraxNoise,
                                                           Ogre::Plane(Ogre::Vector3(0,1,0), Ogre::Vector3(0,0,0)),
                                                           Hydrax::MaterialManager::NM_VERTEX,
									                                         hydraxModuleOptions);

          // Tell Hydrax to use this geometry module.
          hydrax->setModule(hydraxModule, true);

          // Component configuration.
          // You can comment each of following blocks.
          // You can also load all those parameters from a config file.

          // Configure sun component.
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_SUN));
          hydrax->setSunPosition(Ogre::Vector3(0.0f, 1000.0f, 0.0f));
	        hydrax->setSunStrength(1.75f);
	        hydrax->setSunArea(150.0f);
	        hydrax->setSunColor(Ogre::Vector3(1.0f, 0.9f, 0.6f));

          // Create a light, same parameters as sun component
		      Ogre::Light* ogreLight = ogreSceneManager->createLight("Light");
		      ogreLight->setPosition(Ogre::Vector3(0.0f, 1000.0f, 0.0f));
		      ogreLight->setDiffuseColour(1, 1, 1);
		      ogreLight->setSpecularColour(1.0f, 0.9f, 0.6f);

          // Configure foam component.
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_FOAM));
          hydrax->setFoamMaxDistance(75000000.0f);
          hydrax->setFoamScale(0.0075f);
          hydrax->setFoamStart(0.0f);
          hydrax->setFoamTransparency(1.0f);

          // Configure depth component.
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_DEPTH));
          hydrax->setDepthLimit(90.0f);

          // Configure smooth component.
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_SMOOTH));
          hydrax->setSmoothPower(5.0f);

          // Configure caustics component.
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_CAUSTICS));
          hydrax->setCausticsScale(135.0f);
          hydrax->setCausticsPower(10.5f);
          hydrax->setCausticsEnd(0.8f);

          // Configure underwater rendering
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_UNDERWATER));
          
          // Configure underwater rendering
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_UNDERWATER_REFLECTIONS));

          // Configure god rays component.
          hydrax->setComponents(static_cast<Hydrax::HydraxComponent> (hydrax->getComponents() | Hydrax::HYDRAX_COMPONENT_UNDERWATER_GODRAYS));
          hydrax->setGodRaysExposure(Ogre::Vector3(0.76f, 2.46f, 2.29f));
          hydrax->setGodRaysIntensity(0.015f);
          hydrax->getGodRaysManager()->setSimulationSpeed(5.0f);
          hydrax->getGodRaysManager()->setNumberOfRays(100);
          hydrax->getGodRaysManager()->setRaysSize(0.03f);
          hydrax->getGodRaysManager()->setObjectIntersectionsEnabled(false);

          // Configure hydrax rtt automatically.
          hydrax->getRttManager()->setTextureSize(Hydrax::RttManager::RTT_REFLECTION, Hydrax::Size(0, 0));
	        hydrax->getRttManager()->setTextureSize(Hydrax::RttManager::RTT_REFRACTION, Hydrax::Size(0, 0));
	        hydrax->getRttManager()->setTextureSize(Hydrax::RttManager::RTT_DEPTH, Hydrax::Size(0, 0));
	        hydrax->getRttManager()->setTextureSize(Hydrax::RttManager::RTT_DEPTH_REFLECTION, Hydrax::Size(0, 0));
	        hydrax->getRttManager()->setTextureSize(Hydrax::RttManager::RTT_GPU_NORMAL_MAP, Hydrax::Size(0, 0));

          // Everything configured, create.
          if(!hydrax->isCreated())
              hydrax->create();

          // Add a cloudy sky
          ogreSceneManager->setSkyDome(true, "clouds");

          // Add a beach (or kind of...)
          Ogre::Plane plane(Ogre::Vector3::UNIT_Y, -200);
          Ogre::MeshManager::getSingleton().createPlane("beachPlane", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 1500, 1500, 10, 10, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);
          Ogre::Entity* ogreBeach = ogreSceneManager->createEntity("beach", "beachPlane");
          ogreBeach->setMaterialName("beach");
          Ogre::SceneNode* ogreBeachNode = ogreSceneManager->getRootSceneNode()->createChildSceneNode();
		      ogreBeachNode->attachObject(ogreBeach);

          // Add the depth technique to our material
          hydrax->getMaterialManager()->addDepthTechnique(static_cast<Ogre::MaterialPtr>(Ogre::MaterialManager::getSingleton().getByName("beach"))->createTechnique());

          // Add the mandatory Ogre head ;)
          Ogre::Entity* ogreHead = ogreSceneManager->createEntity("Head", "ogrehead.mesh");
          Ogre::SceneNode* ogreHeadNode = ogreSceneManager->getRootSceneNode()->createChildSceneNode();
		      ogreHeadNode->attachObject(ogreHead);

          // Move the camera automatically, track the ogre head
          ogreCamera->setAutoTracking(true, ogreHeadNode);

          // Create a camera node and attach camera to it
          Ogre::SceneNode* ogreCameraNode = ogreSceneManager->getRootSceneNode()->createChildSceneNode();
          ogreCameraNode->attachObject(ogreCamera);

		      // set up a 10 second animation for our camera, using spline interpolation for nice curves
          Ogre::Animation* ogreCameraAnimation = ogreSceneManager->createAnimation("CameraTrack", 10);
          ogreCameraAnimation->setInterpolationMode(Ogre::Animation::IM_SPLINE);

		      // Create a track to animate the camera's node
          Ogre::NodeAnimationTrack* ogreCameraAnimationTrack = ogreCameraAnimation->createNodeTrack(0, ogreCameraNode);

          // create keyframes for our track
          ogreCameraAnimationTrack->createNodeKeyFrame(0)->setTranslate(Ogre::Vector3(0, 400, 400));
          ogreCameraAnimationTrack->createNodeKeyFrame(2.5)->setTranslate(Ogre::Vector3(-400, 0, 0));
          ogreCameraAnimationTrack->createNodeKeyFrame(5)->setTranslate(Ogre::Vector3(0, -400, -400));
          ogreCameraAnimationTrack->createNodeKeyFrame(7.5)->setTranslate(Ogre::Vector3(400, 0, 0));
          ogreCameraAnimationTrack->createNodeKeyFrame(10)->setTranslate(Ogre::Vector3(0, 400, 400));

          // create a new animation state to track this
          Ogre::AnimationState* ogreCameraAnimationState = ogreSceneManager->createAnimationState("CameraTrack");
          ogreCameraAnimationState->setEnabled(true);

          // Start auto-rendering
          Ogre::Timer currentTimer;
          unsigned long lastTime = 0ul;

          // Rendering loop
          bool exitRequested = false;
          while(!exitRequested)
          {
              // Compute frame time
              unsigned long currentTime = currentTimer.getMilliseconds();
              unsigned long deltaTime = currentTime - lastTime;
              lastTime = currentTime;
              Ogre::Real deltaTimeInSeconds = static_cast<Ogre::Real>(deltaTime) / 1000.0f;

              // Tell OS to call the events (like "Draw" for example)
              Ogre::WindowEventUtilities::messagePump();

              // Update camera animation state
              ogreCameraAnimationState->addTime(deltaTimeInSeconds);

              // Quit when a frame listener return false. No need to manually update Hydrax anymore, handle automatically here.
              // Use Hydrax::setTimeMultiplier(0.0f) to pause, or setTimeMultiplier(1.0f) to run at normal speed.
              ogreRoot->renderOneFrame();

              // Render for 10 seconds
              if(currentTimer.getMilliseconds() >= 10000)
                  exitRequested = true;
          }
      }
  }
	catch (Ogre::Exception &e)
	{
		  std::cerr << "An exception has occured: " << e.getFullDescription();
      success = false;
	}

  // Clear hydrax system
  if(hydrax)
  {
      delete(hydrax);
      hydraxModule = 0; // Module is deleted by Hydrax::Hydrax dtor.
      hydraxNoise = 0;  // Noise is deleted by Hydrax::Module dtor.
      hydrax = 0;
  }

  // Clear scene
  if(ogreSceneManager)
  {
	    ogreSceneManager->clearScene();
	    ogreRoot->destroySceneManager(ogreSceneManager);
  	  ogreSceneManager = 0;
  }

	if(ogreRoot)
	{
		  delete(ogreRoot);
		  ogreRoot = 0;
	}

  if(!success)
      return -1;
  else
	    return 0;
}
