//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2004-2015 Steve Baker <sjbaker1@airmail.net>
//  Copyright (C) 2008-2015 Steve Baker, Joerg Henrichs
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 3
//  of the License, or (at your option) any later version.
//
//  This program 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
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


#include "io/file_manager.hpp"

#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material_manager.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/skin.hpp"
#include "karts/kart_properties_manager.hpp"
#include "tracks/track_manager.hpp"
#include "utils/command_line.hpp"
#include "utils/extract_mobile_assets.hpp"
#include "utils/file_utils.hpp"
#include "utils/log.hpp"
#include "utils/mem_utils.hpp"
#include "utils/string_utils.hpp"

#ifdef ANDROID
#include "io/assets_android.hpp"
#endif

#ifdef __HAIKU__
#include <Path.h>
#include <FindDirectory.h>
#endif

#include <stdio.h>
#include <stdexcept>
#include <sstream>
#include <sys/stat.h>
#include <iostream>
#include <string>

#include <IFileSystem.h>

namespace irr {
    namespace io
    {
        IFileSystem* createFileSystem();
    }
}

// For mkdir
#if !defined(WIN32)
#  include <sys/stat.h>
#  include <sys/types.h>
#  include <dirent.h>
#  include <unistd.h>
#else
#  define WIN32_LEAN_AND_MEAN
#  include <direct.h>
#  include <windows.h>
#  include <stdio.h>
#  if !defined(__MINGW32__)
     /*Needed by the remove directory function */
#    define S_ISDIR(mode)  (((mode) & S_IFMT) == S_IFDIR)
#    define S_ISREG(mode)  (((mode) & S_IFMT) == S_IFREG)
#  endif
#endif


std::vector<std::string> FileManager::m_root_dirs;
std::string              FileManager::m_stdout_filename = "stdout.log";
std::string              FileManager::m_stdout_dir;

#ifdef __APPLE__
// dynamic data path detection onmac
#  include <CoreFoundation/CoreFoundation.h>

bool macSetBundlePathIfRelevant(std::string& data_dir)
{
    Log::debug("[FileManager]", "Checking whether we are using an app bundle... ");
    // the following code will enable STK to find its data when placed in an
    // app bundle on mac OS X.
    // returns true if path is set, returns false if path was not set
    char path[1024];
    CFBundleRef main_bundle = CFBundleGetMainBundle(); assert(main_bundle);
    CFURLRef main_bundle_URL = CFBundleCopyBundleURL(main_bundle);
    assert(main_bundle_URL);
    CFStringRef cf_string_ref = CFURLCopyFileSystemPath(main_bundle_URL,
                                                        kCFURLPOSIXPathStyle);
    assert(cf_string_ref);
    CFStringGetCString(cf_string_ref, path, 1024, kCFStringEncodingUTF8);
    CFRelease(main_bundle_URL);
    CFRelease(cf_string_ref);

    // IOS version of stk store data folder directly inside supertuxkart.app
    std::string contents = std::string(path) + "/";
#ifndef IOS_STK
    contents += std::string("Contents");
#endif
    if(contents.find(".app") != std::string::npos)
    {
#ifdef IOS_STK
        data_dir = contents;
        return true;
#else
        Log::debug("[FileManager]", "yes");
        // executable is inside an app bundle, use app bundle-relative paths
        data_dir = contents + std::string("/Resources/");
        return true;
#endif
    }
    else
    {
        Log::debug("[FileManager]", "no");
        return false;
    }
}
#endif

// ============================================================================
FileManager* file_manager = 0;

/** The constructor of the file manager creates an irrlicht file system and
 *  detects paths for the user config file and assets base directory (data).
 *  A second initialisation is done later once (see init()), once the user
 *  config file is read. This is necessary since part of discoverPaths
 *  depend on artist debug mode.
 */
FileManager::FileManager()
{
    m_root_dirs.clear();
    resetSubdir();
#ifdef __APPLE__
    // irrLicht's createDevice method has a nasty habit of messing the CWD.
    // since the code above may rely on it, save it to be able to restore
    // it after.
    char buffer[256];
    getcwd(buffer, 256);
#endif

#ifdef __APPLE__
    chdir( buffer );
#endif

    m_file_system = irr::io::createFileSystem();

#ifdef ANDROID
    AssetsAndroid android_assets(this);
    android_assets.init();
#endif

    std::string exe_path;

    // Search for the root directory
    // =============================

    // Also check for data dirs relative to the path of the executable.
    // This is esp. useful for Visual Studio, since it's not necessary
    // to define the working directory when debugging, it works automatically.
    std::string root_dir;
    const std::string version = std::string("supertuxkart.") + STK_VERSION;
    if (fileExists(CommandLine::getExecName()))
    {
        exe_path = StringUtils::getPath(CommandLine::getExecName());
    }
    if(exe_path.size()==0 || exe_path[exe_path.size()-1]!='/')
        exe_path += "/";
    if ( getenv ( "SUPERTUXKART_DATADIR" ) != NULL )
        root_dir = std::string(getenv("SUPERTUXKART_DATADIR"))+"/data/" ;
#ifdef __APPLE__
    else if( macSetBundlePathIfRelevant( root_dir ) ) { root_dir = root_dir + "data/"; }
#endif
#ifdef __SWITCH__
    else if(fileExists("sdmc:/stk-data/", version))
        root_dir = "sdmc:/stk-data/";
    else if(fileExists("romfs:/data/", version))
        root_dir = "romfs:/data/";
#endif
    else if(fileExists("./data/", version))
        root_dir = "./data/" ;
    else if(fileExists("../data/", version))
        root_dir = "../data/" ;
    else if(fileExists("../../data/", version))
        root_dir = "../../data/" ;
    // Test for old style build environment, with executable in root of stk
    else if(fileExists(exe_path+"data/"+version))
        root_dir = (exe_path+"data/").c_str();
    // Check for windows cmake style: bld/Debug/bin/supertuxkart.exe
    else if (fileExists(exe_path + "../../../data/"+version))
        root_dir = exe_path + "../../../data/";
    else if (fileExists(exe_path + "../data/"+version))
    {
        root_dir = exe_path.c_str();
        root_dir += "../data/";
    }
    else
    {
#ifdef SUPERTUXKART_DATADIR
        root_dir = SUPERTUXKART_DATADIR"/data/";
#else
        root_dir = "/usr/local/share/games/supertuxkart/";
#endif
    }

    if (!m_file_system->existFile((root_dir + version).c_str()))
    {
        Log::error("FileManager", "Could not find file '%s'in any "
                   "standard location (esp. ../data).", version.c_str());
        Log::error("FileManager",
                   "Last location checked '%s'.", root_dir.c_str());
        Log::fatal("FileManager",
                   "Set $SUPERTUXKART_DATADIR to point to the data directory.");
        // fatal will exit the application
    }

    addRootDirs(root_dir);

    std::string assets_dir;
#ifdef MOBILE_STK
    m_stk_assets_download_dir = getenv("HOME");
#ifdef IOS_STK
    m_stk_assets_download_dir += "/Library/Application Support/SuperTuxKart/stk-assets/";
#elif defined (ANDROID)
    m_stk_assets_download_dir += "/stk-assets/";
#else
#error You must set m_stk_assets_download_dir to appropriate place for your platform
#endif

#else
    if (getenv("SUPERTUXKART_ASSETS_DIR") != NULL)
    {
        assets_dir = std::string(getenv("SUPERTUXKART_ASSETS_DIR"));
    }
    else if (fileExists(root_dir + "../../stk-assets"))
    {
        assets_dir = root_dir + "../../stk-assets";
    }
    else if (fileExists(root_dir + "../../supertuxkart-assets"))
    {
        assets_dir = root_dir + "../../supertuxkart-assets";
    }
    else if (getenv("SUPERTUXKART_ROOT_PATH") != NULL)
    {
        //is this needed?
        assets_dir = std::string(getenv("SUPERTUXKART_ROOT_PATH"));
    }
#endif
    if (!assets_dir.empty() && assets_dir != root_dir)
    {
        addRootDirs(assets_dir);
    }

    checkAndCreateConfigDir();
    checkAndCreateAddonsDir();
    checkAndCreateScreenshotDir();
    checkAndCreateReplayDir();
    checkAndCreateCachedTexturesDir();
    checkAndCreateGPDir();

    redirectOutput();
}   // FileManager

