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

#include <osg/ref_ptr>
#include <osg/ArgumentParser>

#include <osgDB/DynamicLibrary>
#include <osgDB/ReaderWriter>
#include <osgDB/DotOsgWrapper>
#include <osgDB/DatabasePager>

#include <vector>
#include <map>
#include <string>


namespace osgDB {

/** basic structure for custom runtime inheritance checking */
struct basic_type_wrapper {
    virtual bool matches(const osg::Object *proto) const = 0;
};

/** a class template that checks inheritance between a given
    Object's class and a class defined at compile time through
        the template parameter T.
        This is used in conjunction with readObjectOfType() to
        specify an abstract class as reference type.
**/
template<class T>
struct type_wrapper: basic_type_wrapper {
    bool matches(const osg::Object *proto) const
    {
        return dynamic_cast<const T*>(proto) != 0;
    }
};


/**
    Registry is a singleton factory which stores
    the reader/writers which are linked in
    at runtime for reading non-native file formats.

    The RegisterDotOsgWrapperProxy can be used to automatically register
    DotOsgWrappers, at runtime with the Registry. A DotOsgWrapper encapsulates
    the functions that can read and write to the .osg for each osg::Object.

    The RegisterReaderWriterProxy can be used to automatically
    register at runtime a reader/writer with the Registry.
*/
class OSGDB_EXPORT Registry : public osg::Referenced
{
    public:



        
        static Registry* instance(bool erase = false);

        /** read the command line arguments.*/
        void readCommandLine(osg::ArgumentParser& commandLine);

        /** register an .fileextension alias to mapExt toExt, the later
	  * should the the extension name of the readerwriter plugin library.
	  * For example to map .tif files to the tiff loader, use
	  * addExtAlias("tif","tiff") which will enable .tif to be read
	  * by the libdb_tiff readerwriter plugin.*/
	void addFileExtensionAlias(const std::string mapExt, const std::string toExt);

        void addDotOsgWrapper(DotOsgWrapper* wrapper);
        void removeDotOsgWrapper(DotOsgWrapper* wrapper);

        void addReaderWriter(ReaderWriter* rw);
        void removeReaderWriter(ReaderWriter* rw);

        /** create the platform specific library name associated with file.*/
        std::string createLibraryNameForFile(const std::string& fileName);

        /** create the platform specific library name associated with file extension.*/
        std::string createLibraryNameForExtension(const std::string& ext);

        /** create the platform specific library name associated with nodekit library name.*/
        std::string createLibraryNameForNodeKit(const std::string& name);

        /** find the library in the SG_LIBRARY_PATH and load it.*/
        bool loadLibrary(const std::string& fileName);
        /** close the attached library with specified name.*/
        bool closeLibrary(const std::string& fileName);
        /** close all libraries.*/
        void closeAllLibraries();

        /** get a reader writer which handles specified extension.*/
        ReaderWriter* getReaderWriterForExtension(const std::string& ext);
 
        osg::Object*         readObjectOfType(const osg::Object& compObj,Input& fr);       
        osg::Object*         readObjectOfType(const basic_type_wrapper &btw, Input& fr);

        osg::Object*         readObject(Input& fr);
        osg::Image*          readImage(Input& fr);
        osg::Drawable*       readDrawable(Input& fr);
        osg::Uniform*        readUniform(Input& fr);
        osg::StateAttribute* readStateAttribute(Input& fr);
        osg::Node*           readNode(Input& fr);
        
        bool                 writeObject(const osg::Object& obj,Output& fw);

        
        class ReadFileCallback : public osg::Referenced
        {
        public:
        
            virtual ReaderWriter::ReadResult openArchive(const std::string& filename,ReaderWriter::ArchiveStatus status, unsigned int indexBlockSizeHint, const ReaderWriter::Options* useObjectCache)
            {
                return osgDB::Registry::instance()->openArchiveImplementation(filename, status, indexBlockSizeHint, useObjectCache);
            }
            
            virtual ReaderWriter::ReadResult readObject(const std::string& filename, const ReaderWriter::Options* options)
            {
                return osgDB::Registry::instance()->readObjectImplementation(filename,options);
            }

            virtual ReaderWriter::ReadResult readImage(const std::string& filename, const ReaderWriter::Options* options)
            {
                return osgDB::Registry::instance()->readImageImplementation(filename,options);
            }
            
