/**
 * @file lldir.cpp
 * @brief implementation of directory utilities base class
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * 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 GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#if !LL_WINDOWS
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#else
#include <direct.h>
#endif

#include "lldir.h"

#include "llerror.h"
#include "lltimer.h"    // ms_sleep()
#include "lluuid.h"

#include "lldiriterator.h"
#include "stringize.h"
#include "llstring.h"
#include <boost/filesystem.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <algorithm>

using boost::assign::list_of;
using boost::assign::map_list_of;

#if LL_WINDOWS
#include "lldir_win32.h"
LLDir_Win32 gDirUtil;
#elif LL_DARWIN
#include "lldir_mac.h"
LLDir_Mac gDirUtil;
#else
#include "lldir_linux.h"
LLDir_Linux gDirUtil;
#endif

LLDir *gDirUtilp = (LLDir *)&gDirUtil;

/// Values for findSkinnedFilenames(subdir) parameter
const char
    *LLDir::XUI      = "xui",
    *LLDir::TEXTURES = "textures",
    *LLDir::SKINBASE = "";

static const char* const empty = "";
std::string LLDir::sDumpDir = "";

LLDir::LLDir()
:   mAppName(""),
    mExecutablePathAndName(""),
    mExecutableFilename(""),
    mExecutableDir(""),
    mAppRODataDir(""),
    mOSUserDir(""),
    mOSUserAppDir(""),
    mLindenUserDir(""),
    mOSCacheDir(""),
    mCAFile(""),
    mTempDir(""),
    mDirDelimiter("/"), // fallback to forward slash if not overridden
    mLanguage("en"),
    mUserName("undefined")
{
}

LLDir::~LLDir()
{
}

std::vector<std::string> LLDir::getFilesInDir(const std::string &dirname)
{
    //Returns a vector of fullpath filenames.

#ifdef LL_WINDOWS // or BOOST_WINDOWS_API
    boost::filesystem::path p(utf8str_to_utf16str(dirname));
#else
    boost::filesystem::path p(dirname);
#endif

    std::vector<std::string> v;

    if (exists(p))
    {
        if (is_directory(p))
        {
            boost::filesystem::directory_iterator end_iter;
            for (boost::filesystem::directory_iterator dir_itr(p);
                 dir_itr != end_iter;
                 ++dir_itr)
            {
                if (boost::filesystem::is_regular_file(dir_itr->status()))
                {
                    v.push_back(dir_itr->path().filename().string());
                }
            }
        }
    }
    return v;
}

S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask)
{
    S32 count = 0;
    std::string filename;
    std::string fullpath;
    S32 result;

    // File masks starting with "/" will match nothing, so we consider them invalid.
    if (LLStringUtil::startsWith(mask, getDirDelimiter()))
    {
        LL_WARNS() << "Invalid file mask: " << mask << LL_ENDL;
        llassert(!"Invalid file mask");
    }

    LLDirIterator iter(dirname, mask);
    while (iter.next(filename))
    {
        fullpath = add(dirname, filename);

        if(LLFile::isdir(fullpath))
        {
            // skipping directory traversal filenames
            count++;
            continue;
        }

        S32 retry_count = 0;
        while (retry_count < 5)
        {
            if (0 != LLFile::remove(fullpath))
            {
                retry_count++;
                result = errno;
                LL_WARNS() << "Problem removing " << fullpath << " - errorcode: "
                        << result << " attempt " << retry_count << LL_ENDL;

                if(retry_count >= 5)
                {
                    LL_WARNS() << "Failed to remove " << fullpath << LL_ENDL ;
                    return count ;
                }

                ms_sleep(100);
            }
            else
            {
                if (retry_count)
                {
                    LL_WARNS() << "Successfully removed " << fullpath << LL_ENDL;
                }
                break;
            }
        }
        count++;
    }
    return count;
}

U32 LLDir::deleteDirAndContents(const std::string& dir_name)
{
    //Removes the directory and its contents.  Returns number of files deleted.

    U32 num_deleted = 0;

    try
    {
#ifdef LL_WINDOWS // or BOOST_WINDOWS_API
        boost::filesystem::path dir_path(utf8str_to_utf16str(dir_name));
#else
        boost::filesystem::path dir_path(dir_name);
#endif

       if (boost::filesystem::exists(dir_path))
       {
          if (!boost::filesystem::is_empty(dir_path))
          {   // Directory has content
             num_deleted = (U32)boost::filesystem::remove_all(dir_path);
          }
          else
          {   // Directory is empty
             boost::filesystem::remove(dir_path);
          }
       }
    }
    catch (boost::filesystem::filesystem_error &er)
    {
        LL_WARNS() << "Failed to delete " << dir_name << " with error " << er.code().message() << LL_ENDL;
    }
    return num_deleted;
}

const std::string LLDir::findFile(const std::string &filename,
                           const std::string& searchPath1,
                           const std::string& searchPath2,
                           const std::string& searchPath3) const
{
    std::vector<std::string> search_paths;
    search_paths.push_back(searchPath1);
    search_paths.push_back(searchPath2);
    search_paths.push_back(searchPath3);
    return findFile(filename, search_paths);
}

const std::string LLDir::findFile(const std::string& filename, const std::vector<std::string> search_paths) const
{
    std::vector<std::string>::const_iterator search_path_iter;
    for (search_path_iter = search_paths.begin();
        search_path_iter != search_paths.end();
        ++search_path_iter)
    {
        if (!search_path_iter->empty())
        {
            std::string filename_and_path = (*search_path_iter);
            if (!filename.empty())
            {
                filename_and_path += getDirDelimiter() + filename;
            }
            if (fileExists(filename_and_path))
            {
                return filename_and_path;
            }
        }
    }
    return "";
}


const std::string &LLDir::getExecutablePathAndName() const
{
    return mExecutablePathAndName;
}

const std::string &LLDir::getExecutableFilename() const
{
    return mExecutableFilename;
}

const std::string &LLDir::getExecutableDir() const
{
    return mExecutableDir;
}

const std::string &LLDir::getWorkingDir() const
{
    return mWorkingDir;
}

const std::string &LLDir::getAppName() const
{
    return mAppName;
}

const std::string &LLDir::getAppRODataDir() const
{
    return mAppRODataDir;
}

const std::string &LLDir::getOSUserDir() const
{
    return mOSUserDir;
}

const std::string &LLDir::getOSUserAppDir() const
{
    return mOSUserAppDir;
}

const std::string &LLDir::getLindenUserDir() const
{
    if (mLindenUserDir.empty())
    {
        LL_DEBUGS() << "getLindenUserDir() called early, we don't have the user name yet - returning empty string to caller" << LL_ENDL;
    }

    return mLindenUserDir;
}

const std::string& LLDir::getChatLogsDir() const
{
    return mChatLogsDir;
}

void LLDir::setDumpDir( const std::string& path )
{
    LLDir::sDumpDir = path;
    if (LLStringUtil::endsWith(sDumpDir, mDirDelimiter))
    {
        sDumpDir.erase(sDumpDir.size() - mDirDelimiter.size());
    }
}

const std::string &LLDir::getDumpDir() const
{
    if (sDumpDir.empty() )
    {
        LLUUID uid;
        uid.generate();

        sDumpDir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "")
                    + "dump-" + uid.asString();

        dir_exists_or_crash(sDumpDir);
    }

    return LLDir::sDumpDir;
}

bool LLDir::dumpDirExists() const
{
    return !sDumpDir.empty();
}

const std::string &LLDir::getPerAccountChatLogsDir() const
{
    return mPerAccountChatLogsDir;
}

const std::string &LLDir::getTempDir() const
{
    return mTempDir;
}

const std::string  LLDir::getCacheDir(bool get_default) const
{
    if (mCacheDir.empty() || get_default)
    {
        if (!mDefaultCacheDir.empty())
        {   // Set at startup - can't set here due to const API
            return mDefaultCacheDir;
        }

        std::string res = buildSLOSCacheDir();
        return res;
    }
    else
    {
        return mCacheDir;
    }
}

// Return the default cache directory
std::string LLDir::buildSLOSCacheDir() const
{
    std::string res;
    if (getOSCacheDir().empty())
    {
        if (getOSUserAppDir().empty())
        {
            res = "data";
        }
        else
        {
            res = add(getOSUserAppDir(), "cache");
        }
    }
    else
    {
        res = add(getOSCacheDir(), "SecondLife");
    }
    return res;
}



const std::string &LLDir::getOSCacheDir() const
{
    return mOSCacheDir;
}


const std::string &LLDir::getCAFile() const
{
    return mCAFile;
}

const std::string &LLDir::getDirDelimiter() const
{
    return mDirDelimiter;
}

const std::string& LLDir::getDefaultSkinDir() const
{
    return mDefaultSkinDir;
}

const std::string &LLDir::getSkinDir() const
{
    return mSkinDir;
}

const std::string &LLDir::getUserDefaultSkinDir() const
{
    return mUserDefaultSkinDir;
}

const std::string &LLDir::getUserSkinDir() const
{
    return mUserSkinDir;
}

const std::string LLDir::getSkinBaseDir() const
{
    return mSkinBaseDir;
}

const std::string &LLDir::getLLPluginDir() const
{
    return mLLPluginDir;
}

const std::string &LLDir::getUserName() const
{
    return mUserName;
}

static std::string ELLPathToString(ELLPath location)
{
    typedef std::map<ELLPath, const char*> ELLPathMap;
#define ENT(symbol) (symbol, #symbol)
    static const ELLPathMap sMap = map_list_of
        ENT(LL_PATH_NONE)
        ENT(LL_PATH_USER_SETTINGS)
        ENT(LL_PATH_APP_SETTINGS)
        ENT(LL_PATH_PER_SL_ACCOUNT) // returns/expands to blank string if we don't know the account name yet
        ENT(LL_PATH_CACHE)
        ENT(LL_PATH_CHARACTER)
        ENT(LL_PATH_HELP)
        ENT(LL_PATH_LOGS)
        ENT(LL_PATH_TEMP)
        ENT(LL_PATH_SKINS)
        ENT(LL_PATH_TOP_SKIN)
        ENT(LL_PATH_CHAT_LOGS)
        ENT(LL_PATH_PER_ACCOUNT_CHAT_LOGS)
        ENT(LL_PATH_USER_SKIN)
        ENT(LL_PATH_LOCAL_ASSETS)
        ENT(LL_PATH_EXECUTABLE)
        ENT(LL_PATH_DEFAULT_SKIN)
        ENT(LL_PATH_FONTS)
        ENT(LL_PATH_LAST)
    ;
#undef ENT

    ELLPathMap::const_iterator found = sMap.find(location);
    if (found != sMap.end())
        return found->second;
    return STRINGIZE("Invalid ELLPath value " << location);
}

std::string LLDir::getExpandedFilename(ELLPath location, const std::string& filename) const
{
    return getExpandedFilename(location, "", filename);
}

std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subdir, const std::string& filename) const
{
    return getExpandedFilename(location, "", subdir, filename);
}

std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subdir1, const std::string& subdir2, const std::string& in_filename) const
{
    std::string prefix;
    switch (location)
    {
    case LL_PATH_NONE:
        // Do nothing
        break;

    case LL_PATH_APP_SETTINGS:
        prefix = add(getAppRODataDir(), "app_settings");
        break;

    case LL_PATH_CHARACTER:
        prefix = add(getAppRODataDir(), "character");
        break;

    case LL_PATH_HELP:
        prefix = "help";
        break;

    case LL_PATH_CACHE:
        prefix = getCacheDir();
        break;

    case LL_PATH_DUMP:
        prefix=getDumpDir();
        break;

    case LL_PATH_USER_SETTINGS:
        prefix = add(getOSUserAppDir(), "user_settings");
        break;

    case LL_PATH_PER_SL_ACCOUNT:
        prefix = getLindenUserDir();
        if (prefix.empty())
        {
            // if we're asking for the per-SL-account directory but we haven't
            // logged in yet (or otherwise don't know the account name from
            // which to build this string), then intentionally return a blank
            // string to the caller and skip the below warning about a blank
            // prefix.
            LL_DEBUGS("LLDir") << "getLindenUserDir() not yet set: "
                               << ELLPathToString(location)
                               << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
                               << "' => ''" << LL_ENDL;
            return std::string();
        }
        break;

    case LL_PATH_CHAT_LOGS:
        prefix = getChatLogsDir();
        break;

    case LL_PATH_PER_ACCOUNT_CHAT_LOGS:
        prefix = getPerAccountChatLogsDir();
        if (prefix.empty())
        {
            // potentially directory was not set yet
            // intentionally return a blank string to the caller
            LL_DEBUGS("LLDir") << "Conversation log directory is not yet set" << LL_ENDL;
            return std::string();
        }
        break;

    case LL_PATH_LOGS:
        prefix = add(getOSUserAppDir(), "logs");
        break;

    case LL_PATH_TEMP:
        prefix = getTempDir();
        break;

    case LL_PATH_TOP_SKIN:
        prefix = getSkinDir();
        break;

    case LL_PATH_DEFAULT_SKIN:
        prefix = getDefaultSkinDir();
        break;

    case LL_PATH_USER_SKIN:
        prefix = getUserSkinDir();
        break;

    case LL_PATH_SKINS:
        prefix = getSkinBaseDir();
        break;

    case LL_PATH_LOCAL_ASSETS:
        prefix = add(getAppRODataDir(), "local_assets");
        break;

    case LL_PATH_EXECUTABLE:
        prefix = getExecutableDir();
        break;

    case LL_PATH_FONTS:
        prefix = add(getAppRODataDir(), "fonts");
        break;

    default:
        llassert(0);
    }

    if (prefix.empty())
    {
        LL_WARNS() << ELLPathToString(location)
                << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
                << "': prefix is empty, possible bad filename" << LL_ENDL;
    }

    std::string expanded_filename = add(prefix, subdir1, subdir2);
    if (expanded_filename.empty() && in_filename.empty())
    {
        return "";
    }
    // Use explicit concatenation here instead of another add() call. Callers
    // passing in_filename as "" expect to obtain a pathname ending with
    // mDirSeparator so they can later directly concatenate with a specific
    // filename. A caller using add() doesn't care, but there's still code
    // loose in the system that uses std::string::operator+().
    expanded_filename += mDirDelimiter;
    expanded_filename += in_filename;

    LL_DEBUGS("LLDir") << ELLPathToString(location)
                       << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
                       << "' => '" << expanded_filename << "'" << LL_ENDL;
    return expanded_filename;
}

std::string LLDir::getBaseFileName(const std::string& filepath, bool strip_exten) const
{
    std::size_t offset = filepath.find_last_of(getDirDelimiter());
    offset = (offset == std::string::npos) ? 0 : offset+1;
    std::string res = filepath.substr(offset, std::string::npos);
    if (strip_exten)
    {
        offset = res.find_last_of('.');
        if (offset != std::string::npos &&
            offset != 0) // if basename STARTS with '.', don't strip
        {
            res = res.substr(0, offset);
        }
    }
    return res;
}

std::string LLDir::getDirName(const std::string& filepath) const
{
    std::size_t offset = filepath.find_last_of(getDirDelimiter());
    auto len = (offset == std::string::npos) ? 0 : offset;
    std::string dirname = filepath.substr(0, len);
    return dirname;
}

std::string LLDir::getExtension(const std::string& filepath) const
{
    if (filepath.empty())
        return std::string();
    std::string basename = getBaseFileName(filepath, false);
    std::size_t offset = basename.find_last_of('.');
    std::string exten = (offset == std::string::npos || offset == 0) ? "" : basename.substr(offset+1);
    LLStringUtil::toLower(exten);
    return exten;
}

std::string LLDir::findSkinnedFilenameBaseLang(const std::string &subdir,
                                               const std::string &filename,
                                               ESkinConstraint constraint) const
{
    // This implementation is basically just as described in the declaration comments.
    std::vector<std::string> found(findSkinnedFilenames(subdir, filename, constraint));
    if (found.empty())
    {
        return "";
    }
    return found.front();
}

std::string LLDir::findSkinnedFilename(const std::string &subdir,
                                       const std::string &filename,
                                       ESkinConstraint constraint) const
{
    // This implementation is basically just as described in the declaration comments.
    std::vector<std::string> found(findSkinnedFilenames(subdir, filename, constraint));
    if (found.empty())
    {
        return "";
    }
    return found.back();
}

// This method exists because the two code paths for
// findSkinnedFilenames(ALL_SKINS) and findSkinnedFilenames(CURRENT_SKIN) must
// generate the list of candidate pathnames in identical ways. The only
// difference is in the body of the inner loop.
template <typename FUNCTION>
void LLDir::walkSearchSkinDirs(const std::string& subdir,
                               const std::vector<std::string>& subsubdirs,
                               const std::string& filename,
                               const FUNCTION& function) const
{
    for (const std::string& skindir : mSearchSkinDirs)
    {
        std::string subdir_path(add(skindir, subdir));
        for (const std::string& subsubdir : subsubdirs)
        {
            std::string full_path(add(subdir_path, subsubdir, filename));
            if (fileExists(full_path))
            {
                function(subsubdir, full_path);
            }
        }
    }
}

// ridiculous little helper function that should go away when we can use lambda
inline void push_back(std::vector<std::string>& vector, const std::string& value)
{
    vector.push_back(value);
}

typedef std::map<std::string, std::string> StringMap;
// ridiculous little helper function that should go away when we can use lambda
inline void store_in_map(StringMap& map, const std::string& key, const std::string& value)
{
    map[key] = value;
}

std::vector<std::string> LLDir::findSkinnedFilenames(const std::string& subdir,
                                                     const std::string& filename,
                                                     ESkinConstraint constraint) const
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;

    // Recognize subdirs that have no localization.
    static const std::set<std::string> sUnlocalized = list_of
        ("")                        // top-level directory not localized
        ("textures")                // textures not localized
    ;

    LL_DEBUGS("LLDir") << "subdir '" << subdir << "', filename '" << filename
                       << "', constraint "
                       << ((constraint == CURRENT_SKIN)? "CURRENT_SKIN" : "ALL_SKINS")
                       << LL_ENDL;

    // Build results vector.
    std::vector<std::string> results;
    // Disallow filenames that may escape subdir
    if (filename.find("..") != std::string::npos)
    {
        LL_WARNS("LLDir") << "Ignoring potentially relative filename '" << filename << "'" << LL_ENDL;
        return results;
    }

    // Cache the default language directory for each subdir we've encountered.
    // A cache entry whose value is the empty string means "not localized,
    // don't bother checking again."
    static StringMap sLocalized;

    // Check whether we've already discovered if this subdir is localized.
    StringMap::const_iterator found = sLocalized.find(subdir);
    if (found == sLocalized.end())
    {
        // We have not yet determined that. Is it one of the subdirs "known"
        // to be unlocalized?
        if (sUnlocalized.find(subdir) != sUnlocalized.end())
        {
            // This subdir is known to be unlocalized. Remember that.
            found = sLocalized.insert(StringMap::value_type(subdir, "")).first;
        }
        else
        {
            // We do not recognize this subdir. Investigate.
            std::string subdir_path(add(getDefaultSkinDir(), subdir));
            if (fileExists(add(subdir_path, "en")))
            {
                // defaultSkinDir/subdir contains subdir "en". That's our
                // default language; this subdir is localized.
                found = sLocalized.insert(StringMap::value_type(subdir, "en")).first;
            }
            else if (fileExists(add(subdir_path, "en-us")))
            {
                // defaultSkinDir/subdir contains subdir "en-us" but not "en".
                // Set as default language; this subdir is localized.
                found = sLocalized.insert(StringMap::value_type(subdir, "en-us")).first;
            }
            else
            {
                // defaultSkinDir/subdir contains neither "en" nor "en-us".
                // Assume it's not localized. Remember that assumption.
                found = sLocalized.insert(StringMap::value_type(subdir, "")).first;
            }
        }
    }
    // Every code path above should have resulted in 'found' becoming a valid
    // iterator to an entry in sLocalized.
    llassert(found != sLocalized.end());

    // Now -- is this subdir localized, or not? The answer determines what
    // subdirectories we check (under subdir) for the requested filename.
    std::vector<std::string> subsubdirs;
    if (found->second.empty())
    {
        // subdir is not localized. filename should be located directly within it.
        subsubdirs.push_back("");
    }
    else
    {
        // subdir is localized, and found->second is the default language
        // directory within it. Check both the default language and the
        // current language -- if it differs from the default, of course.
        subsubdirs.push_back(found->second);
        if (mLanguage != found->second)
        {
            subsubdirs.push_back(mLanguage);
        }
    }

    // The process we use depends on 'constraint'.
    if (constraint != CURRENT_SKIN) // meaning ALL_SKINS
    {
        // ALL_SKINS is simpler: just return every pathname generated by
        // walkSearchSkinDirs(). Tricky bit: walkSearchSkinDirs() passes its
        // FUNCTION the subsubdir as well as the full pathname. We just want
        // the full pathname.
        walkSearchSkinDirs(subdir, subsubdirs, filename,
                           boost::bind(push_back, boost::ref(results), _2));
    }
    else                            // CURRENT_SKIN
    {
        // CURRENT_SKIN turns out to be a bit of a misnomer because we might
        // still return files from two different skins. In any case, this
        // value of 'constraint' means we will return at most two paths: one
        // for the default language, one for the current language (supposing
        // those differ).
        // It is important to allow a user to override only the localization
        // for a particular file, for all viewer installs, without also
        // overriding the default-language file.
        // It is important to allow a user to override only the default-
        // language file, for all viewer installs, without also overriding the
        // applicable localization of that file.
        // Therefore, treat the default language and the current language as
        // two separate cases. For each, capture the most-specialized file
        // that exists.
        // Use a map keyed by subsubdir (i.e. language code). This allows us
        // to handle the case of a single subsubdirs entry with the same logic
        // that handles two. For every real file path generated by
        // walkSearchSkinDirs(), update the map entry for its subsubdir.
        StringMap path_for;
        walkSearchSkinDirs(subdir, subsubdirs, filename,
                           boost::bind(store_in_map, boost::ref(path_for), _1, _2));
        // Now that we have a path for each of the default language and the
        // current language, copy them -- in proper order -- into results.
        // Don't drive this by walking the map itself: it matters that we
        // generate results in the same order as subsubdirs.
        for (const std::string& subsubdir : subsubdirs)
        {
            StringMap::const_iterator found(path_for.find(subsubdir));
            if (found != path_for.end())
            {
                results.push_back(found->second);
            }
        }
    }

    LL_DEBUGS("LLDir") << empty;
    const char* comma = "";
    for (const std::string& path : results)
    {
        LL_CONT << comma << "'" << path << "'";
        comma = ", ";
    }
    LL_CONT << LL_ENDL;

    return results;
}

std::string LLDir::getTempFilename() const
{
    LLUUID random_uuid;
    std::string uuid_str;

    random_uuid.generate();
    random_uuid.toString(uuid_str);

    return add(getTempDir(), uuid_str + ".tmp");
}

// static
std::string LLDir::getScrubbedFileName(std::string_view uncleanFileName)
{
    std::string name(uncleanFileName);
    const std::string illegalChars(getForbiddenFileChars());
    // replace any illegal file chars with and underscore '_'
    for (const char& ch : illegalChars)
    {
        std::string::size_type j{ 0 };
        while ((j = name.find(ch, j)) != std::string::npos)
        {
            name[j] = '_';
        }
    }
    return name;
}

std::string LLDir::getDumpLogsDirPath(const std::string &file_name)
{
    return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "dump_logs", file_name);
}

// static
std::string LLDir::getForbiddenFileChars()
{
    return "\\/:*?\"<>|";
}

void LLDir::setLindenUserDir(const std::string &username)
{
    // if the username isn't set, that's bad
    if (!username.empty())
    {
        // some platforms have case-sensitive filesystems, so be
        // utterly consistent with our firstname/lastname case.
        std::string userlower(username);
        LLStringUtil::toLower(userlower);
        LLStringUtil::replaceChar(userlower, ' ', '_');
        mLindenUserDir = add(getOSUserAppDir(), userlower);
    }
    else
    {
        LL_ERRS() << "NULL name for LLDir::setLindenUserDir" << LL_ENDL;
    }

    dumpCurrentDirectories();
}

void LLDir::setChatLogsDir(const std::string &path)
{
    if (!path.empty() )
    {
        mChatLogsDir = path;
    }
    else
    {
        LL_WARNS() << "Invalid name for LLDir::setChatLogsDir" << LL_ENDL;
    }
}

void LLDir::updatePerAccountChatLogsDir()
{
    mPerAccountChatLogsDir = add(getChatLogsDir(), mUserName);
}

void LLDir::setPerAccountChatLogsDir(const std::string &username)
{
    // if both first and last aren't set, assume we're grabbing the cached dir
    if (!username.empty())
    {
        // some platforms have case-sensitive filesystems, so be
        // utterly consistent with our firstname/lastname case.
        std::string userlower(username);
        LLStringUtil::toLower(userlower);
        LLStringUtil::replaceChar(userlower, ' ', '_');

        mUserName = userlower;
        updatePerAccountChatLogsDir();
    }
    else
    {
        LL_ERRS() << "NULL name for LLDir::setPerAccountChatLogsDir" << LL_ENDL;
    }
}

void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language)
{
    LL_DEBUGS("LLDir") << "Setting skin '" << skin_folder << "', language '" << language << "'"
                       << LL_ENDL;
    mSkinName = skin_folder;
    mLanguage = language;

    // This method is called multiple times during viewer initialization. Each
    // time it's called, reset mSearchSkinDirs.
    mSearchSkinDirs.clear();

    // base skin which is used as fallback for all skinned files
    // e.g. c:\program files\secondlife\skins\default
    mDefaultSkinDir = getSkinBaseDir();
    append(mDefaultSkinDir, "default");
    // This is always the most general of the search skin directories.
    addSearchSkinDir(mDefaultSkinDir);

    mSkinDir = getSkinBaseDir();
    append(mSkinDir, skin_folder);
    // Next level of generality is a skin installed with the viewer.
    addSearchSkinDir(mSkinDir);

    // user modifications to skins, current and default
    // e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle
    mUserSkinDir = getOSUserAppDir();
    append(mUserSkinDir, "skins");
    mUserDefaultSkinDir = mUserSkinDir;
    append(mUserDefaultSkinDir, "default");
    append(mUserSkinDir, skin_folder);
    // Next level of generality is user modifications to default skin...
    addSearchSkinDir(mUserDefaultSkinDir);
    // then user-defined skins.
    addSearchSkinDir(mUserSkinDir);
}

void LLDir::addSearchSkinDir(const std::string& skindir)
{
    if (std::find(mSearchSkinDirs.begin(), mSearchSkinDirs.end(), skindir) == mSearchSkinDirs.end())
    {
        LL_DEBUGS("LLDir") << "search skin: '" << skindir << "'" << LL_ENDL;
        mSearchSkinDirs.push_back(skindir);
    }
}

std::string LLDir::getSkinFolder() const
{
    return mSkinName;
}

std::string LLDir::getLanguage() const
{
    return mLanguage;
}

bool LLDir::setCacheDir(const std::string &path)
{
    if (path.empty() )
    {
        // reset to default
        mCacheDir = "";
        return true;
    }
    else
    {
        LLFile::mkdir(path);
        std::string tempname = add(path, "temp");
        LLFILE* file = LLFile::fopen(tempname,"wt");
        if (file)
        {
            fclose(file);
            LLFile::remove(tempname);
            mCacheDir = path;
            return true;
        }
        return false;
    }
}

void LLDir::dumpCurrentDirectories(LLError::ELevel level)
{
    LL_VLOGS(level, "AppInit","Directories") << "Current Directories:" << LL_ENDL;

    LL_VLOGS(level, "AppInit", "Directories") << "  CurPath:               " << getCurPath() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  AppName:               " << getAppName() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  ExecutableFilename:    " << getExecutableFilename() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  ExecutableDir:         " << getExecutableDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  ExecutablePathAndName: " << getExecutablePathAndName() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  WorkingDir:            " << getWorkingDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  AppRODataDir:          " << getAppRODataDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  OSUserDir:             " << getOSUserDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  OSUserAppDir:          " << getOSUserAppDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  LindenUserDir:         " << getLindenUserDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  TempDir:               " << getTempDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  CAFile:                " << getCAFile() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  SkinBaseDir:           " << getSkinBaseDir() << LL_ENDL;
    LL_VLOGS(level, "AppInit", "Directories") << "  SkinDir:               " << getSkinDir() << LL_ENDL;
}

void LLDir::append(std::string& destpath, const std::string& name) const
{
    // Delegate question of whether we need a separator to helper method.
    SepOff sepoff(needSep(destpath, name));
    if (sepoff.first)               // do we need a separator?
    {
        destpath += mDirDelimiter;
    }
    // If destpath ends with a separator, AND name starts with one, skip
    // name's leading separator.
    destpath += name.substr(sepoff.second);
}

LLDir::SepOff LLDir::needSep(const std::string& path, const std::string& name) const
{
    if (path.empty() || name.empty())
    {
        // If either path or name are empty, we do not need a separator
        // between them.
        return SepOff(false, 0);
    }
    // Here we know path and name are both non-empty. But if path already ends
    // with a separator, or if name already starts with a separator, we need
    // not add one.
    std::string::size_type seplen(mDirDelimiter.length());
    bool path_ends_sep(path.substr(path.length() - seplen) == mDirDelimiter);
    bool name_starts_sep(name.substr(0, seplen) == mDirDelimiter);
    if ((! path_ends_sep) && (! name_starts_sep))
    {
        // If neither path nor name brings a separator to the junction, then
        // we need one.
        return SepOff(true, 0);
    }
    if (path_ends_sep && name_starts_sep)
    {
        // But if BOTH path and name bring a separator, we need not add one.
        // Moreover, we should actually skip the leading separator of 'name'.
        return SepOff(false, (unsigned short)seplen);
    }
    // Here we know that either path_ends_sep or name_starts_sep is true --
    // but not both. So don't add a separator, and don't skip any characters:
    // simple concatenation will do the trick.
    return SepOff(false, 0);
}

void dir_exists_or_crash(const std::string &dir_name)
{
#if LL_WINDOWS
    // *FIX: lame - it doesn't do the same thing on windows. not so
    // important since we don't deploy simulator to windows boxes.
    LLFile::mkdir(dir_name, 0700);
#else
    struct stat dir_stat;
    if(0 != LLFile::stat(dir_name, &dir_stat))
    {
        S32 stat_rv = errno;
        if(ENOENT == stat_rv)
        {
           if(0 != LLFile::mkdir(dir_name, 0700))       // octal
           {
               LL_ERRS() << "Unable to create directory: " << dir_name << LL_ENDL;
           }
        }
        else
        {
            LL_ERRS() << "Unable to stat: " << dir_name << " errno = " << stat_rv
                   << LL_ENDL;
        }
    }
    else
    {
        // data_dir exists, make sure it's a directory.
        if(!S_ISDIR(dir_stat.st_mode))
        {
            LL_ERRS() << "Data directory collision: " << dir_name << LL_ENDL;
        }
    }
#endif
}