// ----------------------------------------------------------------------------
/** Reset subdirectories to initial state, for example after download assets
 */
void FileManager::resetSubdir()
{
    m_subdir_name.clear();
    m_subdir_name.resize(ASSET_COUNT);
    m_subdir_name[CHALLENGE  ] = "challenges";
    m_subdir_name[GFX        ] = "gfx";
    m_subdir_name[GRANDPRIX  ] = "grandprix";
    m_subdir_name[GUI_ICON   ] = "gui/icons";
    m_subdir_name[GUI_SCREEN ] = "gui/screens";
    m_subdir_name[GUI_DIALOG ] = "gui/dialogs";
    m_subdir_name[LIBRARY    ] = "library";
    m_subdir_name[MODEL      ] = "models";
    m_subdir_name[MUSIC      ] = "music";
    m_subdir_name[REPLAY     ] = "replay";
    m_subdir_name[SCRIPT     ] = "tracks";
    m_subdir_name[SFX        ] = "sfx";
    m_subdir_name[SKIN       ] = "skins";
    m_subdir_name[SHADER     ] = "shaders";
    m_subdir_name[TEXTURE    ] = "textures";
    m_subdir_name[TTF        ] = "ttf";
    m_subdir_name[TRANSLATION] = "po";
}   // resetSubdir

// ----------------------------------------------------------------------------
/** Detects where the assets are stored.
 */
void FileManager::discoverPaths()
{
    resetSubdir();
    // We can't use _() here, since translations will only be initalised
    // after the filemanager (to get the path to the tranlsations from it)
    for(unsigned int i=0; i<m_root_dirs.size(); i++)
        Log::info("[FileManager]", "Data files will be fetched from: '%s'",
                   m_root_dirs[i].c_str());
    Log::info("[FileManager]", "User directory is '%s'.",
              m_user_config_dir.c_str());
    Log::info("[FileManager]", "Addons files will be stored in '%s'.",
               m_addons_dir.c_str());
    Log::info("[FileManager]", "Screenshots will be stored in '%s'.",
               m_screenshot_dir.c_str());
    Log::info("[FileManager]", "User-defined grand prix will be stored in '%s'.",
               m_gp_dir.c_str());

    // Reset for re-downloading assets if needed
    TrackManager::removeTrackSearchDirs();
    KartPropertiesManager::removeKartSearchDirs();

    /** Now search for the path to all needed subdirectories. */
    // ==========================================================
    // This must be done here since otherwise translations will not be found.
    std::vector<bool> dir_found;
    dir_found.resize(ASSET_COUNT, false);
#ifdef MOBILE_STK
    assert(!m_root_dirs.empty());
    for (unsigned j = ASSET_MIN; j <= BUILTIN_ASSETS; j++)
    {
        if (!dir_found[j] && fileExists(m_root_dirs[0] + m_subdir_name[j]))
        {
            dir_found[j] = true;
            m_subdir_name[j] = m_root_dirs[0] + m_subdir_name[j] + "/";
        }
    }

    bool has_full_assets = ExtractMobileAssets::hasFullAssets();
    // Clear previous assets version to free space
    if (!has_full_assets && fileExists(m_stk_assets_download_dir))
        removeDirectory(m_stk_assets_download_dir);

    // Use stk-assets-full for karts, tracks, textures..., otherwise in data/
    std::string assets_root = has_full_assets ?
        m_stk_assets_download_dir : m_root_dirs[0];
    for (unsigned j = LIBRARY; j <= ASSET_MAX; j++)
    {
        if (!dir_found[j] && fileExists(assets_root + m_subdir_name[j]))
        {
            dir_found[j] = true;
            m_subdir_name[j] = assets_root + m_subdir_name[j] + "/";
        }
    }
    if (fileExists(assets_root + "tracks/"))
        TrackManager::addTrackSearchDir(assets_root + "tracks/");
    if (fileExists(assets_root + "karts/"))
        KartPropertiesManager::addKartSearchDir(assets_root + "karts/");

    if (UserConfigParams::m_artist_debug_mode)
    {
        if (fileExists(assets_root + "wip-tracks/"))
            TrackManager::addTrackSearchDir(assets_root + "wip-tracks/");
        if (fileExists(assets_root + "wip-karts/"))
            KartPropertiesManager::addKartSearchDir(assets_root + "wip-karts/");
    }
#else
    for(unsigned int i=0; i<m_root_dirs.size(); i++)
    {
        if(fileExists(m_root_dirs[i]+"tracks/"))
            TrackManager::addTrackSearchDir(m_root_dirs[i]+"tracks/");
        if(fileExists(m_root_dirs[i]+"karts/"))
            KartPropertiesManager::addKartSearchDir(m_root_dirs[i]+"karts/");

        // If artist debug mode is enabled, add
        // work-in-progress tracks and karts
        if (UserConfigParams::m_artist_debug_mode)
        {
            if(fileExists(m_root_dirs[i] + "wip-tracks/"))
                TrackManager::addTrackSearchDir(m_root_dirs[i] + "wip-tracks/");
            if(fileExists(m_root_dirs[i] + "wip-karts/"))
                KartPropertiesManager::addKartSearchDir(m_root_dirs[i] + "wip-karts/");
        }
        for(unsigned int j=ASSET_MIN; j<=ASSET_MAX; j++)
        {
            if(!dir_found[j] && fileExists(m_root_dirs[i]+m_subdir_name[j]))
            {
                dir_found[j] = true;
                m_subdir_name[j] = m_root_dirs[i]+m_subdir_name[j]+"/";
            }   // !dir_found && file_exist
        }   // for j=ASSET_MIN; j<=ASSET_MAX
    }   // for i<m_root_dirs
#endif

    bool was_error = false;
    for(unsigned int i=ASSET_MIN; i<=ASSET_MAX; i++)
    {
        if(!dir_found[i])
        {
            Log::warn("[FileManager]", "Directory '%s' not found, aborting.",
                      m_subdir_name[i].c_str());
            was_error = true;
        }
        else
            Log::info("[FileManager]", "Asset %d will be loaded from '%s'.",
                      i, m_subdir_name[i].c_str());
    }
    if(was_error)
        Log::fatal("[FileManager]", "Not all assets found - aborting.");

}  // discoverPaths