            virtual ReaderWriter::ReadResult readHeightField(const std::string& filename, const ReaderWriter::Options* options)
            {
                return osgDB::Registry::instance()->readHeightFieldImplementation(filename,options);
            }
            
            virtual ReaderWriter::ReadResult readNode(const std::string& filename, const ReaderWriter::Options* options)
            {
                return osgDB::Registry::instance()->readNodeImplementation(filename,options);
            }
            
        protected:
               virtual ~ReadFileCallback() {}
        };

        /** Set the Registry callback to use in place of the default readFile calls.*/
        void setReadFileCallback( ReadFileCallback* cb) { _readFileCallback = cb; }

        /** Get the readFile callback.*/
        ReadFileCallback* getReadFileCallback() { return _readFileCallback.get(); }
        
        /** Get the const readFile callback.*/
        const ReadFileCallback* getReadFileCallback() const { return _readFileCallback.get(); }


        ReaderWriter::ReadResult openArchive(const std::string& fileName,ReaderWriter::ArchiveStatus status, unsigned int indexBlockSizeHint, const ReaderWriter::Options* options)
        {
            if (_readFileCallback.valid()) return _readFileCallback->openArchive(fileName, status, indexBlockSizeHint, options);
            else return openArchiveImplementation(fileName, status, indexBlockSizeHint, options);
        }
        ReaderWriter::ReadResult openArchiveImplementation(const std::string& fileName, ReaderWriter::ArchiveStatus status, unsigned int indexBlockSizeHint, const ReaderWriter::Options* options);

        ReaderWriter::ReadResult readObject(const std::string& fileName,const ReaderWriter::Options* options)
        {
            if (_readFileCallback.valid()) return _readFileCallback->readObject(fileName,options);
            else return readObjectImplementation(fileName,options);
        }
        ReaderWriter::ReadResult readObjectImplementation(const std::string& fileName,const ReaderWriter::Options* options);
        
        ReaderWriter::ReadResult readImage(const std::string& fileName,const ReaderWriter::Options* options)
        {
            if (_readFileCallback.valid()) return _readFileCallback->readImage(fileName,options);
            else return readImageImplementation(fileName,options);
        }
        ReaderWriter::ReadResult readImageImplementation(const std::string& fileName,const ReaderWriter::Options* options);

        ReaderWriter::ReadResult readHeightField(const std::string& fileName,const ReaderWriter::Options* options)
        {
            if (_readFileCallback.valid()) return _readFileCallback->readHeightField(fileName,options);
            else return readHeightFieldImplementation(fileName,options);
        }
        ReaderWriter::ReadResult readHeightFieldImplementation(const std::string& fileName,const ReaderWriter::Options* options);

        ReaderWriter::ReadResult readNode(const std::string& fileName,const ReaderWriter::Options* options)
        {
            if (_readFileCallback.valid()) return _readFileCallback->readNode(fileName,options);
            else return readNodeImplementation(fileName,options);
        }
        ReaderWriter::ReadResult readNodeImplementation(const std::string& fileName,const ReaderWriter::Options* options);




        class WriteFileCallback : public osg::Referenced
        {
        public:
        
            virtual ReaderWriter::WriteResult writeObject(const osg::Object& obj, const std::string& fileName)
            {
                return osgDB::Registry::instance()->writeObjectImplementation(obj,fileName);
            }

            virtual ReaderWriter::WriteResult writeImage(const osg::Image& obj, const std::string& fileName)
            {
                return osgDB::Registry::instance()->writeImageImplementation(obj,fileName);
            }
            
            virtual ReaderWriter::WriteResult writeHeightField(const osg::HeightField& obj, const std::string& fileName)
            {
                return osgDB::Registry::instance()->writeHeightFieldImplementation(obj,fileName);
            }
            
            virtual ReaderWriter::WriteResult writeNode(const osg::Node& obj, const std::string& fileName)
            {
                return osgDB::Registry::instance()->writeNodeImplementation(obj,fileName);
            }
            
        protected:
               virtual ~WriteFileCallback() {}
        };

        /** Set the Registry callback to use in place of the default writeFile calls.*/
        void setWriteFileCallback( WriteFileCallback* cb) { _writeFileCallback = cb; }

