/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2009 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 OSGVOLUME_PROPERTY
#define OSGVOLUME_PROPERTY 1

#include <osg/TransferFunction>
#include <osg/Uniform>
#include <osg/AlphaFunc>

#include <osgGA/GUIEventHandler>

#include <osgVolume/Export>

namespace osgVolume {

// forward decarle
class Property;
class CompositeProperty;
class SwitchProperty;
class TransferFunctionProperty;
class ScalarProperty;
class IsoSurfaceProperty;
class MaximumIntensityProjectionProperty;
class LightingProperty;
class AlphaFuncProperty;
class SampleRatioProperty;
class SampleRatioWhenMovingProperty;
class SampleDensityProperty;
class SampleDensityWhenMovingProperty;
class TransparencyProperty;
class ExteriorTransparencyFactorProperty;
class VolumeSettings;

class OSGVOLUME_EXPORT PropertyVisitor
{
    public:

        PropertyVisitor(bool traverseOnlyActiveChildren=true);

        virtual ~PropertyVisitor() {}

        virtual void apply(Property&);
        virtual void apply(CompositeProperty&);
        virtual void apply(SwitchProperty&);
        virtual void apply(TransferFunctionProperty&);
        virtual void apply(ScalarProperty&);
        virtual void apply(IsoSurfaceProperty&);
        virtual void apply(AlphaFuncProperty&);
        virtual void apply(MaximumIntensityProjectionProperty&);
        virtual void apply(LightingProperty&);
        virtual void apply(SampleRatioProperty&);
        virtual void apply(SampleRatioWhenMovingProperty&);
        virtual void apply(SampleDensityProperty&);
        virtual void apply(SampleDensityWhenMovingProperty&);
        virtual void apply(TransparencyProperty&);
        virtual void apply(ExteriorTransparencyFactorProperty&);
        virtual void apply(VolumeSettings&);

        bool _traverseOnlyActiveChildren;
};


class OSGVOLUME_EXPORT Property : public osg::Object
{
    public:

        Property();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        Property(const Property&,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, Property);

        void dirty() { ++_modifiedCount; }

        void setModifiedCount(unsigned int c) { _modifiedCount = c; }
        unsigned int getModifiedCount() const { return _modifiedCount; }

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }
        virtual void traverse(PropertyVisitor& pv) {}

    protected:

        virtual ~Property();

        unsigned int _modifiedCount;
};

class OSGVOLUME_EXPORT CompositeProperty : public Property
{
    public:

        CompositeProperty();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        CompositeProperty(const CompositeProperty& compositeProperty,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, CompositeProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

        virtual void traverse(PropertyVisitor& pv)
        {
            for(Properties::iterator itr = _properties.begin();
                itr != _properties.end();
                ++itr)
            {
                (*itr)->accept(pv);
            }
        }

        void clear();

        typedef std::vector< osg::ref_ptr<Property> > Properties;

        void setProperty(unsigned int i, Property* property) { if (i>=_properties.size()) _properties.resize(i+1); _properties[i] = property; }

        Property* getProperty(unsigned int i) { return i<_properties.size() ? _properties[i].get() : 0; }

        const Property* getProperty(unsigned int i) const { return i<_properties.size() ? _properties[i].get() : 0; }

        void addProperty(Property* property) { _properties.push_back(property); dirty(); }

        void removeProperty(unsigned int i) { _properties.erase(_properties.begin()+i); }

        unsigned int getNumProperties() const { return _properties.size(); }

    protected:

        virtual ~CompositeProperty() {}


        Properties _properties;
};


class OSGVOLUME_EXPORT SwitchProperty : public CompositeProperty
{
    public:

        SwitchProperty();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        SwitchProperty(const SwitchProperty& switchProperty,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, SwitchProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

        virtual void traverse(PropertyVisitor& pv)
        {
            if (pv._traverseOnlyActiveChildren)
            {
                if (_activeProperty>=0 && static_cast<unsigned int>(_activeProperty)<=getNumProperties())
                {
                    _properties[_activeProperty]->accept(pv);
                }
            }
            else
            {
                CompositeProperty::traverse(pv);
            }
        }


        /** Set which child property is active.
          * -1 disables all children.*/
        void setActiveProperty(int i) { _activeProperty = i; dirty(); }

        /** Get the active property.*/
        int getActiveProperty() const { return _activeProperty; }

    protected:

        virtual ~SwitchProperty() {}

        int _activeProperty;
};

class OSGVOLUME_EXPORT TransferFunctionProperty : public Property
{
    public:

