#include <Ogre.h>
#include <OgreApplicationContext.h>
#include <OgrePredefinedControllers.h>
// #ifdef RTSHADER_SYSTEM_BUILD_EXT_SHADERS
#include <OgreShaderExHardwareSkinning.h>
#include <OgreBillboard.h>
#include <OgreCameraMan.h>
#include "TimeEvents.h"

// #endif

/*  The RootMotionApplier applies the transforms from a specified
    NodeAnimationTrack to an Entity's parent SceneNode instead of
    to its Skeleton's Bone.

    Won’t work if root bone has an ancestor that is not
    origin-positioned, identity-oriented, and unity-scaled.

    If root bone has a non-origin-positioned, non-identity-oriented,
    or non-unity-scaled binding pose, playing multiple root-movement
    animations will apply the binding pose to the SceneNode multiple
    times (ie mess it up) (i think)

    TODO:

    - add setEnabled() or disable() or unapply() that unapplies all,
      some, or no components of applied rotation.

    - add option of which components of translation and rotation to
      retain/reset when looping (instead of the currently hardcoded
      reseting of translation.y and retaining of rotation.y)

    - maybe implement scale for shrinking and growing animations lol
*/
class RootMotionApplier {
public:
    RootMotionApplier(Ogre::AnimationState *animationState,
              Ogre::Entity *entity, Ogre::NodeAnimationTrack *track)
        : mEntity(entity)
        , mTrack(track)
        , mAppliedTranslation(Ogre::Vector3::ZERO)
        , mAppliedRotation(Ogre::Quaternion::IDENTITY)
    {
        assert(animationState);
        assert(entity);
        assert(track);

        // Get root bone binding pose.

        Ogre::Bone *rootBone =
            entity->getSkeleton()->getBone(track->getHandle());
        mRootBindingPosition = rootBone->getInitialPosition();
        mRootBindingOrientation = rootBone->getInitialOrientation();
        mRootBindingOrientationInverse =
            mRootBindingOrientation.Inverse();

        // Measure total transformation for root.

        Ogre::TransformKeyFrame *tkfBeg = track->getNodeKeyFrame(0);
        Ogre::TransformKeyFrame *tkfEnd =
            track->getNodeKeyFrame(track->getNumKeyFrames() - 1);

        Ogre::Quaternion begRotation = mRootBindingOrientation *
                           tkfBeg->getRotation() *
                           mRootBindingOrientationInverse;
        Ogre::Quaternion endRotation = mRootBindingOrientation *
                           tkfEnd->getRotation() *
                           mRootBindingOrientationInverse;
        mLoopRotation = endRotation * begRotation.Inverse();

        // TODO: there's probably a smarter way to limit rotation to y-axis
        Ogre::Matrix3 mat;
        mLoopRotation.ToRotationMatrix(mat);
        Ogre::Radian yAngle, zAngle, xAngle;
        mat.ToEulerAnglesYZX(yAngle, zAngle, xAngle);
        mat.FromEulerAnglesYZX(yAngle, Ogre::Radian(0.0f),
                       Ogre::Radian(0.0f));
        mLoopRotation.FromRotationMatrix(mat);

        mLoopRotationInverse = mLoopRotation.Inverse();

        Ogre::Vector3 begTranslation =
            mRootBindingPosition + tkfBeg->getTranslate() -
            begRotation * mRootBindingPosition;
        Ogre::Vector3 endTranslation =
            mRootBindingPosition + tkfEnd->getTranslate() -
            endRotation * mRootBindingPosition;
        mLoopTranslation =
            endTranslation - mLoopRotation * begTranslation;
        mLoopTranslation.y = 0.0f;

        // Suppress root bone movement

        if (!animationState->hasBlendMask()) {
            animationState->createBlendMask(
                entity->getSkeleton()->getNumBones(), 1.0f);
        }
        animationState->setBlendMaskEntry(track->getHandle(), 0.0f);
    }

    void apply(int loops, float thisTime)
    {
        Ogre::SceneNode *sceneNode = mEntity->getParentSceneNode();
        Ogre::TransformKeyFrame tkf(0, 0);

        // Unapply transform from last frame

        sceneNode->rotate(mAppliedRotation.Inverse());
        sceneNode->translate(-mAppliedTranslation,
                     Ogre::Node::TS_LOCAL);

        // Apply periodic loop transforms

        while (loops < 0) {
            sceneNode->rotate(mLoopRotationInverse);
            sceneNode->translate(-mLoopTranslation,
                         Ogre::Node::TS_LOCAL);
            loops++;
        }
        while (loops > 0) {
            sceneNode->translate(mLoopTranslation,
                         Ogre::Node::TS_LOCAL);
            sceneNode->rotate(mLoopRotation);
            loops--;
        }

        // Apply transform from this frame

        mTrack->getInterpolatedKeyFrame(thisTime, &tkf);

        mAppliedRotation = mRootBindingOrientation * tkf.getRotation() *
                   mRootBindingOrientationInverse;
        mAppliedTranslation = mRootBindingPosition +
                      tkf.getTranslate() -
                      mAppliedRotation * mRootBindingPosition;

        sceneNode->translate(mAppliedTranslation, Ogre::Node::TS_LOCAL);
        sceneNode->rotate(mAppliedRotation);
    }

private:
    Ogre::Entity *mEntity;
    Ogre::NodeAnimationTrack *mTrack;

