#include <Urho3D/Audio/Sound.h>
#include <Urho3D/Audio/SoundSource3D.h>
#include <Urho3D/Core/Context.h>
#include <Urho3D/Graphics/AnimationController.h>
#include <Urho3D/IO/MemoryBuffer.h>
#include <Urho3D/Physics/PhysicsEvents.h>
#include <Urho3D/Physics/PhysicsWorld.h>
#include <Urho3D/Physics/RigidBody.h>
#include <Urho3D/Resource/ResourceCache.h>
#include <Urho3D/Scene/Scene.h>
#include <Urho3D/Scene/SceneEvents.h>

#include "Airborne.h"

Airborne::Airborne(Context* context) :
    LogicComponent(context),
    onGround_(false),
    previousStepOnGround_(false),
    inAirTimer_(0.0f)
{
    // Only the physics update event is needed: unsubscribe from the rest for optimization
    SetUpdateEventMask(USE_FIXEDUPDATE);
}

void Airborne::RegisterObject(Context* context)
{
    context->RegisterFactory<Airborne>();

    URHO3D_ATTRIBUTE("On Ground", bool, onGround_, false, AM_DEFAULT);
    URHO3D_ATTRIBUTE("In Air Timer", float, inAirTimer_, 0.0f, AM_DEFAULT);
}

void Airborne::Start()
{
    // Component has been inserted into its scene node. Subscribe to events now
    SubscribeToEvent(GetNode(), E_NODECOLLISION, URHO3D_HANDLER(Airborne, HandleNodeCollision));
}

void Airborne::FixedUpdate(float timeStep)
{
    RigidBody* body = GetComponent<RigidBody>();

    // Update the in air timer. Reset if grounded
    if (!onGround_)
        inAirTimer_ += timeStep;
    else
        inAirTimer_ = 0.0f;
    bool softGrounded = inAirTimer_ < 0.1f;

    if (previousStepOnGround_ && !onGround_) {
        auto const path = "fds/uh-1.wav";
        auto const sound = GetSubsystem<ResourceCache>()->GetResource<Sound>(path);
        auto const source = GetNode()->CreateComponent<SoundSource3D>();
        source->SetAutoRemoveMode(REMOVE_COMPONENT);
        source->SetNearDistance(1.0f);
        source->SetFarDistance(50.0f);
        auto const halfFR = 0.10f / 2.0f;
        source->SetFrequency(
                sound->GetFrequency() * Clamp(Random(1.0f - halfFR, 1.0f + halfFR)
                / GetNode()->GetScale().Length() * 4.0f, 0.3f, 2.0f)
        );
        source->SetGain(0.4f);
        source->SetSoundType(SOUND_EFFECT);
        source->Play(sound);
    }

    previousStepOnGround_ =  onGround_;
    onGround_ = false;
}

void Airborne::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
{
    // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
    using namespace NodeCollision;

    MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());

    while (!contacts.IsEof())
    {
        Vector3 contactPosition = contacts.ReadVector3();
        Vector3 contactNormal = contacts.ReadVector3();
        /*float contactDistance = */contacts.ReadFloat();
        /*float contactImpulse = */contacts.ReadFloat();

        // If contact is below node center and pointing up, assume it's a ground contact
        if (contactPosition.y_ < (node_->GetPosition().y_ + 1.0f))
        {
            float level = contactNormal.y_;
            if (level > 0.75) {
                onGround_ = true;
            }
        }
    }
}