        TransferFunctionProperty(osg::TransferFunction* tf = 0);

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        TransferFunctionProperty(const TransferFunctionProperty& tfp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, TransferFunctionProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

        /** Set the transfer function.*/
        void setTransferFunction(osg::TransferFunction* tf) { _tf = tf; }

        /** Get the transfer function.*/
        osg::TransferFunction* getTransferFunction() { return _tf.get(); }

        /** Get the const transfer function.*/
        const osg::TransferFunction* getTransferFunction() const { return _tf.get(); }

    protected:

        virtual ~TransferFunctionProperty() {}

        osg::ref_ptr<osg::TransferFunction> _tf;
};



class OSGVOLUME_EXPORT ScalarProperty : public Property
{
    public:

        ScalarProperty(const std::string& scaleName, float value);

        ScalarProperty(const ScalarProperty& scalarProperty,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, ScalarProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

        /** Set the value.*/
        virtual void setValue(float v) { _uniform->set(v); dirty(); }

        /** Get the value.*/
        float getValue() const { float v; _uniform->get(v); return v; }

        /** Get the underlying uniform.*/
        osg::Uniform* getUniform() { return _uniform.get(); }

        /** Get the underlying uniform.*/
        const osg::Uniform* getUniform() const { return _uniform.get(); }

    protected:

        virtual ~ScalarProperty() {}

        ScalarProperty();

        osg::ref_ptr<osg::Uniform>  _uniform;
};


class OSGVOLUME_EXPORT IsoSurfaceProperty : public ScalarProperty
{
    public:

        IsoSurfaceProperty(float value=1.0f);

        IsoSurfaceProperty(const IsoSurfaceProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, IsoSurfaceProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~IsoSurfaceProperty() {}
};

class OSGVOLUME_EXPORT AlphaFuncProperty : public ScalarProperty
{
    public:

        AlphaFuncProperty(float value=1.0f);

        AlphaFuncProperty(const AlphaFuncProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, AlphaFuncProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

        virtual void setValue(float v);

        osg::AlphaFunc* getAlphaFunc() { return _alphaFunc.get(); }

        const osg::AlphaFunc* getAlphaFunc() const { return _alphaFunc.get(); }


    protected:

        virtual ~AlphaFuncProperty() {}

        osg::ref_ptr<osg::AlphaFunc> _alphaFunc;
};

class OSGVOLUME_EXPORT MaximumIntensityProjectionProperty : public Property
{
    public:

        MaximumIntensityProjectionProperty();

        MaximumIntensityProjectionProperty(const MaximumIntensityProjectionProperty& mipp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, MaximumIntensityProjectionProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~MaximumIntensityProjectionProperty() {}
};


class OSGVOLUME_EXPORT LightingProperty : public Property
{
    public:

        LightingProperty();

        LightingProperty(const LightingProperty& mipp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, LightingProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~LightingProperty() {}
};


/** Sample density to use when the volume is static relative to the eye point or when moving if no SampleDensityWhenMovingProperty is assigned.*/
class OSGVOLUME_EXPORT SampleDensityProperty : public ScalarProperty
{
    public:

        SampleDensityProperty(float value=1.0f);

        SampleDensityProperty(const SampleDensityProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, SampleDensityProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~SampleDensityProperty() {}
};

/** Sample density to use when the volume is moving relative to the eye point.*/
class OSGVOLUME_EXPORT SampleDensityWhenMovingProperty : public ScalarProperty
{
    public:

        SampleDensityWhenMovingProperty(float value=1.0f);

        SampleDensityWhenMovingProperty(const SampleDensityWhenMovingProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, SampleDensityWhenMovingProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~SampleDensityWhenMovingProperty() {}
};

/** Sample ratioto use when the volume is static relative to the eye point or when moving if no SampleRatioWhenMovingProperty is assigned.*/
class OSGVOLUME_EXPORT SampleRatioProperty : public ScalarProperty
{
    public:

        SampleRatioProperty(float value=1.0f);

        SampleRatioProperty(const SampleRatioProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, SampleRatioProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~SampleRatioProperty() {}
};

/** Sample density to use when the volume is moving relative to the eye point.*/
class OSGVOLUME_EXPORT SampleRatioWhenMovingProperty : public ScalarProperty
{
    public:

        SampleRatioWhenMovingProperty(float value=1.0f);

        SampleRatioWhenMovingProperty(const SampleRatioWhenMovingProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, SampleRatioWhenMovingProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~SampleRatioWhenMovingProperty() {}
};


class OSGVOLUME_EXPORT TransparencyProperty : public ScalarProperty
{
    public:

        TransparencyProperty(float value=1.0f);

