/*
 *  The ManaPlus Client
 *  Copyright (C) 2004-2009  The Mana World Development Team
 *  Copyright (C) 2009-2010  The Mana Developers
 *  Copyright (C) 2011-2015  The ManaPlus Developers
 *
 *  This file is part of The ManaPlus Client.
 *
 *  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 2 of the License, or
 *  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, see <http://www.gnu.org/licenses/>.
 */

#include "dirs.h"

#include "client.h"
#include "configuration.h"
#include "logger.h"
#include "settings.h"

#include "utils/base64.h"
#if defined(__native_client__) || (defined(ANDROID) && defined(USE_SDL2))
#include "utils/files.h"
#endif
#include "utils/gettext.h"
#include "utils/mkdir.h"
#include "utils/paths.h"
#include "utils/physfstools.h"

#include "resources/resourcemanager.h"

#ifdef ANDROID
#ifdef USE_SDL2
#include "render/graphics.h"
#endif
#endif

#ifdef __APPLE__
#include <CoreFoundation/CFBundle.h>
#endif

#ifdef WIN32
#include <SDL_syswm.h>
#include "utils/specialfolder.h"
#endif

#include <sys/stat.h>

#include "debug.h"

#if defined __native_client__
#define _nacl_dir std::string("/persistent/manaplus")
#endif

#ifdef ANDROID
#ifdef USE_SDL2

int loadingProgressCounter = 1;

static void updateProgress(int cnt)
{
    const int progress = cnt + loadingProgressCounter;
    const int h = mainGraphics->mHeight;
    mainGraphics->setColor(Color(255, 255, 255));
    const int maxSize = mainGraphics->mWidth - 100;
    const int width = maxSize * progress / 450;
    mainGraphics->fillRectangle(Rect(50, h - 100, width, 50));
    mainGraphics->updateScreen();
}

void Dirs::setProgress()
{
    loadingProgressCounter++;
    updateProgress(loadingProgressCounter);
}

static void resetProgress()
{
    loadingProgressCounter = 0;
    updateProgress(loadingProgressCounter);
}

void extractAssets()
{
    if (!getenv("APPDIR"))
    {
        logger->log("error: APPDIR is not set!");
        return;
    }
    const std::string fileName = std::string(getenv(
        "APPDIR")).append("/data.zip");
    logger->log("Extracting asset into: " + fileName);
    uint8_t *buf = new uint8_t[1000000];

    FILE *const file = fopen(fileName.c_str(), "w");
    for (int f = 0; f < 100; f ++)
    {
        std::string part = strprintf("manaplus-data.zip%u%u",
            static_cast<unsigned int>(f / 10),
            static_cast<unsigned int>(f % 10));
        logger->log("testing asset: " + part);
        SDL_RWops *const rw = SDL_RWFromFile(part.c_str(), "r");
        if (rw)
        {
            const int size = SDL_RWsize(rw);
            int size2 = SDL_RWread(rw, buf, 1, size);
            logger->log("asset size: %d", size2);
            fwrite(buf, 1, size2, file);
            SDL_RWclose(rw);
            Dirs::setProgress();
        }
        else
        {
            break;
        }
    }
    fclose(file);

    const std::string fileName2 = std::string(getenv(
        "APPDIR")).append("/locale.zip");
    FILE *const file2 = fopen(fileName2.c_str(), "w");
    SDL_RWops *const rw = SDL_RWFromFile("manaplus-locale.zip", "r");
    if (rw)
    {
        const int size = SDL_RWsize(rw);
        int size2 = SDL_RWread(rw, buf, 1, size);
        fwrite(buf, 1, size2, file2);
        SDL_RWclose(rw);
        Dirs::setProgress();
    }
    fclose(file2);

    delete [] buf;
}

#endif
#endif

void Dirs::updateDataPath()
{
    if (settings.options.dataPath.empty()
        && !branding.getStringValue("dataPath").empty())
    {
        if (isRealPath(branding.getStringValue("dataPath")))
        {
            settings.options.dataPath = branding.getStringValue("dataPath");
        }
        else
        {
            settings.options.dataPath = branding.getDirectory().append(
                dirSeparator) + branding.getStringValue("dataPath");
        }
        settings.options.skipUpdate = true;
    }
}

