/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include <algorithm>
#include <iterator>
#include "hsTimer.h"
#include "plPhysicsSoundMgr.h"
#include "plPhysicalSndGroup.h"
#include "pnKeyedObject/plFixedKey.h"
#include "plStatusLog/plStatusLog.h"
#include "plMessage/plAnimCmdMsg.h"
#include "pfAudio/plRandomSoundMod.h"

#define MIN_VOLUME 0.0001f

void plPhysicsSoundMgr::AddContact(plPhysical* phys1, plPhysical* phys2, const hsPoint3& hitPoint, const hsVector3& hitNormal)
{
    CollidePair cp(phys1->GetKey(), phys2->GetKey(), hitPoint, hitNormal);
    fCurCollisions.insert(cp);
}

void plPhysicsSoundMgr::Update()
{
    // Get all the physicals that only in the new list (started colliding)
    CollideSet startedColliding;
    std::set_difference(fCurCollisions.begin(), fCurCollisions.end(),
        fPrevCollisions.begin(), fPrevCollisions.end(),
        std::inserter(startedColliding, startedColliding.begin()));

    for (CollideSet::iterator it = startedColliding.begin(); it != startedColliding.end(); it++)
        IStartCollision(*it);

    for (CollideSet::iterator it = fPrevCollisions.begin(); it != fPrevCollisions.end(); it++)
    {
        CollideSet::iterator old = fCurCollisions.find(*it);
        if (old != fCurCollisions.end())
        {
            IUpdateCollision(*it);
        }
        else
        {
            IStopCollision(*it);
        }
    }

    fPrevCollisions = fCurCollisions;
    fCurCollisions.clear();
}

void plPhysicsSoundMgr::IStartCollision(const CollidePair& cp)
{
    hsVector3 v1, v2;
    const hsScalar strengthThreshold = 20.0f;

    plPhysical* physicalA = cp.FirstPhysical();
    plPhysical* physicalB = cp.SecondPhysical();
    if (!physicalA || !physicalB)
        return;

    plPhysicalSndGroup* sndA = physicalA->GetSoundGroup();
    plPhysicalSndGroup* sndB = physicalB->GetSoundGroup();

    // If no impact sounds were specified in max don't do anything here.
    if (!sndA->HasImpactSound(sndB->GetGroup()) &&
        !sndB->HasImpactSound(sndA->GetGroup()))
        return;

    physicalA->GetLinearVelocitySim(v1);
    physicalB->GetLinearVelocitySim(v2);
    hsVector3 vel = v1 - v2;
    hsScalar strength = vel.MagnitudeSquared();
    
    if (strength >= strengthThreshold)
    {
        if (sndA->HasImpactSound(sndB->GetGroup()))
        {
            sndA->PlayImpactSound(sndB->GetGroup());
        }
        else
        {
            sndB->PlayImpactSound(sndA->GetGroup());
        }
    }
}

void plPhysicsSoundMgr::IStopCollision(const CollidePair& cp)
{
    plPhysical* physicalA = cp.FirstPhysical();
    plPhysical* physicalB = cp.SecondPhysical();
    if (physicalA && physicalB)
    {
        plPhysicalSndGroup* sndA = physicalA->GetSoundGroup();
        plPhysicalSndGroup* sndB = physicalB->GetSoundGroup();

        if (sndA->HasSlideSound(sndB->GetGroup()))
        {
            if(sndA->IsSliding())
            {
                sndA->StopSlideSound(sndB->GetGroup()); 
            }
        }
        if (sndB->HasSlideSound(sndA->GetGroup()))
        {
            if(sndB->IsSliding())
            {
                sndB->StopSlideSound(sndA->GetGroup());     
            }
        }
    }
}

void plPhysicsSoundMgr::IUpdateCollision(const CollidePair& cp)
{
    const hsScalar slideThreshhold = 0.f;   
    hsVector3 v1, v2;
    plPhysical* physicalA = cp.FirstPhysical();
    plPhysical* physicalB = cp.SecondPhysical();
    if (!physicalA || !physicalB)
        return;

    plPhysicalSndGroup* sndA = physicalA->GetSoundGroup();
    plPhysicalSndGroup* sndB = physicalB->GetSoundGroup();

    physicalA->GetLinearVelocitySim(v1);
    physicalB->GetLinearVelocitySim(v2);
    hsVector3 vel = v1 - v2;
    hsScalar strength = vel.MagnitudeSquared();
    
    // scale strength to use as volume
    strength /= 16*8;
    if(strength < MIN_VOLUME)
        strength = 0;
    
    if (sndA->HasSlideSound(sndB->GetGroup()))
        IProcessSlide(sndA, sndB, strength);
    else
        IProcessSlide(sndB, sndA, strength);
}

void plPhysicsSoundMgr::IProcessSlide(plPhysicalSndGroup* sndA, plPhysicalSndGroup* sndB, hsScalar strength)
{
    sndA->SetSlideSoundVolume(sndB->GetGroup(), strength);

    if(strength > MIN_VOLUME)
    {
        if(!sndA->IsSliding())
        {
            sndA->PlaySlideSound(sndB->GetGroup());
        }
    }
}

//////////////////////////////////////////////////////////////////////////

plPhysicsSoundMgr::CollidePair::CollidePair(const plKey& firstPhys, const plKey& secondPhys, const hsPoint3& point, const hsVector3& normal)
{
    // To simplify searching and sorting, all pairs are set up with the pointer value of the
    // first element greater than the pointer value of the second.
    if (firstPhys->GetObjectPtr() < secondPhys->GetObjectPtr())
    {
        this->firstPhysKey = secondPhys;
        this->secondPhysKey = firstPhys;
    }
    else
    {
        this->firstPhysKey = firstPhys;
        this->secondPhysKey = secondPhys;
    }

    this->point = point;
    this->normalForce = normal;
}

bool plPhysicsSoundMgr::CollidePair::operator<(const CollidePair& rhs) const
{
    return (firstPhysKey->GetObjectPtr() < rhs.firstPhysKey->GetObjectPtr() 
        || (rhs.firstPhysKey->GetObjectPtr() == firstPhysKey->GetObjectPtr() && secondPhysKey->GetObjectPtr() < rhs.secondPhysKey->GetObjectPtr()));
}

bool plPhysicsSoundMgr::CollidePair::operator==(const CollidePair& rhs) const
{
    if (firstPhysKey == rhs.firstPhysKey && secondPhysKey == rhs.secondPhysKey)
        return true;
    return false;
}

plPhysical* plPhysicsSoundMgr::CollidePair::FirstPhysical() const
{
    return firstPhysKey ? static_cast<plPhysical*>(firstPhysKey->GetObjectPtr()) : nil;
}

plPhysical* plPhysicsSoundMgr::CollidePair::SecondPhysical() const
{
    return secondPhysKey ? static_cast<plPhysical*>(secondPhysKey->GetObjectPtr()) : nil;
}