/* -*-c++-*- OpenSceneGraph - Copyright (C) Sketchfab
 *
 * This application is open source and may be redistributed and/or modified
 * freely and without restriction, both in commercial and non commercial
 * applications, as long as this copyright notice is maintained.
 *
 * This application 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.
 *
*/

#ifndef GEOMETRY_UNIQUE_VISITOR_H
#define GEOMETRY_UNIQUE_VISITOR_H

#include <osg/NodeVisitor>
#include <osg/Geometry>
#include <osg/Geode>

#include <osgAnimation/RigGeometry>
#include <osgAnimation/MorphGeometry>

#include <set>
#include <string>

#include "StatLogger"


class GeometryUniqueVisitor : public osg::NodeVisitor {
public:
    GeometryUniqueVisitor(const std::string label=std::string("GeometryUniqueVisitor")):
        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
        _logger(formatStatLabel(label))
    {}

    virtual void apply(osg::Geode& geode){
        for (unsigned int i = 0; i < geode.getNumDrawables(); i++) {
            apply(*geode.getDrawable(i));
        }
    }

    virtual void apply(osg::Drawable& drawable){
        if (osg::Geometry* geometry = drawable.asGeometry()) {
            // call overloaded geometry processing
            apply(*geometry);
        }
    }

    virtual void apply(osg::Geometry& geometry) {
        // skip already processed geometry
        if (isProcessed(&geometry)) {
            return;
        }

        if(osgAnimation::RigGeometry* rigGeometry = dynamic_cast<osgAnimation::RigGeometry*>(&geometry)) {
            process(*rigGeometry);
        }
        else if(osgAnimation::MorphGeometry* morphGeometry = dynamic_cast<osgAnimation::MorphGeometry*>(&geometry)) {
            process(*morphGeometry);
        }
        else {
            process(geometry);
        }

        // flag geometry as processed
        setProcessed(&geometry);
    }

    virtual void process(osg::Geometry& node) = 0;

    virtual void process(osgAnimation::MorphGeometry& node) {
        // by default process a MorphGeometry like a regular geometry. If a visitor updates
        // vertices, it *has* to override this apply otherwise there might be a mismatch between the
        // 'master' morph geometry and morph targets.
        process(static_cast<osg::Geometry&>(node));
    }

    virtual void process(osgAnimation::RigGeometry& rigGeometry) {
        // by default, consider RigGeometry is just considered as a "shell" and visitor
        // processing should be performed on the rig source
        if(rigGeometry.getSourceGeometry()) {
            apply(*rigGeometry.getSourceGeometry());
        }
    }

protected:
    bool isProcessed(osg::Geometry* node) {
        return _processed.find(node) != _processed.end();
    }

    void setProcessed(osg::Geometry* node) {
        _processed.insert(node);
    }

    std::string formatStatLabel(const std::string& label) const {
        return label + "::apply(..)";
    }

    std::set<osg::Geometry*> _processed;
    StatLogger _logger;
};

#endif