void Dirs::extractDataDir()
{
#ifdef ANDROID
#ifdef USE_SDL2
    Files::setCopyCallBack(&updateProgress);
    resetProgress();
    extractAssets();

    const std::string zipName = std::string(getenv(
        "APPDIR")).append("/data.zip");
    const std::string dirName = std::string(getenv(
        "APPDIR")).append("/data");
    Files::extractZip(zipName, "data", dirName);
    Files::extractLocale();
#endif
#endif

#if defined __native_client__
    const std::string dirName = _nacl_dir.append("/data");
    Files::extractZip("/http/data.zip", "data", dirName);
#endif
}

void Dirs::mountDataDir()
{
    const ResourceManager *const resman = ResourceManager::getInstance();
    resman->addToSearchPath(PKG_DATADIR "data/perserver/default", false);
    resman->addToSearchPath("data/perserver/default", false);

#if defined __APPLE__
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
    char path[PATH_MAX];
    if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (uint8_t*)path,
        PATH_MAX))
    {
        fprintf(stderr, "Can't find Resources directory\n");
    }
    CFRelease(resourcesURL);
    // possible crash
    strncat(path, "/data", PATH_MAX - 1);
    resman->addToSearchPath(path, false);
// possible this need for support run client from dmg images.
//    mPackageDir = path;
#endif
    resman->addToSearchPath(PKG_DATADIR "data", false);
    setPackageDir(PKG_DATADIR "data");
    resman->addToSearchPath("data", false);

#ifdef ANDROID
#ifdef USE_SDL2
    if (getenv("APPDIR"))
    {
        const std::string appDir = getenv("APPDIR");
        resman->addToSearchPath(appDir + "/data", false);
        resman->addToSearchPath(appDir + "/data/perserver/default", false);
    }
#endif
#endif

#if defined __native_client__
    resman->addToSearchPath(_nacl_dir.append("/data"), false);
#endif

    // Add branding/data to PhysFS search path
    if (!settings.options.brandingPath.empty())
    {
        std::string path = settings.options.brandingPath;

        // Strip blah.manaplus from the path
#ifdef WIN32
        const int loc1 = path.find_last_of('/');
        const int loc2 = path.find_last_of('\\');
        const int loc = static_cast<int>(std::max(loc1, loc2));
#else
        const int loc = static_cast<int>(path.find_last_of('/'));
#endif
        if (loc > 0)
        {
            resman->addToSearchPath(path.substr(
                0, loc + 1).append("data"), false);
        }
    }
}

void Dirs::initRootDir()
{
    settings.rootDir = PhysFs::getBaseDir();
    const std::string portableName = settings.rootDir + "portable.xml";
    struct stat statbuf;

    if (!stat(portableName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode))
    {
        std::string dir;
        Configuration portable;
        portable.init(portableName);

        if (settings.options.brandingPath.empty())
        {
            branding.init(portableName);
            branding.setDefaultValues(getBrandingDefaults());
        }

        logger->log("Portable file: %s", portableName.c_str());

        if (settings.options.localDataDir.empty())
        {
            dir = portable.getValue("dataDir", "");
            if (!dir.empty())
            {
                settings.options.localDataDir = settings.rootDir + dir;
                logger->log("Portable data dir: %s",
                    settings.options.localDataDir.c_str());
            }
        }

        if (settings.options.configDir.empty())
        {
            dir = portable.getValue("configDir", "");
            if (!dir.empty())
            {
                settings.options.configDir = settings.rootDir + dir;
                logger->log("Portable config dir: %s",
                    settings.options.configDir.c_str());
            }
        }

        if (settings.options.screenshotDir.empty())
        {
            dir = portable.getValue("screenshotDir", "");
            if (!dir.empty())
            {
                settings.options.screenshotDir = settings.rootDir + dir;
                logger->log("Portable screenshot dir: %s",
                    settings.options.screenshotDir.c_str());
            }
        }
    }
}

/**
 * Initializes the home directory. On UNIX and FreeBSD, ~/.mana is used. On
 * Windows and other systems we use the current working directory.
 */
void Dirs::initHomeDir()
{
    initLocalDataDir();
    initTempDir();
    initConfigDir();
}

