/*==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 "hsTypes.h"
#include "plBlower.h"
#include "plgDispatch.h"
#include "hsFastMath.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pnMessage/plTimeMsg.h"
#include "hsTimer.h"

plRandom plBlower::fRandom;

static const hsScalar kDefaultMasterPower = 20.f;
static const hsScalar kDefaultMasterFrequency = 2.f;
static const hsScalar kDefaultDirectRate = 1.f;
static const hsScalar kDefaultImpulseRate = 1.e2f;
static const hsScalar kDefaultSpringKonst = 20.f;
static const hsScalar kDefaultBias = 0.25f;
static const hsScalar kInitialMaxOffDist = 1.f;

plBlower::plBlower()
:   
    fMasterPower(kDefaultMasterPower),
    fMasterFrequency(kDefaultMasterFrequency),
    fDirectRate(kDefaultDirectRate),
    fImpulseRate(kDefaultImpulseRate),
    fSpringKonst(kDefaultSpringKonst),
    fBias(kDefaultBias),
    fMaxOffsetDist(kInitialMaxOffDist),
    fAccumTime(0)
{
    fRestPos.Set(0,0,0);
    fLocalRestPos.Set(0,0,0);
    fCurrDel.Set(0,0,0);

    fDirection.Set(fRandom.RandMinusOneToOne(), fRandom.RandMinusOneToOne(), 0);
    hsFastMath::NormalizeAppr(fDirection);
}

plBlower::~plBlower()
{
}

void plBlower::IBlow(double secs, hsScalar delSecs)
{
    hsPoint3 worldPos = fTarget->GetLocalToWorld().GetTranslate();
    hsPoint3 localPos = fTarget->GetLocalToParent().GetTranslate();

    // fast oscillation vs slow

    // Completely random walk in the rotation

    // Strength = Strength + rnd01 * (MaxStrength - Strength)

    hsScalar t = (fAccumTime += delSecs);

    hsScalar strength = 0;
    int i;
    for( i = 0; i < fOscillators.GetCount(); i++ )
    {
        hsScalar c, s;
        t *= fOscillators[i].fFrequency * fMasterFrequency;
        t += fOscillators[i].fPhase;
        hsFastMath::SinCosAppr(t, s, c);
        c += fBias;
        strength += c * fOscillators[i].fPower;
    }
    strength *= fMasterPower;

    if( strength < 0 )
        strength = 0;

    fDirection.fX += fRandom.RandMinusOneToOne() * delSecs * fDirectRate;
    fDirection.fY += fRandom.RandMinusOneToOne() * delSecs * fDirectRate;

    hsFastMath::NormalizeAppr(fDirection);

    hsScalar offDist = hsVector3(&fRestPos, &worldPos).Magnitude();
    if( offDist > fMaxOffsetDist )
        fMaxOffsetDist = offDist;

    hsVector3 force = fDirection * strength;

    static hsScalar kOffsetDistFrac = 0.5f; // make me const
    if( offDist > fMaxOffsetDist * kOffsetDistFrac )
    {
        offDist /= fMaxOffsetDist;
        offDist *= fMasterPower;

        hsScalar impulse = offDist * delSecs * fImpulseRate;

        force.fX += impulse * fRandom.RandMinusOneToOne();
        force.fY += impulse * fRandom.RandMinusOneToOne();
        force.fZ += impulse * fRandom.RandMinusOneToOne();
    }
    const hsScalar kOffsetDistDecay = 0.999f;
    fMaxOffsetDist *= kOffsetDistDecay;

    hsVector3 accel = force;
    accel += fSpringKonst * hsVector3(&fLocalRestPos, &localPos);

    hsVector3 del = accel * (delSecs * delSecs);

    fCurrDel = del;
}

hsBool plBlower::IEval(double secs, hsScalar delSecs, UInt32 dirty)
{
    const hsScalar kMaxDelSecs = 0.1f;
    if( delSecs > kMaxDelSecs )
        delSecs = kMaxDelSecs;
    IBlow(secs, delSecs);

    ISetTargetTransform();

    return true;
}

void plBlower::ISetTargetTransform()
{
    plCoordinateInterface* ci = IGetTargetCoordinateInterface(0);
    if( ci )
    {
        hsMatrix44 l2p = ci->GetLocalToParent();
        hsMatrix44 p2l = ci->GetParentToLocal();

        hsPoint3 pos = l2p.GetTranslate();
        pos += fCurrDel;

        l2p.SetTranslate(&pos);
        p2l.SetTranslate(&-pos);
    
        ci->SetLocalToParent(l2p, p2l);
    }
}

void plBlower::SetTarget(plSceneObject* so)
{
    plSingleModifier::SetTarget(so);

    if( fTarget )
    {
        fRestPos = fTarget->GetLocalToWorld().GetTranslate();
        fLocalRestPos = fTarget->GetLocalToParent().GetTranslate();
        plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
    }

    IInitOscillators();
}

void plBlower::Read(hsStream* s, hsResMgr* mgr)
{
    plSingleModifier::Read(s, mgr);

    fMasterPower = s->ReadSwapScalar();
    fDirectRate = s->ReadSwapScalar();
    fImpulseRate = s->ReadSwapScalar();
    fSpringKonst = s->ReadSwapScalar();
}

void plBlower::Write(hsStream* s, hsResMgr* mgr)
{
    plSingleModifier::Write(s, mgr);

    s->WriteSwapScalar(fMasterPower);
    s->WriteSwapScalar(fDirectRate);
    s->WriteSwapScalar(fImpulseRate);
    s->WriteSwapScalar(fSpringKonst);
}

void plBlower::IInitOscillators()
{
    const hsScalar kBasePower = 5.f;
    fOscillators.SetCount(5);
    int i;
    for( i = 0; i < fOscillators.GetCount(); i++ )
    {
        hsScalar fi = hsScalar(i+1);
        fOscillators[i].fFrequency = fi / hsScalarPI * fRandom.RandRangeF(0.75f, 1.25f);
//      fOscillators[i].fFrequency = 1.f / hsScalarPI * fRandom.RandRangeF(0.5f, 1.5f);
        fOscillators[i].fPhase = fRandom.RandZeroToOne();
        fOscillators[i].fPower = kBasePower * fRandom.RandRangeF(0.75f, 1.25f);
    }
}

void plBlower::SetConstancy(hsScalar f)
{
    if( f < 0 )
        f = 0;
    else if( f > 1.f )
        f = 1.f;

    fBias = f;
}

hsScalar plBlower::GetConstancy() const
{
    return fBias;
}