// -*-c++-*-

/*
 * Wavefront OBJ loader for Open Scene Graph
 *
 * Copyright (C) 2001 Ulrich Hertlein <u.hertlein@web.de>
 *
 * Modified by Robert Osfield to support per Drawable coord, normal and
 * texture coord arrays, bug fixes, and support for texture mapping.
 *
 * Writing support added 2007 by Stephan Huber, http://digitalmind.de,
 * some ideas taken from the dae-plugin
 *
 * The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for
 * real-time rendering of large 3D photo-realistic models.
 * The OSG homepage is http://www.openscenegraph.org/
 */

 #ifndef OBJ_WRITER_NODE_VISITOR_HEADER__
 #define OBJ_WRITER_NODE_VISITOR_HEADER__


#include <string>
#include <stack>
#include <sstream>

#include <osg/Notify>
#include <osg/Node>
#include <osg/MatrixTransform>
#include <osg/Geode>

#include <osg/Geometry>
#include <osg/StateSet>
#include <osg/Material>
#include <osg/Texture2D>
#include <osg/TexGen>
#include <osg/TexMat>

#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>




#include <map>
#include <set>

class OBJWriterNodeVisitor: public osg::NodeVisitor {

    public:
        OBJWriterNodeVisitor(std::ostream& fout, const std::string materialFileName = "", bool outputTextureFiles = false, const osgDB::Options* options = NULL) :
            osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
            _fout(fout),
            _currentStateSet(new osg::StateSet()),
            _lastVertexIndex(1),
            _lastNormalIndex(1),
            _lastTexIndex(1),
            _outputTextureFiles(outputTextureFiles),
            _options(options)
        {
            _fout << "# file written by OpenSceneGraph" << std::endl << std::endl;

            if (!materialFileName.empty()) {
                _fout << "mtllib " << materialFileName << std::endl << std::endl;
            }
        }

        virtual void apply(osg::Geometry & geometry);
        virtual void apply(osg::Geode & node);

        virtual void apply(osg::Group & node)
        {
            pushStateSet(node.getStateSet());
            _nameStack.push_back( node.getName().empty() ? node.className() : node.getName() );

            _fout << std::endl;
            _fout << "g " << getUniqueName() << std::endl;

            traverse( node );

            _nameStack.pop_back();

            popStateSet(node.getStateSet());
        }

        void pushStateSet(osg::StateSet* ss)
        {
          if (NULL!=ss) {
            // Save our current stateset
            _stateSetStack.push(_currentStateSet.get());

            // merge with node stateset
            _currentStateSet = static_cast<osg::StateSet*>(_currentStateSet->clone(osg::CopyOp::SHALLOW_COPY));
            _currentStateSet->merge(*ss);
          }
        }


        void popStateSet(osg::StateSet* ss)
        {
            if (NULL!=ss) {
              // restore the previous stateset
              _currentStateSet = _stateSetStack.top();
              _stateSetStack.pop();
            }
        }


        void writeMaterials(std::ostream& fout);



        class OBJMaterial {
            public:
                OBJMaterial() {}
                OBJMaterial(osg::Material* mat, osg::Texture* tex, bool outputTextureFiles = false, const osgDB::Options* options = NULL);

                osg::Vec4  diffuse, ambient, specular;
                float shininess;
                std::string    image;
                std::string name;
        };

    protected:
        struct CompareStateSet
        {
            bool operator()(const osg::ref_ptr<osg::StateSet>& ss1, const osg::ref_ptr<osg::StateSet>& ss2) const
            {
                //std::cout << "CompareStateSet: " << ss1->compare(*ss2, false) << " " << ss1 << " " << ss2 << std::endl;
                return ss1->compare(*ss2, true) < 0;
            }
        };


    private:

        OBJWriterNodeVisitor& operator = (const OBJWriterNodeVisitor&) { return *this; }

        void processGeometry(osg::Geometry* geo, osg::Matrix& m);
        void processArray(const std::string& key, osg::Array* array, const osg::Matrix& m = osg::Matrix::identity(), bool isNormal = false);

        void processStateSet(osg::StateSet* stateset);

        std::string getUniqueName(const std::string& defaultValue = "");

        typedef std::stack<osg::ref_ptr<osg::StateSet> > StateSetStack;
        typedef std::map< osg::ref_ptr<osg::StateSet>, OBJMaterial, CompareStateSet> MaterialMap;


        std::ostream&                           _fout;
        std::list<std::string>                  _nameStack;
        StateSetStack                           _stateSetStack;
        osg::ref_ptr<osg::StateSet>             _currentStateSet;
        std::map<std::string, unsigned int>     _nameMap;
        unsigned int                            _lastVertexIndex, _lastNormalIndex, _lastTexIndex;
        MaterialMap                             _materialMap;
        bool                                    _outputTextureFiles;
        osg::ref_ptr<const osgDB::Options>      _options;

};

#endif
