#include "Ogre.h"
#include "OgreConfigFile.h"
#include "OgreKeyEvent.h"
#include "OgreEventListeners.h"
#include "OgreStringConverter.h"
#include "OgreException.h"

using namespace Ogre;

class ExampleFrameListener: public FrameListener, public KeyListener
{
protected:
  int mSceneDetailIndex ;
  Real mMoveSpeed;
  Degree mRotateSpeed;
  Overlay* mDebugOverlay;
  float zoom;

  void updateStats(void)
  {
    static int count = 0;

    count += 1;
    if (count % 100 == 0)
      {
        const RenderTarget::FrameStats& stats = mWindow->getStatistics();
        std::cout << stats.avgFPS << std::endl;
      }
    static String currFps = "Current FPS: ";
    static String avgFps = "Average FPS: ";
    static String bestFps = "Best FPS: ";
    static String worstFps = "Worst FPS: ";
    static String tris = "Triangle Count: ";

    // update stats when necessary
    try {
      GuiElement* guiAvg = GuiManager::getSingleton().getGuiElement("Core/AverageFps");
      GuiElement* guiCurr = GuiManager::getSingleton().getGuiElement("Core/CurrFps");
      GuiElement* guiBest = GuiManager::getSingleton().getGuiElement("Core/BestFps");
      GuiElement* guiWorst = GuiManager::getSingleton().getGuiElement("Core/WorstFps");

      const RenderTarget::FrameStats& stats = mWindow->getStatistics();

      guiAvg->setCaption(avgFps + StringConverter::toString(stats.avgFPS));
      guiCurr->setCaption(currFps + StringConverter::toString(stats.lastFPS));
      guiBest->setCaption(bestFps + StringConverter::toString(stats.bestFPS)
                          +" "+StringConverter::toString(stats.bestFrameTime)+" ms");
      guiWorst->setCaption(worstFps + StringConverter::toString(stats.worstFPS)
                           +" "+StringConverter::toString(stats.worstFrameTime)+" ms");

      GuiElement* guiTris = GuiManager::getSingleton().getGuiElement("Core/NumTris");
      guiTris->setCaption(tris + StringConverter::toString(stats.triangleCount));

      GuiElement* guiDbg = GuiManager::getSingleton().getGuiElement("Core/DebugText");
      guiDbg->setCaption(mWindow->getDebugText());
    }
    catch(...)
      {
        // ignore
      }
  }

public:
  // Constructor takes a RenderWindow because it uses that to determine input context
  ExampleFrameListener(RenderWindow* win, Camera* cam, bool useBufferedInputKeys = false, bool useBufferedInputMouse = false)
  {
    zoom = 170;
    mDebugOverlay = (Overlay*)OverlayManager::getSingleton().getByName("Core/DebugOverlay");
    mUseBufferedInputKeys = useBufferedInputKeys;
    mUseBufferedInputMouse = useBufferedInputMouse;
    mInputTypeSwitchingOn = mUseBufferedInputKeys || mUseBufferedInputMouse;
    mRotateSpeed = 36;
    mMoveSpeed = 100;

    if (mInputTypeSwitchingOn)
      {
        mEventProcessor = new EventProcessor();
        mEventProcessor->initialise(win);
        OverlayManager::getSingleton().createCursorOverlay();
        mEventProcessor->startProcessingEvents();
        mEventProcessor->addKeyListener(this);
        mInputDevice = mEventProcessor->getInputReader();

      }
    else
      {
        mInputDevice = PlatformManager::getSingleton().createInputReader();
        mInputDevice->initialise(win,true, true);
      }

    mCamera = cam;
    mWindow = win;
    mStatsOn = true;
    mNumScreenShots = 0;
    mTimeUntilNextToggle = 0;
    mSceneDetailIndex = 0;
    mMoveScale = 0.0f;
    mRotScale = 0.0f;
    mTranslateVector = Vector3::ZERO;
    mAniso = 1;
    mFiltering = TFO_BILINEAR;

    showDebugOverlay(true);
  }
  virtual ~ExampleFrameListener()
  {
    if (mInputTypeSwitchingOn)
      {
        delete mEventProcessor;
      }
    else
      {
        PlatformManager::getSingleton().destroyInputReader( mInputDevice );
      }
  }

