/*==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 "plQuatChannel.h"
#include "plPointChannel.h"
#include "plMatrixChannel.h"

#include "pnSceneObject/plDrawInterface.h"
#include "pnSceneObject/plSimulationInterface.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pnSceneObject/plAudioInterface.h"
#include "plInterp/plAnimTimeConvert.h"

#include "hsMatrix44.h"

////////////////
// PLQUATCHANNEL
////////////////

// CTOR
plQuatChannel::plQuatChannel()
: plAGChannel()
{
    fResult.Identity();
}

// DTOR
plQuatChannel::~plQuatChannel()
{
}

// VALUE (time)
const hsQuat &plQuatChannel::Value(double time)
{
    return fResult;
}

// VALUE (quaternion, time)
void plQuatChannel::Value(hsQuat &quat, double time)
{
    quat = Value(time);
}

// CANCOMBINE
hsBool plQuatChannel::CanCombine(plAGChannel *channelA)
{
    return false;
    if(plPointChannel::ConvertNoRef(channelA))
    {
        return true;
    } else {
        return false;
    }
}

// MAKECOMBINE
plAGChannel * plQuatChannel::MakeCombine(plAGChannel *channelA)
{
    if(plPointChannel::ConvertNoRef(channelA))
    {
        return TRACKED_NEW plQuatPointCombine(this, (plPointChannel *)channelA);
    } else {
        return nil;
    }
}

// MAKEBLEND
plAGChannel *plQuatChannel::MakeBlend(plAGChannel *channelB, plScalarChannel *channelBias, int blendPriority)
{
    plQuatChannel *chanB = plQuatChannel::ConvertNoRef(channelB);
    plScalarChannel *chanBias = plScalarChannel::ConvertNoRef(channelBias);
    if(chanB && chanBias)
    {
        return TRACKED_NEW plQuatBlend(this, chanB, chanBias);
    } else {
        hsStatusMessageF("Blend operation failed.");
        return this;
    }
}

// MAKEZEROSTATE
plAGChannel * plQuatChannel::MakeZeroState()
{
    return TRACKED_NEW plQuatConstant(Value(0));
}

// MAKETIMESCALE
plAGChannel * plQuatChannel::MakeTimeScale(plScalarChannel *timeSource)
{
    return TRACKED_NEW plQuatTimeScale(this, timeSource);
}

/////////////////
// PLQUATCONSTANT
/////////////////

// CTOR
plQuatConstant::plQuatConstant()
: plQuatChannel()
{
}

// CTOR(name, quaternion)
plQuatConstant::plQuatConstant(const hsQuat &quaternion)
{
    fResult = quaternion;
}

// DTOR
plQuatConstant::~plQuatConstant()
{
}

void plQuatConstant::Read(hsStream *stream, hsResMgr *mgr)
{
    plQuatChannel::Read(stream, mgr);
    fResult.Read(stream);
}

void plQuatConstant::Write(hsStream *stream, hsResMgr *mgr)
{
    plQuatChannel::Write(stream, mgr);
    fResult.Write(stream);
}

////////////////////
// PLQUATTIMESCALE
////////////////////
// Insert into the graph when you need to change the speed or direction of time
// Also serves as a handy instancing node, since it just passes its data through.

// CTOR
plQuatTimeScale::plQuatTimeScale()
: fTimeSource(nil),
  fChannelIn(nil)
{
}

// CTOR (channel, converter)
plQuatTimeScale::plQuatTimeScale(plQuatChannel *channel, plScalarChannel *timeSource)
: fChannelIn(channel),
  fTimeSource(timeSource)
{
}

// DTOR
plQuatTimeScale::~plQuatTimeScale()
{
}

hsBool plQuatTimeScale::IsStoppedAt(double time)
{
    return fTimeSource->IsStoppedAt(time);
}

// VALUE
const hsQuat & plQuatTimeScale::Value(double time)
{
    fResult = fChannelIn->Value(fTimeSource->Value(time));

    return fResult;
}

// DETACH
plAGChannel * plQuatTimeScale::Detach(plAGChannel * channel)
{
    plAGChannel *result = this;

    fChannelIn = plQuatChannel::ConvertNoRef(fChannelIn->Detach(channel));

    if(!fChannelIn || channel == this)
        result = nil;

    if (result != this)
        delete this;

    return result;
}

//////////////
// PLQUATBLEND
//////////////

// CTOR
plQuatBlend::plQuatBlend()
: fQuatA(nil),
  fQuatB(nil),
  fChannelBias(nil)
{
}

// CTOR(channelA, channelB, blend)
plQuatBlend::plQuatBlend(plQuatChannel *channelA, plQuatChannel *channelB, plScalarChannel *channelBias)
: fQuatA(channelA),
  fQuatB(channelB),
  fChannelBias(channelBias)
{
}

// DTOR
plQuatBlend::~plQuatBlend()
{
    //if (fQuatA) delete fQuatA;
    //if (fQuatB) delete fQuatB;
    fQuatA = fQuatB = nil;
    fChannelBias = nil;
}

hsBool plQuatBlend::IsStoppedAt(double time)
{
    hsScalar blend = fChannelBias->Value(time);
    if (blend == 0)
        return fQuatA->IsStoppedAt(time);
    if (blend == 1)
        return fQuatB->IsStoppedAt(time);

    return (fQuatA->IsStoppedAt(time) && fQuatB->IsStoppedAt(time));
}

// VALUE(time)
const hsQuat &plQuatBlend::Value(double time)
{
    hsQuat quatA = fQuatA->Value(time);
    hsQuat quatB = fQuatB->Value(time);

    fResult.SetFromSlerp(quatA, quatB, fChannelBias->Value(time));

    return fResult;
}


// REMOVE
// Remove the given channel wherever it may be in the graph (including this node)
plAGChannel * plQuatBlend::Detach(plAGChannel *remove)
{
    plAGChannel *result = this;

    hsAssert(remove != this, "Cannot remove blenders explicitly. Remove blended source instead.");

    if (remove != this)
    {
        fChannelBias = plScalarChannel::ConvertNoRef(fChannelBias->Detach(remove));
        if (!fChannelBias)
        {
            // No more bias channel, assume it's zero from now on, (a.k.a. We just want channelA)
            result = fQuatA;
        }
        else
        {
            fQuatA = (plQuatChannel *)fQuatA->Detach(remove);
            if(fQuatA)
            {
                // channel a still here(although children may be gone); try channel b
                fQuatB = (plQuatChannel *)fQuatB->Detach(remove);

                if(!fQuatB)
                {
                    result = fQuatA;    // channel b is gone: return channel a as blender's replacement
                }
            } else {
                result = fQuatB;        // channel a is gone: return channel b
            }

            if (result != this)
            {
                delete this;            // lost one of our channels: kill the blender.
            }
        }
    }
    return result;
}

//////////////////////////////////////////////////////////////////////////////////////
// Applicators

void plQuatChannelApplicator::IApply(const plAGModifier *mod, double time)
{
    plQuatChannel *quatChan = plQuatChannel::ConvertNoRef(fChannel);
    hsAssert(quatChan, "Invalid channel in plQuatChannelApplicator");

    const hsQuat &rotate = quatChan->Value(time);

    plCoordinateInterface *CI = IGetCI(mod);

    hsMatrix44 l2w;
    hsMatrix44 w2l;

    rotate.MakeMatrix(&l2w);
    l2w.GetInverse(&w2l);

    CI->SetLocalToParent(l2w, w2l);
}