/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 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 <map>
#include <istream>
#include <float.h>

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

namespace osg {

/** AnimationPath encapsulates a time varying transformation pathway. Can be
  * used for updating camera position and model object position.
  * AnimationPathCallback can be attached directly to Transform nodes to
  * move subgraphs around the scene.
*/
class OSG_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);

        class ControlPoint
        {
        public:
            ControlPoint():
                _scale(1.0,1.0,1.0) {}

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

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

            ControlPoint(const osg::Vec3d& position, const osg::Quat& rotation, const osg::Vec3d& scale):
                _position(position),
                _rotation(rotation),
                _scale(scale) {}

            void setPosition(const osg::Vec3d& position) { _position = position; }
            const osg::Vec3d& getPosition() const { return _position; }

            void setRotation(const osg::Quat& rotation) { _rotation = rotation; }
            const osg::Quat& getRotation() const { return _rotation; }

            void setScale(const osg::Vec3d& scale) { _scale = scale; }
            const osg::Vec3d& getScale() const { return _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 interpolate(double ratio,const ControlPoint& first, const ControlPoint& second)
            {
                double one_minus_ratio = 1.0-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.makeRotate(_rotation);
                matrix.preMultScale(_scale);
                matrix.postMultTranslate(_position);
            }

            inline void getMatrix(Matrixd& matrix) const
            {
                matrix.makeRotate(_rotation);
                matrix.preMultScale(_scale);
                matrix.postMultTranslate(_position);
            }

            inline void getInverse(Matrixf& matrix) const
            {
                matrix.makeRotate(_rotation.inverse());
                matrix.postMultScale(osg::Vec3d(1.0/_scale.x(),1.0/_scale.y(),1.0/_scale.z()));
                matrix.preMultTranslate(-_position);
            }

            inline void getInverse(Matrixd& matrix) const
            {
                matrix.makeRotate(_rotation.inverse());
                matrix.postMultScale(osg::Vec3d(1.0/_scale.x(),1.0/_scale.y(),1.0/_scale.z()));
                matrix.preMultTranslate(-_position);
            }

        protected:

            osg::Vec3d _position;
            osg::Quat _rotation;
            osg::Vec3d _scale;

        };


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

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

        /** Given a specific time, return the inverse transformation matrix for a point. */
        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;
        }

        /** Given a specific time, return the local ControlPoint frame for a point. */
        virtual bool getInterpolatedControlPoint(double time,ControlPoint& controlPoint) const;

        /** Insert a control point into the AnimationPath.*/
        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;

        void setTimeControlPointMap(TimeControlPointMap& tcpm) { _timeControlPointMap=tcpm; }

        TimeControlPointMap& getTimeControlPointMap() { return _timeControlPointMap; }

        const TimeControlPointMap& getTimeControlPointMap() const { return _timeControlPointMap; }

        bool empty() const { return _timeControlPointMap.empty(); }

        void clear() { _timeControlPointMap.clear(); }

        /** Read the animation path from a flat ASCII file stream. */
        void read(std::istream& in);

        /** Write the animation path to a flat ASCII file stream. */
        void write(std::ostream& out) const;

        /** Write the control point to a flat ASCII file stream. */
        void write(TimeControlPointMap::const_iterator itr, std::ostream& out) const;

    protected:

        virtual ~AnimationPath() {}

        TimeControlPointMap _timeControlPointMap;
        LoopMode            _loopMode;

};


class OSG_EXPORT AnimationPathCallback : public NodeCallback
{
    public:

        AnimationPathCallback():
            _pivotPoint(0.0,0.0,0.0),
            _useInverseMatrix(false),
            _timeOffset(0.0),
            _timeMultiplier(1.0),
            _firstTime(DBL_MAX),
            _latestTime(0.0),
            _pause(false),
            _pauseTime(0.0) {}

        AnimationPathCallback(const AnimationPathCallback& apc,const CopyOp& copyop):
            NodeCallback(apc,copyop),
            _animationPath(apc._animationPath),
            _pivotPoint(apc._pivotPoint),
            _useInverseMatrix(apc._useInverseMatrix),
            _timeOffset(apc._timeOffset),
            _timeMultiplier(apc._timeMultiplier),
            _firstTime(apc._firstTime),
            _latestTime(apc._latestTime),
            _pause(apc._pause),
            _pauseTime(apc._pauseTime) {}


        META_Object(osg,AnimationPathCallback);

        /** Construct an AnimationPathCallback with a specified animation path.*/
        AnimationPathCallback(AnimationPath* ap,double timeOffset=0.0,double timeMultiplier=1.0):
            _animationPath(ap),
            _pivotPoint(0.0,0.0,0.0),
            _useInverseMatrix(false),
            _timeOffset(timeOffset),
            _timeMultiplier(timeMultiplier),
            _firstTime(DBL_MAX),
            _latestTime(0.0),
            _pause(false),
            _pauseTime(0.0) {}

        /** Construct an AnimationPathCallback and automatically create an animation path to produce a rotation about a point.*/
        AnimationPathCallback(const osg::Vec3d& pivot,const osg::Vec3d& axis,float angularVelocity);


        void setAnimationPath(AnimationPath* path) { _animationPath = path; }
        AnimationPath* getAnimationPath() { return _animationPath.get(); }
        const AnimationPath* getAnimationPath() const { return _animationPath.get(); }

        inline void setPivotPoint(const Vec3d& pivot) { _pivotPoint = pivot; }
        inline const Vec3d& getPivotPoint() const { return _pivotPoint; }

        void setUseInverseMatrix(bool useInverseMatrix) { _useInverseMatrix = useInverseMatrix; }
        bool getUseInverseMatrix() const { return _useInverseMatrix; }

        void setTimeOffset(double offset) { _timeOffset = offset; }
        double getTimeOffset() const { return _timeOffset; }

        void setTimeMultiplier(double multiplier) { _timeMultiplier = multiplier; }
        double getTimeMultiplier() const { return _timeMultiplier; }


        virtual void reset();

        void setPause(bool pause);
        bool getPause() const { return _pause; }

        /** Get the animation time that is used to specify the position along
          * the AnimationPath. Animation time is computed from the formula:
          *   ((_latestTime-_firstTime)-_timeOffset)*_timeMultiplier.*/
        virtual double getAnimationTime() const;

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

        void update(osg::Node& node);

    public:

        ref_ptr<AnimationPath>  _animationPath;
        osg::Vec3d              _pivotPoint;
        bool                    _useInverseMatrix;
        double                  _timeOffset;
        double                  _timeMultiplier;
        double                  _firstTime;
        double                  _latestTime;
        bool                    _pause;
        double                  _pauseTime;

    protected:

        ~AnimationPathCallback(){}

};

}

#endif