//-----------------------------------------------------------------------------
/** This function is used to initialise the file-manager after reading in
 *  the user configuration data. Esp. discovering the paths of all assets
 *  depends on the user config file (artist debug mode).
 */
void FileManager::init()
{
    discoverPaths();
    addAssetsSearchPath();
    m_cert_bundle_location = FileUtils::getPortableWritingPath(
        m_file_system->getAbsolutePath(getAsset("cacert.pem").c_str()).c_str());

    // Clean up left-over files in addons/tmp that are older than 24h
    // ==============================================================
    // (The 24h delay is useful when debugging a problem with a zip file)
    // We do when starting STK because for mobile STK destructor of file
    // manager may never be called if only home button is pressed
    std::set<std::string> allfiles;
    std::string tmp=getAddonsFile("tmp");
    listFiles(allfiles, tmp);
    for(std::set<std::string>::iterator i=allfiles.begin();
        i!=allfiles.end(); i++)
    {
        if((*i)=="." || (*i)=="..") continue;
        // For now there should be only zip files or .part files
        // (not fully downloaded files) in tmp. Warn about any
        // other files.
        std::string full_path=tmp+"/"+*i;
        if(StringUtils::getExtension(*i)!="zip" &&
           StringUtils::getExtension(*i)!="part"    )
        {
            Log::warn("[FileManager]", "Unexpected tmp file '%s' found.",
                       full_path.c_str());
            continue;
        }
        if(isDirectory(full_path))
        {
            // Gee, a .zip file which is a directory - stay away from it
            Log::warn("[FileManager]", "'%s' is a directory and will not be deleted.",
                      full_path.c_str());
            continue;
        }
        struct stat mystat;
        FileUtils::statU8Path(full_path, &mystat);
        StkTime::TimeType current = StkTime::getTimeSinceEpoch();
        if(current - mystat.st_ctime <24*3600)
        {
            if(UserConfigParams::logAddons())
                Log::verbose("[FileManager]", "'%s' is less than 24h old "
                             "and will not be deleted.",
                             full_path.c_str());
            continue;
        }
        if(UserConfigParams::logAddons())
            Log::verbose("[FileManager]", "Deleting tmp file'%s'.",full_path.c_str());
        removeFile(full_path);

    }   // for i in all files in tmp
}   // init

//-----------------------------------------------------------------------------
void FileManager::addAssetsSearchPath()
{
    // Note that we can't push the texture search path in the constructor
    // since this also adds a file archive to the file system - and
    // m_file_system is deleted (in irr_driver)
    pushTextureSearchPath(m_subdir_name[TEXTURE], "textures");
    pushTextureSearchPath(m_subdir_name[TEXTURE]+"skybox/", "skybox");

    if (fileExists(m_subdir_name[TEXTURE]+"deprecated/"))
        pushTextureSearchPath(m_subdir_name[TEXTURE]+"deprecated/", "deprecatedtex");

    pushTextureSearchPath(m_subdir_name[GUI_ICON], "gui/icons");

    pushModelSearchPath  (m_subdir_name[MODEL]);
    pushMusicSearchPath  (m_subdir_name[MUSIC]);

    // Add more paths from the STK_MUSIC_PATH environment variable
    if(getenv("SUPERTUXKART_MUSIC_PATH")!=NULL)
    {
        std::string path=getenv("SUPERTUXKART_MUSIC_PATH");
        std::vector<std::string> dirs = StringUtils::splitPath(path);
        for(int i=0;i<(int)dirs.size(); i++)
            pushMusicSearchPath(dirs[i]);
    }
}   // addAssetsSearchPath

//-----------------------------------------------------------------------------
void FileManager::reinitAfterDownloadAssets()
{
    m_file_system->removeAllFileArchives();
    m_texture_search_path.clear();
    m_model_search_path.clear();
    m_music_search_path.clear();
    discoverPaths();
    addAssetsSearchPath();
    // Add back addons search path
    KartPropertiesManager::addKartSearchDir(
                 file_manager->getAddonsFile("karts/"));
    track_manager->addTrackSearchDir(
                 file_manager->getAddonsFile("tracks/"));
}   // reinitAfterDownloadAssets

//-----------------------------------------------------------------------------
FileManager::~FileManager()
{
    // Clean up rest of file manager
    // =============================
    popMusicSearchPath();
    popModelSearchPath();
    popTextureSearchPath();
    popTextureSearchPath();
    m_file_system->drop();
    m_file_system = NULL;
}   // ~FileManager

// ----------------------------------------------------------------------------
/** Returns true if the specified file exists.
 */
bool FileManager::fileExists(const std::string& path) const
{
#ifdef DEBUG
    bool exists = m_file_system->existFile(path.c_str());
    if(exists) return true;
    // Now the original file was not found. Test if replacing \ with / helps:
    std::string s = StringUtils::replace(path, "\\", "/");
    exists = m_file_system->existFile(s.c_str());
    if(exists)
        Log::warn("FileManager", "File '%s' does not exists, but '%s' does!",
        path.c_str(), s.c_str());
    return exists;
#else
    return m_file_system->existFile(path.c_str());
#endif
}   // fileExists
//-----------------------------------------------------------------------------
/** Adds paths to the list of stk root directories.
 *  \param roots A ":" separated string of directories to add.
 */
void FileManager::addRootDirs(const std::string &roots)
{
    std::vector<std::string> all = StringUtils::splitPath(roots);
    for(unsigned int i=0; i<all.size(); i++)
    {
        if(all[i].size()==0 || all[i][all[i].size()-1]!='/')
            all[i] += "/";
        m_root_dirs.push_back(all[i]);
    }
}   // addRootDirs

//-----------------------------------------------------------------------------
io::IXMLReader *FileManager::createXMLReader(const std::string &filename)
{
    return m_file_system->createXMLReader(filename.c_str());
}   // getXMLReader
//-----------------------------------------------------------------------------
/** Reads in a XML file and converts it into a XMLNode tree.
 *  \param filename Name of the XML file to read.
 */
XMLNode *FileManager::createXMLTree(const std::string &filename)
{
    try
    {
        XMLNode* node = new XMLNode(filename);
        return node;
    }
    catch (std::runtime_error& e)
    {
        if (UserConfigParams::logMisc())
        {
            Log::error("[FileManager]", "createXMLTree: %s", e.what());
        }
        return NULL;
    }
}   // createXMLTree

//-----------------------------------------------------------------------------
/** Reads in XML from a string and converts it into a XMLNode tree.
 *  \param content the string containing the XML content.
 */