void Dirs::initLocalDataDir()
{
    settings.localDataDir = settings.options.localDataDir;

    if (settings.localDataDir.empty())
    {
#ifdef __APPLE__
        // Use Application Directory instead of .mana
        settings.localDataDir = std::string(PhysFs::getUserDir()) +
            "/Library/Application Support/" +
            branding.getValue("appName", "ManaPlus");
#elif defined __HAIKU__
        settings.localDataDir = std::string(PhysFs::getUserDir()) +
           "/config/data/Mana";
#elif defined WIN32
        settings.localDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA);
        if (settings.localDataDir.empty())
            settings.localDataDir = std::string(PhysFs::getUserDir());
        settings.localDataDir.append("/Mana");
#elif defined __ANDROID__
        settings.localDataDir = getSdStoragePath() + branding.getValue(
            "appShort", "ManaPlus") + "/local";
#elif defined __native_client__
        settings.localDataDir = _nacl_dir.append("/local");
#else
        settings.localDataDir = std::string(PhysFs::getUserDir()) +
            ".local/share/mana";
#endif
    }

    if (mkdir_r(settings.localDataDir.c_str()))
    {
        // TRANSLATORS: directory creation error
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
            "Exiting."), settings.localDataDir.c_str()));
    }
#ifdef USE_PROFILER
    Perfomance::init(settings.localDataDir + "/profiler.log");
#endif
}

void Dirs::initTempDir()
{
    settings.tempDir = settings.localDataDir + dirSeparator + "temp";

    if (mkdir_r(settings.tempDir.c_str()))
    {
        // TRANSLATORS: directory creation error
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
            "Exiting."), settings.tempDir.c_str()));
    }
//    ResourceManager::deleteFilesInDirectory(settings.tempDir);
}

void Dirs::initConfigDir()
{
    settings.configDir = settings.options.configDir;

    if (settings.configDir.empty())
    {
#ifdef __APPLE__
        settings.configDir = settings.localDataDir + dirSeparator
            + branding.getValue("appShort", "mana");
#elif defined __HAIKU__
        settings.configDir = std::string(PhysFs::getUserDir()) +
           "/config/settings/Mana" +
           branding.getValue("appName", "ManaPlus");
#elif defined WIN32
        settings.configDir = getSpecialFolderLocation(CSIDL_APPDATA);
        if (settings.configDir.empty())
        {
            settings.configDir = settings.localDataDir;
        }
        else
        {
            settings.configDir.append("/mana/").append(branding.getValue(
                "appShort", "mana"));
        }
#elif defined __ANDROID__
        settings.configDir = getSdStoragePath() + branding.getValue(
            "appShort", "ManaPlus").append("/config");
#elif defined __native_client__
        settings.configDir = _nacl_dir.append("/config");
#else
        settings.configDir = std::string(PhysFs::getUserDir()).append(
            "/.config/mana/").append(branding.getValue("appShort", "mana"));
#endif
        logger->log("Generating config dir: " + settings.configDir);
    }

    if (mkdir_r(settings.configDir.c_str()))
    {
        // TRANSLATORS: directory creation error
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
            "Exiting."), settings.configDir.c_str()));
    }
}

/**
 * Parse the update host and determine the updates directory
 * Then verify that the directory exists (creating if needed).
 */