    Ogre::Vector3 mRootBindingPosition;
    Ogre::Quaternion mRootBindingOrientation;
    Ogre::Quaternion mRootBindingOrientationInverse;

    Ogre::Vector3 mLoopTranslation;
    Ogre::Quaternion mLoopRotation;
    Ogre::Quaternion mLoopRotationInverse;

    Ogre::Vector3 mAppliedTranslation;
    Ogre::Quaternion mAppliedRotation;
};

class AnimationUpdater : public Ogre::ControllerValue<float> {
public:
    AnimationUpdater(Ogre::AnimationState *animationState)
        : mAnimationState(animationState)
    {
        assert(animationState);
    }

    float getValue(void) const override
    {
        return mAnimationState->getTimePosition() /
               mAnimationState->getLength();
    }

    void setValue(float timeDelta) override
    {
        // Don't assume AnimationState::addTime()'s internal time-updating (ie modulo) implementation.

        float lastTime = mAnimationState->getTimePosition();
        mAnimationState->addTime(timeDelta);
        float thisTime = mAnimationState->getTimePosition();

        float length = mAnimationState->getLength();
        bool loop = mAnimationState->getLoop();
        int loops = loop ? (int)std::round(
                       (lastTime + timeDelta - thisTime) /
                       length) :
                   0;

        // Apply Movement

        if (mRootMotionApplier) {
            mRootMotionApplier->apply(loops, thisTime);
        }

        // Dispatch Events

        if (mTimeEventDispatcher) {
            mTimeEventDispatcher->dispatch(lastTime, thisTime,
                               loops, length);
        }
    }

    void setUseRootMotion(Ogre::Entity *entity,
                  Ogre::NodeAnimationTrack *track)
    {
        mRootMotionApplier.reset(
            new RootMotionApplier(mAnimationState, entity, track));
    }

    RootMotionApplier *getRootMotionApplier()
    {
        return mRootMotionApplier.get();
    }

    void setUseTimeEvents(bool use)
    {
        if (use) {
            if (!mTimeEventDispatcher) {
                mTimeEventDispatcher.reset(
                    new TimeEventDispatcher);
            }
        } else {
            mTimeEventDispatcher.reset();
        }
    }

    TimeEventDispatcher *getTimeEventDispatcher()
    {
        return mTimeEventDispatcher.get();
    }

private:
    Ogre::AnimationState *mAnimationState;
    std::unique_ptr<RootMotionApplier> mRootMotionApplier;
    std::unique_ptr<TimeEventDispatcher> mTimeEventDispatcher;
};

class SoundwaveUpdater : public Ogre::ControllerValue<float> {
public:
    SoundwaveUpdater(Ogre::BillboardSet *bbs)
        : mBbs(bbs)
        , kSoundwaveColor(1.0f, 0.4f, 0.0f)
        , kSoundwaveSizeBeg(2.0f)
        , kSoundwaveSizeEnd(10.0f)
        , kSoundwaveTime(0.3f)
    {
    }

    static std::shared_ptr<SoundwaveUpdater> create(Ogre::BillboardSet *bbs)
    {
        return std::make_shared<SoundwaveUpdater>(bbs);
    }

    void addSoundwave(const Ogre::Vector3 &pos)
    {
        Ogre::Billboard *bb =
            mBbs->createBillboard(pos, kSoundwaveColor);
        bb->setDimensions(kSoundwaveSizeBeg, kSoundwaveSizeBeg);
        mBbs->_updateBounds();
    }

private:
    float getValue(void) const override
    {
        return 0.0f;
    }

    void setValue(float timeDelta) override
    {
        float grow = (kSoundwaveSizeEnd - kSoundwaveSizeBeg) *
                 timeDelta / kSoundwaveTime;

        for (int i = 0; i < mBbs->getNumBillboards();
             /* conditional inc in loop */) {
            Ogre::Billboard *bb = mBbs->getBillboard(i);

            float size = bb->getOwnWidth() + grow;

            if (size <= kSoundwaveSizeEnd) {
                float d = Ogre::Math::inverseLerp(
                    kSoundwaveSizeBeg, kSoundwaveSizeEnd,
                    size);

                bb->setDimensions(size, size);
                bb->setColour(kSoundwaveColor * (1.0f - d));
                ++i;
            } else {
                mBbs->removeBillboard(i);
            }
        }
    }

    Ogre::BillboardSet *mBbs;