XMLNode *FileManager::createXMLTreeFromString(const std::string & content)
{
    try
    {
        char *b = new char[content.size()];
        assert(b);
        memcpy(b, content.c_str(), content.size());
        io::IReadFile * ireadfile =
            m_file_system->createMemoryReadFile(b, (int)content.size(),
                                                "tempfile", true);
        io::IXMLReader * reader = m_file_system->createXMLReader(ireadfile);
        XMLNode* node = new XMLNode(reader);
        reader->drop();
        ireadfile->drop();
        return node;
    }
    catch (std::runtime_error& e)
    {
        if (UserConfigParams::logMisc())
        {
            Log::error("[FileManager]", "createXMLTreeFromString: %s", e.what());
        }
        return NULL;
    }
}   // createXMLTreeFromString

//-----------------------------------------------------------------------------
/** In order to add and later remove paths we have to specify the absolute
 *  filename (and replace '\' with '/' on windows).
 */
io::path FileManager::createAbsoluteFilename(const std::string &f)
{
    io::path abs_path=m_file_system->getAbsolutePath(f.c_str());
    abs_path=m_file_system->flattenFilename(abs_path);
    return abs_path;
}   // createAbsoluteFilename

//-----------------------------------------------------------------------------
/** Adds a model search path to the list of model search paths.
 *  This path will be searched before any other existing paths.
 */
void FileManager::pushModelSearchPath(const std::string& path)
{
    m_model_search_path.push_back(path);
    std::unique_lock<std::recursive_mutex> ul = m_file_system->acquireFileArchivesMutex();

    const int n=m_file_system->getFileArchiveCount();
    m_file_system->addFileArchive(createAbsoluteFilename(path),
                                  /*ignoreCase*/false,
                                  /*ignorePaths*/false,
                                  io::EFAT_FOLDER);
    // A later added file archive should be searched first (so that
    // track specific models are found before models in data/models).
    // This is not necessary if this is the first member, or if the
    // addFileArchive call did not add this file systems (this can
    // happen if the file archive has been added prevously, which
    // commonly happens since each kart/track specific path is added
    // twice: once for textures and once for models).
    if(n>0 && (int)m_file_system->getFileArchiveCount()>n)
    {
        // In this case move the just added file archive
        // (which has index n) to position 0 (by -n positions):
        m_file_system->moveFileArchive(n, -n);
    }
}   // pushModelSearchPath

//-----------------------------------------------------------------------------
/** Adds a texture search path to the list of texture search paths.
 *  This path will be searched before any other existing paths.
 */
void FileManager::pushTextureSearchPath(const std::string& path, const std::string& container_id)
{
    m_texture_search_path.push_back(TextureSearchPath(path, container_id));
    std::unique_lock<std::recursive_mutex> ul = m_file_system->acquireFileArchivesMutex();

    const int n=m_file_system->getFileArchiveCount();
    m_file_system->addFileArchive(createAbsoluteFilename(path),
                                  /*ignoreCase*/false,
                                  /*ignorePaths*/false,
                                  io::EFAT_FOLDER);
    // A later added file archive should be searched first (so that
    // e.g. track specific textures are found before textures in
    // data/textures).
    // This is not necessary if this is the first member, or if the
    // addFileArchive call did not add this file systems (this can
    // happen if the file archive has been added previously, which
    // commonly happens since each kart/track specific path is added
    // twice: once for textures and once for models).
    if(n>0 && (int)m_file_system->getFileArchiveCount()>n)
    {
        // In this case move the just added file archive
        // (which has index n) to position 0 (by -n positions):
        m_file_system->moveFileArchive(n, -n);
    }
}   // pushTextureSearchPath

//-----------------------------------------------------------------------------
/** Removes the last added texture search path from the list of paths.
 */
void FileManager::popTextureSearchPath()
{
    if (!m_texture_search_path.empty())
    {
        TextureSearchPath dir = m_texture_search_path.back();
        m_texture_search_path.pop_back();
        m_file_system->removeFileArchive(createAbsoluteFilename(dir.m_texture_search_path));
    }
}   // popTextureSearchPath

//-----------------------------------------------------------------------------
/** Removes the last added model search path from the list of paths.
 */
void FileManager::popModelSearchPath()
{
    if (!m_model_search_path.empty())
    {
        std::string dir = m_model_search_path.back();
        m_model_search_path.pop_back();
        m_file_system->removeFileArchive(createAbsoluteFilename(dir));
    }
}   // popModelSearchPath

// ------------------------------------------------------------------------
/** Removes the last added directory from the music search path.
 */
void FileManager::popMusicSearchPath()
{
    if(!m_music_search_path.empty())
    {
        m_music_search_path.pop_back();
    }
}

//-----------------------------------------------------------------------------
/** Tries to find the specified file in any of the given search paths.
 *  \param full_path On return contains the full path of the file, or
 *         "" if the file is not found.
 *  \param file_name The name of the file to look for.
 *  \param search_path The list of paths to search for the file.
 *  \return True if the file is found, false otherwise.
 */
bool FileManager::findFile(std::string& full_path,
                      const std::string& file_name,
                      const std::vector<std::string>& search_path) const
{
    for(std::vector<std::string>::const_reverse_iterator
        i = search_path.rbegin();
        i != search_path.rend(); ++i)
    {
        full_path = *i + file_name;
        if(m_file_system->existFile(full_path.c_str())) return true;
    }
    full_path="";
    return false;
}   // findFile

//-----------------------------------------------------------------------------
/** Tries to find the specified file in any of the given search paths.
*  \param full_path On return contains the full path of the file, or
*         "" if the file is not found.
*  \param file_name The name of the file to look for.
*  \param search_path The list of paths to search for the file.
*  \return True if the file is found, false otherwise.
*/
bool FileManager::findFile(std::string& full_path,
    const std::string& file_name,
    const std::vector<TextureSearchPath>& search_path) const
{
    for (std::vector<TextureSearchPath>::const_reverse_iterator
        i = search_path.rbegin();
        i != search_path.rend(); ++i)
    {
        full_path = i->m_texture_search_path + file_name;
        if (m_file_system->existFile(full_path.c_str())) return true;
    }
    full_path = "";
    return false;
}   // findFile

//-----------------------------------------------------------------------------
std::string FileManager::getAssetChecked(FileManager::AssetType type,
                                         const std::string& name,
                                         bool abort_on_error) const
{
    std::string path = m_subdir_name[type]+name;
    if(fileExists(path))
        return path;

    if(abort_on_error)
    {
        Log::fatal("[FileManager]", "Can not find file '%s' in '%s'",
                   name.c_str(), m_subdir_name[type].c_str());
    }
    return "";
}   // getAssetChecked

//-----------------------------------------------------------------------------
/** Returns the full path of a file of the given asset class. It is not
 *  checked if the file actually exists (use getAssetChecked() instead if
 *  checking is needed).
 *  \param type Type of the asset class.
 *  \param name Name of the file to search.
 *  \return Full path to the file.
 */