void Dirs::initUpdatesDir()
{
    std::stringstream updates;

    // If updatesHost is currently empty, fill it from config file
    if (settings.updateHost.empty())
        settings.updateHost = config.getStringValue("updatehost");
    if (!checkPath(settings.updateHost))
        return;

    // Don't go out of range int he next check
    if (settings.updateHost.length() < 2)
    {
        if (settings.updatesDir.empty())
        {
            settings.updatesDir = std::string("updates/").append(
                settings.serverName);
        }
        return;
    }

    const size_t sz = settings.updateHost.size();
    // Remove any trailing slash at the end of the update host
    if (settings.updateHost.at(sz - 1) == '/')
        settings.updateHost.resize(sz - 1);

    // Parse out any "http://" or "https://", and set the updates directory
    const size_t pos = settings.updateHost.find("://");
    if (pos != settings.updateHost.npos)
    {
        if (pos + 3 < settings.updateHost.length()
            && !settings.updateHost.empty())
        {
            updates << "updates/" << settings.updateHost.substr(pos + 3);
            settings.updatesDir = updates.str();
        }
        else
        {
            logger->log("Error: Invalid update host: %s",
                settings.updateHost.c_str());
            // TRANSLATORS: update server initialisation error
            errorMessage = strprintf(_("Invalid update host: %s."),
                settings.updateHost.c_str());
            client->setState(STATE_ERROR);
        }
    }
    else
    {
        logger->log1("Warning: no protocol was specified for the update host");
        updates << "updates/" << settings.updateHost;
        settings.updatesDir = updates.str();
    }

#ifdef WIN32
    if (settings.updatesDir.find(":") != std::string::npos)
        replaceAll(settings.updatesDir, ":", "_");
#endif

    const std::string updateDir("/" + settings.updatesDir);

    // Verify that the updates directory exists. Create if necessary.
    if (!PhysFs::isDirectory(updateDir.c_str()))
    {
        if (!PhysFs::mkdir(updateDir.c_str()))
        {
#if defined WIN32
            std::string newDir = settings.localDataDir
                + "\\" + settings.updatesDir;
            size_t loc = newDir.find("/", 0);

            while (loc != std::string::npos)
            {
                newDir.replace(loc, 1, "\\");
                loc = newDir.find("/", loc);
            }

            if (!CreateDirectory(newDir.c_str(), nullptr) &&
                GetLastError() != ERROR_ALREADY_EXISTS)
            {
                logger->log("Error: %s can't be made, but doesn't exist!",
                            newDir.c_str());
                // TRANSLATORS: update server initialisation error
                errorMessage = _("Error creating updates directory!");
                client->setState(STATE_ERROR);
            }
#else
            logger->log("Error: %s/%s can't be made, but doesn't exist!",
                settings.localDataDir.c_str(), settings.updatesDir.c_str());
            // TRANSLATORS: update server initialisation error
            errorMessage = _("Error creating updates directory!");
            client->setState(STATE_ERROR);
#endif
        }
    }
    const std::string updateLocal = updateDir + "/local";
    const std::string updateFix = updateDir + "/fix";
    if (!PhysFs::isDirectory(updateLocal.c_str()))
        PhysFs::mkdir(updateLocal.c_str());
    if (!PhysFs::isDirectory(updateFix.c_str()))
        PhysFs::mkdir(updateFix.c_str());
}

void Dirs::initScreenshotDir()
{
    if (!settings.options.screenshotDir.empty())
    {
        settings.screenshotDir = settings.options.screenshotDir;
        if (mkdir_r(settings.screenshotDir.c_str()))
        {
            // TRANSLATORS: directory creation error
            logger->log(strprintf(
                _("Error: %s doesn't exist and can't be created! "
                "Exiting."), settings.screenshotDir.c_str()));
        }
    }
    else if (settings.screenshotDir.empty())
    {
        settings.screenshotDir = decodeBase64String(
            config.getStringValue("screenshotDirectory3"));
        if (settings.screenshotDir.empty())
        {
#ifdef __ANDROID__
            settings.screenshotDir = getSdStoragePath()
                + std::string("/images");

            if (mkdir_r(settings.screenshotDir.c_str()))
            {
                // TRANSLATORS: directory creation error
                logger->log(strprintf(
                    _("Error: %s doesn't exist and can't be created! "
                    "Exiting."), settings.screenshotDir.c_str()));
            }
#else
            settings.screenshotDir = getPicturesDir();
#endif
            if (config.getBoolValue("useScreenshotDirectorySuffix"))
            {
                const std::string configScreenshotSuffix =
                    branding.getValue("screenshots", "ManaPlus");

                if (!configScreenshotSuffix.empty())
                {
                    settings.screenshotDir.append(dirSeparator).append(
                        configScreenshotSuffix);
                }
            }
            config.setValue("screenshotDirectory3",
                encodeBase64String(settings.screenshotDir));
        }
    }
    logger->log("screenshotDirectory: " + settings.screenshotDir);
}

void Dirs::initUsersDir()
{
    settings.usersDir = settings.serverConfigDir + "/users/";
    if (mkdir_r(settings.usersDir.c_str()))
    {
        // TRANSLATORS: directory creation error
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
            "Exiting."), settings.usersDir.c_str()));
    }

    settings.npcsDir = settings.serverConfigDir + "/npcs/";
    if (mkdir_r(settings.npcsDir.c_str()))
    {
        // TRANSLATORS: directory creation error
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
            "Exiting."), settings.npcsDir.c_str()));
    }
}