    const Ogre::ColourValue kSoundwaveColor;
    const float kSoundwaveSizeBeg;
    const float kSoundwaveSizeEnd;
    const float kSoundwaveTime;
};

class FootfallListener final : public TimeEventListener {
public:
    FootfallListener(Ogre::Entity *entity,
             SoundwaveUpdater *soundwaveUpdater)
        : mEntity(entity)
        , mSoundwaveUpdater(soundwaveUpdater)
    {
    }

private:
    void eventOccurred(const std::string &name,
               TimeEventDirection direction) override
    {
        Ogre::Bone *toe;
        if (name == "footfall-l") {
            toe = mEntity->getSkeleton()->getBone("Ltoe");
        } else if (name == "footfall-r") {
            toe = mEntity->getSkeleton()->getBone("Rtoe");
        } else {
            return;
        }

        Ogre::Vector3 toePos =
            mEntity->getParentSceneNode()
                ->convertLocalToWorldPosition(
                    toe->_getDerivedPosition());
        toePos.y = 0.0f;

        mSoundwaveUpdater->addSoundwave(toePos);
    }

    Ogre::Entity *mEntity;
    SoundwaveUpdater *mSoundwaveUpdater;
};

/*-----------------------------------------------------------------------------
| The jaiqua mesh has the vertices baked quite a distance from local origin.
| This moves the mesh to the origin and moves the skeleton's Spineroot bone.
-----------------------------------------------------------------------------*/
static void tweakJaiquaMesh(const Ogre::MeshPtr &mesh, Ogre::Bone *rootBone)
{
    Ogre::SkeletonPtr skeleton = mesh->getSkeleton();
    // Get root bone's binding position
    const Ogre::Vector3 bindPos = rootBone->getInitialPosition();

    // Re-bind it at origin (preserving y)

    rootBone->setPosition(0.0f, bindPos.y, 0.0f);
    skeleton->setBindingPose();

    // Move all the vertices by the same amount
    // There's no shared vertices and only 1 submesh

    const Ogre::VertexData *vertexData =
        mesh->getSubMeshes()[0]->vertexData;
    const Ogre::VertexElement *posElem =
        vertexData->vertexDeclaration->findElementBySemantic(
            Ogre::VES_POSITION);
    Ogre::HardwareVertexBufferSharedPtr vbuf =
        vertexData->vertexBufferBinding->getBuffer(
            posElem->getSource());
    Ogre::DefaultHardwareBufferManagerBase bfrMgr;
    Ogre::HardwareVertexBufferPtr shadowBuffer = bfrMgr.createVertexBuffer(
        vbuf->getVertexSize(), vbuf->getNumVertices(),
        Ogre::HBU_CPU_ONLY);
    shadowBuffer->copyData(*vbuf);

    Ogre::HardwareBufferLockGuard vertexLock(
        shadowBuffer, Ogre::HardwareBuffer::HBL_NORMAL);
    unsigned char *vertex = static_cast<unsigned char *>(vertexLock.pData);
    float *pFloat;

    for (size_t i = 0; i < vertexData->vertexCount; ++i) {
        posElem->baseVertexPointerToElement(vertex, &pFloat);
        pFloat[0] -= bindPos.x;
        pFloat[2] -= bindPos.z;
        vertex += shadowBuffer->getVertexSize();
    }

    vertexLock.unlock();

    vbuf->copyData(*shadowBuffer);
}

/*-----------------------------------------------------------------------------
| The jaiqua sneak animation doesn't loop properly. This method tweaks the
| animation to loop properly by altering the Spineroot bone track.
| We also move the Sneak animation to start closer to the origin.
-----------------------------------------------------------------------------*/
static void tweakSneakAnim(const Ogre::SkeletonPtr &skel)
{
    // Move Sneak animation closer to origin
    Ogre::Bone *rootBone = skel->getBone("Spineroot");
    Ogre::Animation *animation = skel->getAnimation("Sneak");
    Ogre::NodeAnimationTrack *rootTrack =
        animation->getNodeTrack(rootBone->getHandle());

    Ogre::Vector3 start = rootTrack->getNodeKeyFrame(0)->getTranslate();
    start.y = 0.0f;

    for (size_t i = 0; i < rootTrack->getNumKeyFrames(); ++i) {
        Ogre::TransformKeyFrame *kf = rootTrack->getNodeKeyFrame(i);
        kf->setTranslate(kf->getTranslate() - start);
    }

    // Tweak Sneak animation
    const float ANIM_CHOP = 8.0f;
    for (const auto &it :
         animation->_getNodeTrackList()) // for every node track...
    {
        Ogre::NodeAnimationTrack *track = it.second;

        // get the keyframe at the chopping point
        Ogre::TransformKeyFrame oldKf(0, 0);
        track->getInterpolatedKeyFrame(ANIM_CHOP, &oldKf);

        // drop all keyframes after the chopping point
        while (track->getKeyFrame(track->getNumKeyFrames() - 1)
                   ->getTime() >= ANIM_CHOP - 0.3f)
            track->removeKeyFrame(track->getNumKeyFrames() - 1);

        // create a new keyframe at chopping point, and get the first keyframe
        Ogre::TransformKeyFrame *newKf =
            track->createNodeKeyFrame(ANIM_CHOP);
        Ogre::TransformKeyFrame *startKf = track->getNodeKeyFrame(0);

        Ogre::Bone *bone = skel->getBone(track->getHandle());

        if (bone->getName() ==
            "Spineroot") // adjust spine root relative to new location
        {
            newKf->setTranslate(oldKf.getTranslate());
            newKf->setRotation(oldKf.getRotation());
            newKf->setScale(oldKf.getScale());
        } else // make all other bones loop back
        {
            newKf->setTranslate(startKf->getTranslate());
            newKf->setRotation(startKf->getRotation());
            newKf->setScale(startKf->getScale());
        }
    }

    animation->setLength(ANIM_CHOP);
}