std::string FileManager::getAsset(FileManager::AssetType type,
                                  const std::string &name) const
{
    if (type == GUI_ICON && GUIEngine::getSkin()->hasIconTheme())
    {
        return GUIEngine::getSkin()->getThemedIcon("gui/icons/" + name);
    }
    return m_subdir_name[type] + name;
}   // getAsset

//-----------------------------------------------------------------------------
/** Searches in all root directories for the specified file.
 *  \param name Name of the file to find.
 *  \return Full path of the file, or "" if not found.
 */
std::string FileManager::getAsset(const std::string &name) const
{
    std::string path;
    findFile(path, name, m_root_dirs);
    return path;
}   // getAsset

//-----------------------------------------------------------------------------
/** Returns the directory in which screenshots should be stored.
 */
std::string FileManager::getScreenshotDir() const
{
    return m_screenshot_dir;
}   // getScreenshotDir

//-----------------------------------------------------------------------------
/** Returns the directory in which replay file should be stored.
 */
std::string FileManager::getReplayDir() const
{
    return m_replay_dir;
}   // getReplayDir

//-----------------------------------------------------------------------------
/** Returns the directory in which resized textures should be cached.
*/
std::string FileManager::getCachedTexturesDir() const
{
    return m_cached_textures_dir;
}   // getCachedTexturesDir

//-----------------------------------------------------------------------------
/** Returns the directory in which user-defined grand prix should be stored.
 */
std::string FileManager::getGPDir() const
{
    return m_gp_dir;
}   // getGPDir

//-----------------------------------------------------------------------------
/** Returns the directory in which the stdout file should be stored.
 */
std::string FileManager::getStdoutDir() const
{
    return m_stdout_dir;
}   // getStdoutDir

//-----------------------------------------------------------------------------
/** Returns the full path of a texture file name by searching in all
 *  directories currently in the texture search path. The difference to
 *  a call getAsset(TEXTURE,...) is that the latter will only return
 *  textures from .../textures, while the searchTexture will also
 *  search e.g. in kart or track directories (depending on what is currently
 *  being loaded).
 *  \param file_name Name of the texture file to search.
 *  \return The full path for the texture, or "" if the texture was not found.
 */
std::string FileManager::searchTexture(const std::string& file_name) const
{
    std::string path;
    findFile(path, file_name, m_texture_search_path);
    return path;
}   // searchTexture

//-----------------------------------------------------------------------------

bool FileManager::searchTextureContainerId(std::string& container_id,
    const std::string& file_name) const
{
    std::string full_path;
    for (std::vector<TextureSearchPath>::const_reverse_iterator
        i = m_texture_search_path.rbegin();
        i != m_texture_search_path.rend(); ++i)
    {
        full_path = i->m_texture_search_path + file_name;
        if (m_file_system->existFile(full_path.c_str()))
        {
            container_id = i->m_container_id;
            return true;
        }
    }
    full_path = "";
    return false;
}   // findFile

//-----------------------------------------------------------------------------
/** Returns the list of all directories in which music files are searched.
 */
std::vector<std::string> FileManager::getMusicDirs() const
{
    return m_music_search_path;
}   // getMusicDirs

//-----------------------------------------------------------------------------
/** If the directory specified in path does not exist, it is created. This
 *  function does not support recursive operations, so if a directory "a/b"
 *  is tested, and "a" does not exist, this function will fail.
 *  \params path Directory to test.
 *  \return  True if the directory exists or could be created,
 *           false otherwise.
 */
bool FileManager::checkAndCreateDirectory(const std::string &path)
{
    // irrlicht apparently returns true for files and directory
    // (using access/_access internally):
    if(m_file_system->existFile(io::path(path.c_str())))
        return true;

    Log::info("FileManager", "Creating directory '%s'.", path.c_str());

    // Otherwise try to create the directory:
#if defined(WIN32)
    bool error = _wmkdir(StringUtils::utf8ToWide(path).c_str()) != 0;
#else
    bool error = mkdir(path.c_str(), 0777) != 0;
#endif
    return !error;
}   // checkAndCreateDirectory

//-----------------------------------------------------------------------------
/** If the directory specified in path does not exist, it is created
 *  recursively (mkdir -p style).
 *  \params path Directory to test.
 *  \return  True if the directory exists or could be created, false otherwise.
 */
bool FileManager::checkAndCreateDirectoryP(const std::string &path)
{
    // irrlicht apparently returns true for files and directory
    // (using access/_access internally):
    if(m_file_system->existFile(io::path(path.c_str())))
        return true;

    Log::info("[FileManager]", "Creating directory(ies) '%s'", path.c_str());

    std::vector<std::string> split = StringUtils::split(path,'/');
    std::string current_path = "";
    for (unsigned int i=0; i<split.size(); i++)
    {
        current_path += split[i] + "/";
        //Log::verbose("[FileManager]", "Checking for: '%s",
        //            current_path.c_str());

        if (!checkAndCreateDirectory(current_path))
        {
            Log::error("[FileManager]", "Can't create dir '%s'",
                    current_path.c_str());
            break;
        }
    }
    bool error = checkAndCreateDirectory(path);

    return error;
}   // checkAndCreateDirectory

//-----------------------------------------------------------------------------
/** Checks if the config directory exists, and it not, tries to create it.
 *  It will set m_user_config_dir to the path to which user-specific config
 *  files are stored.
 */