  virtual bool processUnbufferedKeyInput(const FrameEvent& evt)
  {
    if (mInputDevice->isKeyDown(KC_A))
      {
        // Move camera left
        mTranslateVector.x = -mMoveScale;
      }

    if (mInputDevice->isKeyDown(KC_D))
      {
        // Move camera RIGHT
        mTranslateVector.x = mMoveScale;
      }

    /* Move camera forward by keypress. */
    if (mInputDevice->isKeyDown(KC_UP) || mInputDevice->isKeyDown(KC_W) )
      {
        mTranslateVector.z = -mMoveScale;
      }

    /* Move camera backward by keypress. */
    if (mInputDevice->isKeyDown(KC_DOWN) || mInputDevice->isKeyDown(KC_S) )
      {
        mTranslateVector.z = mMoveScale;
      }

    if (mInputDevice->isKeyDown(KC_L))
      {
        zoom += 0.1f;
      }
    if (mInputDevice->isKeyDown(KC_S))
      {
        zoom -= 0.1f;
      }
    if (mInputDevice->isKeyDown(KC_PGUP))
      {
        // Move camera up
        mTranslateVector.y = mMoveScale;
      }

    if (mInputDevice->isKeyDown(KC_PGDOWN))
      {
        // Move camera down
        mTranslateVector.y = -mMoveScale;
      }

    if (mInputDevice->isKeyDown(KC_RIGHT))
      {
        mCamera->yaw(-mRotScale);
      }
		
    if (mInputDevice->isKeyDown(KC_LEFT))
      {
        mCamera->yaw(mRotScale);
      }

    if( mInputDevice->isKeyDown( KC_ESCAPE) )
      {            
        return false;
      }

    // see if switching is on, and you want to toggle 
    if (mInputTypeSwitchingOn && mInputDevice->isKeyDown(KC_M) && mTimeUntilNextToggle <= 0)
      {
        switchMouseMode();
        mTimeUntilNextToggle = 1;
      }

    if (mInputTypeSwitchingOn && mInputDevice->isKeyDown(KC_K) && mTimeUntilNextToggle <= 0)
      {
        // must be going from immediate keyboard to buffered keyboard
        switchKeyMode();
        mTimeUntilNextToggle = 1;
      }
    if (mInputDevice->isKeyDown(KC_F) && mTimeUntilNextToggle <= 0)
      {
        mStatsOn = !mStatsOn;
        showDebugOverlay(mStatsOn);

        mTimeUntilNextToggle = 1;
      }
    if (mInputDevice->isKeyDown(KC_T) && mTimeUntilNextToggle <= 0)
      {
        switch(mFiltering)
          {
          case TFO_BILINEAR:
            mFiltering = TFO_TRILINEAR;
            mAniso = 1;
            break;
          case TFO_TRILINEAR:
            mFiltering = TFO_ANISOTROPIC;
            mAniso = 8;
            break;
          case TFO_ANISOTROPIC:
            mFiltering = TFO_BILINEAR;
            mAniso = 1;
            break;
          default:
            break;
          }
        MaterialManager::getSingleton().setDefaultTextureFiltering(mFiltering);
        MaterialManager::getSingleton().setDefaultAnisotropy(mAniso);


        showDebugOverlay(mStatsOn);

        mTimeUntilNextToggle = 1;
      }

    if (mInputDevice->isKeyDown(KC_SYSRQ) && mTimeUntilNextToggle <= 0)
      {
        char tmp[20];
        sprintf(tmp, "screenshot_%d.png", ++mNumScreenShots);
        mWindow->writeContentsToFile(tmp);
        mTimeUntilNextToggle = 0.5;
        mWindow->setDebugText(String("Wrote ") + tmp);
      }
		
    if (mInputDevice->isKeyDown(KC_R) && mTimeUntilNextToggle <=0)
      {
        mSceneDetailIndex = (mSceneDetailIndex+1)%3 ;
        switch(mSceneDetailIndex) {
        case 0 : mCamera->setDetailLevel(SDL_SOLID) ; break ;
        case 1 : mCamera->setDetailLevel(SDL_WIREFRAME) ; break ;
        case 2 : mCamera->setDetailLevel(SDL_POINTS) ; break ;
        }
        mTimeUntilNextToggle = 0.5;
      }

    static bool displayCameraDetails = false;
    if (mInputDevice->isKeyDown(KC_P) && mTimeUntilNextToggle <= 0)
      {
        displayCameraDetails = !displayCameraDetails;
        mTimeUntilNextToggle = 0.5;
        if (!displayCameraDetails)
          mWindow->setDebugText("");
      }
    if (displayCameraDetails)
      {
        // Print camera details
        mWindow->setDebugText("P: " + StringConverter::toString(mCamera->getDerivedPosition()) + " " + 
                              "O: " + StringConverter::toString(mCamera->getDerivedOrientation()));
      }

    // Return true to continue rendering
    return true;
  }