static void insertFootfallEvents(Ogre::Entity *entity, TimeEventList &eventList)
{
    /*  This function creates events coinciding with the footfalls in the sneak animation.
        Events are just strings associated with a timestamp that will be triggered during
        the playback of an animation and dispatched to a user-provided TimeEventListener.

        Normally these would be manually created along with the animation itself. Here we
        create them programmatically by stepping through the animation, applying the pose
        to a skeleton, and looking for when each toe bone crosses the y=0 plane.
     */

    Ogre::SkeletonInstance *skeleton = entity->getSkeleton();
    Ogre::Bone *lToeBone = skeleton->getBone("Ltoe");
    Ogre::Bone *rToeBone = skeleton->getBone("Rtoe");
    Ogre::Animation *animation = skeleton->getAnimation("Sneak");
    Ogre::AnimationState *as = entity->getAnimationState("Sneak");

    // We want the root transforms applied.

    if (as->hasBlendMask()) {
        as->setBlendMaskEntry(
            skeleton->getBone("Spineroot")->getHandle(), 1.0f);
    }

    // Get ending position before loop to compare with start position.

    as->setTimePosition(as->getLength());
    entity->_updateSkeleton();

    float lToePrevY = lToeBone->_getDerivedPosition().y;
    float rToePrevY = rToeBone->_getDerivedPosition().y;

    // This particular animation has keyframes for all tracks on a regular
    // framerate so we can use keyframe times from any track for sampling.

    Ogre::NodeAnimationTrack *lToeTrack =
        animation->getNodeTrack(lToeBone->getHandle());

    for (size_t i = 0; i < lToeTrack->getNumKeyFrames(); ++i) {
        Ogre::TransformKeyFrame *tkf = lToeTrack->getNodeKeyFrame(i);
        float time = tkf->getTime();

        as->setTimePosition(time);
        entity->_updateSkeleton();

        float lToeY = lToeBone->_getDerivedPosition().y;
        float rToeY = rToeBone->_getDerivedPosition().y;

        //        printf("t: %f   l: %f   r: %f\n", tkf->getTime(), lToeY, rToeY);

        if ((lToePrevY > 0.0f) && (lToeY < 0.0f)) {
            eventList.insert(std::make_pair(time, "footfall-l"));
        }
        if ((rToePrevY > 0.0f) && (rToeY < 0.0f)) {
            eventList.insert(std::make_pair(time, "footfall-r"));
        }

        lToePrevY = lToeY;
        rToePrevY = rToeY;
    }

    /*
    // Do it manually

    eventList.clear();

    mFootfallEvents.insert(std::make_pair(0.666667f, "footfall-l"));
    mFootfallEvents.insert(std::make_pair(2.000000f, "footfall-r"));
    mFootfallEvents.insert(std::make_pair(3.500000f, "footfall-l"));
    mFootfallEvents.insert(std::make_pair(4.166667f, "footfall-r"));
    mFootfallEvents.insert(std::make_pair(4.666667f, "footfall-l"));
     */
}

static void generateBoundingBox(Ogre::Entity *entity)
{
    // This is a hacky way to make a close-fitting bounding box
    // for the Sneak animation with the root movement suppressed.
    Ogre::Skeleton *skeleton = entity->getSkeleton();
    Ogre::Bone *rootBone = skeleton->getBone("Spineroot");
    Ogre::Animation *animation = skeleton->getAnimation("Sneak");
    Ogre::AnimationState *as = entity->getAnimationState("Sneak");
    Ogre::NodeAnimationTrack *rootTrack =
        animation->getNodeTrack(rootBone->getHandle());

    // Suppress root-bone movement.

    if (!as->hasBlendMask()) {
        as->createBlendMask(skeleton->getNumBones(), 1.0f);
    }
    as->setBlendMaskEntry(rootBone->getHandle(), 0.0f);
    as->setEnabled(true);

    // Generate bounding box from skeleton.

    entity->setUpdateBoundingBoxFromSkeleton(true);

    Ogre::AxisAlignedBox sneakBounds = Ogre::AxisAlignedBox::EXTENT_NULL;

    for (size_t i = 0; i < rootTrack->getNumKeyFrames(); ++i) {
        Ogre::TransformKeyFrame *kf = rootTrack->getNodeKeyFrame(i);

        as->setTimePosition(kf->getTime());
        entity->_updateSkeleton();

        Ogre::AxisAlignedBox bbox = entity->getBoundingBox();
        sneakBounds.merge(bbox);
    }

    entity->getMesh()->_setBounds(sneakBounds);
}

