// This is one solution for searching in archives specified as directories in OSG_FILE_PATH. However, it is too painful
// so it will not be used. The pain comes from the fact that Registry::read() will search archives in alphabetical order
// instead of in the order specified in OSG_FILE_PATH, and Registry::read() cannot be overridden by a callback. A simpler
// solution seems to be to use a modified OSG with mods to FileUtils.cpp and Registry.cpp.

#include <osgDB/Archive>
// This allows files to be found in archives much like they are found in directories.  However, due to limitations of
// Registry::read(const ReadFunctor& readFunctor), it will only find referenced data files in an archive if they
// are specified as a simple filename with no path.  The problem is that referenced data files will be passed to
// Registry::read before every being searched for by findDataFile().  For example, a filename such as "texture.png"
// or even "data.zip\texture.png" would be found, but something like "C:\user\directory\texture.png" would not be found.
//
// When using this callback, after every loading of a file you must call Registry::instance()->clearArchiveCache();
// This is because Registry caches all archives in a map, which is an ordered list. During file reads, these archives
// will be checked in alphabetical order before myFindFileInPath() is used to find the file. Clearing the cache prevents
// the files from being found by this method so that they will be found by myFindFileInPath() using the archive order
// specified in OSG_FILE_PATH.
class MyFindFileCallback : public FindFileCallback {
public:
// This is a copy of osgDB::Registry::findDataFileImplementation() with instances of findFileInPath replaced with
// myFindFileInPath. Also, the variable _previousArchiveFilename is used to thwart infinite loops that occur when
// when opening archives.
std::string findDataFile(const std::string& filename, const Options* options, CaseSensitivity caseSensitivity)
{
    if (filename.empty()) return filename;

    // Archive filename is searched for before archive is opened. This early-out from this function prevents an
	 // infinite loop of endlessly searching for the same archive archive.
    if(filename == _previousArchiveFilename)
        return filename;

    // if data file contains a server address then we can't find it in local directories so return empty string.
    if (containsServerAddress(filename)) return std::string();

    bool absolutePath = osgDB::isAbsolutePath(filename);

    if (absolutePath && fileExists(filename))
    {
        OSG_DEBUG << "FindFileInPath(" << filename << "): returning " << filename << std::endl;
        return filename;
    }

    std::string fileFound;
    bool pathsContainsCurrentWorkingDirectory = false;

    if (options && !options->getDatabasePathList().empty())
    {
        fileFound = myFindFileInPath(filename, options->getDatabasePathList(), caseSensitivity);
        if (!fileFound.empty()) return fileFound;

        if (osgDB::containsCurrentWorkingDirectoryReference(options->getDatabasePathList()))
        {
            pathsContainsCurrentWorkingDirectory = true;
        }

    }

    const FilePathList& filepaths = Registry::instance()->getDataFilePathList();
    if (!filepaths.empty())
    {
        fileFound = myFindFileInPath(filename, filepaths, caseSensitivity);
        if (!fileFound.empty()) return fileFound;

        if (!pathsContainsCurrentWorkingDirectory && osgDB::containsCurrentWorkingDirectoryReference(filepaths))
        {
            pathsContainsCurrentWorkingDirectory = true;
        }
    }

    if (!absolutePath && !pathsContainsCurrentWorkingDirectory)
    {
        // check current working directory
        if (fileExists(filename))
        {
            return filename;
        }
    }


    // if a directory is included in the filename, get just the (simple) filename itself and try that
    std::string simpleFileName = getSimpleFileName(filename);
    if (simpleFileName!=filename)
    {

        if(fileExists(simpleFileName))
        {
            OSG_DEBUG << "FindFileInPath(" << filename << "): returning " << filename << std::endl;
            return simpleFileName;
        }

        if (options && !options->getDatabasePathList().empty())
        {
            fileFound = myFindFileInPath(simpleFileName, options->getDatabasePathList(), caseSensitivity);
            if (!fileFound.empty()) return fileFound;
        }

        if (!filepaths.empty())
        {
            fileFound = myFindFileInPath(simpleFileName, filepaths,caseSensitivity);
            if (!fileFound.empty()) return fileFound;
        }

    }

    // return empty string.
    return std::string();
}

private:
// This is a copy of osgDB::findFileInPath(), with the addition of code that searches archives as if they were directories.
std::string myFindFileInPath(const std::string& filename, const FilePathList& filepath,CaseSensitivity caseSensitivity)
{
    if (filename.empty())
        return filename;

    if (!isFileNameNativeStyle(filename))
        return findFileInPath(convertFileNameToNativeStyle(filename), filepath, caseSensitivity);


    for(FilePathList::const_iterator itr=filepath.begin();
        itr!=filepath.end();
        ++itr)
    {
        OSG_DEBUG << "itr='" <<*itr<< "'\n";
        std::string path = itr->empty() ? filename : concatPaths(*itr, filename);

        path = getRealPath(path);

        OSG_DEBUG << "myFindFileInPath() : trying " << path << " ...\n";
        if(fileExists(path))
        {
            OSG_DEBUG << "myFindFileInPath() : USING " << path << "\n";
            return path;
        }
#ifndef WIN32
// windows already case insensitive so no need to retry..
        else if (caseSensitivity==CASE_INSENSITIVE)
        {
            std::string foundfile = findFileInDirectory(filename,*itr,CASE_INSENSITIVE);
            if (!foundfile.empty()) return foundfile;
        }
#endif

        Registry::ArchiveExtensionList& archiveextlist = (Registry::ArchiveExtensionList&)Registry::instance()->getArchiveExtensions();
        
        for(Registry::ArchiveExtensionList::iterator aitr=archiveextlist.begin();
            aitr!=archiveextlist.end();
            ++aitr)
        {
            std::string dotext = std::string(".") + *aitr;
            std::string::size_type pos(path.find(dotext));
            if(pos != std::string::npos)
                {
                std::string archive_filename(path, 0, pos + dotext.size());
                if(!fileExists(archive_filename))
                    continue;
                _previousArchiveFilename = archive_filename;
                ReaderWriter::ReadResult rr = Registry::instance()->openArchive(archive_filename, ReaderWriter::READ, 4096, NULL);
                if(rr.validArchive())
                    {
                    std::string filename_in_archive(path, pos + dotext.size());
                    while(filename_in_archive.length() > 0 && (filename_in_archive[0] == '/' || filename_in_archive[0] == '\\'))
                        filename_in_archive.erase(0, 1);
                    if(rr.getArchive()->fileExists(filename_in_archive))
                    {
                        OSG_DEBUG << "myFindFileInPath() : USING " << path << "\n";
                        return path;
                    }
                }
            }
        }
    }

    return std::string();
}

    std::string _previousArchiveFilename;
};


// Add this to the top of main()
Registry::instance()->setFindFileCallback(new MyFindFileCallback);
// Prime the loader code in MyFindFileCallback() by forcing all archives to be detected. Without this, the first
// file you attempt to load will be skipped due to  the sequence of operations in osgDB::Registry::read()
readNodeFile("This_file_does_not_exist.osg");


// Add this after each file is loaded to have archives searched in order on OSG_FILE_PATH instead of alphabetical order.
Registry::instance()->clearArchiveCache();