  bool processUnbufferedMouseInput(const FrameEvent& evt)
  {
    /* Rotation factors, may not be used if the second mouse button is pressed. */

    /* If the second mouse button is pressed, then the mouse movement results in 
       sliding the camera, otherwise we rotate. */
    if( mInputDevice->getMouseButton( 1 ) )
      {
        mTranslateVector.x += mInputDevice->getMouseRelativeX() * 0.13;
        mTranslateVector.y -= mInputDevice->getMouseRelativeY() * 0.13;
      }
    else
      {
        mRotX = Degree(-mInputDevice->getMouseRelativeX() * 0.13);
        mRotY = Degree(-mInputDevice->getMouseRelativeY() * 0.13);
      }


    return true;
  }

  void moveCamera()
  {
    // Make all the changes to the camera
    // Note that YAW direction is around a fixed axis (freelook style) rather than a natural YAW (e.g. airplane)
    mCamera->yaw(mRotX);
    mCamera->pitch(mRotY);
    mCamera->moveRelative(mTranslateVector);

    //std::cout << zoom  << std::endl;
    if (zoom >= 179.9)
      zoom = 179.9;
    else if (zoom < 10)
      zoom = 10;

    //mCamera->setFOVy(Radian(Degree(zoom)));
  }

  void showDebugOverlay(bool show)
  {
    if (mDebugOverlay)
      {
        if (show)
          {
            mDebugOverlay->show();
          }
        else
          {
            mDebugOverlay->hide();
          }
      }
  }

  // Override frameStarted event to process that (don't care about frameEnded)
  bool frameStarted(const FrameEvent& evt)
  {
    if(mWindow->isClosed())
      return false;

    if (!mInputTypeSwitchingOn)
      {
        mInputDevice->capture();
      }


    if ( !mUseBufferedInputMouse || !mUseBufferedInputKeys)
      {
        // one of the input modes is immediate, so setup what is needed for immediate mouse/key movement
        if (mTimeUntilNextToggle >= 0) 
          mTimeUntilNextToggle -= evt.timeSinceLastFrame;

        // If this is the first frame, pick a speed
        if (evt.timeSinceLastFrame == 0)
          {
            mMoveScale = 1;
            mRotScale = 0.1;
          }
        // Otherwise scale movement units by time passed since last frame
        else
          {
            // Move about 100 units per second,
            mMoveScale = mMoveSpeed * evt.timeSinceLastFrame;
            // Take about 10 seconds for full rotation
            mRotScale = mRotateSpeed * evt.timeSinceLastFrame;
          }
        mRotX = 0;
        mRotY = 0;
        mTranslateVector = Vector3::ZERO;
      }

    if (mUseBufferedInputKeys)
      {
        // no need to do any processing here, it is handled by event processor and 
        // you get the results as KeyEvents
      }
    else
      {
        if (processUnbufferedKeyInput(evt) == false)
          {
            return false;
          }
      }
    if (mUseBufferedInputMouse)
      {
        // no need to do any processing here, it is handled by event processor and 
        // you get the results as MouseEvents
      }
    else
      {
        if (processUnbufferedMouseInput(evt) == false)
          {
            return false;
          }
      }

    if ( !mUseBufferedInputMouse || !mUseBufferedInputKeys)
      {
        // one of the input modes is immediate, so update the movement vector

        moveCamera();

      }

    return true;
  }

