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

#include <osgDB/Export>
#include <string>
#include <map>

namespace osg
{
    class Image;
}

namespace osgDB
{

    class Options;

    /// This helper allows 'intelligent' writing of images in plugins.
    /// It stores a set of images and avoids writing twice the same image.
    /// It also tries to keep relative paths of source images if desired and possible (ex: If image is initially "imageDir/image.jpg" relatively to the source dir, then we'd like to get "destDir/imageDir/image.jpg").
    ///\author Sukender
    class OSGDB_EXPORT PluginImageWriter
    {
    public:
        /// Builds the helper class with all options.
        ///\param srcDirectory Directory of the initial model file (if any), used as a base when relativising images names. Not used if keepRelativePaths==false.
        ///\param destDirectory Directory where to write the model file.
        ///\param keepRelativePaths If true, then relative paths of source images are kept if possible (ex: If image is initially "imageDir/image.jpg" relatively to the source dir, then we'd like to get "destDir/imageDir/image.jpg"). If false, then only the simple file name is used to write the image file.
        ///\param allowUpDirs When relativising images paths, sets the maximum number of direcories the images can be written "up" the destination directory. Not used if keepRelativePaths==false. Examples: If image is initially "../image.jpg" relatively to the source dir *AND* if we allow one dir level up, then we'd like to get "destDirParent/destDir/../image.jpg" (= "destDirParent/image.jpg"). If we *DO NOT* allow one dir level up, then we'd like to get "destDir/image.jpg".
        PluginImageWriter(const std::string & srcDirectory, const std::string & destDirectory, bool keepRelativePaths, unsigned int allowUpDirs=0);

        /// Short constructor used when not relativising images paths, or when having no initial model file (which is pretty the same here).
        PluginImageWriter(const std::string & destDirectory);

        /// Defines the image we're working on.
        /// This updates the internal state of the class and makes methods such as getCurRelativePath() return an appropriate value.
        void setCurImage(const osg::Image * img);
        /// Retreives the path of the image to be written, relative to the destination directory if possible, or absolute if not.
        const std::string & getCurRelativeOrAbsolutePath() const { return valid() ? cur->second.relativePath : _destDirectory; }
        /// Retreives absolute path of the image to be written.
        const std::string & getCurAbsolutePath() const { return valid() ? cur->second.absolutePath : _destDirectory; }
        /// Writes the current image to disk if not already done.
        void write(const osgDB::Options * options) const;

        /// Data about writing an image.
        struct ImageData {
            ImageData() : written(false) {}
            ImageData(const std::string & relativePath, const std::string & absolutePath) : relativePath(relativePath), absolutePath(absolutePath), written(false) {}
            std::string relativePath;        ///< Path to write the image to, relative to the destination dir if possible, else absolute
            std::string absolutePath;        ///< Absolute path to write the image to.
            bool written;                    ///< Flag indicating if write() already called on that image.
        };

        /// Set of images entered with setCurImage().
        typedef std::map<const osg::Image*, ImageData> ImagesSet;

        const ImagesSet & getImages() const { return _images; }

    protected:
        // Dev note:
        // A multi-indexed structure would be more efficient for ImagesSet (such as boost::multi_index, indexed on image pointer (unique), and hashed indexed on absolute path (unique)).
        // In order to get a correct search time, SearchMap "replaces" the multi-index structure for hashed indexes on absolute paths.
        typedef std::multimap<unsigned int, const osg::Image*> SearchMap;
        typedef unsigned int ImageIndex;        ///< Integer type used for indices of unnamed images
        ImagesSet _images;
        SearchMap _searchMap;
        ImageIndex _lastGeneratedImageIndex;
        const std::string _srcDirectory;
        const std::string _destDirectory;
        bool _keepRelativePaths;
        const unsigned int _allowUpDirs;
        ImagesSet::iterator cur;

        bool valid() const { return cur != _images.end(); }

        /// Generates a unique name for an image to be written on disk.
        /// Side effect: updates _lastGeneratedImageIndex to the index associated withe the returned name.
        void generateImageName(std::string & out_relativePath, std::string & out_absolutePath);

        bool absoluteImagePathExists(const std::string & path);
    private:
        PluginImageWriter & operator=(const PluginImageWriter &);
        PluginImageWriter(const PluginImageWriter &);
    };
}

#endif    // OSGDB_PLUGIN_IMAGE_WRITER