static void createSoundwaveMaterial(const Ogre::String &material_name,
                    const Ogre::String &group_name)
{
    // Make image

    const int w = 128;
    const int h = 128;

    Ogre::Image image(Ogre::PF_BYTE_L, w, h);
    image.setTo(Ogre::ColourValue::Black);

    // Draw ring
    float r = (w - 1) / 2.0f;
    float rr = r * r;

    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            float dx = x - r;
            float dy = y - r;
            float dd = dx * dx + dy * dy;

            if (dd > rr)
                continue;

            *image.getData(x, y) = 255 * dd / rr;
        }
    }

    // Make texture from image

    Ogre::TexturePtr t = Ogre::TextureManager::getSingleton().loadImage(
        material_name, group_name, image);

    // Make material

    Ogre::MaterialPtr material =
        Ogre::MaterialManager::getSingleton().create(material_name,
                                 Ogre::RGN_DEFAULT);

    material->setCullingMode(Ogre::CULL_NONE);
    material->setSceneBlending(Ogre::SBT_ADD);
    material->setLightingEnabled(false);
    material->setDepthWriteEnabled(false);
    material->setDepthBias(0, 1);

    Ogre::Pass *pass = material->getTechnique(0)->getPass(0);

    pass->setVertexColourTracking(Ogre::TVC_EMISSIVE);

    // Attach the texture to the material texture unit (single layer) and setup properties
    Ogre::TextureUnitState *tus = pass->createTextureUnitState();
    tus->setTexture(t);
}

enum VisualiseBoundingBoxMode { kVisualiseNone, kVisualiseOne, kVisualiseAll };

struct KeyHandler : public OgreBites::InputListener {
    VisualiseBoundingBoxMode mVisualiseBoundingBoxMode;
    KeyHandler()
        : OgreBites::InputListener()
    {
        mVisualiseBoundingBoxMode = kVisualiseNone;
    }
    bool keyPressed(const OgreBites::KeyboardEvent &evt) override
    {
#if 0
        if (true) {
            // Handle keypresses.
            switch (evt.keysym.sym) {
            case 'v':
                // Toggle visualise bounding boxes.
                switch (mVisualiseBoundingBoxMode) {
                case kVisualiseNone:
                    setVisualiseBoundingBoxMode(
                        kVisualiseOne);
                    break;
                case kVisualiseOne:
                    setVisualiseBoundingBoxMode(
                        kVisualiseAll);
                    break;
                case kVisualiseAll:
                    setVisualiseBoundingBoxMode(
                        kVisualiseNone);
                    break;
                }
                return true;
                break;

            case 'b': {
                // Toggle bone based bounding boxes for all models.
                enableBoneBoundingBoxMode(!mBoneBoundingBoxes);
                return true;
            } break;
            default:
                break;
            }
        }
#endif
        return false;
    }
};
class App : public OgreBites::ApplicationContext {
    void locateResources() override
    {
        Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
            "Characters", true);
        Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
            "Water", true);
        Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
            "LuaScripts", false);
        Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
            "./lua-scripts", "FileSystem", "LuaScripts", true,
            true);
        Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
            "./characters/male", "FileSystem", "Characters", false,
            true);
        Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
            "./characters/female", "FileSystem", "Characters",
            false, true);
        OgreBites::ApplicationContext::locateResources();
    }
    void loadResources() override
    {
        Ogre::TextureManager::getSingleton().load("flare.png",
                              "General");
        Ogre::TextureManager::getSingleton().load("blue_jaiqua.jpg",
                              "General");
        Ogre::MaterialManager::getSingleton().load("Examples/Flare",
                               "General");
        Ogre::MaterialManager::getSingleton().load("Examples/Rockwall",
                               "General");
        Ogre::MaterialManager::getSingleton().load("jaiqua", "General");
        Ogre::MeshManager::getSingleton().load("jaiqua.mesh",
                               "General");
    }
    KeyHandler kbd;

