/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2010 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

#ifndef OSGEARTHSYMBOLOGY_SYMBOLICNODE_H
#define OSGEARTHSYMBOLOGY_SYMBOLICNODE_H 1

#include <osgEarthSymbology/Common>
#include <osgEarthSymbology/Style>
#include <osgEarthSymbology/Symbolizer>
#include <osg/Node>

namespace osgEarth { namespace Symbology
{
    struct SymbolicNodeBase : public osg::Group
    {
        virtual void updateSymbology() =0;
        SymbolicNodeBase() { }
        SymbolicNodeBase( const SymbolicNodeBase& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) 
            : osg::Group(rhs, op) { }
    };

    struct SymUpdateCallback : public osg::NodeCallback
    {
        virtual void operator ()(osg::Node* node, osg::NodeVisitor* nv)
        {
            static_cast<SymbolicNodeBase*>(node)->updateSymbology();
            traverse( node, nv );
        }
    };

    /**
     * Abstract base class for all Symbol types.
     */
    template<typename STATE_TYPE>
    class SymbolicNode : public SymbolicNodeBase // header-only class.
    {
    public:
        /** Constructs a new symbolic node. */
        SymbolicNode()
        {
            _prototypeState = new STATE_TYPE();

            _symGroup = new osg::Group();
            _symGroup->setDataVariance( osg::Object::DYNAMIC );
            this->addChild( _symGroup.get() );

            // install an update callback for managing content and style changes.
            this->addUpdateCallback( new SymUpdateCallback() );
        }

        /** Copy constructor. */
        SymbolicNode( const SymbolicNode& rhs, const osg::CopyOp& op =osg::CopyOp::DEEP_COPY_ALL ) :
            SymbolicNodeBase( rhs, op ),
            _symGroup( rhs._symGroup )
        {
            if ( rhs._prototypeState.valid() )
            {
                _prototypeState = osg::clone( rhs._prototypeState.get(), op );
            }

            // this will cause a new state to form and correctly copy the state values
            setSymbolizer( rhs._symbolizer.get() );
        }

        META_Node(osgEarth::Symbology, SymbolicNode);

        /** The symbolizer that will render the geometry under this node. */
        void setSymbolizer( Symbolizer<STATE_TYPE>* sym )
        {
            // copy the old state over to the new state.
            osg::ref_ptr<STATE_TYPE> oldState = _state.get();

            _symbolizer = sym;
            _state = 0L;

            if ( _symbolizer.valid() )
            {
                _state = getOrCreateState();
                if ( oldState.valid() )
                {
                    _state->setContent( oldState->getContent() );
                    _state->setStyle( oldState->getStyle() );
                    _state->setContext( oldState->getContext() );
                }
                else
                {
                    _state->setContent( _prototypeState->getContent() );
                    _state->setStyle( _prototypeState->getStyle() );
                    _prototypeState = new STATE_TYPE(); // reinitialize.
                }
            }
        }

        Symbolizer<STATE_TYPE>* getSymbolizer() { 
            return _symbolizer.get(); }

        STATE_TYPE* getState() {
            return getOrCreateState(); }
    public:
        /** 
         * Updates the symbology if a change is detected in the input dataset or the style.
         * You usually do not need to call this function directly; it gets called during 
         * the update traversal.
         */
        void updateSymbology()
        {
            if ( _symbolizer.valid() && _state.valid() )
            {   
                bool contentDirty = _state->contentDirty();
                bool styleDirty = _state->styleDirty();
                if ( _symbolizer->getAlwaysCompile() || contentDirty || styleDirty )
                {
                    _symbolizer->compile( _state.get(), _symGroup.get() );

                    if ( _state->getContent() )
                        _state->getContent()->sync( _state->_contentRevision );

                    if ( _state->getStyle() )
                        _state->getStyle()->sync( _state->_styleRevision );

                    this->dirtyBound();
                }
            }
        }

    protected:
        osg::ref_ptr<Symbolizer<STATE_TYPE> >  _symbolizer;
        osg::ref_ptr<STATE_TYPE>               _state;
        osg::ref_ptr<osg::Group>               _symGroup;
        osg::ref_ptr<STATE_TYPE>               _prototypeState;
        //osg::ref_ptr<SymbolizerContext>        _context;

        STATE_TYPE* getOrCreateState()
        {
            if ( !_state.valid() && _symbolizer.valid() )
            {
                _state = new STATE_TYPE();
            }

            return _state.get();
        }
    };


} } // namespace osgEarth::Symbology

#endif // OSGEARTHSYMBOLOGY_SYMBOLICNODE_H
