// SkyX Demo 2: Volumetric cloud system

// ---------------------------------------------------------------------------
// Include SkyX header files
// ---------------------------------------------------------------------------
#include <Skyx.h>

// ----------------------------------------------------------------------------
// Include the main OGRE header files
// Ogre.h just expands to including lots of individual OGRE header files
// ----------------------------------------------------------------------------
#include <Ogre.h>
#include <OgreTextAreaOverlayElement.h>

// ----------------------------------------------------------------------------
// Include the OGRE example framework
// This includes the classes defined to make getting an OGRE application running
// a lot easier. It automatically sets up all the main objects and allows you to
// just override the bits you want to instead of writing it all from scratch.
// ----------------------------------------------------------------------------
#include <ExampleApplication.h>

// ----------------------------------------------------------------------------
// SkyX Object
// ----------------------------------------------------------------------------
SkyX::SkyX* mSkyX = 0;

// ----------------------------------------------------------------------------
// Misc
// ----------------------------------------------------------------------------
Ogre::TextAreaOverlayElement* mTextArea = 0;
bool mShowInformation = false;


Ogre::String getConfigStringFromSkyXAtmosphereOptions(const SkyX::AtmosphereManager::Options& Options)
{
	int hour = static_cast<int>(Options.Time.x);
	int min  = static_cast<int>((Options.Time.x - hour)*60);

	Ogre::String timeStr = Ogre::StringConverter::toString(hour) + ":" + Ogre::StringConverter::toString(min);
	Ogre::String str = "SkyX Plugin demo (Press F1 to show/hide information)" + static_cast<Ogre::String>(mShowInformation ? " - Simuation paused - \n" : "\n-------------------------------------------------------------\nTime: " + timeStr + "\n");

	if (mShowInformation)
	{
		str += "-------------------------------------------------------------\n";
		str += "Time: " + timeStr + " [1, Shift+1] (+/-).\n";
		str += "Rayleigh multiplier: " + Ogre::StringConverter::toString(Options.RayleighMultiplier) + " [2, Shift+2] (+/-).\n";
		str += "Mie multiplier: " + Ogre::StringConverter::toString(Options.MieMultiplier) + " [3, Shift+3] (+/-).\n";
		str += "Exposure: " + Ogre::StringConverter::toString(Options.Exposure) + " [4, Shift+4] (+/-).\n";
		str += "Inner radius: " + Ogre::StringConverter::toString(Options.InnerRadius) + " [5, Shift+5] (+/-).\n";
		str += "Outer radius: " + Ogre::StringConverter::toString(Options.OuterRadius) + " [6, Shift+6] (+/-).\n";
		str += "Number of samples: " + Ogre::StringConverter::toString(Options.NumberOfSamples) + " [7, Shift+7] (+/-).\n";
		str += "Height position: " + Ogre::StringConverter::toString(Options.HeightPosition) + " [8, Shift+8] (+/-).\n";
		str += "-------------------------------------------------------------\n";
		str += "Wind speed: " + Ogre::StringConverter::toString(mSkyX->getVCloudsManager()->getWindSpeed()) + " [9, Shift+9] (+/-).\n";
		if (mSkyX->getVCloudsManager()->isCreated())
		{
			str += "Wind direction: " + Ogre::StringConverter::toString(mSkyX->getVCloudsManager()->getVClouds()->getWindDirection().valueDegrees()) + " degrees [0, Shift+0] (+/-).\n";
			str += "Clouds roughness: " + Ogre::StringConverter::toString(mSkyX->getVCloudsManager()->getVClouds()->getNoiseScale()) + " [p, Shift+p] (+/-).\n";
		}
	}

	return str;
}

// ----------------------------------------------------------------------------
// Define the application object
// This is derived from ExampleApplication which is the class OGRE provides to
// make it easier to set up OGRE without rewriting the same code all the time.
// You can override extra methods of ExampleApplication if you want to further
// specialise the setup routine, otherwise the only mandatory override is the
// 'createScene' method which is where you set up your own personal scene.
// ----------------------------------------------------------------------------
class SkyXDemoFrameListener : public ExampleFrameListener
{
public:
    SceneManager *mSceneMgr;
    Real mKeyBuffer;

    SkyXDemoFrameListener(RenderWindow* win, Camera* cam, SceneManager *sm)
            : ExampleFrameListener(win,cam)
            , mSceneMgr(sm)
            , mKeyBuffer(-1)
    {
    }