void FileManager::checkAndCreateConfigDir()
{
    if(getenv("SUPERTUXKART_SAVEDIR") &&
        checkAndCreateDirectory(getenv("SUPERTUXKART_SAVEDIR")) )
    {
        m_user_config_dir = getenv("SUPERTUXKART_SAVEDIR");
    }
    else
    {

#if defined(WIN32)

        // Try to use the APPDATA directory to store config files and highscore
        // lists. If not defined, used the current directory.
        std::vector<wchar_t> env;
        // An environment variable has a maximum size limit of 32,767 characters
        env.resize(32767, 0);
        DWORD length = GetEnvironmentVariable(L"APPDATA", env.data(), 32767);
        if (length != 0)
        {
            m_user_config_dir = StringUtils::wideToUtf8(env.data());
            if (!checkAndCreateDirectory(m_user_config_dir))
            {
                Log::error("[FileManager]", "Can't create config dir '%s"
                            ", falling back to '.'.", m_user_config_dir.c_str());
                m_user_config_dir = ".";
            }
        }
        else
            m_user_config_dir = ".";

        m_user_config_dir += "/supertuxkart";

#elif defined(__APPLE__)

        if (getenv("HOME") != NULL)
        {
            m_user_config_dir = getenv("HOME");
        }
        else
        {
            Log::error("[FileManager]",
                        "No home directory, this should NOT happen!");
            // Fall back to system-wide app data (rather than
            // user-specific data), but should not happen anyway.
            m_user_config_dir = "";
        }
        m_user_config_dir += "/Library/Application Support/";
        const std::string CONFIGDIR("SuperTuxKart");
        m_user_config_dir += CONFIGDIR;

#elif defined(__HAIKU__)

        BPath settings_dir;
        if (find_directory(B_USER_SETTINGS_DIRECTORY, &settings_dir, true, NULL) == B_OK)
        {
            m_user_config_dir = std::string(settings_dir.Path());
        }
        else if (getenv("HOME") != NULL)
        {
            m_user_config_dir = getenv("HOME");
            m_user_config_dir += "/config/settings";
        }
        m_user_config_dir += "/SuperTuxKart";

#else

        // Remaining unix variants. Use the new standards for config directory
        // i.e. either XDG_CONFIG_HOME or $HOME/.config
        if (getenv("XDG_CONFIG_HOME") !=NULL)
        {
            m_user_config_dir = getenv("XDG_CONFIG_HOME");
            checkAndCreateDirectory(m_user_config_dir);
        }
        else if (!getenv("HOME"))
        {
            Log::error("[FileManager]",
                        "No home directory, this should NOT happen "
                        "- trying '.' for config files!");
            m_user_config_dir = ".";
        }
        else
        {
            m_user_config_dir  = getenv("HOME");
            checkAndCreateDirectory(m_user_config_dir);

            m_user_config_dir += "/.config";
            if(!checkAndCreateDirectory(m_user_config_dir))
            {
                // If $HOME/.config can not be created:
                Log::error("[FileManager]",
                            "Cannot create directory '%s', falling back to use '%s'",
                            m_user_config_dir.c_str(), getenv("HOME"));
                m_user_config_dir = getenv("HOME");
            }
        }
        m_user_config_dir += "/supertuxkart";

#endif

    }   // if(getenv("SUPERTUXKART_SAVEDIR") && checkAndCreateDirectory(...))

    if(m_user_config_dir.size()>0 && *m_user_config_dir.rbegin()!='/')
        m_user_config_dir += "/";

    m_user_config_dir += "config-0.10/";
    if(!checkAndCreateDirectoryP(m_user_config_dir))
    {
        Log::warn("FileManager", "Can not  create config dir '%s', "
                  "falling back to '.'.", m_user_config_dir.c_str());
        m_user_config_dir = "./";
    }

    if (m_stdout_dir.empty())
    {
        m_stdout_dir = m_user_config_dir;
    }

    return;
}   // checkAndCreateConfigDir

// ----------------------------------------------------------------------------
/** Creates the directories for the addons data. This will set m_addons_dir
 *  with the appropriate path, and also create the subdirectories in this
 *  directory.
 */
void FileManager::checkAndCreateAddonsDir()
{
#if defined(WIN32)
    m_addons_dir  = m_user_config_dir+"../addons/";
#elif defined(__HAIKU__)
    m_addons_dir  = m_user_config_dir+"addons/";
#elif defined(__APPLE__)
    m_addons_dir  = getenv("HOME");
    m_addons_dir += "/Library/Application Support/SuperTuxKart/Addons/";
#elif defined(__HAIKU__)
    m_addons_dir  = m_user_config_dir+"addons/";
#else
    m_addons_dir = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
                                          ".local/share", ".stkaddons");
    m_addons_dir += "addons/";
#endif

    if(!checkAndCreateDirectory(m_addons_dir))
    {
        Log::error("FileManager", "Can not create add-ons dir '%s', "
                   "falling back to '.'.", m_addons_dir.c_str());
        m_addons_dir = "./";
    }

    if (!checkAndCreateDirectory(m_addons_dir + "icons/"))
    {
        Log::error("FileManager", "Failed to create add-ons icon dir at '%s'.",
                   (m_addons_dir + "icons/").c_str());
    }
    if (!checkAndCreateDirectory(m_addons_dir + "tmp/"))
    {
        Log::error("FileManager", "Failed to create add-ons tmp dir at '%s'.",
                   (m_addons_dir + "tmp/").c_str());
    }

}   // checkAndCreateAddonsDir

// ----------------------------------------------------------------------------
/** Creates the directories for screenshots. This will set m_screenshot_dir
 *  with the appropriate path.
 */
void FileManager::checkAndCreateScreenshotDir()
{
#if defined(WIN32) || defined(__HAIKU__)
    m_screenshot_dir  = m_user_config_dir+"screenshots/";
#elif defined(__APPLE__)
    m_screenshot_dir  = getenv("HOME");
    m_screenshot_dir += "/Library/Application Support/SuperTuxKart/Screenshots/";
#else
    m_screenshot_dir  = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
                                          ".local/share", ".stkscreenshots");
    m_screenshot_dir += "screenshots/";
#endif

    if(!checkAndCreateDirectory(m_screenshot_dir))
    {
        Log::error("FileManager", "Can not create screenshot directory '%s', "
                   "falling back to '.'.", m_screenshot_dir.c_str());
        m_screenshot_dir = ".";
    }

}   // checkAndCreateScreenshotDir

// ----------------------------------------------------------------------------
/** Creates the directories for replay recorded. This will set m_replay_dir
 *  with the appropriate path.
 */
void FileManager::checkAndCreateReplayDir()
{
#if defined(WIN32) || defined(__HAIKU__)
    m_replay_dir = m_user_config_dir + "replay/";
#elif defined(__APPLE__)
    m_replay_dir  = getenv("HOME");
    m_replay_dir += "/Library/Application Support/SuperTuxKart/replay/";
#else
    m_replay_dir = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
                                          ".local/share", ".supertuxkart");
    m_replay_dir += "replay/";
#endif

    if(!checkAndCreateDirectory(m_replay_dir))
    {
        Log::error("FileManager", "Can not create replay directory '%s', "
                   "falling back to '.'.", m_replay_dir.c_str());
        m_replay_dir = ".";
    }

}   // checkAndCreateReplayDir

// ----------------------------------------------------------------------------
/** Creates the directories for cached textures. This will set
*  m_cached_textures_dir with the appropriate path.
*/
void FileManager::checkAndCreateCachedTexturesDir()
{
#if defined(WIN32) || defined(__HAIKU__)
    m_cached_textures_dir = m_user_config_dir + "cached-textures/";
#elif defined(__APPLE__)
    m_cached_textures_dir = getenv("HOME");
    m_cached_textures_dir += "/Library/Application Support/SuperTuxKart/CachedTextures/";
#else
    m_cached_textures_dir = checkAndCreateLinuxDir("XDG_CACHE_HOME", "supertuxkart", ".cache/", ".");
    m_cached_textures_dir += "cached-textures/";
#endif

    if (!checkAndCreateDirectory(m_cached_textures_dir))
    {
        Log::error("FileManager", "Can not create cached textures directory '%s', "
            "falling back to '.'.", m_cached_textures_dir.c_str());
        m_cached_textures_dir = ".";
    }

}   // checkAndCreateCachedTexturesDir

// ----------------------------------------------------------------------------
/** Creates the directories for user-defined grand prix. This will set m_gp_dir
 *  with the appropriate path.
 */
