/*
 * TerrainApp.cpp
 *
 *  Created on: Mar 16, 2011
 *      Author: cpresser
 */
#include <CEGUISystem.h>
#include <CEGUISchemeManager.h>
#include <RendererModules/Ogre/CEGUIOgreRenderer.h>

#include "TerrainApp.h"
#include <set>

TerrainApp::TerrainApp() {


}

TerrainApp::~TerrainApp() {
        // We created the query, and we are also responsible for deleting it.
        mSceneMgr->destroyQuery(mRaySceneQuery);
}

//-------------------------------------------------------------------------------------
void TerrainApp::createScene(void)
{
        // Set ambient light
        mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
        mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);

        // World geometry
        //mSceneMgr->setWorldGeometry("terrain.cfg");
        mSceneMgr->setWorldGeometry("myTerrain.cfg");

        // Set camera look point
        mCamera->setPosition(40, 100, 580);
        //mCamera->pitch(Ogre::Degree(-30));
        mCamera->yaw(Ogre::Degree(-45));

        // CEGUI setup
        mGUIRenderer = &CEGUI::OgreRenderer::bootstrapSystem();// Mouse
        CEGUI::SchemeManager::getSingleton().create((CEGUI::utf8*)"TaharezLook.scheme");
        CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseTarget");//Arrow");

        mFlying = false;

        //std::cerr << "SCENE MANAGER TYPE: " << mSceneMgr->getTypeName() << std::endl;
}

void TerrainApp::createFrameListener(void)
{
        BaseApplication::createFrameListener();
        // Setup default variables
        mCount = 0;
        mCurrentObject = NULL;
        mLMouseDown = false;
        mRMouseDown = false;

        // Reduce rotate speed
        mRotateSpeed =.1;
        // Create RaySceneQuery
        mRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray());

        /*
        mBoundingSphere.setRadius(5);
        mSphereQuery = mSceneMgr->createSphereQuery(mBoundingSphere);
        //what kinds of things can a sphere scene query

        //It is stuff like this that gets me annoyed with C++
        //Yes the first four lines are a variable declaration for the variable s
        //All this because Ogre uses a different allocator
        const std::set<Ogre::SceneQuery::WorldFragmentType,
                std::less<Ogre::SceneQuery::WorldFragmentType>,
                Ogre::STLAllocator<Ogre::SceneQuery::WorldFragmentType,
                Ogre::CategorisedAllocPolicy<Ogre::MEMCATEGORY_GENERAL> > >* s =

                        mSphereQuery->getSupportedWorldFragmentTypes();


        std::set<Ogre::SceneQuery::WorldFragmentType,
                std::less<Ogre::SceneQuery::WorldFragmentType>,
                Ogre::STLAllocator<Ogre::SceneQuery::WorldFragmentType,
                Ogre::CategorisedAllocPolicy<Ogre::MEMCATEGORY_GENERAL> > >::iterator it;

        for(it = s->begin(); it != s->end(); it++){
                std::cerr << "World Fragment Type: " << *it << std::endl;
        }
         */


        mOldPosition = mCamera->getPosition();
        mRaySceneQuery2 = mSceneMgr->createRayQuery(Ogre::Ray());
        mRaySceneQuery2->setSortByDistance(true, 1);

        mDirection = Ogre::Vector3::ZERO;
}

void TerrainApp::chooseSceneManager(void)
{
        // Use the terrain scene manager.
        mSceneMgr = mRoot->createSceneManager(Ogre::ST_EXTERIOR_CLOSE);
}

bool TerrainApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
{
        //return BaseApplication::frameRenderingQueued(evt);
        // Process the base frame listener code.  Since we are going to be
        // manipulating the translate vector, we need this to happen first.
        //if (!BaseApplication::frameRenderingQueued(evt))
        //      return false;
        if (mWindow->isClosed())
                return false;
        if (mShutDown)
                return false;

        mKeyboard->capture();
        mMouse->capture();
        mTrayMgr->frameRenderingQueued(evt);


        //update the camera, man.
        //mCameraMan->frameRenderingQueued(evt);



        // Setup the scene query
        //Ogre::Vector3 camPos = mCamera->getPosition();
        mOldPosition = mCamera->getPosition();

        //make the direction vector relative to the camera orientation
        Ogre::Vector3 dir = mCamera->getOrientation()*mDirection;
        Ogre::Vector3 newCamPos = mOldPosition + (dir * evt.timeSinceLastFrame);


        Ogre::Ray cameraRay(Ogre::Vector3(newCamPos.x, 5000.0f, newCamPos.z),
                        Ogre::Vector3::NEGATIVE_UNIT_Y);
        mRaySceneQuery->setRay(cameraRay);

        // Perform the scene query
        Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute();
        Ogre::RaySceneQueryResult::iterator itr = result.begin();

        // Get the results, set the camera height
        if (itr != result.end() && itr->worldFragment)
        {
                Ogre::Real terrainHeight = itr->worldFragment->singleIntersection.y;
                //if ((terrainHeight + 10.0f) > camPos.y)
                int newHeight = terrainHeight + COLLIDE_DIST;//10.0f;

                if(!mFlying || newCamPos.y < newHeight) {
                        //mOldPosition.y = newHeight;
                        newCamPos.y = newHeight;
                        //mCamera->setPosition( camPos.x, newHeight, camPos.z );
                }


        }
        mCamera->setPosition(newCamPos);

        //camPos = mCamera->getPosition();

        //newCamPos - mOldPosition;
        //make sure we are moving at all
        if(dir.length() > CLOSE_TO_ZERO){
                dir.normalise();
                //check if we are going directly up or down
                //these cause trouble, so if we are close to one or the other
                //we just snap to the vector so the special case can kick in
                if(dir.y > CLOSE_TO_ONE)
                        dir = Ogre::Vector3::UNIT_Y;
                else if(dir.y < -1*CLOSE_TO_ONE)
                        dir = Ogre::Vector3::NEGATIVE_UNIT_Y;

                //std::cout << "RayCast direction: " << dir << std::endl;
                Ogre::Ray directionRay(newCamPos, dir);
                mRaySceneQuery2->setRay(directionRay);
                mRaySceneQuery2->execute(this);
        }

        return true;
}