        TransparencyProperty(const TransparencyProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, TransparencyProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~TransparencyProperty() {}
};

class OSGVOLUME_EXPORT ExteriorTransparencyFactorProperty : public ScalarProperty
{
    public:

        ExteriorTransparencyFactorProperty(float value=0.0f);

        ExteriorTransparencyFactorProperty(const ExteriorTransparencyFactorProperty& isp,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY);

        META_Object(osgVolume, ExteriorTransparencyFactorProperty);

        virtual void accept(PropertyVisitor& pv) { pv.apply(*this); }

    protected:

        virtual ~ExteriorTransparencyFactorProperty() {}
};


class OSGVOLUME_EXPORT CollectPropertiesVisitor : public osgVolume::PropertyVisitor
{
    public:

        CollectPropertiesVisitor(bool traverseOnlyActiveChildren=true);

        virtual void apply(TransferFunctionProperty&);
        virtual void apply(ScalarProperty&);
        virtual void apply(IsoSurfaceProperty& iso);
        virtual void apply(AlphaFuncProperty& af);
        virtual void apply(MaximumIntensityProjectionProperty& mip);
        virtual void apply(LightingProperty& lp);
        virtual void apply(SampleDensityProperty& sdp);
        virtual void apply(SampleDensityWhenMovingProperty& sdp);
        virtual void apply(SampleRatioProperty& sdp);
        virtual void apply(SampleRatioWhenMovingProperty& sdp);
        virtual void apply(TransparencyProperty& tp);
        virtual void apply(ExteriorTransparencyFactorProperty& tp);

        osg::ref_ptr<TransferFunctionProperty>              _tfProperty;
        osg::ref_ptr<IsoSurfaceProperty>                    _isoProperty;
        osg::ref_ptr<AlphaFuncProperty>                     _afProperty;
        osg::ref_ptr<MaximumIntensityProjectionProperty>    _mipProperty;
        osg::ref_ptr<LightingProperty>                      _lightingProperty;
        osg::ref_ptr<SampleDensityProperty>                 _sampleDensityProperty;
        osg::ref_ptr<SampleDensityWhenMovingProperty>       _sampleDensityWhenMovingProperty;
        osg::ref_ptr<SampleRatioProperty>                   _sampleRatioProperty;
        osg::ref_ptr<SampleRatioWhenMovingProperty>         _sampleRatioWhenMovingProperty;
        osg::ref_ptr<TransparencyProperty>                  _transparencyProperty;
        osg::ref_ptr<ExteriorTransparencyFactorProperty>    _exteriorTransparencyFactorProperty;

};

class OSGVOLUME_EXPORT PropertyAdjustmentCallback : public osgGA::GUIEventHandler, public osg::StateSet::Callback
{
    public:

        PropertyAdjustmentCallback();

        PropertyAdjustmentCallback(const PropertyAdjustmentCallback&,const osg::CopyOp&);

        META_Object(osgVolume, PropertyAdjustmentCallback);

        virtual bool run(osg::Object* object, osg::Object* data) { return osgGA::GUIEventHandler::run(object, data); }

        void setKeyEventCycleForward(int key) { _cyleForwardKey = key; }
        int getKeyEventCycleForward() const { return _cyleForwardKey; }

        void setKeyEventCycleBackward(int key) { _cyleBackwardKey = key; }
        int getKeyEventCycleBackward() const { return _cyleBackwardKey; }

        void setKeyEventActivatesTransparencyAdjustment(int key) { _transparencyKey = key; }
        int getKeyEventActivatesTransparencyAdjustment() const { return _transparencyKey; }

        void setKeyEventActivatesExteriorTransparencyFactorAdjustment(int key) { _exteriorTransparencyFactorKey = key; }
        int getKeyEventActivatesExteriorTransparencyFactorAdjustment() const { return _exteriorTransparencyFactorKey; }

        void setKeyEventActivatesSampleDensityAdjustment(int key) { _sampleDensityKey = key; }
        int getKeyEventActivatesSampleDensityAdjustment() const { return _sampleDensityKey; }

        void setKeyEventActivatesAlphaFuncAdjustment(int key) { _alphaFuncKey = key; }
        int getKeyEventActivatesAlphaFuncAdjustment() const { return _alphaFuncKey; }

        virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&, osg::Object* object, osg::NodeVisitor*);

        int     _cyleForwardKey;
        int     _cyleBackwardKey;
        int     _transparencyKey;
        int     _exteriorTransparencyFactorKey;
        int     _alphaFuncKey;
        int     _sampleDensityKey;

        bool    _updateTransparency;
        bool    _updateExteriorTransparencyFactor;
        bool    _updateAlphaCutOff;
        bool    _updateSampleDensity;
};

}

#endif