        /** Get the writeFile callback.*/
        WriteFileCallback* getWriteFileCallback() { return _writeFileCallback.get(); }
        
        /** Get the const writeFile callback.*/
        const WriteFileCallback* getWriteFileCallback() const { return _writeFileCallback.get(); }


        ReaderWriter::WriteResult writeObject(const osg::Object& obj, const std::string& fileName)
        {
            if (_writeFileCallback.valid()) return _writeFileCallback->writeObject(obj,fileName);
            else return writeObjectImplementation(obj,fileName);
        }
        ReaderWriter::WriteResult writeObjectImplementation(const osg::Object& obj, const std::string& fileName);

        ReaderWriter::WriteResult writeImage(const osg::Image& obj, const std::string& fileName)
        {
            if (_writeFileCallback.valid()) return _writeFileCallback->writeImage(obj,fileName);
            else return writeImageImplementation(obj,fileName);
        }
        ReaderWriter::WriteResult writeImageImplementation(const osg::Image& obj, const std::string& fileName);

        ReaderWriter::WriteResult writeHeightField(const osg::HeightField& obj, const std::string& fileName)
        {
            if (_writeFileCallback.valid()) return _writeFileCallback->writeHeightField(obj,fileName);
            else return writeHeightFieldImplementation(obj,fileName);
        }
        ReaderWriter::WriteResult writeHeightFieldImplementation(const osg::HeightField& obj, const std::string& fileName);

        ReaderWriter::WriteResult writeNode(const osg::Node& node, const std::string& fileName)
        {
            if (_writeFileCallback.valid()) return _writeFileCallback->writeNode(node,fileName);
            else return writeNodeImplementation(node,fileName);
        }
        ReaderWriter::WriteResult writeNodeImplementation(const osg::Node& node, const std::string& fileName);
        
        

        void setCreateNodeFromImage(bool flag) { _createNodeFromImage = flag; }
        bool getCreateNodeFromImage() const { return _createNodeFromImage; }


        void setOptions(ReaderWriter::Options* opt) { _options = opt; }
        ReaderWriter::Options* getOptions() { return _options.get(); }
        const ReaderWriter::Options*  getOptions() const { return _options.get(); }


        /** initilize both the Data and Library FilePaths, by default called by the 
          * constructor, so it should only be required if you want to force
          * the re-reading of environmental variables.*/
        void initFilePathLists() { initDataFilePathList(); initLibraryFilePathList(); }
        
        /** initilize the Data FilePath by reading the OSG_FILE_PATH environmental variable.*/
        void initDataFilePathList();

        /** Set the data file path using a list of paths stored in a FilePath, which is used when search for data files.*/
        void setDataFilePathList(const FilePathList& filepath) { _dataFilePath = filepath; }

        /** Set the data file path using a single string deliminated either with ';' (Windows) or ':' (All other platforms), which is used when search for data files.*/
        void setDataFilePathList(const std::string& paths);

        /** get the data file path which is used when search for data files.*/
        FilePathList& getDataFilePathList() { return _dataFilePath; }

        /** get the const data file path which is used when search for data files.*/
        const FilePathList& getDataFilePathList() const { return _dataFilePath; }

        /** initilize the Library FilePath by reading the OSG_LIBRARY_PATH 
          * and the appropriate system environmental variables*/
        void initLibraryFilePathList();

        /** Set the library file path using a list of paths stored in a FilePath, which is used when search for data files.*/
        void setLibraryFilePathList(const FilePathList& filepath) { _libraryFilePath = filepath; }

        /** Set the library file path using a single string deliminated either with ';' (Windows) or ':' (All other platforms), which is used when search for data files.*/
        void setLibraryFilePathList(const std::string& paths);

        /** get the library file path which is used when search for library (dso/dll's) files.*/
        FilePathList& getLibraryFilePathList() { return _libraryFilePath; }
        
        /** get the const library file path which is used when search for library (dso/dll's) files.*/
        const FilePathList& getLibraryFilePathList() const { return _libraryFilePath; }

        /** For each object in the cache which has an reference count greater than 1 
          * (and therefore referenced by elsewhere in the application) set the time stamp
          * for that object in the cache to specified time.
          * This would typically be called once per frame by applications which are doing database paging,
          * and need to prune objects that are no longer required.
          * Time value is time in sceonds.*/
        void updateTimeStampOfObjectsInCacheWithExtenalReferences(double currentTime);