public:
    App()
        : OgreBites::ApplicationContext("FeatureCharactersDemo")
        , NUM_MODELS(6)
    {
        mBoundingBoxModelIndex = 0;
        mBoneBoundingBoxes = false;
        mBoneBoundingBoxesItemName = "Bone AABBs";
    }
    void setVisualiseBoundingBoxMode(VisualiseBoundingBoxMode mode)
    {
        kbd.mVisualiseBoundingBoxMode = mode;
        for (int i = 0; i < NUM_MODELS; i++) {
            switch (kbd.mVisualiseBoundingBoxMode) {
            case kVisualiseNone:
                mModelNodes[i]->setDisplaySceneNode(false);
                mModelNodes[i]->showBoundingBox(false);
                mEntities[i]->showBoundingSphere(false);
                break;
            case kVisualiseOne:
                mModelNodes[i]->setDisplaySceneNode(
                    i == mBoundingBoxModelIndex);
                mModelNodes[i]->showBoundingBox(
                    i == mBoundingBoxModelIndex);
                mEntities[i]->showBoundingSphere(
                    i == mBoundingBoxModelIndex);
                break;
            case kVisualiseAll:
                mModelNodes[i]->setDisplaySceneNode(true);
                mModelNodes[i]->showBoundingBox(true);
                mEntities[i]->showBoundingSphere(true);
                break;
            }
        }
    }
    void enableBoneBoundingBoxMode(bool enable)
    {
        // update bone bounding box mode for all models
        mBoneBoundingBoxes = enable;
        for (int iModel = 0; iModel < NUM_MODELS; iModel++) {
            Ogre::SceneNode *node = mModelNodes[iModel];
            for (unsigned int iObj = 0;
                 iObj < node->numAttachedObjects(); ++iObj) {
                if (Ogre::Entity *ent =
                        dynamic_cast<Ogre::Entity *>(
                            node->getAttachedObject(
                                iObj))) {
                    ent->setUpdateBoundingBoxFromSkeleton(
                        mBoneBoundingBoxes);
                    Ogre::Node::queueNeedUpdate(
                        node); // when turning off bone bounding boxes, need to force an update
                }
            }
        }
    }

public:
    void setupContent(Ogre::Viewport *mViewport,
              Ogre::SceneManager *mSceneMgr,
              OgreBites::CameraMan *mCameraMan)
    {
#ifdef OGRE_BUILD_COMPONENT_RTSHADERSYSTEM
        // Make this viewport work with shader generator scheme.
        mShaderGenerator->invalidateScheme(Ogre::MSN_SHADERGEN);
        mViewport->setMaterialScheme(Ogre::MSN_SHADERGEN);

        //Add the hardware skinning to the shader generator default render state
        auto srsHardwareSkinning =
            mShaderGenerator->createSubRenderState(
                Ogre::RTShader::SRS_HARDWARE_SKINNING);
        Ogre::RTShader::RenderState *renderState =
            mShaderGenerator->getRenderState(Ogre::MSN_SHADERGEN);
        renderState->addTemplateSubRenderState(srsHardwareSkinning);

        Ogre::MaterialPtr pCast1 =
            Ogre::MaterialManager::getSingleton().getByName(
                "Ogre/RTShader/shadow_caster_dq_skinning_1weight");
        Ogre::MaterialPtr pCast2 =
            Ogre::MaterialManager::getSingleton().getByName(
                "Ogre/RTShader/shadow_caster_dq_skinning_2weight");
        Ogre::MaterialPtr pCast3 =
            Ogre::MaterialManager::getSingleton().getByName(
                "Ogre/RTShader/shadow_caster_dq_skinning_3weight");
        Ogre::MaterialPtr pCast4 =
            Ogre::MaterialManager::getSingleton().getByName(
                "Ogre/RTShader/shadow_caster_dq_skinning_4weight");

        Ogre::RTShader::HardwareSkinningFactory::
            setCustomShadowCasterMaterials(
                Ogre::RTShader::ST_DUAL_QUATERNION, pCast1,
                pCast2, pCast3, pCast4);

        //        auto srs = mShaderGenerator->createSubRenderState(
        //            Ogre::RTShader::SRS_SHADOW_MAPPING);
        //        srs->setParameter("light_count", "2");
        //        renderState->addTemplateSubRenderState(srs);

        // set shadow properties
        // mSceneMgr->setShadowTechnique(SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED);
        mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_NONE);
        mSceneMgr->setShadowTextureCount(2);
        mSceneMgr->setShadowTextureSize(512);
