/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 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 <vector>
#include <map>
#include <string>
#include <deque>


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;
	}
};

/** list of directories to search through which searching for files. */
typedef std::deque<std::string> FilePathList;

/**
    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();

        /** 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);

        /** 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::StateAttribute* readStateAttribute(Input& fr);
        osg::Node*           readNode(Input& fr);
        
        bool                 writeObject(const osg::Object& obj,Output& fw);

        
        ReaderWriter::ReadResult readObject(const std::string& fileName,bool useObjectCache);
        ReaderWriter::WriteResult writeObject(const osg::Object& obj, const std::string& fileName);

        ReaderWriter::ReadResult readImage(const std::string& fileName,bool useObjectCache);
        ReaderWriter::WriteResult writeImage(const osg::Image& obj, const std::string& fileName);

        ReaderWriter::ReadResult readNode(const std::string& fileName,bool useObjectCache);
        ReaderWriter::WriteResult writeNode(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) { _dataFilePath.clear(); convertStringPathIntoFilePathList(paths,_dataFilePath); }

        /** 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) { _libraryFilePath.clear(); convertStringPathIntoFilePathList(paths,_libraryFilePath); }

        /** 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; }

        /** convert a string containing a list of paths  deliminated either with ';' (Windows) or ':' (All other platforms) into FilePath represetation.*/
        static void convertStringPathIntoFilePathList(const std::string& paths,FilePathList& filepath);


        /** 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);
        
        /** Set whether the Registry::ObjectCache should be used by default.*/
        void setUseObjectCacheHint(bool useObjectCache) { _useObjectCacheHint = useObjectCache; }

        /** Get whether the Registry::ObjectCache should be used by default.*/
        bool getUseObjectCacheHint() const { return _useObjectCacheHint; }

        /** get the attached library with specified name.*/
        DynamicLibrary*              getLibrary(const std::string& fileName);

    protected:

        virtual ~Registry();

        typedef std::map< std::string, osg::ref_ptr<DotOsgWrapper> >    DotOsgWrapperMap;
        typedef std::vector< osg::ref_ptr<ReaderWriter> >               ReaderWriterList;
        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;

        /** 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);

        ReaderWriter::ReadResult readObject(const std::string& fileName);
        ReaderWriter::ReadResult readImage(const std::string& fileName);
        ReaderWriter::ReadResult readNode(const std::string& fileName);

        DotOsgWrapperMap   _objectWrapperMap;
        DotOsgWrapperMap   _imageWrapperMap;
        DotOsgWrapperMap   _drawableWrapperMap;
        DotOsgWrapperMap   _stateAttrWrapperMap;
        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;

        bool                _useObjectCacheHint;        
        ObjectCache         _objectCache;

};

/** 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());
            }
        }
        
    protected:
        osg::ref_ptr<T> _rw;
};

}

#endif