        /** Removed object in the cache which have a time stamp at or before the specified expiry time.
          * This would typically be called once per frame by applications which are doing database paging,
          * and need to prune objects that are no longer required, and called after the a called
          * after the call to updateTimeStampOfObjectsInCacheWithExtenalReferences(currentTime). 
          * Note, the currentTime is not the expiryTime, one would typically set the expiry time
          * to a fixed amount of time before currentTime, such as expiryTime = currentTime-10.0.
          * Time value is time in sceonds.*/
        void removeExpiredObjectsInCache(double expiryTime);
        
        /** Remove all objects in the cache regardless of having external references or expiry times.*/ 
        void clearObjectCache();

        /** Add a filename,object,timestamp tripple to the Registry::ObjectCache.*/
        void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0);

	/** Get an object from the object cache*/ 
	osg::Object* getFromObjectCache(const std::string& fileName);
        
        /** Add archive to archive cache so that future calls reference this archive.*/
        void addToArchiveCache(const std::string& fileName, osgDB::Archive* archive);

        /** Remove archive from cache.*/
        void removeFromArchiveCache(const std::string& fileName);
        
        /** Get an archive from the archive cache*/ 
        osgDB::Archive* getFromArchiveCache(const std::string& fileName);
        
        /** Remove all archives from the archive cache.*/
        void clearArchiveCache();
        
         /** If State is non-zero, this function releases OpenGL objects for
           * the specified graphics context. Otherwise, releases OpenGL objexts
           * for all graphics contexts. */
        void releaseGLObjects(osg::State* state=0);

        /** get the attached library with specified name.*/
        DynamicLibrary*              getLibrary(const std::string& fileName);
        
        /** Set the DatabasePager.*/
        void setDatabasePager(DatabasePager* databasePager) { _databasePager = databasePager; }

        /** Get the DatabasePager, creating one if one is not already created.*/
        DatabasePager* getOrCreateDatabasePager();
        
        /** Get the DatabasePager. Return 0 if no DatabasePager has been assigned.*/
        DatabasePager* getDatabasePager() { return _databasePager.get(); }

        
        /** Set the SharedStateManager.*/
        void setSharedStateManager(SharedStateManager* SharedStateManager) { _sharedStateManager = SharedStateManager; }

        /** Get the SharedStateManager, creating one if one is not already created.*/
        SharedStateManager* getOrCreateSharedStateManager();
        
        /** Get the SharedStateManager. Return 0 if no SharedStateManager has been assigned.*/
        SharedStateManager* getSharedStateManager() { return _sharedStateManager.get(); }


    protected:

        virtual ~Registry();

        typedef std::vector< osg::ref_ptr<ReaderWriter> >               ReaderWriterList;
        typedef std::map< std::string, osg::ref_ptr<DotOsgWrapper> >    DotOsgWrapperMap;
        typedef std::vector< osg::ref_ptr<DynamicLibrary> >             DynamicLibraryList;
        typedef std::map< std::string, std::string>                     ExtensionAliasMap;
        
        typedef std::pair<osg::ref_ptr<osg::Object>, double >           ObjectTimeStampPair;
        typedef std::map<std::string, ObjectTimeStampPair >             ObjectCache;
        typedef std::map<std::string, osg::ref_ptr<osgDB::Archive> >    ArchiveCache;

        /** constructor is private, as its a singleton, preventing
            construction other than via the instance() method and
            therefore ensuring only one copy is ever constructed*/
        Registry();
        
        /** get the attached library with specified name.*/
        DynamicLibraryList::iterator getLibraryItr(const std::string& fileName);

        bool _createNodeFromImage;

        osg::Object*       readObject(DotOsgWrapperMap& dowMap,Input& fr);

        void eraseWrapper(DotOsgWrapperMap& wrappermap,DotOsgWrapper* wrapper);

    public:
        /** Functor used in internal implementations.*/
        struct ReadFunctor
        {
            ReadFunctor(const std::string& filename, const ReaderWriter::Options* options):
                _filename(filename),
                _options(options) {}

            virtual ~ReadFunctor() {}
            virtual ReaderWriter::ReadResult doRead(ReaderWriter& rw) const = 0;
            virtual bool isValid(ReaderWriter::ReadResult& readResult) const = 0;
            virtual bool isValid(osg::Object* object) const = 0;