#endif

        // add a little ambient lighting
        mSceneMgr->setAmbientLight(Ogre::ColourValue(0.3, 0.3, 0.3));

        Ogre::SceneNode *lightsBbsNode =
            mSceneMgr->getRootSceneNode()->createChildSceneNode();
        Ogre::BillboardSet *bbs;

        // Create billboard set for lights .
        bbs = mSceneMgr->createBillboardSet();
        bbs->setMaterialName("Examples/Flare");
        lightsBbsNode->attachObject(bbs);

        // add a blue spotlight
        Ogre::Light *l =
            mSceneMgr->createLight(Ogre::Light::LT_SPOTLIGHT);
        Ogre::Vector3 pos(-40, 180, -10);
        Ogre::SceneNode *ln =
            mSceneMgr->getRootSceneNode()->createChildSceneNode(
                pos);
        ln->attachObject(l);
        ln->setDirection(-pos);
        l->setDiffuseColour(0.0, 0.0, 0.5);
        bbs->createBillboard(pos)->setColour(l->getDiffuseColour());

        // add a green spotlight.
        l = mSceneMgr->createLight(Ogre::Light::LT_SPOTLIGHT);
        pos = Ogre::Vector3(0, 150, -100);
        ln = mSceneMgr->getRootSceneNode()->createChildSceneNode(pos);
        ln->attachObject(l);
        ln->setDirection(-pos);
        l->setDiffuseColour(0.0, 0.5, 0.0);
        bbs->createBillboard(pos)->setColour(l->getDiffuseColour());

        // create a floor mesh resource
        Ogre::MeshManager::getSingleton().createPlane(
            "floor", Ogre::RGN_DEFAULT,
            Ogre::Plane(Ogre::Vector3::UNIT_Y, -1), 250, 250, 25,
            25, true, 1, 15, 15, Ogre::Vector3::UNIT_Z);

        // add a floor to our scene using the floor mesh we created
        Ogre::Entity *floor = mSceneMgr->createEntity("Floor", "floor");
        floor->setMaterialName("Examples/Rockwall");
        floor->setCastShadows(false);
        mSceneMgr->getRootSceneNode()->attachObject(floor);

        // set camera initial transform and speed
        mCameraMan->setStyle(OgreBites::CS_ORBIT);
        setWindowGrab(false);
        mCameraMan->setYawPitchDist(Ogre::Degree(0), Ogre::Degree(25),
                        100);
        mCameraMan->setTopSpeed(50);

        setupModels(mSceneMgr, mViewport);
    }

    void setupModels(Ogre::SceneManager *mSceneMgr,
             Ogre::Viewport *mViewport)
    {
        // Load the mesh with shadow buffers so we can access vertex data
        Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
            "jaiqua.mesh", Ogre::RGN_DEFAULT, Ogre::HBU_CPU_TO_GPU,
            Ogre::HBU_CPU_TO_GPU, true, true);
        Ogre::SkeletonPtr skeleton = mesh->getSkeleton();
        Ogre::Bone *rootBone = skeleton->getBone("Spineroot");
        Ogre::Animation *animation = skeleton->getAnimation("Sneak");
        Ogre::NodeAnimationTrack *sneakRootTrack =
            animation->getNodeTrack(rootBone->getHandle());

        tweakJaiquaMesh(mesh, rootBone);
        tweakSneakAnim(skeleton);

        Ogre::SceneNode *sn = NULL;
        Ogre::Entity *ent = NULL;
        Ogre::AnimationState *as = NULL;

        auto &controllerMgr = Ogre::ControllerManager::getSingleton();

        // Create soundwave material, billboard set, and updater.

        createSoundwaveMaterial("soundwave", Ogre::RGN_DEFAULT);

        Ogre::BillboardSet *soundwaveBbs =
            mSceneMgr->createBillboardSet();
        soundwaveBbs->setMaterialName("soundwave");
        soundwaveBbs->setBillboardType(Ogre::BBT_PERPENDICULAR_COMMON);
        soundwaveBbs->setCommonDirection(Ogre::Vector3::UNIT_Y);
        soundwaveBbs->setCommonUpVector(Ogre::Vector3::NEGATIVE_UNIT_Z);
        mSceneMgr->getRootSceneNode()
            ->createChildSceneNode()
            ->attachObject(soundwaveBbs);

        std::shared_ptr<SoundwaveUpdater> soundwaveUpdater =
            SoundwaveUpdater::create(soundwaveBbs);
        controllerMgr.createFrameTimePassthroughController(
            soundwaveUpdater);

        // Create models, animation updaters, and footfall listeners.

        for (int i = 0; i < NUM_MODELS; i++) {
            // create scene nodes for the models at regular angular intervals
            sn = mSceneMgr->getRootSceneNode()
                     ->createChildSceneNode();
            sn->yaw(Ogre::Radian(Ogre::Math::TWO_PI * (float)i /
                         (float)NUM_MODELS));
            sn->translate(0, 0, -20, Ogre::Node::TS_LOCAL);
            mModelNodes.push_back(sn);

            // create and attach a jaiqua entity
            ent = mSceneMgr->createEntity(
                "Jaiqua" +
                    Ogre::StringConverter::toString(i + 1),
                "jaiqua.mesh");
            ent->setMaterialName("jaiqua");
            mEntities.push_back(ent);
            sn->attachObject(ent);

            mFootfallListeners.push_back(
                std::make_unique<FootfallListener>(
                    ent, soundwaveUpdater.get()));

            // enable the entity's sneaking animation at a random speed and loop it manually since translation is involved
            as = ent->getAnimationState("Sneak");
            as->setEnabled(true);
            as->setLoop(true);

            auto updater = std::make_shared<AnimationUpdater>(as);
            updater->setUseRootMotion(ent, sneakRootTrack);
            updater->setUseTimeEvents(true);
            TimeEventDispatcher *ted =
                updater->getTimeEventDispatcher();
            ted->addEventList(&mSneakEvents);
            ted->addListener(mFootfallListeners[i].get());

            controllerMgr.createController(
                controllerMgr.getFrameTimeSource(), updater,
                Ogre::ScaleControllerFunction::create(
                    Ogre::Math::RangeRandom(0.5, 1.5)));

            mAnimUpdaters.push_back(updater.get());
        }

        insertFootfallEvents(mEntities[0], mSneakEvents);
        generateBoundingBox(mEntities[0]);

        // create name and value for skinning mode
        Ogre::StringVector names;
        names.push_back("Help");
        names.push_back("Skinning");
        names.push_back(mBoneBoundingBoxesItemName);

        // create a params panel to display the help and skinning mode
        Ogre::String value = "Software";
        enableBoneBoundingBoxMode(false); // update status panel entry

        // make sure we query the correct scheme
        Ogre::MaterialManager::getSingleton().setActiveScheme(
            mViewport->getMaterialScheme());

        // change the value if hardware skinning is enabled
        Ogre::MaterialPtr entityMaterial =
            ent->getSubEntity(0)->getMaterial();
        if (entityMaterial) {
            Ogre::Technique *bestTechnique =
                entityMaterial->getBestTechnique();
            if (bestTechnique) {
                Ogre::Pass *pass = bestTechnique->getPass(0);
                if (pass && pass->hasVertexProgram() &&
                    pass->getVertexProgram()
                        ->isSkeletalAnimationIncluded()) {
                    value = "Hardware";
                }
            }
        }
    }

