/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSG_ANIMATIONPATH
#define OSG_ANIMATIONPATH 1

#include <osg/Matrixf>
#include <osg/Matrixd>
#include <osg/Quat>
#include <osg/NodeCallback>

#include <map>

namespace osg {

/** AnimationPath for specify the time varying transformation pathway to use when update camera and model objects.
  * Subclassed from Transform::ComputeTransformCallback allows AnimationPath to
  * be attached directly to Transform nodes to move subgraphs around the scene.
*/
class SG_EXPORT AnimationPath : public virtual osg::Object
{
    public:
    
        AnimationPath():_loopMode(LOOP) {}

        AnimationPath(const AnimationPath& ap, const CopyOp& copyop=CopyOp::SHALLOW_COPY):
            Object(ap,copyop),
            _timeControlPointMap(ap._timeControlPointMap),
            _loopMode(ap._loopMode) {}

        META_Object(osg,AnimationPath);

        struct ControlPoint
        {
            ControlPoint():
                _scale(1.0f,1.0f,1.0f) {}

            ControlPoint(const osg::Vec3& position):
                _position(position),
                _rotation(),
                _scale(1.0f,1.0f,1.0f) {}

            ControlPoint(const osg::Vec3& position, const osg::Quat& rotation):
                _position(position),
                _rotation(rotation),
                _scale(1.0f,1.0f,1.0f) {}

            ControlPoint(const osg::Vec3& position, const osg::Quat& rotation, const osg::Vec3& scale):
                _position(position),
                _rotation(rotation),
                _scale(scale) {}
        
            osg::Vec3 _position;
            osg::Quat _rotation;
            osg::Vec3 _scale;
            
            inline void interpolate(float ratio,const ControlPoint& first, const ControlPoint& second)
            {
                float one_minus_ratio = 1.0f-ratio;
                _position = first._position*one_minus_ratio + second._position*ratio;
                _rotation.slerp(ratio,first._rotation,second._rotation);
                _scale = first._scale*one_minus_ratio + second._scale*ratio;
            }
            
            inline void getMatrix(Matrixf& matrix) const
            {
                matrix.makeScale(_scale);
                matrix.postMult(osg::Matrixf::rotate(_rotation));
                matrix.postMult(osg::Matrixf::translate(_position));
            }

            inline void getMatrix(Matrixd& matrix) const
            {
                matrix.makeScale(_scale);
                matrix.postMult(osg::Matrixd::rotate(_rotation));
                matrix.postMult(osg::Matrixd::translate(_position));
            }

            inline void getInverse(Matrixf& matrix) const
            {
                matrix.makeScale(1.0f/_scale.x(),1.0f/_scale.y(),1.0f/_scale.y());
                matrix.postMult(osg::Matrixf::rotate(_rotation.inverse()));
                matrix.postMult(osg::Matrixf::translate(-_position));
            }

            inline void getInverse(Matrixd& matrix) const
            {
                matrix.makeScale(1.0f/_scale.x(),1.0f/_scale.y(),1.0f/_scale.y());
                matrix.postMult(osg::Matrixd::rotate(_rotation.inverse()));
                matrix.postMult(osg::Matrixd::translate(-_position));
            }
        };
        

        /** get the transformation matrix for a point in time.*/        
        bool getMatrix(double time,Matrixf& matrix) const
        {
            ControlPoint cp;
            if (!getInterpolatedControlPoint(time,cp)) return false;
            cp.getMatrix(matrix);
            return true;
        }

        /** get the transformation matrix for a point in time.*/        
        bool getMatrix(double time,Matrixd& matrix) const
        {
            ControlPoint cp;
            if (!getInterpolatedControlPoint(time,cp)) return false;
            cp.getMatrix(matrix);
            return true;
        }

        /** get the inverse transformation matrix for a point in time.*/        
        bool getInverse(double time,Matrixf& matrix) const
        {
            ControlPoint cp;
            if (!getInterpolatedControlPoint(time,cp)) return false;
            cp.getInverse(matrix);
            return true;
        }
        
        bool getInverse(double time,Matrixd& matrix) const
        {
            ControlPoint cp;
            if (!getInterpolatedControlPoint(time,cp)) return false;
            cp.getInverse(matrix);
            return true;
        }

        /** get the local ControlPoint frame for a point in time.*/
        virtual bool getInterpolatedControlPoint(double time,ControlPoint& controlPoint) const;
        
        void insert(double time,const ControlPoint& controlPoint);
        
        double getFirstTime() const { if (!_timeControlPointMap.empty()) return _timeControlPointMap.begin()->first; else return 0.0;}
        double getLastTime() const { if (!_timeControlPointMap.empty()) return _timeControlPointMap.rbegin()->first; else return 0.0;}
        double getPeriod() const { return getLastTime()-getFirstTime();}
        
        enum LoopMode
        {
            SWING,
            LOOP,
            NO_LOOPING
        };
        
        void setLoopMode(LoopMode lm) { _loopMode = lm; }
        
        LoopMode getLoopMode() const { return _loopMode; }


        typedef std::map<double,ControlPoint> TimeControlPointMap;
        
        TimeControlPointMap& getTimeControlPointMap() { return _timeControlPointMap; }
        
        const TimeControlPointMap& getTimeControlPointMap() const { return _timeControlPointMap; }

    protected:
    
        virtual ~AnimationPath() {}

        TimeControlPointMap _timeControlPointMap;
        LoopMode            _loopMode;

};


class SG_EXPORT AnimationPathCallback : public NodeCallback
{
    public:

        AnimationPathCallback():
            _timeOffset(0.0),
            _timeMultiplier(1.0),
            _firstTime(0.0),
            _animationTime(0.0) {}
            

        AnimationPathCallback(const AnimationPathCallback& apc,const CopyOp& copyop):
            NodeCallback(apc,copyop),
            _animationPath(apc._animationPath),
            _timeOffset(apc._timeOffset),
            _timeMultiplier(apc._timeMultiplier),
            _firstTime(apc._firstTime),
            _animationTime(apc._animationTime) {}

        
        META_Object(osg,AnimationPathCallback);

        AnimationPathCallback(AnimationPath* ap,double timeOffset=0.0f,double timeMultiplier=1.0f):
            _animationPath(ap),
            _timeOffset(timeOffset),
            _timeMultiplier(timeMultiplier),
            _firstTime(0.0),
            _animationTime(0.0) {}
            
            
            
        void setAnimationPath(AnimationPath* path) { _animationPath = path; }

        AnimationPath* getAnimationPath() { return _animationPath.get(); }

        const AnimationPath* getAnimationPath() const { return _animationPath.get(); }


        /** implements the callback*/
        virtual void operator()(Node* node, NodeVisitor* nv);
        

    public:

        ref_ptr<AnimationPath>  _animationPath;
        double                  _timeOffset;
        double                  _timeMultiplier;
        double                  _firstTime;
        mutable double          _animationTime;

    protected:
    
        ~AnimationPathCallback(){}

};

}

#endif