  bool frameEnded(const FrameEvent& evt)
  {
    updateStats();
    return true;
  }

  void switchMouseMode() 
  {
    mUseBufferedInputMouse = !mUseBufferedInputMouse;
    mInputDevice->setBufferedInput(mUseBufferedInputKeys, mUseBufferedInputMouse);
  }
  void switchKeyMode() 
  {
    mUseBufferedInputKeys = !mUseBufferedInputKeys;
    mInputDevice->setBufferedInput(mUseBufferedInputKeys, mUseBufferedInputMouse);
  }

  void keyClicked(KeyEvent* e) 
  {
    if (e->getKeyChar() == 'm')
      {
        switchMouseMode();
      }
    else if (e->getKeyChar() == 'k')
      {

        switchKeyMode();
      }

  }
  void keyPressed(KeyEvent* e) {}
  void keyReleased(KeyEvent* e) {}

protected:
  EventProcessor* mEventProcessor;
  InputReader* mInputDevice;
  Camera* mCamera;

  Vector3 mTranslateVector;
  RenderWindow* mWindow;
  bool mStatsOn;
  bool mUseBufferedInputKeys, mUseBufferedInputMouse, mInputTypeSwitchingOn;
  unsigned int mNumScreenShots;
  float mMoveScale;
  Degree mRotScale;
  // just to stop toggles flipping too fast
  Real mTimeUntilNextToggle ;
  Radian mRotX, mRotY;
  TextureFilterOptions mFiltering;
  int mAniso;

};

/** Base class which manages the standard startup of an Ogre application.
    Designed to be subclassed for specific examples if required.
*/
class ExampleApplication
{
public:
  /// Standard constructor
  ExampleApplication()
  {
    mFrameListener = 0;
    mRoot = 0;
  }
  /// Standard destructor
  virtual ~ExampleApplication()
  {
    delete mFrameListener;
    delete mRoot;
  }

  /// Start the example
  virtual void go(void)
  {
    if (!setup())
      return;

    mRoot->startRendering();
  }

protected:
  Root *mRoot;
  Camera* mCamera;
  SceneManager* mSceneMgr;
  ExampleFrameListener* mFrameListener;
  RenderWindow* mWindow;

  // These internal methods package up the stages in the startup process
  /** Sets up the application - returns false if the user chooses to abandon configuration. */
  virtual bool setup(void)
  {
    mRoot = new Root();

    setupResources();

    bool carryOn = configure();
    if (!carryOn) return false;

    chooseSceneManager();
    createCamera();
    createViewports();

    // Set default mipmap level (NB some APIs ignore this)
    TextureManager::getSingleton().setDefaultNumMipMaps(5);

    // Create the scene
    createScene();

    createFrameListener();

    return true;

  }
  /** Configures the application - returns false if the user chooses to abandon configuration. */
  virtual bool configure(void)
  {
    // Show the configuration dialog and initialise the system
    // You can skip this and use root.restoreConfig() to load configuration
    // settings if you were sure there are valid ones saved in ogre.cfg
    if(mRoot->showConfigDialog())
      {
        // If returned true, user clicked OK so initialise
        // Here we choose to let the system create a default rendering window by passing 'true'
        mWindow = mRoot->initialise(true);
        return true;
      }
    else
      {
        return false;
      }
  }