public:
    void cleanupContent()
    {
        mModelNodes.clear();
        mEntities.clear();
        mAnimUpdaters.clear();
        mFootfallListeners.clear();
        Ogre::MeshManager::getSingleton().remove("floor",
                             Ogre::RGN_DEFAULT);
        Ogre::MaterialManager::getSingleton().remove("soundwave",
                                 Ogre::RGN_DEFAULT);
        Ogre::TextureManager::getSingleton().remove("soundwave",
                                Ogre::RGN_DEFAULT);
    }

    const int NUM_MODELS;
    int mBoundingBoxModelIndex; // which model to show the bounding box for
    bool mBoneBoundingBoxes;
    Ogre::String mBoneBoundingBoxesItemName;

    std::vector<Ogre::SceneNode *> mModelNodes;
    std::vector<Ogre::Entity *> mEntities;
    std::vector<AnimationUpdater *> mAnimUpdaters;
    std::vector<std::unique_ptr<FootfallListener> > mFootfallListeners;

    TimeEventList mSneakEvents;
};
int main()
{
    App ctx;
    ctx.initApp();
    Ogre::Root *root = ctx.getRoot();
    Ogre::SceneManager *scnMgr = root->createSceneManager();
    Ogre::RTShader::ShaderGenerator *shadergen =
        Ogre::RTShader::ShaderGenerator::getSingletonPtr();
    shadergen->addSceneManager(scnMgr);

    Ogre::Light *light = scnMgr->createLight("MainLight");
    Ogre::SceneNode *lightNode =
        scnMgr->getRootSceneNode()->createChildSceneNode();
    lightNode->setPosition(0, 10, 15);
    lightNode->attachObject(light);

    Ogre::SceneNode *camNode =
        scnMgr->getRootSceneNode()->createChildSceneNode();
    camNode->setPosition(0, 0, 15);
    camNode->lookAt(Ogre::Vector3(0, 0, -1), Ogre::Node::TS_PARENT);

    Ogre::Camera *cam = scnMgr->createCamera("myCam");
    cam->setNearClipDistance(5);
    cam->setAutoAspectRatio(true);
    camNode->attachObject(cam);

    ctx.getRenderWindow()->addViewport(cam);

#if 0
    Ogre::Entity *ent = scnMgr->createEntity("Sinbad.mesh");
    Ogre::SceneNode *node =
        scnMgr->getRootSceneNode()->createChildSceneNode();
    node->attachObject(ent);
#endif
    KeyHandler keyHandler;
    ctx.addInputListener(&keyHandler);
    OgreBites::CameraMan *mCameraMan = new OgreBites::CameraMan(camNode);

    ctx.setupContent(cam->getViewport(), scnMgr, mCameraMan);

    ctx.getRoot()->startRendering();
    ctx.setWindowGrab(false);
    ctx.cleanupContent();
    ctx.closeApp();
    return 0;
}