void FileManager::checkAndCreateGPDir()
{
#if defined(WIN32) || defined(__HAIKU__)
    m_gp_dir = m_user_config_dir + "grandprix/";
#elif defined(__APPLE__)
    m_gp_dir  = getenv("HOME");
    m_gp_dir += "/Library/Application Support/SuperTuxKart/grandprix/";
#else
    m_gp_dir = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
                                          ".local/share", ".supertuxkart");
    m_gp_dir += "grandprix/";
#endif

    if(!checkAndCreateDirectory(m_gp_dir))
    {
        Log::error("FileManager", "Can not create user-defined grand prix directory '%s', "
                   "falling back to '.'.", m_gp_dir.c_str());
        m_gp_dir = ".";
    }

}   // checkAndCreateGPDir

// ----------------------------------------------------------------------------
#if !defined(WIN32) && !defined(__APPLE__)

/** Find a directory to use for remaining unix variants. Use the new standards
 *  for config directory based on XDG_* environment variables, or a
 *  subdirectory under $HOME, trying two different fallbacks. It will also
 *  check if the directory 'dirname' can be created (to avoid problems that
 *  e.g. $env_name is '/', which exists, but can not be written to.
 *  \param env_name  Name of the environment variable to test first.
 *  \param dir_name  Name of the directory to create
 *  \param fallback1 Subdirectory under $HOME to use if the environment
 *         variable is not defined or can not be created.
 *  \param fallback2 Subdirectory under $HOME to use if the environment
 *         variable and fallback1 are not defined or can not be created.
 */
std::string FileManager::checkAndCreateLinuxDir(const char *env_name,
                                                const char *dir_name,
                                                const char *fallback1,
                                                const char *fallback2)
{
    bool dir_ok = false;
    std::string dir;

    if (getenv(env_name)!=NULL)
    {
        dir = getenv(env_name);
        dir_ok = checkAndCreateDirectory(dir);
        if(!dir_ok)
            Log::warn("FileManager", "Cannot create $%s.", env_name);

        if(dir[dir.size()-1]!='/') dir += "/";
        // Do an additional test here, e.g. in case that XDG_DATA_HOME is '/'
        // and since dir_ok is set, it would not test any of the other options
        // like $HOME/.local/share
        dir_ok = checkAndCreateDirectory(dir+dir_name);
        if(!dir_ok)
            Log::warn("FileManager", "Cannot create $%s/%s.", dir.c_str(),
                      dir_name);
    }

    if(!dir_ok && getenv("HOME"))
    {
        // Use ~/.local/share :
        dir  = getenv("HOME");
        if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
        dir += fallback1;
        // This will create each individual subdirectory if
        // dir_name contains "/".
        dir_ok = checkAndCreateDirectoryP(dir);
        if(!dir_ok)
            Log::warn("FileManager", "Cannot create $HOME/%s.",
                      fallback1);
    }
    if(!dir_ok && fallback2 && getenv("HOME"))
    {
        dir  = getenv("HOME");
        if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
        dir += fallback2;
        dir_ok = checkAndCreateDirectory(dir);
        if(!dir_ok)
            Log::warn("FileManager", "Cannot create $HOME/%s.",
                      fallback2);
    }

    if(!dir_ok)
    {
        Log::warn("FileManager", "Falling back to use '.'.");
        dir = "./";
    }

    if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
    dir += dir_name;
    dir_ok = checkAndCreateDirectory(dir);
    if(!dir_ok)
    {
        // If the directory can not be created
        Log::error("FileManager", "Cannot create directory '%s', "
                   "falling back to use '.'.", dir.c_str());
        dir="./";
    }
    if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
    return dir;
}   // checkAndCreateLinuxDir
#endif

//-----------------------------------------------------------------------------
/** Sets the name for the stdout log file.
 *  \param filename Filename to use (relative to the user config dir).
 */
void FileManager::setStdoutName(const std::string& filename)
{
    m_stdout_filename = filename;
}   // setStdoutName

//-----------------------------------------------------------------------------
/** Sets the directory for the stdout log file.
 *  \param dir Directory to use
 */
void FileManager::setStdoutDir(const std::string& dir)
{
    m_stdout_dir = dir;

    if (!m_stdout_dir.empty() && m_stdout_dir[m_stdout_dir.size() - 1] != '/')
    {
        m_stdout_dir += "/";
    }
}   // setStdoutDir

//-----------------------------------------------------------------------------
/** Redirects output to go into files in the user's config directory
 *  instead of to the console. It keeps backup copies of previous stdout files
 *  (3 atm), which can help to diagnose problems caused by a previous crash.
 */
void FileManager::redirectOutput()
{
    // Do a simple log rotate: stdout.log.2 becomes stdout.log.3 etc
    const int NUM_BACKUPS=3;
    std::string logoutfile = m_stdout_dir + m_stdout_filename;
    for(int i=NUM_BACKUPS; i>1; i--)
    {
        std::ostringstream out_old;
        out_old << logoutfile << "." << i;
        removeFile(out_old.str());
        std::ostringstream out_new;
        out_new << logoutfile << "." << i-1;
        if(fileExists(out_new.str()))
        {
            FileUtils::renameU8Path(out_new.str(), out_old.str());
        }
    }   // for i in NUM_BACKUPS

    if(fileExists(logoutfile))
    {
        std::ostringstream out;
        out << logoutfile<<".1";
        // No good place to log error messages when log is not yet initialised
        FileUtils::renameU8Path(logoutfile, out.str());
    }

    //Enable logging of stdout and stderr to logfile
    Log::verbose("main", "Error messages and other text output will "
                         "be logged to %s.", logoutfile.c_str());
    Log::openOutputFiles(logoutfile);
}   // redirectOutput

//-----------------------------------------------------------------------------
/** Returns the directory for addon files. */
const std::string &FileManager::getAddonsDir() const
{
    return m_addons_dir;
}   // getAddonsDir

//-----------------------------------------------------------------------------
/** Returns the full path of a file in the addons directory.
 *  \param name Name of the file.
 */
std::string FileManager::getAddonsFile(const std::string &name)
{
    return getAddonsDir()+name;
}   // getAddonsFile

//-----------------------------------------------------------------------------
/** Returns the full path of the config directory.
 */
std::string FileManager::getUserConfigFile(const std::string &fname) const
{
    return m_user_config_dir+fname;
}   // getUserConfigFile

//-----------------------------------------------------------------------------
/** Returns the full path of a music file by searching all music search paths.
 *  It throws an exception if the file is not found.
 *  \param file_name File name to search for.
 */
std::string FileManager::searchMusic(const std::string& file_name) const
{
    std::string path;
    bool success = findFile(path, file_name, m_music_search_path);
    if(!success)
    {
        // If a music file is not found in any of the music search paths
        // check all root dirs. This is used by stk_config to load the
        // title music before any music search path is defined)
        path = getAsset(MUSIC, file_name);
        success = fileExists(path);
    }
    if (!success)
    {
        throw std::runtime_error(
            "[FileManager::getMusicFile] Cannot find music file '"
            +file_name+"'.");
    }
    return path;
}   // searchMusic

//-----------------------------------------------------------------------------
/** Returns the full path of a model file by searching all model search paths.
 *  It throws an exception if the file is not found.
 *  \param file_name File name to search for.
 */