  virtual void chooseSceneManager(void)
  {
    // Get the SceneManager, in this case a generic one
    mSceneMgr = mRoot->getSceneManager(ST_GENERIC);
  }
  virtual void createCamera(void)
  {
    // Create the camera
    mCamera = mSceneMgr->createCamera("PlayerCam");

    // Position it at 500 in Z direction
    mCamera->setPosition(Vector3(50,50,50));
    // Look back along -Z
    mCamera->lookAt(Vector3(0,0,0));
    mCamera->setNearClipDistance(5);

    //mCamera->setFOVy(Radian(Degree(170)));
    //mCamera->setProjectionType(PT_ORTHOGRAPHIC);

  }
  virtual void createFrameListener(void)
  {
    mFrameListener= new ExampleFrameListener(mWindow, mCamera);
    mFrameListener->showDebugOverlay(true);
    mRoot->addFrameListener(mFrameListener);
  }

  virtual void createViewports(void)
  {
    // Create one viewport, entire window
    Viewport* vp = mWindow->addViewport(mCamera);
    vp->setBackgroundColour(ColourValue(0,0,0));

    // Alter the camera aspect ratio to match the viewport
    mCamera->setAspectRatio(
                            Real(vp->getActualWidth()) / Real(vp->getActualHeight()));
  }

  /// Method which will define the source of resources (other than current folder)
  virtual void setupResources(void)
  {
    // Load resource paths from config file
    ConfigFile cf;
    cf.load("resources.cfg");

    // Go through all settings in the file
    ConfigFile::SettingsIterator i = cf.getSettingsIterator();

    String typeName, archName;
    while (i.hasMoreElements())
      {
        typeName = i.peekNextKey();
        archName = i.getNext();
        ResourceManager::addCommonArchiveEx( archName, typeName );
      }

    //Root::addResourceLocation()
  }

  void createScene() 
  {
    // Create a skybox
    //mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");
    std::cout << "XXXXXXXXXXXXXXXXXXXX" << std::endl;
    std::cout << "CCCCCCXXXXXXXXXXXXXXXXXXXX" << std::endl;
    //Entity* head = mSceneMgr->createEntity("MrFoo", "farm2/Plane.001.mesh");
    //mSceneMgr->getRootSceneNode()->attachObject(head);
    
    int s = 50;
    for(int y = 0; y < s; ++y)
      {
        for(int x = 0; x < s; ++x)
          {
            char str[1024];
            snprintf(str, 1024, "foo%d", (y*s+x));
            Entity* newentity = mSceneMgr->createEntity(str, "farm2/Plane.001.mesh");
            //Entity* newentity = mSceneMgr->createEntity(str, "coalmine/Plane.001.mesh");
            SceneNode* mShipNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
            mShipNode->attachObject(newentity);
            mShipNode->translate((x-s) * 20, 0, (y-s) * 20);
          }
      }

    mSceneMgr->setAmbientLight(ColourValue(.5, 0.5, .5));

    // White light in "off" state
    Light* mWhiteLight = mSceneMgr->createLight("WhiteFlyingLight");
    mWhiteLight->setType(Light::LT_POINT);
    mWhiteLight->setPosition(Vector3(150,50,50));
    mWhiteLight->setDiffuseColour(ColourValue(1.0, 1.0, 1.0));


    //mWhiteLight->setCastShadows(true);
    //mWhiteLight->setAttenuation(8000,1,0.0005,0);
            
    mSceneMgr->getRootSceneNode()->attachObject(mWhiteLight);


    Light* mWhiteLight2 = mSceneMgr->createLight("WhiteFlyingLight2");
    mWhiteLight2->setType(Light::LT_POINT);
    mWhiteLight2->setPosition(Vector3(-150,-50,-50));
    mWhiteLight2->setDiffuseColour(ColourValue(1.0, 1.0, 1.0));
    mSceneMgr->getRootSceneNode()->attachObject(mWhiteLight2);

    //mSceneMgr->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE);

  }
};


int main(int argc, char** argv)
{
  // Create application object
  ExampleApplication  app;
  try {
    app.go();
  } catch( Ogre::Exception& e ) {

    std::cerr << "An exception has occured: " <<
      e.getFullDescription().c_str() << std::endl;
  }

  return 0;
}

/* EOF */