// OIS::KeyListener
bool TerrainApp::keyPressed( const OIS::KeyEvent& evt ){

    switch (evt.key)
    {
    case OIS::KC_ESCAPE:
        mShutDown = true;
        break;
    case OIS::KC_W:
    case OIS::KC_UP:
        mDirection.z = -SPEED;
        break;

    case OIS::KC_S:
    case OIS::KC_DOWN:
        mDirection.z = SPEED;
        break;

    case OIS::KC_A:
    case OIS::KC_LEFT:
        mDirection.x = -SPEED;
        break;

    case OIS::KC_D:
    case OIS::KC_RIGHT:
        mDirection.x = SPEED;
        break;

    case OIS::KC_PGUP:
        mDirection.y = SPEED;
        break;

    case OIS::KC_PGDOWN:
        mDirection.y = -SPEED;
        break;

    default:
        break;
    }

    //mCameraMan->injectKeyDown(evt);
        return true;
}


bool TerrainApp::keyReleased( const OIS::KeyEvent& evt ){

        switch (evt.key)
        {

    case OIS::KC_ESCAPE:
        mShutDown = true;
        break;
    case OIS::KC_W:
    case OIS::KC_UP:
        mDirection.z = 0;
        break;

    case OIS::KC_S:
    case OIS::KC_DOWN:
        mDirection.z = 0;
        break;

    case OIS::KC_A:
    case OIS::KC_LEFT:
        mDirection.x = 0;
        break;

    case OIS::KC_D:
    case OIS::KC_RIGHT:
        mDirection.x = 0;
        break;

    case OIS::KC_PGUP:
        mDirection.y = 0;
        break;

    case OIS::KC_PGDOWN:
        mDirection.y = 0;
        break;

        default:
            break;
        }


    //mCameraMan->injectKeyUp(evt);
        return true;
}
bool TerrainApp::mouseMoved(const OIS::MouseEvent &arg)
{
    // If we are dragging the left mouse button.
    if (mLMouseDown) {

        // Update CEGUI with the mouse motion
         CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
    } // if

    // If we are dragging the right mouse button.
    else if (mRMouseDown)
    {
    } // else if
    else {
        mCamera->yaw(Ogre::Degree(-arg.state.X.rel * mRotateSpeed));
        mCamera->pitch(Ogre::Degree(-arg.state.Y.rel * mRotateSpeed));
    }
        return true;
}

bool TerrainApp::mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
        // Left mouse button down
    if (id == OIS::MB_Left)
    {
        mLMouseDown = true;
        mFlying = !mFlying;
        CEGUI::MouseCursor::getSingleton().setVisible(!mFlying);

    } // if

    // Right mouse button down
    else if (id == OIS::MB_Right)
    {
        CEGUI::MouseCursor::getSingleton().hide();
        mRMouseDown = true;
    } // else if
        return true;
}

bool TerrainApp::mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
        // Left mouse button up
    if (id == OIS::MB_Left)
    {
        mLMouseDown = false;
    } // if

    // Right mouse button up
    else if (id == OIS::MB_Right)
    {
        CEGUI::MouseCursor::getSingleton().show();
        mRMouseDown = false;
    } // else if
        return true;
}

//Called when a WorldFragment is returned by a query.
bool TerrainApp::queryResult (Ogre::SceneQuery::WorldFragment *fragment, Ogre::Real dist){

        if(dist < COLLIDE_DIST)
                mCamera->setPosition(mOldPosition);
        //std::cout << "Fragment: " << dist <<  std::endl;

        return true;
}


//Called when a MovableObject is returned by a query.
bool TerrainApp::queryResult  (Ogre::MovableObject  *object, Ogre::Real dist){
        //object->getParentSceneNode()->showBoundingBox(true);
        //std::cout << "Movable: " << dist << std::endl;
        return true;
}

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
        // Create application object
        TerrainApp app;

        try {
                app.go();
        } catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
                MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
                std::cerr << "An exception has occurred: " <<
                                e.getFullDescription().c_str() << std::endl;
#endif
        }

        return 0;
}

#ifdef __cplusplus
}
#endif