std::string FileManager::searchModel(const std::string& file_name) const
{
    std::string path;
    bool success = findFile(path, file_name, m_model_search_path);
    if (!success)
    {
        throw std::runtime_error(
            "[FileManager::searchModel] Cannot find model file '"
            +file_name+"'.");
    }
    return path;
}   // searchModel

//-----------------------------------------------------------------------------
/** Returns true if the given name is a directory.
 *  \param path File name to test.
 */
bool FileManager::isDirectory(const std::string &path)
{
    struct stat mystat;
    std::string s(path);
    // At least on windows stat returns an error if there is
    // a '/' at the end of the path.
    if(s[s.size()-1]=='/')
        s.erase(s.end()-1, s.end());
    if(FileUtils::statU8Path(s, &mystat) < 0) return false;
    return S_ISDIR(mystat.st_mode);
}   // isDirectory

//-----------------------------------------------------------------------------
/** Returns a list of files in a given directory.
 *  \param result A reference to a std::vector<std::string> which will
 *         hold all files in a directory. The vector will be cleared.
 *  \param dir The director for which to get the directory listing.
 *  \param make_full_path If set to true, all listed files will be full paths.
 */
void FileManager::listFiles(std::set<std::string>& result,
                            const std::string& dir,
                            bool make_full_path) const
{
    result.clear();

    if (!isDirectory(dir))
        return;

    irr::io::IFileList* files = m_file_system->createFileList(dir.c_str());

    for (int n = 0; n < (int)files->getFileCount(); n++)
    {
        result.insert(make_full_path ? dir + "/" + files->getFileName(n).c_str()
                                     : files->getFileName(n).c_str());
    }

    files->drop();
}   // listFiles

//-----------------------------------------------------------------------------
/** Creates a directory for an addon.
 *  \param addons_name Name of the directory to create.
 *  \param addons_type The type, which is used as a subdirectory. E.g.:
 *         'karts' (m_addons_dir/karts/name will be created).
 */
void FileManager::checkAndCreateDirForAddons(const std::string &dir)
{
    // Tries to create directory recursively
    bool success = checkAndCreateDirectoryP(dir);
    if(!success)
    {
        Log::warn("FileManager", "There is a problem with the addons dir.");
        return;
    }
}   // checkAndCreateDirForAddons

// ----------------------------------------------------------------------------
/** Removes the specified file.
 *  \return True if successful, or false if the file is not a regular file or
 *           can not be removed.
 */
bool FileManager::removeFile(const std::string &name) const
{
    // If the file does not exists, everything is fine
    if(!fileExists(name))
       return true;

    struct stat mystat;
    if(FileUtils::statU8Path(name, &mystat) < 0) return false;
    if( S_ISREG(mystat.st_mode))
    {
#if defined(WIN32)
        return _wremove(StringUtils::utf8ToWide(name).c_str()) == 0;
#else
        return remove(name.c_str()) == 0;
#endif
    }
    return false;
}   // removeFile

// ----------------------------------------------------------------------------
/** Removes a directory (including all files contained). The function could
 *  easily recursively delete further subdirectories, but this is commented
 *  out atm (to limit the amount of damage in case of a bug).
 *  \param name Directory name to remove.
 *  \param return True if removal was successful.
 */
bool FileManager::removeDirectory(const std::string &name) const
{
    std::set<std::string> files;
    listFiles(files, name, /*is full path*/ true);

    for (std::string file : files)
    {
        if (file == "." || file == ".." || file == name + "/." ||
            file == name + "/..")
            continue;

        if (UserConfigParams::logMisc())
            Log::verbose("FileManager", "Deleting directory '%s'.",
                         file.c_str());

        if (isDirectory(file))
        {
            // This should not be necessary (since this function is only
            // used to remove addons), and it limits the damage in case
            // of any bugs - i.e. if name should be "/" or so.
            // We need to remove whole data directory on Android though, i.e.
            // when we install newer STK version and new assets are extracted.
            // So enable it only for Android for now.
            #ifdef MOBILE_STK
            removeDirectory(file);
            #else
            if (file.find(m_addons_dir) != std::string::npos)
                removeDirectory(file);
            #endif
        }
        else
        {
            removeFile(file);
        }
    }

#if defined(WIN32)
    return RemoveDirectory(StringUtils::utf8ToWide(name).c_str())==TRUE;
#else
    return remove(name.c_str())==0;
#endif
}   // remove directory

// ----------------------------------------------------------------------------
/** Copies the file source to dest.
 *  \param source The file to read.
 *  \param dest The new filename.
 *  \return True if the copy was successful, false otherwise.
 */
bool FileManager::copyFile(const std::string &source, const std::string &dest)
{
    FILE *f_source = FileUtils::fopenU8Path(source, "rb");
    FILE *f_dest = FileUtils::fopenU8Path(dest, "wb");
    constexpr int BUFFER_SIZE=32768;
    char *buffer = new char[BUFFER_SIZE];
    auto scoped = [&]() {
        if (f_source) fclose(f_source);
        if (f_dest) fclose(f_dest);
        if (buffer) delete [] buffer;
    };
    MemUtils::deref<decltype(scoped)> cls(scoped); 
    if(!f_source || !f_dest || !buffer) return false;
    size_t n;
    while((n=fread(buffer, 1, BUFFER_SIZE, f_source))>0)
    {
        if(fwrite(buffer, 1, n, f_dest)!=n)
        {
            Log::error("FileManager", "Write error copying '%s' to '%s",
                        source.c_str(), dest.c_str());
            return false;

        }   // if fwrite()!=n
    }   // while

    return true;
}   // copyFile

// ----------------------------------------------------------------------------
/** Returns true if the first file is newer than the second. The comparison is
*   based on the modification time of the two files.
*/
bool FileManager::fileIsNewer(const std::string& f1, const std::string& f2) const
{
    struct stat stat1;
    struct stat stat2;
    FileUtils::statU8Path(f1, &stat1);
    FileUtils::statU8Path(f2, &stat2);
    return stat1.st_mtime > stat2.st_mtime;
}   // fileIsNewer

// ----------------------------------------------------------------------------
/** Move the source directory into the target directory location.
*   The target directory must be on the same drive as the source.
*/
bool FileManager::moveDirectoryInto(std::string source, std::string target)
{
    if (!isDirectory(source) || !isDirectory(target))
        return false;

    // Remove the last '/'
    if (source[source.size() - 1] == '/')
        source.erase(source.end() - 1, source.end());
    std::string folder = StringUtils::getBasename(source);
    if (target[target.size() - 1] != '/')
        target += "/";
    target += folder;

    // The result target directory must not already exist
    if (isDirectory(target))
        return false;

#if defined(WIN32)
    return MoveFileExW(StringUtils::utf8ToWide(source).c_str(),
        StringUtils::utf8ToWide(target).c_str(),
        MOVEFILE_WRITE_THROUGH) != 0;
#else
    return rename(source.c_str(), target.c_str()) != -1;
#endif
}   // moveDirectoryInto