    bool frameStarted(const FrameEvent &e)
    {
        mKeyboard->capture();

		// Check camera height
		if (mCamera->getDerivedPosition().y < 275)
		{
			mCamera->setPosition(Ogre::Vector3(mCamera->getDerivedPosition().x, 275, mCamera->getDerivedPosition().z));
		}
		else if (mCamera->getDerivedPosition().y > 1000)
		{
			mCamera->setPosition(Ogre::Vector3(mCamera->getDerivedPosition().x, 1000, mCamera->getDerivedPosition().z));
		}

		SkyX::AtmosphereManager::Options SkyXOptions = mSkyX->getAtmosphereManager()->getOptions();

		// Time
		if (!mShowInformation)
		{
			mSkyX->setTimeMultiplier(0.1f);
		}
		else
		{
			mSkyX->setTimeMultiplier(0.0f);
		}

		if (mKeyboard->isKeyDown(OIS::KC_1) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->setTimeMultiplier(1.0f);
		if (mKeyboard->isKeyDown(OIS::KC_1) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->setTimeMultiplier(-1.0f);

		// Rayleigh multiplier
		if (mKeyboard->isKeyDown(OIS::KC_2) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.RayleighMultiplier += e.timeSinceLastFrame*0.025f;
		if (mKeyboard->isKeyDown(OIS::KC_2) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.RayleighMultiplier -= e.timeSinceLastFrame*0.025f;

		// Mie multiplier
		if (mKeyboard->isKeyDown(OIS::KC_3) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.MieMultiplier += e.timeSinceLastFrame*0.025f;
		if (mKeyboard->isKeyDown(OIS::KC_3) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.MieMultiplier -= e.timeSinceLastFrame*0.025f;

		// Exposure
		if (mKeyboard->isKeyDown(OIS::KC_4) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.Exposure += e.timeSinceLastFrame*0.5f;
		if (mKeyboard->isKeyDown(OIS::KC_4) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.Exposure -= e.timeSinceLastFrame*0.5f;

		// Inner radius
		if (mKeyboard->isKeyDown(OIS::KC_5) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.InnerRadius += e.timeSinceLastFrame*0.25f;
		if (mKeyboard->isKeyDown(OIS::KC_5) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.InnerRadius -= e.timeSinceLastFrame*0.25f;

		// Outer radius
		if (mKeyboard->isKeyDown(OIS::KC_6) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.OuterRadius += e.timeSinceLastFrame*0.25f;
		if (mKeyboard->isKeyDown(OIS::KC_6) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.OuterRadius -= e.timeSinceLastFrame*0.25f;

		// Number of samples
		if (mKeyboard->isKeyDown(OIS::KC_7) && mKeyBuffer < 0 &&!(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
		{
			SkyXOptions.NumberOfSamples ++;
			mKeyBuffer = 0.25f;
		}
		if (mKeyboard->isKeyDown(OIS::KC_7) && mKeyBuffer < 0 && (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
		{
			SkyXOptions.NumberOfSamples --;
			mKeyBuffer = 0.25f;
		}

		// Height position
		if (mKeyboard->isKeyDown(OIS::KC_8) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.HeightPosition += e.timeSinceLastFrame*0.05f;
		if (mKeyboard->isKeyDown(OIS::KC_8) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			SkyXOptions.HeightPosition -= e.timeSinceLastFrame*0.05f;

		mSkyX->getAtmosphereManager()->setOptions(SkyXOptions);

		// Wind speed
		if (mKeyboard->isKeyDown(OIS::KC_9) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->getVCloudsManager()->setWindSpeed(mSkyX->getVCloudsManager()->getWindSpeed() + 100.0f*e.timeSinceLastFrame);
		if (mKeyboard->isKeyDown(OIS::KC_9) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->getVCloudsManager()->setWindSpeed(mSkyX->getVCloudsManager()->getWindSpeed() - 100.0f*e.timeSinceLastFrame);

		// Wind direction
		if (mKeyboard->isKeyDown(OIS::KC_0) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->getVCloudsManager()->getVClouds()->setWindDirection(Ogre::Radian(mSkyX->getVCloudsManager()->getVClouds()->getWindDirection().valueRadians() + 5.0f*e.timeSinceLastFrame));
		if (mKeyboard->isKeyDown(OIS::KC_0) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->getVCloudsManager()->getVClouds()->setWindDirection(Ogre::Radian(mSkyX->getVCloudsManager()->getVClouds()->getWindDirection().valueRadians() - 5.0f*e.timeSinceLastFrame));
		
		if (mSkyX->getVCloudsManager()->getVClouds()->getWindDirection().valueDegrees() < 0)
		{
			mSkyX->getVCloudsManager()->getVClouds()->setWindDirection(Ogre::Degree(0));
		}
		if (mSkyX->getVCloudsManager()->getVClouds()->getWindDirection().valueDegrees() > 360)
		{
			mSkyX->getVCloudsManager()->getVClouds()->setWindDirection(Ogre::Degree(360));
		}

		// Roughness
		if (mKeyboard->isKeyDown(OIS::KC_P) && !(mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->getVCloudsManager()->getVClouds()->setNoiseScale(mSkyX->getVCloudsManager()->getVClouds()->getNoiseScale()+2.5f*e.timeSinceLastFrame);
		if (mKeyboard->isKeyDown(OIS::KC_P) &&  (mKeyboard->isKeyDown(OIS::KC_LSHIFT) || mKeyboard->isKeyDown(OIS::KC_RSHIFT)))
			mSkyX->getVCloudsManager()->getVClouds()->setNoiseScale(mSkyX->getVCloudsManager()->getVClouds()->getNoiseScale()-2.5f*e.timeSinceLastFrame);

		// Show/Hide information
		if (mKeyboard->isKeyDown(OIS::KC_F1) && mKeyBuffer < 0)
        {
			mShowInformation = !mShowInformation;

			mKeyBuffer = 0.25f;
		}

		mTextArea->setCaption(getConfigStringFromSkyXAtmosphereOptions(SkyXOptions));

		mSkyX->update(e.timeSinceLastFrame);

		mKeyBuffer -= e.timeSinceLastFrame;
		
        return true;
    }
};

class SampleApp : public ExampleApplication
{
public:
    // Basic constructor
    SampleApp()
    {}

protected:

    // Just override the mandatory create scene method
    void createScene(void)
    {
		// Set some camera params
        mCamera->setFarClipDistance(30000);
		mCamera->setNearClipDistance(20);
		mCamera->setPosition(20000, 550, 20000);
		mCamera->setDirection(1, 0, 0);

		// Create our text area for display SkyX parameters
		createTextArea();

		// Create SkyX
		mSkyX = new SkyX::SkyX(mSceneMgr, mCamera);
		mSkyX->create();

		// Volumetric clouds
		mSkyX->getVCloudsManager()->create();

		// A little change to default atmosphere settings :)
		SkyX::AtmosphereManager::Options atOpt = mSkyX->getAtmosphereManager()->getOptions();
		atOpt.RayleighMultiplier = 0.0045f;
		mSkyX->getAtmosphereManager()->setOptions(atOpt);

		// Add frame listener
		mRoot->addFrameListener(new SkyXDemoFrameListener(mWindow, mCamera, mSceneMgr));
    }

	void chooseSceneManager()
    {
        // Create the SceneManager, in this case a generic one
		mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC);
    }

	// Create text area for SkyX parameters
	void createTextArea()
	{
		// Create a panel
		Ogre::OverlayContainer* panel = static_cast<Ogre::OverlayContainer*>(
			OverlayManager::getSingleton().createOverlayElement("Panel", "SkyXParametersPanel"));
		panel->setMetricsMode(Ogre::GMM_PIXELS);
		panel->setPosition(10, 10);
		panel->setDimensions(400, 400);

		// Create a text area
		mTextArea = static_cast<Ogre::TextAreaOverlayElement*>(
			OverlayManager::getSingleton().createOverlayElement("TextArea", "SkyXParametersTextArea"));
		mTextArea->setMetricsMode(Ogre::GMM_PIXELS);
		mTextArea->setPosition(0, 0);
		mTextArea->setDimensions(100, 100);
		mTextArea->setCaption("SkyX plugin demo");
		mTextArea->setCharHeight(16);
		mTextArea->setFontName("BlueHighway");
		mTextArea->setColourBottom(ColourValue(0.3, 0.5, 0.3));
		mTextArea->setColourTop(ColourValue(0.5, 0.7, 0.5));

		// Create an overlay, and add the panel
		Ogre::Overlay* overlay = OverlayManager::getSingleton().create("OverlayName");
		overlay->add2D(panel);

		// Add the text area to the panel
		panel->addChild(mTextArea);

		// Show the overlay
		overlay->show();
	}
};

// ----------------------------------------------------------------------------
// Main function, just boots the application object
// ----------------------------------------------------------------------------
int main(int argc, char** argv[])
{
	try
	{
		SampleApp app;
		app.go();
	}
	catch (Ogre::Exception &e)
	{
		std::cerr << "An exception has occured: " << e.getFullDescription();

		return -1;
	}

	return 0;
}
