/*==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 <math.h>

#include "hsTypes.h"

#include "plStereizer.h"
#include "plLineFollowMod.h"

#include "plMessage/plListenerMsg.h"
#include "plgDispatch.h"

#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"

#include "hsFastMath.h"

#include "hsGeometry3.h"
#include "hsMatrix44.h"
#include "hsStream.h"

plStereizer::plStereizer()
:   fInitPos(0,0,0),
    fListPos(0,0,0),
    fListDirection(0,1.f,0),
    fListUp(0,0,1.f)
{
}

plStereizer::~plStereizer()
{
    if( !HasMaster() )
        plgDispatch::Dispatch()->UnRegisterForExactType(plListenerMsg::Index(), GetKey());
}

void plStereizer::Read(hsStream* stream, hsResMgr* mgr)
{
    plSingleModifier::Read(stream, mgr);

    fAmbientDist = stream->ReadSwapScalar();
    fTransition = stream->ReadSwapScalar();

    fMaxSepDist = stream->ReadSwapScalar();
    fMinSepDist = stream->ReadSwapScalar();

    fTanAng = stream->ReadSwapScalar();

    fInitPos.Read(stream);

    if( !HasMaster() )
        plgDispatch::Dispatch()->RegisterForExactType(plListenerMsg::Index(), GetKey());
}

void plStereizer::Write(hsStream* stream, hsResMgr* mgr)
{
    plSingleModifier::Write(stream, mgr);

    stream->WriteSwapScalar(fAmbientDist);
    stream->WriteSwapScalar(fTransition);

    stream->WriteSwapScalar(fMaxSepDist);
    stream->WriteSwapScalar(fMinSepDist);

    stream->WriteSwapScalar(fTanAng);

    fInitPos.Write(stream);
}

hsBool plStereizer::MsgReceive(plMessage* msg)
{
    plListenerMsg* listenMsg = plListenerMsg::ConvertNoRef(msg);
    if( listenMsg )
    {
        SetFromListenerMsg(listenMsg);
        return Stereize();
    }

    return plSingleModifier::MsgReceive(msg);
}

hsBool plStereizer::IEval(double secs, hsScalar del, UInt32 dirty)
{
    return false;
}

hsBool plStereizer::Stereize()
{
    plSceneObject* targ = GetTarget();
    if( !targ )
        return true;

    targ->FlushTransform();

    // Find distance to listener
    hsPoint3 pos = IGetUnStereoPos();
    hsVector3 posToList(&fListPos, &pos);
    hsScalar dist = posToList.Magnitude();

    // If distance less than ambient distance
    //      setup as pure ambient

    // Else if distance greater than ambient distance + transition
    //      setup as pure localized

    // Else
    //      Calc pure ambient position
    //      Calc pure localized position
    //      Interpolate between the two.
    if( dist <= fAmbientDist )
    {
        ISetNewPos(IGetAmbientPos());
    }
    else if( dist >= fAmbientDist + fTransition )
    {
        ISetNewPos(IGetLocalizedPos(posToList, dist));
    }
    else
    {
        hsPoint3 ambPos = IGetAmbientPos();
        hsPoint3 localizePos = IGetLocalizedPos(posToList, dist);

        hsPoint3 newPos(ambPos);
        newPos += (localizePos - ambPos) * ((dist - fAmbientDist) / fTransition);

        ISetNewPos(newPos);
    }

    return true;
}

void plStereizer::ISetNewPos(const hsPoint3& newPos)
{
    hsMatrix44 l2w = GetTarget()->GetLocalToWorld();
    hsMatrix44 w2l = GetTarget()->GetWorldToLocal();

    l2w.NotIdentity();
    l2w.fMap[0][3] = newPos[0];
    l2w.fMap[1][3] = newPos[1];
    l2w.fMap[2][3] = newPos[2];

    hsPoint3 invPos = -newPos;

    w2l.fMap[0][3] = ((hsVector3*)&w2l.fMap[0][0])->InnerProduct(invPos);
    w2l.fMap[1][3] = ((hsVector3*)&w2l.fMap[1][0])->InnerProduct(invPos);
    w2l.fMap[2][3] = ((hsVector3*)&w2l.fMap[2][0])->InnerProduct(invPos);

    IGetTargetCoordinateInterface(0)->SetTransform(l2w, w2l);
}

void plStereizer::SetFromListenerMsg(const plListenerMsg* listMsg)
{
    fListPos = listMsg->GetPosition();
    fListDirection = listMsg->GetDirection();
    fListUp = listMsg->GetUp();
}

hsPoint3 plStereizer::IGetAmbientPos() const
{
    hsPoint3 pos = fListPos;
    hsVector3 axOut = fListDirection % fListUp;
    hsFastMath::NormalizeAppr(axOut);
    if( IsLeftChannel() )
        axOut *= -fMinSepDist;
    else
        axOut *= fMinSepDist;

    pos += axOut;

    return pos;
}

hsPoint3 plStereizer::IGetLocalizedPos(const hsVector3& posToList, hsScalar distToList) const
{
    hsPoint3 pos = IGetUnStereoPos();

    hsVector3 axOut(-posToList.fY, posToList.fX, 0);
    hsFastMath::NormalizeAppr(axOut);

    hsScalar distOut = distToList * fTanAng;
    if( distOut > fMaxSepDist )
        distOut = fMaxSepDist;
    else if( distOut < fMinSepDist )
        distOut = fMinSepDist;
    if( IsLeftChannel() )
        distOut = -distOut;

    axOut *= distOut;

    pos += axOut;

    return pos;
}

void plStereizer::SetSepAngle(hsScalar rads)
{
    fTanAng = hsScalar(tan(rads));
}

hsScalar plStereizer::GetSepAngle() const
{
    return atan(fTanAng);
}

hsPoint3 plStereizer::IGetUnStereoPos() const
{
    return GetWorldInitPos();
}

void plStereizer::SetWorldInitPos(const hsPoint3& pos)
{
    plCoordinateInterface* parent = IGetParent();
    if( parent )
        fInitPos = parent->GetWorldToLocal() * pos;
    else
        fInitPos = pos;
}

hsPoint3 plStereizer::GetWorldInitPos() const
{
    plCoordinateInterface* parent = IGetParent();
    if( parent )
        return parent->GetLocalToWorld() * fInitPos;

    return fInitPos;
}

plCoordinateInterface* plStereizer::IGetParent() const
{
    plCoordinateInterface* coord = IGetTargetCoordinateInterface(0);
    if( coord )
    {
        return coord->GetParent();
    }
    return nil;
}

// Note that (along with it's many other hacky defects), this
// will go down in flames if there are two potential masters.
// Of course, two line follow mods doesn't really make sense
// now anyway, but the point is that this is a simplified placeholder 
// to get the job done. If and when a need is shown for sequencing of
// modifiers, this should be updated to follow that protocol. But
// the rationale is that one simple example of a need for sequencing
// doesn't give enough basis to decide what that protocol should be.
// Or in simpler terms, I want to do it one way, Brice wants to do
// it another, and since either would work for this, we're waiting
// for a tie breaker case that gives one way or the other an advantage.
hsBool plStereizer::CheckForMaster()
{
    ISetHasMaster(false);
    plSceneObject* targ = GetTarget();
    if( !targ )
        return false;

    int n = targ->GetNumModifiers();
    int i;
    for( i = 0; i < n; i++ )
    {
        plLineFollowMod* line = plLineFollowMod::ConvertNoRef(IGetTargetModifier(0, i));
        if( line )
        {
            ISetHasMaster(true);
            line->AddStereizer(GetKey());

            return true;
        }
    }
    return false;
}