            std::string _filename;
            const ReaderWriter::Options* _options;
        };

   protected:
        // forward declare helper classes
        struct ReadObjectFunctor;
        struct ReadImageFunctor;
        struct ReadHeightFieldFunctor;
        struct ReadNodeFunctor;
        struct ReadArchiveFunctor;
        
        // make helper classes friends to get round VS6.0 "issues"
        friend struct ReadFunctor;
        friend struct ReadObjectFunctor;
        friend struct ReadImageFunctor;
        friend struct ReadHeightFieldFunctor;
        friend struct ReadNodeFunctor;
        friend struct ReadArchiveFunctor;

        ReaderWriter::ReadResult read(const ReadFunctor& readFunctor);
        ReaderWriter::ReadResult readImplementation(const ReadFunctor& readFunctor,bool useObjectCache);


        // forward declar helper class
        class AvailableReaderWriterIterator;
        friend class AvailableReaderWriterIterator;


        osg::ref_ptr<ReadFileCallback>      _readFileCallback;
        osg::ref_ptr<WriteFileCallback>     _writeFileCallback;

        DotOsgWrapperMap   _objectWrapperMap;
        DotOsgWrapperMap   _imageWrapperMap;
        DotOsgWrapperMap   _drawableWrapperMap;
        DotOsgWrapperMap   _stateAttrWrapperMap;
        DotOsgWrapperMap   _uniformWrapperMap;
        DotOsgWrapperMap   _nodeWrapperMap;
        
        DotOsgWrapperMap   _classNameWrapperMap;

        ReaderWriterList    _rwList;
        DynamicLibraryList  _dlList;

        bool _openingLibrary;
    
        // map to alias to extensions to plugins.
        ExtensionAliasMap  _extAliasMap;
        
        // options to pass to reader writers.
        osg::ref_ptr<ReaderWriter::Options>     _options;
        
        FilePathList                            _dataFilePath;
        FilePathList                            _libraryFilePath;

        ObjectCache                             _objectCache;
        OpenThreads::Mutex                      _objectCacheMutex;
        
        ArchiveCache                            _archiveCache;
        OpenThreads::Mutex                      _archiveCacheMutex;
        
        
        osg::ref_ptr<DatabasePager>             _databasePager;
        osg::ref_ptr<SharedStateManager>        _sharedStateManager;

};

/** read the command line arguments.*/
inline void readCommandLine(osg::ArgumentParser& parser)
{
    Registry::instance()->readCommandLine(parser);
}

/**  Proxy class for automatic registration of DotOsgWrappers with the Registry.*/
class RegisterDotOsgWrapperProxy
{
    public:
    
        RegisterDotOsgWrapperProxy(osg::Object* proto,
                                   const std::string& name,
                                   const std::string& associates,
                                   DotOsgWrapper::ReadFunc readFunc,
                                   DotOsgWrapper::WriteFunc writeFunc,
                                   DotOsgWrapper::ReadWriteMode readWriteMode=DotOsgWrapper::READ_AND_WRITE)
        {
            if (Registry::instance())
            {
                _wrapper = new DotOsgWrapper(proto,name,associates,readFunc,writeFunc,readWriteMode);
                Registry::instance()->addDotOsgWrapper(_wrapper.get());
            }
        }
        
        ~RegisterDotOsgWrapperProxy()
        {
            if (Registry::instance())
            {
                Registry::instance()->removeDotOsgWrapper(_wrapper.get());
            }
        }
        
    protected:
        osg::ref_ptr<DotOsgWrapper> _wrapper;
};

/** Proxy class for automatic registration of reader/writers with the Registry.*/
template<class T>
class RegisterReaderWriterProxy
{
    public:
        RegisterReaderWriterProxy()
        {
            if (Registry::instance())
            {
                _rw = new T;
                Registry::instance()->addReaderWriter(_rw.get());
            }
        }

        ~RegisterReaderWriterProxy()
        {
            if (Registry::instance())
            {
                Registry::instance()->removeReaderWriter(_rw.get());
            }
        }
        
        T* get() { return _rw.get(); }
        
    protected:
        osg::ref_ptr<T> _rw;
};

}

#endif
